CURRENT MISSION: Conclude the 2026 soyjak.party hack page, aswell as any pages relating to it, such as the Summer 2026 Crisis page.
Update Quotecord with any relevant info. See the blackboard for more info.
User:Tar/common.js: Difference between revisions
From Soyjak Wiki, the free ensoyclopedia
< User:Tar
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 1: | Line 1: | ||
(function () { | mw.loader.using('mediawiki.api', function () { | ||
'use strict'; | 'use strict'; | ||
var api = new mw.Api(); | |||
const config = { | const config = { | ||
minimumCharacters: 2, | minimumCharacters: 2, | ||
maximumResults: 5, | maximumResults: 5, | ||
searchDelay: 200, | searchDelay: 200, | ||
insertFormat: '@[[User:$1]] ' | |||
}; | }; | ||
let codeMirror = null; | |||
let activeMention = null; | |||
let codeMirror; | |||
let | |||
let results = []; | let results = []; | ||
let selectedIndex = 0; | let selectedIndex = 0; | ||
let debounceTimer; | let debounceTimer = null; | ||
let | let requestNumber = 0; | ||
const menu = document.createElement('div'); | const menu = document.createElement('div'); | ||
menu.className = 'username-autocomplete'; | menu.className = 'username-autocomplete'; | ||
menu.hidden = true; | menu.hidden = true; | ||
document.body.appendChild(menu); | document.body.appendChild(menu); | ||
const style = document.createElement('style'); | |||
style.textContent = ` | |||
.username-autocomplete { | |||
position: absolute; | |||
z-index: 999999; | |||
min-width: 200px; | |||
max-width: 350px; | |||
overflow-y: auto; | |||
background: var(--background-color-base, #ffffff); | |||
color: var(--color-base, #202122); | |||
border: 1px solid var(--border-color-base, #a2a9b1); | |||
border-radius: 3px; | |||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.25); | |||
font-family: sans-serif; | |||
font-size: 14px; | |||
} | } | ||
.username-autocomplete-result { | |||
padding: 7px 10px; | |||
cursor: pointer; | |||
white-space: nowrap; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
.username-autocomplete-result:hover, | |||
.username-autocomplete-result.selected { | |||
background: var( | |||
--background-color-interactive-subtle, | |||
#eaecf0 | |||
); | |||
} | |||
.username-autocomplete-empty { | |||
padding: 7px 10px; | |||
color: var(--color-subtle, #72777d); | |||
font-style: italic; | |||
} | |||
`; | |||
document.head.appendChild(style); | |||
function getCodeMirrorFromEvent(event) { | function getCodeMirrorFromEvent(event) { | ||
if (!(event.target instanceof Element)) { | |||
return null; | |||
return wrapper | } | ||
const wrapper = event.target.closest('.CodeMirror'); | |||
if (!wrapper || !wrapper.CodeMirror) { | |||
return null; | |||
} | |||
return wrapper.CodeMirror; | |||
} | } | ||
function getMention(cm) { | |||
function | |||
const cursor = cm.getCursor(); | const cursor = cm.getCursor(); | ||
const | const lineText = cm.getLine(cursor.line); | ||
const textBeforeCursor = lineText.slice(0, cursor.ch); | |||
const match = textBeforeCursor.match( | |||
/(?:^|[\s([{"'])@([^@\n\r\t<>{}\[\]|#/:;,!?]*)$/ | /(?:^|[\s([{"'])@([^@\n\r\t<>{}\[\]|#/:;,!?]*)$/ | ||
); | ); | ||
if (!match) { | if (!match) { | ||
return null; | return null; | ||
} | } | ||
const query = match[1]; | const query = match[1]; | ||
return { | return { | ||
query, | query, | ||
start: { | start: { | ||
line: cursor.line, | line: cursor.line, | ||
ch: cursor.ch - query.length - 1 | ch: cursor.ch - query.length - 1 | ||
}, | }, | ||
end: { | |||
end: cursor | line: cursor.line, | ||
ch: cursor.ch | |||
} | |||
}; | }; | ||
} | } | ||
function mentionsMatch(first, second) { | |||
function | |||
return ( | return ( | ||
first && | |||
second && | |||
first.query === second.query && | |||
first.start.line === second.start.line && | |||
first.start.ch === second.start.ch && | |||
first.end.line === second.end.line && | |||
first.end.ch === second.end.ch | |||
); | ); | ||
} | } | ||
function hideMenu() { | function hideMenu() { | ||
menu.hidden = true; | menu.hidden = true; | ||
menu.replaceChildren(); | menu.replaceChildren(); | ||
activeMention = null; | |||
results = []; | results = []; | ||
selectedIndex = 0; | selectedIndex = 0; | ||
} | } | ||
function positionMenu() { | function positionMenu() { | ||
if (!codeMirror) { | if (!codeMirror) { | ||
return; | return; | ||
} | } | ||
const coordinates = codeMirror.cursorCoords(null, 'page'); | |||
const | menu.style.left = `${coordinates.left}px`; | ||
menu.style.top = `${coordinates.bottom}px`; | |||
menu.style.left = | |||
menu.style.top = | |||
} | } | ||
function setSelectedIndex(index) { | function setSelectedIndex(index) { | ||
const items = menu.querySelectorAll( | const items = menu.querySelectorAll( | ||
'.username-autocomplete-result' | '.username-autocomplete-result' | ||
); | ); | ||
if (!items.length) { | if (!items.length) { | ||
return; | return; | ||
} | } | ||
selectedIndex = | |||
selectedIndex = (index + items.length) % items.length; | ((index % items.length) + items.length) % | ||
items.length; | |||
items.forEach((item, | items.forEach(function (item, itemIndex) { | ||
item.classList.toggle( | item.classList.toggle( | ||
'selected', | 'selected', | ||
itemIndex === selectedIndex | |||
); | ); | ||
}); | }); | ||
if (items.item(selectedIndex)) { | |||
items.item(selectedIndex).scrollIntoView({ | |||
block: 'nearest' | |||
}); | |||
} | |||
} | } | ||
function insertUsername(username) { | |||
function | if (!codeMirror || !activeMention) { | ||
if (!codeMirror || ! | |||
return; | return; | ||
} | } | ||
const insertion = config.insertFormat.replaceAll( | |||
const | '$1', | ||
username | |||
); | |||
codeMirror.replaceRange( | codeMirror.replaceRange( | ||
insertion, | insertion, | ||
activeMention.start, | |||
activeMention.end, | |||
'+autocomplete' | '+username-autocomplete' | ||
); | ); | ||
codeMirror.setCursor({ | codeMirror.setCursor({ | ||
line: | line: activeMention.start.line, | ||
ch: | ch: activeMention.start.ch + insertion.length | ||
}); | }); | ||
codeMirror.focus(); | codeMirror.focus(); | ||
hideMenu(); | hideMenu(); | ||
} | } | ||
function renderResults(usernames) { | |||
function renderResults( | |||
menu.replaceChildren(); | menu.replaceChildren(); | ||
results = usernames; | |||
results = | |||
selectedIndex = 0; | selectedIndex = 0; | ||
if (!usernames.length) { | |||
if (! | |||
const empty = document.createElement('div'); | const empty = document.createElement('div'); | ||
empty.className = 'username-autocomplete-empty'; | empty.className = 'username-autocomplete-empty'; | ||
empty.textContent | empty.textContent = 'No matching users'; | ||
menu.appendChild(empty); | menu.appendChild(empty); | ||
} | menu.hidden = false; | ||
positionMenu(); | |||
return; | |||
} | |||
usernames.forEach(function (username, index) { | |||
const item = document.createElement('div'); | |||
item.className = 'username-autocomplete-result'; | |||
item.textContent = username; | |||
if (index === 0) { | |||
item.classList.add('selected'); | |||
} | |||
item.addEventListener('mouseenter', function () { | |||
setSelectedIndex(index); | |||
}); | |||
item.addEventListener('mousedown', function (event) { | |||
event.preventDefault(); | |||
insertUsername(username); | |||
}); | }); | ||
} | menu.appendChild(item); | ||
}); | |||
menu.hidden = false; | menu.hidden = false; | ||
positionMenu(); | positionMenu(); | ||
} | } | ||
function searchUsers(cm, mention) { | |||
function searchUsers(cm, | const currentRequest = ++requestNumber; | ||
api.get({ | |||
action: 'query', | |||
list: 'allusers', | |||
auprefix: mention.query, | |||
aulimit: config.maximumResults, | |||
formatversion: 2 | |||
}).then(function (data) { | |||
if (currentRequest !== requestNumber) { | |||
return; | |||
} | |||
const currentMention = getMention(cm); | |||
const | if (!mentionsMatch(currentMention, mention)) { | ||
return; | |||
} | |||
codeMirror = cm; | |||
activeMention = currentMention; | |||
const usernames = ( | |||
data.query.allusers || [] | |||
).map(function (user) { | |||
return user.name; | |||
}); | }); | ||
renderResults(usernames); | |||
}).catch(function (error) { | |||
console.error( | |||
'Username autocomplete API error:', | |||
error | |||
); | |||
hideMenu(); | |||
}); | |||
} | } | ||
function processEditor(cm) { | function processEditor(cm) { | ||
const | const mention = getMention(cm); | ||
if ( | if ( | ||
! | !mention || | ||
mention.query.length < config.minimumCharacters | |||
) { | ) { | ||
hideMenu(); | hideMenu(); | ||
return; | return; | ||
} | } | ||
codeMirror = cm; | codeMirror = cm; | ||
activeMention = mention; | |||
clearTimeout(debounceTimer); | clearTimeout(debounceTimer); | ||
debounceTimer = setTimeout(function () { | |||
searchUsers(cm, mention); | |||
}, config.searchDelay); | |||
} | |||
} | } | ||
document.addEventListener( | document.addEventListener( | ||
'keyup', | 'keyup', | ||
event | function (event) { | ||
const cm = getCodeMirrorFromEvent(event); | const cm = getCodeMirrorFromEvent(event); | ||
if (!cm) { | |||
return; | |||
} | |||
if ( | if ( | ||
[ | [ | ||
'ArrowUp', | 'ArrowUp', | ||
| Line 516: | Line 255: | ||
return; | return; | ||
} | } | ||
processEditor(cm); | processEditor(cm); | ||
}, | }, | ||
true | true | ||
); | ); | ||
document.addEventListener( | document.addEventListener( | ||
'keydown', | 'keydown', | ||
event | function (event) { | ||
if ( | if (menu.hidden || !codeMirror) { | ||
return; | |||
getCodeMirrorFromEvent(event) !== codeMirror | } | ||
const cm = getCodeMirrorFromEvent(event); | |||
if (cm !== codeMirror) { | |||
return; | return; | ||
} | } | ||
switch (event.key) { | switch (event.key) { | ||
case 'ArrowDown': | case 'ArrowDown': | ||
| Line 538: | Line 275: | ||
setSelectedIndex(selectedIndex + 1); | setSelectedIndex(selectedIndex + 1); | ||
break; | break; | ||
case 'ArrowUp': | case 'ArrowUp': | ||
event.preventDefault(); | event.preventDefault(); | ||
| Line 544: | Line 280: | ||
setSelectedIndex(selectedIndex - 1); | setSelectedIndex(selectedIndex - 1); | ||
break; | break; | ||
case 'Enter': | case 'Enter': | ||
case 'Tab': | case 'Tab': | ||
| Line 550: | Line 285: | ||
event.preventDefault(); | event.preventDefault(); | ||
event.stopPropagation(); | event.stopPropagation(); | ||
insertUsername(results[selectedIndex]); | |||
} | } | ||
break; | break; | ||
case 'Escape': | case 'Escape': | ||
event.preventDefault(); | event.preventDefault(); | ||
| Line 566: | Line 297: | ||
true | true | ||
); | ); | ||
document.addEventListener('mousedown', function (event) { | |||
document.addEventListener('mousedown', event | |||
if ( | if ( | ||
!menu.contains(event.target) && | !menu.contains(event.target) && | ||
| Line 578: | Line 308: | ||
} | } | ||
}); | }); | ||
document.addEventListener( | |||
window.addEventListener('resize', () | 'scroll', | ||
function () { | |||
if (!menu.hidden) { | |||
positionMenu(); | |||
} | |||
}, | |||
true | |||
); | |||
window.addEventListener('resize', function () { | |||
if (!menu.hidden) { | if (!menu.hidden) { | ||
positionMenu(); | positionMenu(); | ||
} | } | ||
}); | }); | ||
} | }); | ||
Revision as of 00:19, 11 August 2026
mw.loader.using('mediawiki.api', function () {
'use strict';
var api = new mw.Api();
const config = {
minimumCharacters: 2,
maximumResults: 5,
searchDelay: 200,
insertFormat: '@[[User:$1]] '
};
let codeMirror = null;
let activeMention = null;
let results = [];
let selectedIndex = 0;
let debounceTimer = null;
let requestNumber = 0;
const menu = document.createElement('div');
menu.className = 'username-autocomplete';
menu.hidden = true;
document.body.appendChild(menu);
const style = document.createElement('style');
style.textContent = `
.username-autocomplete {
position: absolute;
z-index: 999999;
min-width: 200px;
max-width: 350px;
overflow-y: auto;
background: var(--background-color-base, #ffffff);
color: var(--color-base, #202122);
border: 1px solid var(--border-color-base, #a2a9b1);
border-radius: 3px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.25);
font-family: sans-serif;
font-size: 14px;
}
.username-autocomplete-result {
padding: 7px 10px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.username-autocomplete-result:hover,
.username-autocomplete-result.selected {
background: var(
--background-color-interactive-subtle,
#eaecf0
);
}
.username-autocomplete-empty {
padding: 7px 10px;
color: var(--color-subtle, #72777d);
font-style: italic;
}
`;
document.head.appendChild(style);
function getCodeMirrorFromEvent(event) {
if (!(event.target instanceof Element)) {
return null;
}
const wrapper = event.target.closest('.CodeMirror');
if (!wrapper || !wrapper.CodeMirror) {
return null;
}
return wrapper.CodeMirror;
}
function getMention(cm) {
const cursor = cm.getCursor();
const lineText = cm.getLine(cursor.line);
const textBeforeCursor = lineText.slice(0, cursor.ch);
const match = textBeforeCursor.match(
/(?:^|[\s([{"'])@([^@\n\r\t<>{}\[\]|#/:;,!?]*)$/
);
if (!match) {
return null;
}
const query = match[1];
return {
query,
start: {
line: cursor.line,
ch: cursor.ch - query.length - 1
},
end: {
line: cursor.line,
ch: cursor.ch
}
};
}
function mentionsMatch(first, second) {
return (
first &&
second &&
first.query === second.query &&
first.start.line === second.start.line &&
first.start.ch === second.start.ch &&
first.end.line === second.end.line &&
first.end.ch === second.end.ch
);
}
function hideMenu() {
menu.hidden = true;
menu.replaceChildren();
activeMention = null;
results = [];
selectedIndex = 0;
}
function positionMenu() {
if (!codeMirror) {
return;
}
const coordinates = codeMirror.cursorCoords(null, 'page');
menu.style.left = `${coordinates.left}px`;
menu.style.top = `${coordinates.bottom}px`;
}
function setSelectedIndex(index) {
const items = menu.querySelectorAll(
'.username-autocomplete-result'
);
if (!items.length) {
return;
}
selectedIndex =
((index % items.length) + items.length) %
items.length;
items.forEach(function (item, itemIndex) {
item.classList.toggle(
'selected',
itemIndex === selectedIndex
);
});
if (items.item(selectedIndex)) {
items.item(selectedIndex).scrollIntoView({
block: 'nearest'
});
}
}
function insertUsername(username) {
if (!codeMirror || !activeMention) {
return;
}
const insertion = config.insertFormat.replaceAll(
'$1',
username
);
codeMirror.replaceRange(
insertion,
activeMention.start,
activeMention.end,
'+username-autocomplete'
);
codeMirror.setCursor({
line: activeMention.start.line,
ch: activeMention.start.ch + insertion.length
});
codeMirror.focus();
hideMenu();
}
function renderResults(usernames) {
menu.replaceChildren();
results = usernames;
selectedIndex = 0;
if (!usernames.length) {
const empty = document.createElement('div');
empty.className = 'username-autocomplete-empty';
empty.textContent = 'No matching users';
menu.appendChild(empty);
menu.hidden = false;
positionMenu();
return;
}
usernames.forEach(function (username, index) {
const item = document.createElement('div');
item.className = 'username-autocomplete-result';
item.textContent = username;
if (index === 0) {
item.classList.add('selected');
}
item.addEventListener('mouseenter', function () {
setSelectedIndex(index);
});
item.addEventListener('mousedown', function (event) {
event.preventDefault();
insertUsername(username);
});
menu.appendChild(item);
});
menu.hidden = false;
positionMenu();
}
function searchUsers(cm, mention) {
const currentRequest = ++requestNumber;
api.get({
action: 'query',
list: 'allusers',
auprefix: mention.query,
aulimit: config.maximumResults,
formatversion: 2
}).then(function (data) {
if (currentRequest !== requestNumber) {
return;
}
const currentMention = getMention(cm);
if (!mentionsMatch(currentMention, mention)) {
return;
}
codeMirror = cm;
activeMention = currentMention;
const usernames = (
data.query.allusers || []
).map(function (user) {
return user.name;
});
renderResults(usernames);
}).catch(function (error) {
console.error(
'Username autocomplete API error:',
error
);
hideMenu();
});
}
function processEditor(cm) {
const mention = getMention(cm);
if (
!mention ||
mention.query.length < config.minimumCharacters
) {
hideMenu();
return;
}
codeMirror = cm;
activeMention = mention;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
searchUsers(cm, mention);
}, config.searchDelay);
}
document.addEventListener(
'keyup',
function (event) {
const cm = getCodeMirrorFromEvent(event);
if (!cm) {
return;
}
if (
[
'ArrowUp',
'ArrowDown',
'Enter',
'Tab',
'Escape'
].includes(event.key)
) {
return;
}
processEditor(cm);
},
true
);
document.addEventListener(
'keydown',
function (event) {
if (menu.hidden || !codeMirror) {
return;
}
const cm = getCodeMirrorFromEvent(event);
if (cm !== codeMirror) {
return;
}
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
event.stopPropagation();
setSelectedIndex(selectedIndex + 1);
break;
case 'ArrowUp':
event.preventDefault();
event.stopPropagation();
setSelectedIndex(selectedIndex - 1);
break;
case 'Enter':
case 'Tab':
if (results.length) {
event.preventDefault();
event.stopPropagation();
insertUsername(results[selectedIndex]);
}
break;
case 'Escape':
event.preventDefault();
event.stopPropagation();
hideMenu();
break;
}
},
true
);
document.addEventListener('mousedown', function (event) {
if (
!menu.contains(event.target) &&
!(
event.target instanceof Element &&
event.target.closest('.CodeMirror')
)
) {
hideMenu();
}
});
document.addEventListener(
'scroll',
function () {
if (!menu.hidden) {
positionMenu();
}
},
true
);
window.addEventListener('resize', function () {
if (!menu.hidden) {
positionMenu();
}
});
});