Commit | Line | Data |
---|---|---|
90b4b964 | 1 | /** |
13744d6e | 2 | * Provides the style editor. |
90b4b964 AE |
3 | * |
4 | * @author Alexander Ebert | |
50d96bd8 | 5 | * @copyright 2001-2017 WoltLab GmbH |
90b4b964 | 6 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
58d7e8f8 | 7 | * @module WoltLabSuite/Core/Acp/Ui/Style/Editor |
90b4b964 | 8 | */ |
3926d706 | 9 | define(['Ajax', 'Core', 'Dictionary', 'Dom/Util', 'EventHandler', 'Ui/Screen'], function(Ajax, Core, Dictionary, DomUtil, EventHandler, UiScreen) { |
90b4b964 AE |
10 | "use strict"; |
11 | ||
f2b50825 AE |
12 | var _stylePreviewRegions = new Dictionary(); |
13 | var _stylePreviewRegionMarker = null; | |
59964c3f | 14 | var _stylePreviewWindow = elById('spWindow'); |
f2b50825 | 15 | |
ad310ee6 | 16 | var _isVisible = true; |
76f2a1ce | 17 | var _isSmartphone = false; |
ad310ee6 AE |
18 | var _updateRegionMarker = null; |
19 | ||
90b4b964 | 20 | /** |
58d7e8f8 | 21 | * @module WoltLabSuite/Core/Acp/Ui/Style/Editor |
90b4b964 | 22 | */ |
ad310ee6 | 23 | return { |
90b4b964 AE |
24 | /** |
25 | * Sets up dynamic style options. | |
26 | */ | |
27 | setup: function(options) { | |
28 | this._handleLayoutWidth(); | |
13744d6e | 29 | this._handleScss(options.isTainted); |
90b4b964 AE |
30 | |
31 | if (!options.isTainted) { | |
32 | this._handleProtection(options.styleId); | |
33 | } | |
f2b50825 AE |
34 | |
35 | this._initVisualEditor(options.styleRuleMap); | |
3926d706 MW |
36 | |
37 | UiScreen.on('screen-sm-down', { | |
38 | match: this.hideVisualEditor.bind(this), | |
39 | unmatch: this.showVisualEditor.bind(this), | |
40 | setup: this.hideVisualEditor.bind(this) | |
41 | }); | |
ad310ee6 AE |
42 | |
43 | var callbackRegionMarker = function () { | |
44 | if (_isVisible) _updateRegionMarker(); | |
45 | }; | |
46 | window.addEventListener('resize', callbackRegionMarker); | |
47 | EventHandler.add('com.woltlab.wcf.AcpMenu', 'resize', callbackRegionMarker); | |
48 | EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function (data) { | |
ac229adb | 49 | _isVisible = (data.activeName === 'colors'); |
ad310ee6 AE |
50 | callbackRegionMarker(); |
51 | }); | |
90b4b964 AE |
52 | }, |
53 | ||
54 | /** | |
55 | * Handles the switch between static and fluid layout. | |
56 | */ | |
57 | _handleLayoutWidth: function() { | |
58 | var useFluidLayout = elById('useFluidLayout'); | |
59 | var fluidLayoutMinWidth = elById('fluidLayoutMinWidth'); | |
60 | var fluidLayoutMaxWidth = elById('fluidLayoutMaxWidth'); | |
61 | var fixedLayoutVariables = elById('fixedLayoutVariables'); | |
62 | ||
63 | function change() { | |
64 | var checked = useFluidLayout.checked; | |
65 | ||
66 | fluidLayoutMinWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none'); | |
67 | fluidLayoutMaxWidth.style[(checked ? 'remove' : 'set') + 'Property']('display', 'none'); | |
68 | fixedLayoutVariables.style[(checked ? 'set' : 'remove') + 'Property']('display', 'none'); | |
69 | } | |
70 | ||
71 | useFluidLayout.addEventListener('change', change); | |
72 | ||
73 | change(); | |
74 | }, | |
75 | ||
76 | /** | |
13744d6e | 77 | * Handles SCSS input fields. |
90b4b964 AE |
78 | * |
79 | * @param {boolean} isTainted false if style is in protected mode | |
80 | */ | |
13744d6e | 81 | _handleScss: function(isTainted) { |
97ec0367 MW |
82 | var individualScss = elById('individualScss'); |
83 | var overrideScss = elById('overrideScss'); | |
90b4b964 AE |
84 | |
85 | if (isTainted) { | |
86 | EventHandler.add('com.woltlab.wcf.simpleTabMenu_styleTabMenuContainer', 'select', function(data) { | |
97ec0367 MW |
87 | individualScss.codemirror.refresh(); |
88 | overrideScss.codemirror.refresh(); | |
90b4b964 AE |
89 | }); |
90 | } | |
91 | else { | |
92 | EventHandler.add('com.woltlab.wcf.simpleTabMenu_advanced', 'select', function(data) { | |
93 | if (data.activeName === 'advanced-custom') { | |
97ec0367 MW |
94 | elById('individualScssCustom').codemirror.refresh(); |
95 | elById('overrideScssCustom').codemirror.refresh(); | |
90b4b964 AE |
96 | } |
97 | else if (data.activeName === 'advanced-original') { | |
97ec0367 MW |
98 | individualScss.codemirror.refresh(); |
99 | overrideScss.codemirror.refresh(); | |
90b4b964 AE |
100 | } |
101 | }); | |
102 | } | |
103 | }, | |
104 | ||
105 | _handleProtection: function(styleId) { | |
106 | var button = elById('styleDisableProtectionSubmit'); | |
107 | var checkbox = elById('styleDisableProtectionConfirm'); | |
108 | ||
109 | checkbox.addEventListener('change', function() { | |
110 | button.disabled = !checkbox.checked; | |
111 | }); | |
112 | ||
ac188fc5 | 113 | button.addEventListener(WCF_CLICK_EVENT, function() { |
90b4b964 AE |
114 | Ajax.apiOnce({ |
115 | data: { | |
116 | actionName: 'markAsTainted', | |
117 | className: 'wcf\\data\\style\\StyleAction', | |
118 | objectIDs: [styleId] | |
119 | }, | |
120 | success: function() { | |
121 | window.location.reload(); | |
122 | } | |
123 | }); | |
124 | }); | |
f2b50825 AE |
125 | }, |
126 | ||
127 | _initVisualEditor: function(styleRuleMap) { | |
59964c3f AE |
128 | elBySelAll('[data-region]', _stylePreviewWindow, function(region) { |
129 | _stylePreviewRegions.set(elData(region, 'region'), region); | |
130 | }); | |
f2b50825 AE |
131 | |
132 | _stylePreviewRegionMarker = elCreate('div'); | |
133 | _stylePreviewRegionMarker.id = 'stylePreviewRegionMarker'; | |
134 | _stylePreviewRegionMarker.innerHTML = '<div id="stylePreviewRegionMarkerBottom"></div>'; | |
135 | elHide(_stylePreviewRegionMarker); | |
136 | elById('colors').appendChild(_stylePreviewRegionMarker); | |
137 | ||
b3ac08d1 AE |
138 | var container = elById('spSidebar'); |
139 | var select = elById('spCategories'); | |
f2b50825 AE |
140 | var lastValue = select.value; |
141 | ||
ad310ee6 | 142 | _updateRegionMarker = function() { |
76f2a1ce AE |
143 | if (_isSmartphone) { |
144 | return; | |
145 | } | |
146 | ||
f2b50825 AE |
147 | if (lastValue === 'none') { |
148 | elHide(_stylePreviewRegionMarker); | |
f2b50825 AE |
149 | return; |
150 | } | |
151 | ||
152 | var region = _stylePreviewRegions.get(lastValue); | |
153 | var rect = region.getBoundingClientRect(); | |
154 | ||
39c81cce | 155 | var top = rect.top + (window.scrollY || window.pageYOffset); |
f2b50825 AE |
156 | |
157 | DomUtil.setStyles(_stylePreviewRegionMarker, { | |
158 | height: (region.clientHeight + 20) + 'px', | |
159 | left: (rect.left + document.body.scrollLeft - 10) + 'px', | |
160 | top: (top - 10) + 'px', | |
161 | width: (region.clientWidth + 20) + 'px' | |
162 | }); | |
163 | ||
164 | elShow(_stylePreviewRegionMarker); | |
165 | ||
ac229adb AE |
166 | top = DomUtil.offset(region).top; |
167 | // `+ 80` = account for sticky header + selection markers (20px) | |
168 | var firstVisiblePixel = (window.pageYOffset || window.scrollY) + 80; | |
169 | if (firstVisiblePixel > top) { | |
170 | window.scrollTo(0, Math.max(top - 80, 0)); | |
f2b50825 AE |
171 | } |
172 | else { | |
ac229adb AE |
173 | var lastVisiblePixel = window.innerHeight + (window.pageYOffset || window.scrollY); |
174 | if (lastVisiblePixel < top) { | |
175 | window.scrollTo(0, top); | |
176 | } | |
177 | else { | |
178 | var bottom = top + region.offsetHeight + 20; | |
179 | if (lastVisiblePixel < bottom) { | |
180 | window.scrollBy(0, bottom - top); | |
181 | } | |
f2b50825 | 182 | } |
f2b50825 | 183 | } |
ac229adb | 184 | }; |
f2b50825 | 185 | |
f2b50825 AE |
186 | var element; |
187 | select.addEventListener('change', function() { | |
b3ac08d1 | 188 | element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container); |
f2b50825 AE |
189 | elHide(element); |
190 | ||
191 | lastValue = select.value; | |
b3ac08d1 | 192 | element = elBySel('.spSidebarBox[data-category="' + lastValue + '"]', container); |
f2b50825 AE |
193 | elShow(element); |
194 | ||
195 | // set region marker | |
ad310ee6 | 196 | _updateRegionMarker(); |
f2b50825 AE |
197 | }); |
198 | ||
f2b50825 | 199 | // apply CSS rules |
f5336f4f | 200 | var style = elCreate('style'); |
f2b50825 AE |
201 | style.appendChild(document.createTextNode('')); |
202 | elData(style, 'created-by', 'WoltLab/Acp/Ui/Style/Editor'); | |
203 | document.head.appendChild(style); | |
204 | ||
59964c3f | 205 | function updateCSSRule(identifier, value) { |
f2b50825 | 206 | if (styleRuleMap[identifier] === undefined) { |
f2b50825 AE |
207 | return; |
208 | } | |
209 | ||
b3ac08d1 | 210 | var rule = styleRuleMap[identifier].replace(/VALUE/g, value + ' !important'); |
f2b50825 | 211 | if (!rule) { |
f2b50825 AE |
212 | return; |
213 | } | |
214 | ||
b3ac08d1 AE |
215 | var rules = []; |
216 | if (rule.indexOf('__COMBO_RULE__')) { | |
217 | rules = rule.split('__COMBO_RULE__'); | |
218 | } | |
219 | else { | |
220 | rules = [rule]; | |
221 | } | |
222 | ||
223 | for (var i = 0, length = rules.length; i < length; i++) { | |
f6fc939a AE |
224 | try { |
225 | style.sheet.insertRule(rules[i], style.sheet.cssRules.length); | |
226 | } | |
227 | catch (e) { | |
228 | // ignore errors for unknown placeholder selectors | |
229 | if (!/[a-z]+\-placeholder/.test(rules[i])) { | |
230 | console.debug(e.message); | |
231 | } | |
232 | } | |
b3ac08d1 | 233 | } |
f2b50825 | 234 | } |
2935e690 | 235 | |
ac229adb | 236 | var elements = elByClass('styleVariableColor', elById('spVariablesWrapper')); |
f2b50825 AE |
237 | [].forEach.call(elements, function(colorField) { |
238 | var variableName = elData(colorField, 'store').replace(/_value$/, ''); | |
2935e690 | 239 | |
f2b50825 AE |
240 | var observer = new MutationObserver(function(mutations) { |
241 | mutations.forEach(function(mutation) { | |
242 | if (mutation.attributeName === 'style') { | |
243 | updateCSSRule(variableName, colorField.style.getPropertyValue('background-color')); | |
f2b50825 AE |
244 | } |
245 | }); | |
246 | }); | |
247 | ||
248 | observer.observe(colorField, { | |
249 | attributes: true | |
250 | }); | |
251 | ||
252 | updateCSSRule(variableName, colorField.style.getPropertyValue('background-color')); | |
253 | }); | |
ac229adb AE |
254 | |
255 | // category selection by clicking on the area | |
256 | var buttonToggleColorPalette = elBySel('.jsButtonToggleColorPalette'); | |
257 | var buttonSelectCategoryByClick = elBySel('.jsButtonSelectCategoryByClick'); | |
258 | var toggleSelectionMode = function() { | |
259 | buttonSelectCategoryByClick.classList.toggle('active'); | |
260 | buttonToggleColorPalette.classList.toggle('disabled'); | |
261 | _stylePreviewWindow.classList.toggle('spShowRegions'); | |
262 | select.disabled = !select.disabled; | |
263 | }; | |
264 | ||
265 | buttonSelectCategoryByClick.addEventListener(WCF_CLICK_EVENT, function (event) { | |
266 | event.preventDefault(); | |
267 | ||
268 | toggleSelectionMode(); | |
269 | }); | |
270 | ||
271 | elBySelAll('[data-region]', _stylePreviewWindow, function (region) { | |
272 | region.addEventListener(WCF_CLICK_EVENT, function (event) { | |
273 | if (!_stylePreviewWindow.classList.contains('spShowRegions')) { | |
274 | return; | |
275 | } | |
276 | ||
277 | event.preventDefault(); | |
278 | event.stopPropagation(); | |
279 | ||
280 | toggleSelectionMode(); | |
281 | ||
282 | select.value = elData(region, 'region'); | |
283 | Core.triggerEvent(select, 'change'); | |
284 | }); | |
285 | }); | |
3926d706 MW |
286 | }, |
287 | ||
288 | hideVisualEditor: function() { | |
59964c3f | 289 | elHide(_stylePreviewWindow); |
3926d706 MW |
290 | elById('spVariablesWrapper').style.removeProperty('transform'); |
291 | elHide(elById('stylePreviewRegionMarker')); | |
76f2a1ce AE |
292 | |
293 | _isSmartphone = true; | |
3926d706 MW |
294 | }, |
295 | ||
296 | showVisualEditor: function() { | |
59964c3f | 297 | elShow(_stylePreviewWindow); |
3926d706 MW |
298 | |
299 | window.setTimeout(function() { | |
300 | Core.triggerEvent(elById('spCategories'), 'change'); | |
301 | }, 100); | |
76f2a1ce AE |
302 | |
303 | _isSmartphone = false; | |
90b4b964 AE |
304 | } |
305 | }; | |
90b4b964 | 306 | }); |