MediaWiki:Gadget-tidy-on-keydown.js
OBS: Efter du har publicerat sidan kan du behöva tömma din webbläsares cache för att se ändringarna.
- Firefox / Safari: Håll ned Skift och klicka på Uppdatera sidan eller tryck Ctrl-F5 eller Ctrl-R (⌘-R på Mac)
- Google Chrome: Tryck Ctrl-Skift-R (⌘-Skift-R på Mac)
- Internet Explorer / Edge: Håll ned Ctrl och klicka på Uppdatera eller tryck Ctrl-F5
- Opera: Tryck Ctrl-F5.
// @ts-check
module.exports = processOnKeydown;
/**
* @typedef {import("./tidy-data").Data} Data
* @typedef {import("./tidy-data").Template} Template
* @typedef {import("./tidy-data").LangCode} LangCode
* @typedef {{
* text: string;
* cursor: number;
* }} TextboxInfo
* @typedef {{
* prevBreak: number;
* nextBreak: number;
* start: string;
* end: string;
* }} LineInfo
* @typedef {[before: string, after: string]} InsertedText
* @typedef {(tb: TextboxInfo, line: LineInfo, data: Data) => InsertedText | undefined} Handler
**/
/** @type {Record<string, Handler | undefined>} */
var handlers = {
"|": handlePipe,
"=": handleEq,
">": handleGt,
};
/**
* @param {string} key
* @param {() => TextboxInfo | undefined} getTextbox
* @param {Data} data
* @returns {InsertedText | undefined}
* If the current key should be `preventDefault`ed, return the text to be
* inserted, otherwise return `undefined`.
*/
function processOnKeydown(key, getTextbox, data) {
var handler = handlers[key];
if (!handler) return;
var tb = getTextbox();
if (!tb) return;
var text = tb.text;
var cursor = tb.cursor;
// Get line info.
var prevBreak = text.lastIndexOf("\n", cursor - 1);
prevBreak =
prevBreak === -1
? // Start of text.
0
: // Skip past the `\n` char.
prevBreak + 1;
var nextBreak = text.indexOf("\n", cursor);
if (nextBreak === -1) {
nextBreak = text.length;
}
/** @type {LineInfo} */
var line = {
prevBreak: prevBreak,
nextBreak: nextBreak,
start: text.substring(prevBreak, cursor),
end: text.substring(cursor, nextBreak),
};
return handler(tb, line, data);
}
/**
* @template {string} T
* @param {string} str
* @param {T[]} search
*/
function count(str, search) {
/** @type {Record<T, number>} */
var r = /** @type {*} */ ({});
search.forEach(function (s) {
r[s] = 0;
for (var i = 0; ; i++, r[s]++) {
i = str.indexOf(s, i);
if (i === -1) {
break;
}
}
});
return r;
}
/**
* Handle `|` as part of templates that need a language code.
* @type {Handler}
*/
function handlePipe(tb, line, data) {
var templateStart = line.start.lastIndexOf("{{");
var before = count(line.start, ["{{", "}}"]);
var after = count(line.end, ["{{", "}}"]);
var canInsert =
// At least one template on the start of the line must not have been
// completed.
before["{{"] > before["}}"] &&
// Taken as a whole line, at least one template must not have been
// completed.
before["{{"] + after["{{"] > before["}}"] + after["}}"];
if (!canInsert) {
return;
}
var templateName = /** @type {Template} */ (
line.start.substring(templateStart + 2)
);
/** @type {Map<string, LangCode>} */
var langs;
/** @type {LangCode | undefined | null} */
var langCode;
if (templateName === "ö" || templateName === "ö+") {
langs = data.langCodesByLcName;
var match = /^\*\*?([^:]+):/m.exec(line.start);
if (!match) return;
langCode = langs.get(match[1]);
if (!langCode) {
if (!line.start.startsWith("**")) return;
match = findPrev(tb.text, /^\*([^:*]+):.*?\n/gm, tb.cursor);
langCode = match && langs.get(match[1]);
if (!match || !langCode) return;
// Every line between the matching language and the cursor must be a
// line starting with `**`. Otherwise, there is something weird going
// on, which we shouldn't support.
var onlySubLangsBetween = tb.text
.slice(match.index + match[0].length, tb.cursor)
.split("\n")
.every(function (x) {
return x.startsWith("**");
});
if (!onlySubLangsBetween) return;
}
return ["|" + langCode + "|", "}}"];
} else {
langs = data.langCodesByUcfirstName;
var paramInfo = data.langCodeTemplates.get(templateName);
if (!paramInfo || paramInfo === "språk") return;
var langHeader = findPrev(tb.text, /^==([^=]+)==$/gm, tb.cursor);
if (!langHeader) return;
var langName = langHeader[1];
langCode = langs.get(langName);
if (!langCode) return;
// Special cases for {{uttal}}.
if (templateName === "uttal") {
// Any language code is allowed for {{uttal}} under ====Tvärspråkligt====.
if (langCode === "--") return;
// 2+ parameters are required for {{uttal}}, though exactly 1 numeric.
paramInfo = [2, 2];
}
var min = paramInfo[0];
var max = paramInfo[1];
return [
"|" + langCode + (max === 1 ? "}}" : min > 1 ? "|" : ""),
max === 1 ? "" : "}}",
];
}
}
/**
* Handle `=` at the end of `==Källor==`.
* @type {Handler}
*/
function handleEq(tb, line) {
if (
line.start === "==Källor=" &&
line.end === "" &&
!tb.text.includes("<references")
) {
return ["=\n<references/>", ""];
}
}
/**
* Handle `>` at the end of `<ref>`.
* @type {Handler}
*/
function handleGt(tb, line) {
if (/<ref(?: [^<>/]*|)$/.test(line.start)) {
// We're about to complete a start tag (not self-closing nor end).
// Find the next ref tag (start, self-closing or end).
var re = /<(\/?)ref[ />]/;
re.lastIndex = tb.cursor;
var next = re.exec(tb.text);
if (next && next[1]) {
// If the end tag `</ref>` is next, do nothing.
return;
}
// After the cursor: start tag `<ref>` or self-closing `<ref/>`. We can
// therefor insert the end tag.
return [">", "</ref>"];
}
}
/**
* @param {string} str
* @param {RegExp} regex
* @param {number} index
*/
function findPrev(str, regex, index) {
/** @type {RegExpExecArray | null} */
var prev = null;
for (;;) {
var match = regex.exec(str);
if (!match || index <= match.index) {
break;
}
prev = match;
}
return prev;
}