User:Mikevoir/betterLinkSuggest.js

(function {   // Load dependencies and cache    window.dev = window.dev || {};    if (!window.dev._LIB) { importScriptPage('User:Mikevoir/lib.js', 'community'); }	var lib;	var config = mw.config.get(['wgAction', 'wgNamespaceNumber']);	var linkPrefix;

var betterLinkSuggest = { init: function { // Check we're in the editor screen if (['edit', 'submit'].includes(config.wgAction)) { lib.waitFor('.wikiEditor-ui-linkSuggest', this.noBlankLinkSuggest); }			// Check we're in MessageWall or Main namespace if (config.wgNamespaceNumber == 1200 || config.wgNamespaceNumber == 0) { linkPrefix = window.location.href.replace(/^(.+\/wiki\/).+$/, '$1'); lib.waitFor('#MessageWall', this.messageLinkSuggest); }		},

// Fix the link suggestion popup in editor screen to hide when blank always noBlankLinkSuggest: function { var parent = document.querySelector('.wikiEditor-ui-linkSuggest'); if (parent) { var targetNode = parent.querySelector('.oo-ui-selectWidget'); var observer = new MutationObserver(function(mutations) {					mutations.forEach(function(mutation){ var node = mutation.target; if (node.childElementCount == 0) { parent.classList.add('oo-ui-element-hidden'); }					});				});				observer.observe(targetNode, { childList: true }); }		},		// Adds link suggest functionality to message walls and comment section text editors messageLinkSuggest: function { var api = new mw.Api; var ooui = 0; var SEARCH = {}; // Add necessary styles that dont load outside editor screen var styleEl = document.createElement('style'); document.head.appendChild(styleEl); styleEl.sheet.insertRule(				'.wikiEditor-ui-linkSuggest-suggestion.oo-ui-optionWidget-highlighted, .wikiEditor-ui-linkSuggest-suggestion:hover {\n' +				'background-color: rgba(var(--theme-link-color--rgb),.15);\n' +				'color: var(--theme-link-color);\n' +				'}',				0			); // Suggestion list var suggestBox = document.createElement('div'); suggestBox.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-selectWidget oo-ui-selectWidget-unpressed'; suggestBox.setAttribute('role', 'list'); suggestBox.setAttribute('aria-multiselectable', 'false'); // Build suggestion list's box var box__ = document.createElement('div'); box__.className = 'oo-ui-clippableElement-clippable oo-ui-popupWidget-body'; box__.style.width = '320px'; box__.style['max-width'] = '1889px'; box__.style.height = 'auto'; box__.style['max-height'] = '250px'; box__.style['overflow-y'] = 'scroll'; box__.appendChild(suggestBox); var box_ = document.createElement('div'); box_.className = 'oo-ui-popupWidget-popup wikiEditor-ui-linkSuggest-popup'; box_.style.padding = 0; box_.appendChild(box__); var anchorArrow = document.createElement('div'); anchorArrow.className = 'oo-ui-popupWidget-anchor'; anchorArrow.style.left = '12px'; var box = document.createElement('div'); box.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-floatableElement-floatable oo-ui-popupWidget-anchored oo-ui-popupWidget wikiEditor-ui-linkSuggest oo-ui-popupWidget-anchored-top'; box.style.position = 'absolute'; box.style['margin-top'] = '21px'; box.style['z-index'] = '9999'; box.appendChild(box_); box.appendChild(anchorArrow); // Hide by default box.classList.add('mobile-only', 'hidden'); // Append box to area document.body.insertBefore(box, document.body.children[0]); var methods = { SEARCH: {}, initEvents: function { // Hide and empty out when unfocusing list document.addEventListener('click', function(event) {						if (!event.target.closest('.wikiEditor-ui-linkSuggest')) {							suggestBox.removeAttribute('aria-activedescendant');							box.classList.add('mobile-only', 'hidden');							suggestBox.replaceChildren;						}					}); // Suggestion list hovering logic suggestBox.addEventListener('mouseover', function(event){						if ( event.target.classList.contains('oo-ui-labelElement-label') && event.target.closest('.wikiEditor-ui-linkSuggest-suggestion') ) {							methods.handleOOUI(event);							event.target.closest('.wikiEditor-ui-linkSuggest-suggestion').classList.add('oo-ui-optionWidget-highlighted');						}					}); suggestBox.addEventListener('mouseout', function(event){						if ( event.target.classList.contains('oo-ui-labelElement-label') && event.target.closest('.wikiEditor-ui-linkSuggest-suggestion') && event.target.closest('.wikiEditor-ui-linkSuggest-suggestion').classList.contains('oo-ui-optionWidget-highlighted') ) {							event.target.closest('.wikiEditor-ui-linkSuggest-suggestion').classList.remove('oo-ui-optionWidget-highlighted');						}					}); // Start looking document.addEventListener('mouseup', function(event){						if ( event.target.classList.contains('oo-ui-labelElement-label') || (								event.target.parentNode &&								event.target.parentNode.classList.contains('wikiEditor-ui-linkSuggest-suggestion')							) ) {methods.dispatchLink;}						else if (event.target.closest('.ProseMirror')) {methods.suggestLink(event);}					}); document.addEventListener('keydown', function(event){						if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'].includes(event.key) && event.target.classList.contains('ProseMirror')) {							if (box.classList.contains('hidden')) {								methods.suggestLink(event);							} else if (!box.classList.contains('hidden') && ['ArrowLeft', 'ArrowRight'].includes(event.key)) {								box.classList.add('mobile-only', 'hidden');								suggestBox.removeAttribute('aria-activedescendant');								suggestBox.replaceChildren;								methods.suggestLink(event);							} else if (!box.classList.contains('hidden') && ['ArrowDown', 'ArrowUp'].includes(event.key)) {								event.preventDefault;								methods.handleOOUI(event);							}						} else if (event.key == 'Enter' && event.target.classList.contains('ProseMirror') && suggestBox.getAttribute('aria-activedescendant')) {							event.preventDefault; methods.dispatchLink; } 					});				},				dispatchLink: function(label, url) {					var clickedNode = suggestBox.querySelector('#'+(suggestBox.getAttribute('aria-activedescendant')||'NOTHINGNESS'));					var linkNode = document.createElement('a');					var newNode = methods.SEARCH.node.splitText(methods.SEARCH.offset);					var parentNode = methods.SEARCH.node.parentNode;					newNode.splitText(methods.SEARCH.str.length);					if (label && url) {						linkNode.href = url;						linkNode.append(label);					} else if (clickedNode) {						linkNode.href = clickedNode.getAttribute('link-to');						label = /^[^\|]*\|([\s\S]+?)(\]\]|$)/.exec(methods.SEARCH.str);						linkNode.append((label && label[1]) || document.createTextNode(clickedNode.children[0].innerHTML));					}					parentNode.append(' '); // need to add some text so that the link doesnt get stripped bc prosemirror is cancer to modify					parentNode.insertBefore(linkNode, methods.SEARCH.node.nextSibling); newNode.remove; // Remove original link text used for search // Close suggestion list suggestBox.removeAttribute('aria-activedescendant'); box.classList.add('mobile-only', 'hidden'); suggestBox.replaceChildren; },				handleOOUI: function(event) { if (!suggestBox.getAttribute('aria-activedescendant')) { var firstOpt = suggestBox.querySelector('.oo-ui-labelElement'); if (!firstOpt.id) { ooui = ooui + 1; firstOpt.id = 'ooui-' + ooui; }						firstOpt.classList.add('oo-ui-optionWidget-highlighted'); suggestBox.setAttribute('aria-activedescendant', firstOpt.id); } else if (suggestBox.getAttribute('aria-activedescendant')) { var Tooui = suggestBox.getAttribute('aria-activedescendant'); var currentNode = suggestBox.querySelector('#'+Tooui); var newNode; var scrollChange = 0; if (event.type == 'keydown' && event.key == 'ArrowUp' && currentNode && currentNode.previousSibling) { newNode = currentNode.previousSibling; }						else if (event.type == 'keydown' && event.key == 'ArrowDown' && currentNode && currentNode.nextSibling) { newNode = currentNode.nextSibling; } else if (event.type == 'mouseover' && currentNode && event.target.closest('.wikiEditor-ui-linkSuggest-popup .oo-ui-selectWidget')){ newNode = event.target; if (newNode.classList.contains('oo-ui-labelElement-label')) { newNode = event.target.parentNode; } }						if (newNode) { if (box__.clientHeight < (newNode.offsetTop+newNode.clientHeight)) {scrollChange = newNode.clientHeight;} else if (box__.scrollTop > newNode.offsetTop) {scrollChange = -currentNode.clientHeight;} currentNode.classList.remove('oo-ui-optionWidget-highlighted'); newNode.classList.add('oo-ui-optionWidget-highlighted'); if (!newNode.id) { ooui = ooui + 1; newNode.id = 'ooui-' + ooui; }							box__.scrollTop = box__.scrollTop + scrollChange; suggestBox.setAttribute('aria-activedescendant', newNode.id); }					}				},				suggestLink: function(event) { var caret; var link_regex = /^.*(\[\[)([^\[\]]+)(\]\]|$)/; var ext_link_regex = /^.*(\[)([^\[\]]+)(\])$/; var raw_str = ''; var link_str = ''; var matches; while (true) { caret = methods.getCaret(event.target); if ( caret && caret.data && caret.data.focusNode ) { break; }					}					if ( caret && caret.data && caret.data.focusNode && caret.data.focusNode.nodeValue ) { if (link_regex.test(caret.data.focusNode.nodeValue)) { raw_str = caret.data.focusNode.nodeValue; matches = link_regex.exec(raw_str.slice(0, caret.position)); matches.shift; link_str = matches[1].replace(/^(.*)\|.*$/, '$1'); methods.SEARCH.str = matches.join(''); methods.SEARCH.offset = raw_str.indexOf(methods.SEARCH.str); methods.SEARCH.node = caret.data.focusNode; if (caret.data.type == 'Caret' && caret.position > 0 && link_str.length > 0 && raw_str.length > link_str.length) { methods.getPages(link_str); document.querySelector('.wikiEditor-ui-linkSuggest').style.top = (event.pageY) + 'px'; document.querySelector('.wikiEditor-ui-linkSuggest').style.left = (event.pageX-12) + 'px'; } else if (caret.data.type == 'Range') { methods.getPages(link_str); document.querySelector('.wikiEditor-ui-linkSuggest').style.top = (event.pageY) + 'px'; document.querySelector('.wikiEditor-ui-linkSuggest').style.left = (event.pageX-12) + 'px'; }						} else if (ext_link_regex.test(caret.data.focusNode.nodeValue.slice(0, caret.position))) { raw_str = caret.data.focusNode.nodeValue.slice(0, caret.position); matches = ext_link_regex.exec(raw_str.slice(0, caret.position)); methods.SEARCH.str = matches[1]+matches[2]+matches[3]; methods.SEARCH.offset = raw_str.indexOf(methods.SEARCH.str); methods.SEARCH.node = caret.data.focusNode; var url = prompt('Insert external URL to link to'); if (url && url.length>0) { methods.dispatchLink(matches[2], url); }						}					}				},				getCaret: function(element) { var caretOffset = 0; var doc = element.ownerDocument || element.document; var win = doc.defaultView || doc.parentWindow; var sel; if (typeof win.getSelection != "undefined") { sel = win.getSelection; if (sel.rangeCount > 0) { var range = win.getSelection.getRangeAt(0); var preCaretRange = range.cloneRange; preCaretRange.selectNodeContents(element); preCaretRange.setEnd(range.endContainer, range.endOffset); caretOffset = preCaretRange.toString.length; }					} else if ( (sel = doc.selection) && sel.type != "Control") { var textRange = sel.createRange; var preCaretTextRange = doc.body.createTextRange; preCaretTextRange.moveToElementText(element); preCaretTextRange.setEndPoint("EndToEnd", textRange); caretOffset = preCaretTextRange.text.length; }					return { data: sel, position: caretOffset };				},				getPages: function(prefix) { api.get({						action: 'query',						list: 'prefixsearch',						pssearch: prefix					}).then(function(data) {						if (data && data.query && data.query.prefixsearch.length > 0) {							methods.buildSuggestions(data.query.prefixsearch);						} else {							return;						}					}); },				buildSuggestions: function(pages) { // Build list pages.forEach(						function(page){							var link = document.createElement('div');							link.className = 'oo-ui-widget oo-ui-widget-enabled oo-ui-labelElement oo-ui-optionWidget wikiEditor-ui-linkSuggest-suggestion';							link.role = 'option';							link.tabindex = -1;							link.setAttribute('link-to', (page.linkto || (linkPrefix + page.title)));							var label = document.createElement('span');							label.className = 'oo-ui-labelElement-label';							label.innerHTML = page.title;							link.appendChild(label);							suggestBox.appendChild(link);						}					); document.querySelector('.wikiEditor-ui-linkSuggest-suggestion').focus; box.classList.remove('mobile-only', 'hidden'); }			};			methods.initEvents; }	};	mw.loader.using('mediawiki.api').then(function{		mw.hook('userjs._LIB').add(function(_LIB){ lib = _LIB; betterLinkSuggest.init; });	}); });