Content deleted Content added
PleaseStand (talk | contribs) m window.document is silly. We should be able to assume a sane browser environment. |
PleaseStand (talk | contribs) add refsDiv directly under #editform, not within .wikiEditor-ui (fixes overlap issue) |
||
(10 intermediate revisions by the same user not shown) | |||
Line 27:
*/
/*global window, addOnloadHook, SegregateRefsJsL10n, SegregateRefsJsAllowConversion,
wikEdUseWikEd, WikEdUpdateTextarea, WikEdUpdateFrame*/
// <nowiki>
// Translate the right-hand side of these if necessary.
Line 36:
// var SegregateRefsJsL10n = {
var SegregateRefsJsMsgs = {
convertRefsWarning: "WARNING: You need consensus to migrate an article to list-defined references format (LDR) BEFORE you do so.\n\nClick Cancel now if consensus has not been established in favor of this migration. If there is consensus to make the conversion, click OK to do so.",
groupPrompt: "Please enter the name of a group (as it appears in the wikitext, including any quotes). Leave this blank if unsure.",
refsHeader: "Inline footnotes",
convertHeader: "Generated refs list",
refsCommentComplete: "<!-- Converted to LDR format\n using [[User:PleaseStand/References segregator]] -->\n\n",
convertSummary: "Converted footnotes to LDR format (using [[User:PleaseStand/References segregator|segregate-refs]])",
convertFurther: "This script has done most of the work. However, you still need to do the following:\n\n* Insert the refs list in the new textbox into the proper place in the wikitext.\n* If converting a special group, optionally remove the group attributes.\n* Replace all autogenerated names with human-generated names.\n\nYou can do the above with the Find/Replace command in many text editors. (Always use the quoted form of the attributes.) Then, paste the text back into the edit form and save the page.",
integrateWarning: "The refs listed below are missing from the text. If you continue, they will be permanently deleted. Are you sure?\n\nUnused refs: "
};
( function ( $ ) {
var editForm, refsDiv, refsH2, mainTextbox, refsTextbox, randPrefix, messages,
refsButton, convertButton
/**
* Unquote a wikitext tag attribute.
*
* @param string quotedValue
* @return string
*/
function htmlUnquote( quotedValue ) {
var d = document.createElement( 'div' );
d.innerHTML = '<input value=' + quotedValue + '></input>';
return d.firstChild.value;
}
/**
* Quote a wikitext tag attribute, choosing single quotes versus
* double quotes depending on which is shorter.
*
* @param string value
* @return string
*/
function htmlQuote( value ) {
var sQ, dQ;
value = value.replace( /\&/g, '&' );
sQ = "'" + value.replace( /'/g, ''' ) + "'",
dQ = '"' + value.replace( /"/g, '"' ) + '"';
return sQ.length < dQ.length ? sQ : dQ;
}
// Looks for ref tags in the text, skipping problematic extension tags.
// For example, "references" may contain out-of-line refs, which should be skipped.
function RefScanner( argWikiText ) {
this.wikiText = argWikiText;
this.refScanRegex = /(?:<!--[\s\S]*?-->|<(nowiki|source|references|ref)(?:|\s(?:[^"']|"[^"]*"|'[^']*')*?)(?:\/>|(?:>[\s\S]*?<\/\1(?:|\s[^>]*)>)))/gi;
}
//
RefScanner.prototype.getRef = function
var results;
do {
results = this.refScanRegex.exec( this.wikiText );
if ( !results ) {
return null;
}
if ( results[1] === undefined ) {
}
} while ( results[1].toString().toLowerCase() !== 'ref' );
return results[0];
};
// Extracts attributes from ref tags.
function RefParser( argWikiText ) {
// This is mostly a copy of refScanRegex, except that the whole string must be a ref,
// and no more, and two parts are extracted: $1=attributes, $2=remaining portion of ref
var refParseRegex = /^<ref(|\s(?:[^"']|"[^"]*"|'[^']*')*?)(\/>|(?:>[\s\S]*?<\/ref(?:|\s[^>]*)>))$/i;
this.wikiText = argWikiText;
this.parsedRef = refParseRegex.exec( this.wikiText );
if ( !this.parsedRef ) {
throw new Error( 'invalid ref' );
}
}
// Get all attributes of the tag.
RefParser.prototype.getAttributes = function () {
// In this regex, we need to extract a single name-value pair at a time.
var attParseRegex = /\s([^\s=>]+)\s*=\s*("[^"]*"|'[^']*'|[^\s"']*)/g;
if ( !this.parsedRef ) {
return null;
}
var attributes = Object.create( null ), results;
while ( ( results = attParseRegex.exec( this.parsedRef[1] ) ) ) {
attributes[results[1].toLowerCase()] = htmlUnquote( results[2] );
}
return attributes;
};
/**
* Segregate refs from content.
*
* @param string argWikiText The original wikitext
* @param string group The name of the ref group to process (default group is '')
* @param bool caseCues Mark the original ref code locations using capitalization?
* @return Object
*/
function segregateRefs( argWikiText, group, caseCues ) {
var prefixChars, randNo, randPrefix, refPreferred, scanner, ref, parser, attributes,
refGroup, refName, refStored, refEmpty, refLong, unnamedRefs = 0,
refNames = Object.create( null ), refCodes = [], refShort, outWikiText = '', offset = 0;
// Create a random prefix for autogenerated ref names
prefixChars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
randNo = Math.floor( Math.random() * ( prefixChars.length * prefixChars.length ) );
randPrefix = messages.autoWord
+ prefixChars.charAt( Math.floor( randNo / prefixChars.length ) )
+ prefixChars.charAt(randNo % prefixChars.length) +
'-';
// Create the beginning of the code for a preferred ref ___location
scanner = new RefScanner( argWikiText );
while ( ( ref = scanner.getRef() ) ) {
parser = new RefParser( ref );
attributes = parser.getAttributes();
refGroup = attributes.group || '';
// The ref is in a different group
continue;
}
if ( attributes.name !== undefined ) {
//
refName = attributes.name;
refStored = refName in refNames;
refEmpty = parser.parsedRef[2].slice( -2 ) == '/>' ||
parser.parsedRef[2].slice( 0, 3 ) == '></';
refLong = ref;
} else {
// We have to autogenerate one
refName = randPrefix + ( ++unnamedRefs ).toString(10);
refStored = false;
refEmpty = false;
refLong = '<ref name=' + htmlQuote( refName ) +
parser.parsedRef[1] + parser.parsedRef[2];
}
if ( !refStored ) {
// Found the first ref of this name
refNames[refName] = {
code: refCodes.length,
Line 260 ⟶ 195:
};
refCodes[refNames[refName].code] = refLong;
// Make a short code for the ref
if ( refEmpty ) {
refShort = refLong;
} else if (
refShort = refPreferred +
} else {
refShort = refPreferred +
}
} else if ( !refEmpty && refNames[refName].empty ) {
// Already found an empty ref under this name, yet this one is non-empty
// Fill in the long code for the existing entry
refCodes[refNames[refName].code] = refLong;
refNames[refName].empty = false;
// Make a short code for the ref
refShort +=
}
refShort += '/>';
} else {
// Leave the ref as-is
refShort = caseCues ? refLong.replace( /^<REF/, "<ref" ) : refLong;
}
// Replace the long code with the short code
outWikiText += argWikiText.slice( offset, scanner.refScanRegex.lastIndex - ref.length );
outWikiText += refShort;
}
outWikiText += argWikiText.slice( offset );
return {
wikiText:
refCodes: refCodes,
randPrefix: randPrefix
}
/**
* Insert ref contents back into the text.
*
* @param string argWikiText The wikitext without ref contents
* @param string argRefText The ref contents
* @param string randPrefix The randPrefix value returned by segregateRefs()
* @param string caseCues The caseCues argument passed to segregateRefs()
*/
function integrateRefs(argWikiText, argRefText, randPrefix, caseCues) {
//
function cleanRefLong(dirtyRef) {
var cleanRegex = /^<(ref) name=(?:"[^"]*"|'[^']*'|[^\s"']*)/i;
return dirtyRef.replace(cleanRegex,
}
var scanner, ref, parser, attributes, refCodes = Object.create( null ), usageFreq = Object.create( null ),
preferredRef = Object.create( null ), refLong, outWikiText = '', offset = 0;
// First, we build an associative array of all the ref codes
// that we might need to put back into the text.
scanner = new RefScanner( argRefText );
while ( ( ref = scanner.getRef() ) ) {
parser = new RefParser( ref );
attributes = parser.getAttributes();
if (
// Only use the first ref
if ( !
refCodes[attributes.name] = ref;
}
}
}
// Next, we build an associative array that holds the usage frequency
// of every ref name used in text, and whether there is a preferred ref,
// if caseCues are
scanner = new RefScanner( argWikiText );
while ( ( ref = scanner.getRef() ) ) {
parser = new RefParser( ref );
attributes = parser.getAttributes();
if (
if ( !
usageFreq[attributes.name] = 1;
} else {
usageFreq[attributes.name]++;
}
if ( caseCues && ref.slice( 0, 4 ) == '<REF' ) {
preferredRef[attributes.name] = true;
}
}
}
// Finally, we go through the text again and this time we insert the
// ref codes where we need to, but only in the first place
// a ref name appears (or the first preferred ___location).
scanner = new RefScanner( argWikiText );
while ( ( ref = scanner.getRef() ) ) {
parser = new RefParser( ref );
attributes = parser.getAttributes();
if (
// Is this name on the replacement list?
if (
// If we are using caseCues, and another ___location is
// preferred, skip to the next ref.
if ( caseCues &&
ref.slice( 0, 4 ) !=
) {
continue;
}
// Is this name an autogenerated name?
if ( attributes.name.slice( 0, randPrefix.length ) == randPrefix ) {
// Yes: is the name used multiple times?
if ( usageFreq[attributes.name] > 1 ) {
// Multiple: the replacement code should be the same
// as that stored in the ref textbox.
Line 391 ⟶ 321:
// at least not if the citation was untouched.
// (We don't want to add unnecessary autonames)
refLong = cleanRefLong( refCodes[attributes.name] );
}
} else {
Line 401 ⟶ 331:
// Replace the short code with the long code
outWikiText += argWikiText.slice( offset, scanner.refScanRegex.lastIndex - ref.length );
outWikiText += refLong;
// Delete the name from the replacement list
delete refCodes[attributes.name];
Line 412 ⟶ 340:
}
}
outWikiText += argWikiText.slice( offset );
return {
wikiText: outWikiText,
unusedRefs: refCodes
};
}
/**
* Clear the undo history of a textarea by removing it from the document
* and then inserting it again.
*
* @param HTMLTextareaElement ta The textarea element
*/
function clearUndoHistory(ta) {
var pn = ta.parentNode, ns = ta.nextSibling;
Line 433 ⟶ 370:
// Do the actual integration work
result = integrateRefs(mainTextbox.value, refsTextbox.value, randPrefix, true);
// Find all unused ref names
for(refName in result.unusedRefs) {
unusedRefNamesQuoted.push(htmlQuote(refName));
}
// If any refs are unused, warn and allow the user to cancel;
Line 483 ⟶ 416:
refsButton.parentNode.removeChild(refsButton);
}
// wikEd compatibility (frame -> textarea)
if(typeof wikEdUseWikEd != "undefined" && wikEdUseWikEd) {
Line 499 ⟶ 423:
// Do the actual segregation work and save the random prefix
var segFormat = segregateRefs(mainTextbox.value
if(!segFormat) {
return false;
Line 531 ⟶ 455:
refsTextbox = document.createElement("textarea");
refsTextbox.id = "PsRefsTextbox";
refsTextbox.value = segFormat.refCodes.join("\n\n");
refsTextbox.style.border = "none";
Line 578 ⟶ 496:
// Do the actual segregation work and save the random prefix
var segFormat = segregateRefs(mainTextbox.value
if(!segFormat) {
return false;
Line 619 ⟶ 537:
refsTextbox.value = messages.refsCommentComplete +
segFormat.refCodes.join("\n");
refsTextbox.rows =
refsTextbox.style.border = "none";
Line 647 ⟶ 564:
}
$( function () {
try {
// Handle message translations
messages = (typeof SegregateRefsJsL10n == "object" &&
Line 697 ⟶ 595:
// Make the "convert" button
convertButton = document.createElement("input");
if(typeof SegregateRefsJsAllowConversion == "undefined" ||
Line 711 ⟶ 609:
refsDiv.appendChild(refsButton);
refsDiv.appendChild(convertButton);
// Find position within the edit form to insert it at
var refsDivPos = mainTextbox, refsDivPosParent = refsDivPos.parentNode;
while (refsDivPosParent !== editForm) {
refsDivPos = refsDivPosParent;
refsDivPosParent = refsDivPos.parentNode;
if (!refsDivPosParent) {
refsDivPos = mainTextbox;
refsDivPosParent = refsDivPos.parentNode;
break;
}
}
refsDivPos = refsDivPos.nextSibling;
if (refsDivPos && refsDivPos.classList.contains("wikiEditor-ui-clear")) {
refsDivPos = refsDivPos.nextSibling;
}
refsDivPosParent.insertBefore(refsDiv, refsDivPos);
} catch(e) {
}
} );
})( jQuery );
// </nowiki>
|