User:Monster Domosed/common.js: Difference between revisionsGive feedback
No edit summary Tag: Manual revert |
testing |
||
| Line 1: | Line 1: | ||
/* Patch Formatter */ | |||
( function () { | |||
'use strict'; | |||
// ---- CONFIG --------------------------------------------------------- | |||
var GAME_MODE_SECTIONS = [ 'Street Brawl' ]; | |||
// (RegExp, replacement) — generic terms turned into wiki links. | |||
var WIKI_LINKS = [ | |||
[ /(?<!\w)Urn(?!\w)/g, '[[Urn]]' ], | |||
[ /(?<!\w)parry(?!\w)/g, '[[Parry|parry]]' ] | |||
]; | |||
var CHANGE_PREFIX = ''; | |||
// ---- API ------------------------------------------------------------ | |||
function getWikiData( templateName ) { | |||
var api = new mw.Api(); | |||
return api.get( { | |||
action: 'expandtemplates', | |||
text: '#invoke:MonsterDomosed/patchformatter|' + templateName + '', | |||
prop: 'wikitext', | |||
format: 'json', | |||
formatversion: 2 | |||
} ).then( function ( data ) { | |||
var wikitext = data.expandtemplates.wikitext || ''; | |||
return wikitext.split( ',' ) | |||
.map( function ( item ) { return item.trim().replace( /^'+|'+$/g, '' ); } ) | |||
.filter( function ( item ) { return item.length > 0; } ); | |||
} ); | |||
} | |||
// ---- PARSING -------------------------------------------------------- | |||
function parseEntries( rawText ) { | |||
var entries = []; | |||
var current = null; | |||
rawText.split( '\n' ).forEach( function ( line ) { | |||
var stripped = line.trim(); | |||
if ( stripped.indexOf( '-' ) === 0 ) { | |||
if ( current !== null ) { entries.push( current ); } | |||
current = stripped.slice( 1 ).trim(); | |||
} else if ( current !== null ) { | |||
current += ' ' + stripped; // continuation of previous bullet | |||
} | |||
} ); | |||
if ( current !== null ) { entries.push( current ); } | |||
return entries | |||
.map( function ( e ) { return e.replace( /\s+/g, ' ' ).trim(); } ) | |||
.filter( function ( e ) { return e.length > 0; } ); | |||
} | |||
function classify( entry, heroes, items, gameModes ) { | |||
var idx = entry.indexOf( ':' ); | |||
if ( idx !== -1 ) { | |||
var prefix = entry.slice( 0, idx ).trim(); | |||
var rest = entry.slice( idx + 1 ).trim(); | |||
if ( gameModes.indexOf( prefix ) !== -1 ) { return [ 'mode', prefix, rest ]; } | |||
if ( heroes.indexOf( prefix ) !== -1 ) { return [ 'hero', prefix, rest ]; } | |||
if ( items.indexOf( prefix ) !== -1 ) { return [ 'item', prefix, rest ]; } | |||
} | |||
return [ 'general', null, entry ]; | |||
} | |||
// ---- ICON / LINK WRAPPING ------------------------------------------ | |||
function escapeRegExp( s ) { | |||
return s.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); | |||
} | |||
function formatBody( text, heroes, abilities, items ) { | |||
var protectedTokens = []; | |||
function protect( value ) { | |||
var token = '\u0000' + protectedTokens.length + '\u0000'; | |||
protectedTokens.push( value ); | |||
return token; | |||
} | |||
function wrap( terms, template ) { | |||
// Longest names first so multi-word names win. | |||
terms.slice().sort( function ( a, b ) { return b.length - a.length; } ) | |||
.forEach( function ( term ) { | |||
var pattern = new RegExp( '(?<!\\w)' + escapeRegExp( term ) + '(?!\\w)', 'g' ); | |||
text = text.replace( pattern, function () { | |||
return protect( template.replace( '%s', function () { return term; } ) ); | |||
} ); | |||
} ); | |||
} | |||
wrap( abilities, 'AbilityIcon|%s' ); | |||
wrap( items, 'ItemIcon|%s' ); | |||
wrap( heroes, 'HeroIcon|%s' ); | |||
WIKI_LINKS.forEach( function ( pair ) { | |||
text = text.replace( pair[ 0 ], function () { return protect( pair[ 1 ] ); } ); | |||
} ); | |||
for ( var i = 0; i < protectedTokens.length; i++ ) { | |||
text = text.split( '\u0000' + i + '\u0000' ).join( protectedTokens[ i ] ); | |||
} | |||
return text; | |||
} | |||
// ---- OUTPUT BUILDING ------------------------------------------------ | |||
function buildOutput( entries, heroes, abilities, items, gameModes ) { | |||
var general = []; | |||
var modes = {}, modeOrder = []; | |||
var itemSections = {}, itemOrder = []; | |||
var heroSections = {}, heroOrder = []; | |||
entries.forEach( function ( entry ) { | |||
var c = classify( entry, heroes, items, gameModes ); | |||
var kind = c[ 0 ], name = c[ 1 ]; | |||
var body = formatBody( c[ 2 ], heroes, abilities, items ); | |||
if ( kind === 'general' ) { | |||
general.push( body ); | |||
} else if ( kind === 'mode' ) { | |||
if ( !modes[ name ] ) { modes[ name ] = []; modeOrder.push( name ); } | |||
modes[ name ].push( body ); | |||
} else if ( kind === 'item' ) { | |||
if ( !itemSections[ name ] ) { itemSections[ name ] = []; itemOrder.push( name ); } | |||
itemSections[ name ].push( body ); | |||
} else if ( kind === 'hero' ) { | |||
if ( !heroSections[ name ] ) { heroSections[ name ] = []; heroOrder.push( name ); } | |||
heroSections[ name ].push( body ); | |||
} | |||
} ); | |||
var out = []; | |||
// General changes | |||
general.forEach( function ( entry ) { out.push( '* ' + entry ); } ); | |||
// Game mode sections | |||
modeOrder.forEach( function ( mode ) { | |||
out.push( '', '== [[' + mode + ']] ==' ); | |||
modes[ mode ].forEach( function ( body ) { out.push( '* ' + body ); } ); | |||
} ); | |||
// Items | |||
if ( itemOrder.length ) { | |||
out.push( '', '== Items ==' ); | |||
itemOrder.forEach( function ( item ) { | |||
out.push( '=== ItemIcon|' + item + ' ===' ); | |||
itemSections[ item ].forEach( function ( body ) { | |||
out.push( CHANGE_PREFIX + body ); | |||
out.push( '' ); | |||
} ); | |||
} ); | |||
} | |||
// Heroes | |||
if ( heroOrder.length ) { | |||
out.push( '', '== Heroes ==', '' ); | |||
heroOrder.forEach( function ( hero ) { | |||
out.push( '=== HeroIcon|' + hero + ' ===' ); | |||
heroSections[ hero ].forEach( function ( body ) { | |||
out.push( CHANGE_PREFIX + body ); | |||
out.push( '' ); | |||
} ); | |||
} ); | |||
} | |||
return out.join( '\n' ).trim() + '\n'; | |||
} | |||
// ---- UI ------------------------------------------------------------- | |||
function init() { | |||
var app = document.getElementById( 'patchformatter-app' ); | |||
if ( !app ) { | |||
return; | |||
} | |||
app.innerHTML = | |||
'<p><strong>Raw patch notes</strong></p>' + | |||
'<textarea id="pf-input" rows="14" style="width:100%;font-family:monospace;"></textarea>' + | |||
'<p style="margin-top:8px;">' + | |||
'<button id="pf-run" type="button">Format</button> ' + | |||
'<button id="pf-copy" type="button">Copy output</button>' + | |||
'<span id="pf-status" style="margin-left:10px;"></span>' + | |||
'</p>' + | |||
'<p><strong>Formatted wikitext</strong></p>' + | |||
'<textarea id="pf-output" rows="20" style="width:100%;font-family:monospace;" readonly></textarea>'; | |||
var input = document.getElementById( 'pf-input' ); | |||
var output = document.getElementById( 'pf-output' ); | |||
var runBtn = document.getElementById( 'pf-run' ); | |||
var copyBtn = document.getElementById( 'pf-copy' ); | |||
var status = document.getElementById( 'pf-status' ); | |||
var listsPromise = null; | |||
function loadLists() { | |||
if ( !listsPromise ) { | |||
listsPromise = Promise.all( [ | |||
getWikiData( 'get_released_heroes_list' ), | |||
getWikiData( 'get_all_released_item_names' ), | |||
getWikiData( 'get_all_released_abilities_list' ) | |||
] ); | |||
} | |||
return listsPromise; | |||
} | |||
runBtn.addEventListener( 'click', function () { | |||
status.textContent = 'Loading wiki data...'; | |||
runBtn.disabled = true; | |||
loadLists().then( function ( lists ) { | |||
var heroes = lists[ 0 ]; | |||
var items = lists[ 1 ]; | |||
var abilities = lists[ 2 ]; | |||
var entries = parseEntries( input.value ); | |||
output.value = buildOutput( | |||
entries, | |||
heroes, | |||
abilities, | |||
items, | |||
GAME_MODE_SECTIONS | |||
); | |||
status.textContent = | |||
'Processed ' + entries.length + | |||
' entries (' + | |||
heroes.length + ' heroes, ' + | |||
abilities.length + ' abilities, ' + | |||
items.length + ' items).'; | |||
} ).catch( function ( err ) { | |||
status.textContent = 'Error: ' + err; | |||
} ).then( function () { | |||
runBtn.disabled = false; | |||
} ); | |||
} ); | |||
copyBtn.addEventListener( 'click', function () { | |||
if ( navigator.clipboard ) { | |||
navigator.clipboard.writeText( output.value ).then( function () { | |||
status.textContent = 'Copied.'; | |||
} ); | |||
} else { | |||
output.select(); | |||
document.execCommand( 'copy' ); | |||
status.textContent = 'Copied.'; | |||
} | |||
} ); | |||
} | |||
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () { | |||
$( init ); | |||
} ); | |||
}() ); | |||