MediaWiki:Gadget-skinTogglesNew.js

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
  1 /**
  2  * Toggles for skin cookies
  3  * 
  4  * @author Gaz Lloyd
  5  * @author JaydenKieran
  6  * 
  7  */
  8 ;(function($, mw, rs){
  9 	var READER_COOKIE = 'readermode',
 10 		DARK_COOKIE = 'darkmode',
 11 		DARK_COOKIE_CONDITIONAL = 'darkmode_conditional',
 12 		STICKY_HEADER_COOKIE = 'stickyheader',
 13 		FLOORNUMBER_LS = 'floornumber_display',
 14 		currentReader = $.cookie(READER_COOKIE) === 'true',
 15 		currentDark = $.cookie(DARK_COOKIE) === 'true',
 16 		currentDarkConditional = $.cookie(DARK_COOKIE_CONDITIONAL) === 'true',
 17 		currentSticky = $.cookie(STICKY_HEADER_COOKIE) === 'true',
 18 		currentFloornumber = '_auto',
 19 		prompt = 'dark_prompt',
 20 		now = new Date(),
 21 		hour = now.getHours(),
 22 		conditionalCheck = (hour >= 19 || hour < 7),
 23 		popupButton,
 24 		readerSwitch,
 25 		darkConditionalSwitch,
 26 		darkSwitch,
 27 		stickySwitch,
 28 		floorSelect,
 29 		floorSelectAuto,
 30 		floorSelectUK,
 31 		floorSelectUS,
 32 		applyButton,
 33 		cancelButton,
 34 		portletLink,
 35 		$content,
 36 		formMade = false,
 37 		userLocale = 'UK',
 38 		flsetting,
 39 		browserLocale,
 40 		floorSelectHelp;
 41 
 42 	var self = {
 43 		init: function () {
 44 			if (rs.hasLocalStorage()) {
 45 				currentFloornumber = window.localStorage.getItem(FLOORNUMBER_LS);
 46 				if (currentFloornumber == null) {
 47 					currentFloornumber = '_auto';
 48 				}
 49 			}
 50 			flsetting = currentFloornumber;
 51 			if (window.navigator.languages && window.navigator.languages.length) {
 52 				browserLocale = window.navigator.languages[0];
 53 			} else {
 54 				browserLocale = navigator.userLanguage || navigator.language || navigator.browserLanguage || 'en';
 55 			}
 56 			switch (browserLocale) {
 57 				// all langs in -US or -CA
 58 				case 'en-US':
 59 				case 'es-US':
 60 				case 'en-CA':
 61 				case 'fr-CA':
 62 					userLocale = 'US';
 63 					break;
 64 			}
 65 			if (currentFloornumber == '_auto') {
 66 				flsetting = userLocale;
 67 			}
 68 			switch (flsetting) {
 69 				case 'US':
 70 					flsetting = 'floornumber-setting-us';
 71 					break;
 72 				case 'UK':
 73 				default:
 74 					flsetting = 'floornumber-setting-gb';
 75 					break;
 76 			}
 77 			$('body').addClass(flsetting);
 78 			
 79 			portletLink = mw.util.addPortletLink(
 80 				'p-personal',
 81 				'',
 82 				'',
 83 				'pt-skin-toggles',
 84 				'Your appearance settings',
 85 				null,
 86 				$('#pt-userpage, #pt-anonuserpage')
 87 			);
 88 			
 89 			$(portletLink).find('a').addClass('oo-ui-icon-advanced').add('.floor-convention').click(function(e) {
 90 				e.preventDefault();
 91 				if (!formMade) {
 92 					mw.loader.using(['oojs-ui-core','oojs-ui-windows','oojs-ui-widgets']).then(self.initForm);
 93 				} else {
 94 					window.OOUIWindowManager.openWindow('skin');
 95 				}
 96 			});
 97 			
 98 
 99 			if (currentReader) {
100                 $('body').addClass('wgl-readermode');
101 				mw.util.addPortletLink(
102 					'p-namespaces',
103 					'/',
104 					'Menu',
105 					'ca-reader-menu'
106 				);
107 
108 				// can't use the nextnode parameter in addPortletLink
109 				// because the id of the first tab varies
110 				$('#ca-reader-menu')
111 					.prependTo('#p-namespaces ul');
112 
113 				// move sidebar
114 				$('#mw-panel')
115 					.attr('id', 'ca-reader-dropdown')
116 					.appendTo('#ca-reader-menu');
117 			}
118 
119 			if (currentDark) {
120 				$('body').addClass('wgl-darkmode');
121 			}
122 			if (currentDarkConditional) {
123 				var reloadRequired = (conditionalCheck != currentDark)
124 				$.cookie(DARK_COOKIE, conditionalCheck, {expires: 365, path: '/'});
125 				if (reloadRequired === true) {
126 					window.location.reload(true);
127 				}
128 			}
129 
130 			if (currentSticky) {
131 				window.addEventListener("scroll", function() {
132 					var personal = $('#p-personal');
133 				    if (mw.config.get('wgAction') === 'edit' || window.location.search.includes('veaction')) {
134 				  		// We're on an edit page, do nothing and reset all the stuff
135 						if (personal.is(":hidden")) {
136 							personal.show();
137 							head.removeClass('sticky-hidden');
138 						}
139 				    } else {
140 					  	var targetEle = document.getElementById("mw-head");
141 					  	var head = $('#mw-head');
142 					  	if (window.scrollY > (targetEle.offsetTop + targetEle.offsetHeight)) {
143 							if (personal.is(":visible")) {
144 								personal.hide();
145 								head.addClass('sticky-hidden');
146 							}
147 					  	} else {
148 							if (personal.is(":hidden")) {
149 								personal.show();
150 								head.removeClass('sticky-hidden');
151 							}
152 					  	}
153 				  	}
154 				});
155 				// hidden by css when sticky-hidden is not on
156 				if (mw.config.get('wgIsMainPage') !== true) {
157 					mw.util.addPortletLink(
158 						'p-namespaces',
159 						'/',
160 						'Main Page',
161 						'ca-nstab-mainpage',
162 						'Visit the main page'
163 					);
164 				}
165 			}
166 			
167 			/**
168 			 * Used for prompting users who have prefers-color-scheme set to dark
169 			 * to switch to dark mode (because doing this automatically would
170 			 * require setting a cookie, prompting this is best for privacy/
171 			 * legal reasons)
172 			 **/
173 			 
174 			if (rs.hasLocalStorage()) {
175 			 // This should always be true anyway because browsers that
176 			 // support prefers-color-scheme have LocalStorage API support
177 				if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
178 					if (!currentDark) {
179 						// Only show if they're not currently using dark mode
180 						var alreadyPrompted = localStorage.getItem(prompt)
181 						if (alreadyPrompted === null) {
182 							// Only show if the localStorage key doesn't exist
183 							mw.loader.load( 'ext.gadget.skinTogglesNew-prompt' )
184 						}
185 					}
186 				}
187 			}
188 		},
189 		initForm: function() {
190 			// Treat opening the form as having seen the dark mode prompt.
191 			if (rs.hasLocalStorage() && localStorage.getItem(prompt) === null) {
192 				localStorage.setItem(prompt, 'true')
193 			}
194 
195 			readerSwitch = new OO.ui.ToggleSwitchWidget({
196 				value: currentReader,
197 				classes: ['reader-toggle'],
198 				align: 'right'
199 			});
200 
201 			stickySwitch = new OO.ui.ToggleSwitchWidget({
202 				value: currentSticky,
203 				classes: ['reader-toggle'],
204 				align: 'right'
205 			});
206 
207 			darkConditionalSwitch = new OO.ui.ToggleSwitchWidget({
208 				value: currentDarkConditional,
209 				classes: ['reader-toggle'],
210 				align: 'right'
211 			})
212 
213 			darkSwitch = new OO.ui.ButtonSelectWidget({
214 				classes: ['appearance-buttons'],
215 				items: [
216 					new OO.ui.ButtonOptionWidget({
217 						classes: ['light-mode-button'],
218 						data: false,
219 						label: new OO.ui.HtmlSnippet('<div class="button-img"></div><div class="button-text">Light</div><div class="button-text-selected"></div>'),
220 					}),
221 					new OO.ui.ButtonOptionWidget({
222 						classes: ['dark-mode-button'],
223 						data: true,
224 						label:new OO.ui.HtmlSnippet('<div class="button-img"></div><div class="button-text">Dark</div><div class="button-text-selected"></div>'),
225 						//disabled: true
226 					}),
227 				]
228 			});
229 			
230 			floorSelectAuto = new OO.ui.RadioOptionWidget({
231 						data: '_auto',
232 						label: 'Auto-detect: '+userLocale
233 					});
234 			floorSelectUK = new OO.ui.RadioOptionWidget({
235 						data: 'UK',
236 						label: 'UK'
237 					});
238 			floorSelectUS = new OO.ui.RadioOptionWidget({
239 						data: 'US',
240 						label: 'US'
241 					});
242 			
243 			floorSelect = new OO.ui.RadioSelectWidget({
244 				classes: ['floornumber-select'],
245 				items: [
246 					floorSelectAuto,
247 					floorSelectUK,
248 					floorSelectUS
249 					]
250 			});
251 			floorSelect.selectItemByData(currentFloornumber);
252 			floorSelectHelp = 'Changes how floor numbers are displayed on the wiki - whether the numbering begins at 0 (ground) or 1.';
253 			if (!rs.hasLocalStorage()) {
254 				floorSelect.setDisabled(true);
255 				floorSelectHelp = 'This option requires local storage to be supported and enabled in your browser.';
256 			}
257 			floorSelectAuto.$element.attr('title', 'Automatically detect the type to use from your browser.');
258 			floorSelectUK.$element.attr('title', 'The numbering used in the UK, Europe, and many Commonwealth countries: entrance on the ground floor, then above that is 1st floor, 2nd floor, etc.');
259 			floorSelectUS.$element.attr('title', 'The numbering used in the US and Canada: entrance on the 1st floor, then above that is 2nd floor, 3rd floor, etc.');
260 
261 			darkSwitch.setDisabled(darkConditionalSwitch.getValue())
262 
263 			darkConditionalSwitch.on('change', function() {
264 				darkSwitch.setDisabled(darkConditionalSwitch.getValue())
265 			})
266 
267 			stickySwitch.setDisabled(readerSwitch.getValue())
268 			readerSwitch.setDisabled(stickySwitch.getValue())
269 
270 			readerSwitch.on('change', function() {
271 				if (readerSwitch.getValue() === true) {
272 					stickySwitch.setValue(false)
273 				}
274 				stickySwitch.setDisabled(readerSwitch.getValue())
275 			})
276 
277 			stickySwitch.on('change', function() {
278 				if (stickySwitch.getValue() === true) {
279 					readerSwitch.setValue(false)
280 				}
281 				readerSwitch.setDisabled(stickySwitch.getValue())
282 			})
283 
284 			darkSwitch.selectItemByData(currentDark);
285 
286 			applyButton = new OO.ui.ButtonInputWidget({
287 				label: 'Save',
288 				flags: ['primary', 'progressive'],
289 				classes: ['skin-save-button']
290 			});
291 
292 			applyButton.on('click', function(){
293 				$.cookie(READER_COOKIE, readerSwitch.getValue(), {expires: 365, path: '/'});
294 				$.cookie(DARK_COOKIE_CONDITIONAL, darkConditionalSwitch.getValue(), {expires: 365, path: '/'});
295 				$.cookie(STICKY_HEADER_COOKIE, stickySwitch.getValue(), {expires: 365, path: '/'});
296 				
297 				var darkval = darkSwitch.findSelectedItem(),
298 					darkc = false,
299 					requireReload = false;
300 					
301 				if ((readerSwitch.getValue() !== currentReader) || (stickySwitch.getValue() !== currentSticky)) {
302 					requireReload = true;
303 				}
304 
305 				if (darkConditionalSwitch.getValue() === false) {
306 					if (darkval !== null) {
307 						darkc = darkval.getData();
308 					}
309 				} else if (darkConditionalSwitch.getValue() === true) {
310 					darkc = conditionalCheck
311 				}
312 				
313 				$.cookie(DARK_COOKIE, darkc, {expires: 365, path: '/'});
314 				
315 				if (rs.hasLocalStorage()) {
316 					window.localStorage.setItem(FLOORNUMBER_LS, floorSelect.findSelectedItem().getData());
317 				}
318 
319 				if (darkc === true) {
320 
321 					  $('body').addClass('wgl-darkmode')
322 					  $('body').removeClass('wgl-lightmode')
323 					
324 				} else {
325 					$('body').addClass('wgl-lightmode')
326 					$('body').removeClass('wgl-darkmode')
327 				}
328 				
329 				if (requireReload === true) {
330 					window.location.reload(true);
331 				} else {
332 					window.OOUIWindowManager.closeWindow('skin')
333 				}
334 			});
335 
336 			cancelButton = new OO.ui.ButtonInputWidget({ label: 'Cancel', flags: 'destructive'});
337 			
338 			$content = $('<div>');
339 			$content
340 				.addClass('appearance-modal tile')
341 				.append(
342 					$('<h2>').text('Appearance'),
343 					$('<div>')
344 						.addClass('appearance-buttons')
345 						.append(darkSwitch.$element),
346 					$('<div>')
347 						.addClass('reader-mode')
348 						.append(
349 							darkConditionalSwitch.$element,
350 							$('<div>').addClass('setting-header dark-conditional-header').text('Automatic dark mode'),
351 							$('<p>').addClass('dark-conditional-desc').text('Automatically switch to dark mode from 19:00 to 7:00 local time. Disables the manual setting above.'),
352 							readerSwitch.$element,
353 							$('<div>').addClass('setting-header reader-mode-header').text('Reader mode'),
354 							$('<p>').addClass('reader-mode-desc').text('Increase the font size and switch the wiki to a fixed-width layout. Incompatible with sticky headers.'),
355 							stickySwitch.$element,
356 							$('<div>').addClass('setting-header sticky-header-header').text('Sticky header'),
357 							$('<p>').addClass('sticky-header-desc').text('Pin the navigation bar and search to the top when scrolling. Incompatible with reader mode.'),
358 							floorSelect.$element,
359 							$('<div>').addClass('setting-header floornumber-header').text('Floor numbering'),
360 							$('<p>').addClass('floornumber-desc').text(floorSelectHelp)
361 						),
362 					$('<div>')
363 						.addClass('appearance-save')
364 						.append(
365 							$('<p>').addClass('save-button-desc').html('Saving these changes will reload the page and set <a href="https://weirdgloop.org/privacy">personalisation cookies</a>.'),
366 							$('<div>').addClass('save-button-container')
367 								.append(applyButton.$element)
368 								.append(cancelButton.$element)
369 						)
370 				);
371 
372 			var initModal = function (modal) {
373 				modal.$body.append( $content );
374 				cancelButton.on('click', function(modal){window.OOUIWindowManager.closeWindow(modal);}, [modal]);
375 			};
376 
377 			rs.createOOUIWindow('skin', 'Appearance settings', {size: 'large', classes: ['rsw-skin-toggle-popup']}, initModal, true);
378 			
379 			formMade = true;
380 		}
381 	}
382 
383 	mw.loader.using(['ext.gadget.rsw-util'], function () {
384 		$(self.init);
385 	})
386 
387 }(jQuery, mediaWiki, rswiki));