Участник:IKhitron/popup.js

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
/* global mw, OO */
/*
 * По идее и с теоретической помощью участника:Serhio Magpie, с благодарностью.
 * Создание попапов с заранее заданным содержимым при нажатии на кнопку-ссылку на странице.
 * Этот скрипт создан для шаблонов Интерактивных схем метрополитена,
 * но им можно пользоваться и для создания попапа в других случаях.
 */

if ($('.popup-extend1').length)
	$(function () {
		const change = {};
		let accmode,
			api,
			helpmode,
			helptext,
			windowManager,
			windowManagerHelp;

		function showPage(event, links) {
			const cur = $(event.currentTarget).closest('.popup-table')
				.find('.imgtogglemini'),
				version = mw.html.escape(cur.data('version')),
				pairs = [
				['&lt;templatestyles ', '<templatestyles '],
				['&lt;span ', '<span '],
				['&lt;div ', '<div '],
				['&lt;/', '</'],
				['&#039;', "'"],
				['&gt;', '>']
			];
			windowManager.getWindow(version).then(function() {
					windowManager.openWindow(version);
				})
				.fail(function() {
					const pagename = mw.html.escape(cur.data('pagename')),
						qr = cur.data('query'),
						width = window.innerWidth,
						imagewidth = Math.floor(Math.max(width * 0.6,
							Number(mw.html.escape(String(cur.data('width')))))),
						minwidth = Number(mw.html.escape(String(cur.data('textwidth'))));
					helptext = mw.html.escape(cur.data('help')) || '';
					Object.keys(qr).forEach(function(item) {
						qr[item] = mw.html.escape(qr[item]);
						Object.values(pairs).forEach(function(value) {
							qr[item] = qr[item].replaceAll(value[0], value[1]);
						});
					});
					change[version] = imagewidth + minwidth < width ? imagewidth : false;
					api.get({
							action: 'parse',
							contentmodel: 'wikitext',
							format: 'json',
							formatversion: 2,
							prop: 'text',
							text:
								`${qr[0]}|pagename=${pagename}|language${mw.config.get('wgUserLanguage')}|width=${imagewidth + qr[1]}`
						})
						.done(function(data) {
							const actions = [{
								action: 'close',
								flags: ['safe', 'close']
							}, {
								action: 'help',
								flags: 'primary',
								icon: 'infoFilled',
								title: 'Справка'
							}],
								ProcessDialog = createPopup('myDialog', pagename, actions, data.parse.text, false,
								function(pd) {
									firsttime(cur, version, pd, minwidth);
								},
								function() {
									everytime(version, width, cur.data('helppage'), links);
								});
							windowManager.addWindows({
								[version]: new ProcessDialog({
									classes: ['popup-window', `popup-${version}`],
									size: 'full'
								})
							});
							windowManager.openWindow(version);
						});
				});
		}

		function createPopup(name, title, actions, content, escapable, firstcallback = () => null, everycallback = () => null) {
			const ProcessDialog = function (config) {
				ProcessDialog.super.call(this, config);
			};
			OO.inheritClass(ProcessDialog, OO.ui.ProcessDialog);
			ProcessDialog.static.name = name;
			ProcessDialog.static.title = $('<div>').text(title);
			ProcessDialog.static.escapable = escapable;
			ProcessDialog.static.actions = actions;
			ProcessDialog.prototype.initialize = function () {
				ProcessDialog.super.prototype.initialize.apply(this, arguments);
				this.content = new OO.ui.PanelLayout({
					expanded: true,
					padded: true
				});
				this.content.$element.append(content);
				this.$body.append(this.content.$element);
				firstcallback(this);
				ProcessDialog.prototype.getSetupProcess = function (data) {
					return ProcessDialog.super.prototype.getSetupProcess.call(this, data)
						.next(everycallback, this);
				};
			};
			ProcessDialog.prototype.getActionProcess = function (action) {
				const dialog = this;
				if (action === 'help')
					openhelp();
				else if (action === 'close')
					return new OO.ui.Process(function () {
						dialog.close({
							action: 'close'
						});
					});
				return ProcessDialog.super.prototype.getActionProcess.call(this, action);
			};
			return ProcessDialog;
		}

		function firsttime(cur, version, pd, minwidth) {
			let proposed;
			const curpopup = `.popup-version-opened-${version}`;
			if (change[version]) {
				waitForElm(`${curpopup} .mw-collapsible`).then(function(coll) {
					let div = $(coll).parent()
						.children()
						.first()
						.children()
						.first();
					if ($(div).prop("tagName") !== 'DIV')
						div = div.next();
					div.css({
						float: 'left',
						maxHeight: (proposed = (window.innerHeight - pd.getContentHeight() +
							pd.getBodyHeight() - Math.floor(3.5 *
								$(($(coll).closest('table')
									.find('tr'))[0]).height()))),
						overflowY: 'auto',
						width: Number(div.css('width').match(/\d+/u)[0]) + 20
					});
					$(coll).css({
						maxHeight: proposed,
						overflowY: 'auto'
					});
					if (coll.clientWidth < minwidth + 6) {
						$(coll).css({
							clear: 'both',
							maxHeight: 'none',
							overflowY: 'inherit'
						});
						div.css({
							maxHeight: 'none',
							overflowY: 'inherit'
						});
						$(curpopup).find('.noresize')
							.css('float', 'inherit');
					}
					if (mw.html.escape(cur.data('nohr')) === 'yes')
						$($(`${curpopup} hr`)[0]).css('display', 'none');
				});
			}
			mw.util.addCSS(`${curpopup} .mw-collapsible-toggle {display:none;}`);
		}

		function everytime(version, maxwidth, helppage, links) {
			const curpopup = $(`.popup-version-opened-${version}`).closest('.oo-ui-processDialog-content');
			let des,
				popup;
			curpopup.find('.oo-ui-image-invert').removeClass('oo-ui-image-invert');
			$('body').trigger(`refresh-imagehighlight-${links ? 'links' : 'nolinks'}1`);
			if (helppage) {
				curpopup.find('.oo-ui-processDialog-actions-primary a').attr({
					dataHref: `/wiki/${helppage}`
				});
			}
			if (change[version]) {
				des = curpopup.find('*');
				popup = curpopup.find('.popupclass');
				des.each(function(data) {
					const item = $(des[data]);
					if (item.css('clear') === 'both')
						item.css('clear', 'inherit');
					if (item.css('max-width') !== 'none')
						item.css('max-width', '');
				});
				popup.find('hr').remove();
				popup.find('.noresize').css('float', 'left');
				popup.parent().css('width', Math.floor(Math.max(maxwidth * 0.96, change[version])));
			}
		}

		function waitForElm(selector) {
			return new Promise(function(resolve) {
				if (document.querySelector(selector)) {
					return resolve(document.querySelector(selector));
				}

				const observer = new MutationObserver(function() {
					if (document.querySelector(selector)) {
						resolve(document.querySelector(selector));
						observer.disconnect();
					}
				});

				observer.observe(document.body, {
					childList: true,
					subtree: true
				});
			});
		}

		function createcallback(value) {
			return function() {
				const cur = $(this);
				cur.html($('<a>').html(cur.html()));
				cur.attr({
					role: 'button',
					tabindex: '0'
				});
				cur.click(function(event) {
					if (!windowManager) {
						windowManager = new OO.ui.WindowManager();
						$(document.body).append(windowManager.$element);
					}
					showPage(event, value);
				});
				cur.on('keydown', function (event) {
						if ((event.which === 13 || event.which === 32) && ! (event.shiftKey && event.ctrlKey && event.altKey)) {
							cur.click();
							event.preventDefault();
						}
					});
			};
		}

		function openwindow(version) {
			const premise = windowManagerHelp.openWindow(version);
			premise.opened.then(function () {
				const link = $('.helpscreen .oo-ui-processDialog-actions-primary a');
				link.removeAttr('role')
					.off('click keydown keypress mousedown');
				link.attr({
						href: $('.popup-window:visible .oo-ui-processDialog-actions-primary a').attr('dataHref'),
						target: '_popuphelp'
					});
			});
			premise.closed.then(function () {
				helpmode = false;
			});
		}
		
		function openhelp() {
			const version = 'allthehelp',
				messagetext = [
				'<br/><h3>Режим доступности:</h3>',
				'<b>Стрелка вправо / влево</b> — перейти на следующую / предыдущую ссылку',
				'<b>Стрелка вниз / вверх</b> — перейти на следующую / предыдущую линию',
				'Enter — открыть выделенную ссылку',
				'v — открыть выделенную ссылку в новой вкладке браузера',
				'<b>Home / End</b> — перейти в начало / конец списка ссылок',
				'<b>Page down / up</b> — перейти на страницу вперёд / назад',
				'Shift-стрелки — перемотка схемы',
				'<b>? или i</b> — эта инструкция',
				'Esc — выход из режима доступности',
				'Ctrl-F — как правило, запускает поиск браузера',
				'(Кнопки, включающие режим, выделены жирным шрифтом)'
				];
			helpmode = true;
			if (!windowManagerHelp) {
				windowManagerHelp = new OO.ui.WindowManager();
				$(document.body).append(windowManagerHelp.$element);
			}
			windowManagerHelp.getWindow(version).then(function () {
				openwindow(version);
			})
			.fail(function () {
				let content = helptext.replace('на эту кнопку', 'на кнопку «Подробнее»');
				$.each(messagetext, function (index, elem) {
					content = `${content}<br/>${elem}`;
				});
				const actions = [{
					action: 'close',
					flags: 'safe',
					label: 'ОК'
				}, {
					action: 'morehelp',
					flags: 'primary',
					label: 'Подробнее'
				}],
					ProcessDialog = createPopup('myHelp', 'Справка об интерактивной схеме',
						actions, `${content}<br/><br/>`, true);
				mw.util.addCSS('.helpscreen {line-height: 22px}');
				windowManagerHelp.addWindows({
					[version]: new ProcessDialog({
						classes: ['helpscreen'],
						size: 'large'
					})
				});
				openwindow(version);
			});
		}
	
		function keyalone(event, shift) {
			return $('.popup-window:visible').length > 0 && ! helpmode &&
				! (event.ctrlKey || event.altKey) && (shift || ! event.shiftKey);
		}
		
		function scrollimage(dir, value, event) {
			if (keyalone(event, true)) {
				$('.popup-window:visible .mw-collapsible').parent()
					.children()
					.first()
					.children()
					.first()
					.children()[0].scrollIntoView({
						behavior: 'smooth',
						[dir]: value
					});
				event.preventDefault();
			}
		}
		
		function helpkeys() {
			
			const links = [],
				first = 0,
				last = 1,
				lastline = 2,
				cur = 3;

			function startmode(position) {
				const popup = $('.popup-window:visible');
				let newlink;
				links[first] = popup.find('li:first-child a');
				links[last] = popup.find('li:last-child a');
				links[lastline] = popup.find('li.ts-ОКИ-group').last()
					.find('a');
				newlink = links[position];
				if (links[cur] && links[cur].is(':visible'))
					newlink = links[cur];
				else
					links[cur] = undefined;
				popup.find('li a')
					.on('focus', function () {
						$(this).closest('li')
							.trigger('mouseover');
					})
					.on('blur', function () {
						$(this).closest('li')
							.trigger('mouseleave');
					});
				popup.find('.oo-ui-window-body').css({
					pointerEvents: 'none'
				});
				popup.find('li.liHighlighting').removeClass('liHighlighting');
				accmode = true;
				setTimeout(function() {
					setfocus(newlink);
				}, 0);
			}
			
			function nofocus() {
				return ! $(document.activeElement).closest('li').length;
			}

			function setfocus(link) {
				let newlink = link;
				if (! (newlink && newlink.is(':visible'))) {
						newlink = links[cur];
					if (! (newlink && newlink.is(':visible'))) {
						newlink = links[first];				
						if (! (newlink && newlink.is(':visible')))
							newlink = $('.popup-window:visible').find('li:first-child a');
					}
				}
				newlink.focus();
				links[cur] = newlink;
			}
			
			$(document).on('keydown', function (event) {
				switch (event.which) {
					case 9: // Tab and Shift-Tab
						if (keyalone(event, true)) {
							if (! accmode)
								startmode(first);
							else
								openhelp();
							event.preventDefault();
						}
						break;
					case 27: // Esc
						if (keyalone(event)) {
							if (! accmode)
								$('.popup-window:visible .oo-ui-processDialog-actions-safe span a')[0].click();
							else
								$(document.activeElement).blur();
							$('.popup-window:visible .oo-ui-window-body').css({
								pointerEvents: 'initial'
							});
							accmode = false;
							event.preventDefault();
						}
						break;
					case 33: // Page up
						if (keyalone(event)) {
							if (! accmode)
								startmode(first);
							else if (nofocus())
								setfocus(links[cur]);
							else if (links[first].is(':focus'))
								setfocus(links[lastline]);
							else
								setfocus($(document.activeElement).closest('li')
									.prevAll('li.ts-ОКИ-area:visible')
									.first()
									.find('a'));
							event.preventDefault();
						}
						break;
					case 34: { // Page down
						let nextarea;
						if (keyalone(event)) {
							if (! accmode)
								startmode(first);
							else if (nofocus())
								setfocus(links[cur]);
							else if (links[lastline].is(':focus'))
								setfocus(links[first]);
							else if ((nextarea = $(document.activeElement).closest('li')
									.nextAll('li.ts-ОКИ-area:visible')
									.first()
									.find('a')).length)
								setfocus(nextarea);
							else
								setfocus(links[first]);
							event.preventDefault();
						}
						break;
					}
					case 35: // End
						if (keyalone(event)) {
							if (! accmode)
								startmode(last);
							else
								setfocus(links[last]);
							event.preventDefault();
						}
						break;
					case 36: // Home
						if (keyalone(event)) {
							if (! accmode)
								startmode(first);
							else
								setfocus(links[first]);
							event.preventDefault();
						}
						break;
					case 37: // <-
						if (keyalone(event)) {
							if (! accmode)
								startmode(last);
							else if (nofocus())
								setfocus(links[cur]);
							else if (links[first].is(':focus'))
								setfocus(links[last]);
							else
								setfocus($(document.activeElement).closest('li')
									.prevAll('li:visible')
									.first()
									.find('a'));
							event.preventDefault();
						} else
							scrollimage('inline', 'start', event);
						break;
					case 38: // |^
						if (keyalone(event)) {
							if (! accmode)
								startmode(lastline);
							else if (nofocus())
								setfocus(links[cur]);
							else if (links[first].is(':focus'))
								setfocus(links[lastline]);
							else
								setfocus($(document.activeElement).closest('li')
									.prevAll('li.ts-ОКИ-group:visible')
									.first()
									.find('a'));
							event.preventDefault();
						} else
							scrollimage('block', 'start', event);
						break;
					case 39: // ->
						if (keyalone(event)) {
							if (! accmode)
								startmode(first);
							else if (nofocus())
								setfocus(links[cur]);
							else if (links[last].is(':focus'))
								setfocus(links[first]);
							else
								setfocus($(document.activeElement).closest('li')
									.nextAll('li:visible')
									.first()
									.find('a'));
							event.preventDefault();
						} else
							scrollimage('inline', 'end', event);
						break;
					case 40: { // |v
						let nextline;
						if (keyalone(event)) {
							if (! accmode)
								startmode(first);
							else if (nofocus())
								setfocus(links[cur]);
							else if (links[lastline].is(':focus'))
								setfocus(links[first]);
							else if ((nextline = $(document.activeElement).closest('li')
										.nextAll('li.ts-ОКИ-group:visible')
										.first()
										.find('a')).length)
									setfocus(nextline);
							else
								setfocus(links[first]);
							event.preventDefault();
						} else
							scrollimage('block', 'end', event);
						break;
					}
					case 73: // i
					case 191: // ?
						if (keyalone(event, true)) {
							if (! accmode)
								startmode(first);
							openhelp();
							event.preventDefault();
						}
					break;
					case 86: // v
						if (keyalone(event, true) && accmode && ! nofocus()) {
							window.open($(document.activeElement).prop('href'), '_blank').focus();
							event.preventDefault();
						}
						break;
					default:
				}
			});
		}
	
	mw.loader.using(['mediawiki.util', 'mediawiki.api', 'oojs', 'oojs-ui', 'oojs-ui-core', 'oojs-ui-windows'])
			.then(function () {
				if (!$('.skin-minerva').length)
					$('.popup-placeholder').remove();
				api = new mw.Api();
				$('.metrominiicon').closest('a')
					.attr('target', '_popuphelp');
				if ($('.popup-extend-links').length) {
					$('.popup-extend-links').each(createcallback(true));
					$('.popup-extend-nolinks').each(createcallback(false));
				} else
					$('.popup-extend-nolinks').each(createcallback(true));
				helpkeys();
				$('map area').each(function () {
					if ($(this).attr('title') === $(this).prev()
						.attr('title'))
						$(this).attr('tabindex', '-1');
					else $(this).on ('focus', function () {
							mw.notify($(this).attr('title'),
							{
								autoHideSeconds: 5,
								tag: 'accessinfo',
								type: 'success'
							});
						});
				});
			});
	});