const listingTitle = 'Wikipedia:Corrector_ortogr%C3%A1fico/Listado';
/* Get the text of the Wikipedia page containing the misspellings */
async function getMisspellingListing() {
const url = `https://es.wikipedia.org/w/index.php?title=${listingTitle}&action=raw`;
return (await fetch(url)).text();
}
/* Parse the misspelling listing and get a map of misspelling objects */
function parseMisspellings(listing) {
const misspellings = new Map();
for (let line of listing.split('\n')) {
// Take only into account lines starting with a whitespace
if (!line.startsWith(' ')) {
continue;
}
// Ignore misspelling comments
if (line.trim().startsWith('#')) {
continue;
}
const tokens = line.split('|');
if (tokens.length !== 3) {
console.warn('Invalid line: ' + line);
continue;
}
const word = tokens.trim();
const cs = tokens.trim() === 'cs';
const description = tokens.trim();
// We need to reduce as much as possible the size of the map when caching it as JSON
// The word is used as a key, and it is not needed later as a misspelling value.
// Store the case-sensitive with key "c" and boolean value "1" or "0".
const misspelling = {
c: cs ? 1 : 0,
d: description
};
// By default, the misspellings are case-insensitive.
// Use as a key the misspelling word in lowercase.
// In case of a case-sensitive misspelling,
// introduce it again using as a key the misspelling word as is.
if (cs) {
misspellings.set(word, misspelling);
} else {
misspellings.set(word.toLowerCase(), misspelling);
}
}
return misspellings;
}
const storageMapKey = 'misspellings';
const storageTimestampKey = 'misspellingsTimestamp';
function needsCacheRefresh() {
const lastLoad = localStorage.getItem(storageTimestampKey);
if (lastLoad === null) {
return true;
} else {
// Cache the entry list 1 day
const diffInSeconds = (Date.now() - parseInt(lastLoad)) / 1000;
if (diffInSeconds > 24 * 60 * 60) {
return true;
}
}
return false;
}
async function loadMisspellings() {
const misspellings = localStorage.getItem(storageMapKey);
if (misspellings !== null && !needsCacheRefresh()) {
return new Map(Object.entries(JSON.parse(misspellings)));
} else {
// Load/refresh the misspellings
getMisspellingListing().then(listing => {
const misspellings = parseMisspellings(listing);
const jsonMisspellings = JSON.stringify(Object.fromEntries(misspellings));
console.debug('MISSPELLING LIST SIZE', jsonMisspellings.length);
localStorage.setItem(storageMapKey, jsonMisspellings);
localStorage.setItem(storageTimestampKey, JSON.stringify(Date.now()));
return misspellings;
});
}
}
/* Return if the given character is a valid word character */
function isWordChar(ch) {
// Trick: there are not both uppercase and lowercase versions of punctuation characters,
// numbers, or any other non-alphabetic characters.
return ch.toLowerCase() !== ch.toUpperCase();
}
/*
* Return if the character is a valid word separator.
* Currently, we consider as a valid separator any non-word character.
*/
function isValidSeparator(ch) {
return !isWordChar(ch);
}
/* Find all the words in a text along with its position in the text */
function findAllWords(text) {
const words = ;
let start = 0;
while (start >= 0 && start < text.length) {
// Find start of the word
let startWord = -1;
for (let i = start; i < text.length; i++) {
if (isWordChar(text.charAt(i))) {
startWord = i;
break; // Exit for loop
}
}
if (startWord < 0) {
break; // Exit while loop
}
// Find end of the word
let endWord = text.length; // Default value
for (let i = startWord + 1; i < text.length; i++) {
if (isValidSeparator(text.charAt(i))) {
endWord = i;
break; // Exit for loop
}
}
words.push({
start: startWord,
word: text.substring(startWord, endWord),
});
start = endWord + 1;
}
return words;
}
/* Find the (optional) misspelling related to a given word */
function findWordMisspelling(word, misspellings) {
// First we check the word as is
let misspelling = misspellings.get(word);
if (misspelling !== undefined) {
return misspelling;
} else {
// If not, check with the word in lowercase.
misspelling = misspellings.get(word.toLowerCase());
if (misspelling !== undefined && misspelling.c === 0) {
return misspelling;
}
}
return undefined;
}
/*
* Mark (highlight) the words in the given text node corresponding to misspellings.
* For each found misspelling a new node is created. Return the number of new nodes created.
*/
function markMisspellings(node, misspellings) {
const hits = ;
for (let wordResult of findAllWords(node.data)) {
const misspelling = findWordMisspelling(wordResult.word, misspellings);
if (misspelling !== undefined) {
hits.push({
start: wordResult.start,
text: wordResult.word,
description: misspelling.d,
});
}
}
// Replace the misspelling words with span-nodes
// Traverse the list backwards to keep the consistency of the text
hits.reverse();
let newNodes = 0;
for (let hit of hits) {
// Create new span-element
const newNode = document.createElement("span");
newNode.style.backgroundColor = "#FF9191";
newNode.title = hit.description;
// Split content in three parts: begin, middle and end node
const middleNode = node.splitText(hit.start);
middleNode.splitText(hit.text.length);
// Append a copy of the middle to the new span-node
newNode.appendChild(middleNode.cloneNode(true));
// Replace the middle node with the new span-node
middleNode.parentNode.replaceChild(newNode, middleNode);
newNodes++;
}
return newNodes;
}
// Ignore some HTML elements when finding text-nodes
const nodeTagsIgnored = new Set();
nodeTagsIgnored.add('script').add('style').add('form');
function markNode(node, misspellings) {
let newNodes = 0;
if (node.nodeType === Node.TEXT_NODE) { // Text node
newNodes = markMisspellings(node, misspellings);
} else if ((node.nodeType === Node.ELEMENT_NODE) // element node
&& (node.hasChildNodes()) // with child nodes
&& (!nodeTagsIgnored.has(node.tagName.toLowerCase()))) {
for (let this_child = 0; this_child < node.childNodes.length; this_child++) {
this_child += markNode(node.childNodes, misspellings);
}
}
return newNodes;
}
function spellcheck() {
const bodyContent = document.getElementById('mw-content-text');
loadMisspellings().then(misspellings => {
if (misspellings !== undefined) {
markNode(bodyContent, misspellings);
}
});
}
function RP_load()
{
// Variablenabfrage, ob '''keine''' automatische RP bei jedem Aufruf eines Artikels gewünscht ist.
// wenn automatische RP nicht gewünscht ist, dann einfach "'''var DontAutorunRP = true;'''" vor die Einbindung schreiben
// ansonsten einfach weg lassen.
if ( !window.DontAutorunRP )
{
// Nur beim Betrachten, aber nicht auch unnötigerweise beim Bearbeiten, auf der Versionsgschichte
// etc. laden: spart Wartezeit beim Benutzer und Ressourcen auf dem Toolserver
// Standardmäßig RP nur auf Artikelseiten, wenn RPonAllPages "true" RP in allen Seiten
if (mw.config.get( 'wgAction' ) == 'view' && (($.inArray( mw.config.get( 'wgNamespaceNumber' ), ) > -1 && !mw.config.get( 'wgPageName' ).match( "^(Anexo:Falsos_amigos|Wikipedia:(Café|Consultas|Corrector_ortográfico/Listado|Informes_de_error|Tablón_de_anuncios_de_los_bibliotecarios|Taller_idiomático|Vandalismo_en_curso))" ) ) || window.RPonAllPages ))
{
spellcheck();
}
}
}
$( RP_load );