MediaWiki:Gadget-QuoteSourceRedirects.js: Difference between revisions
From Bodhicitta
MediaWiki:Gadget-QuoteSourceRedirects.js
(VSCode edit) |
(VSCode edit) |
||
| Line 1: | Line 1: | ||
/** | /** | ||
* | * QuoteSourceRedirects | ||
* | * | ||
* | * Controls for [[BCA:QuoteSource redirects to create]]. | ||
* | * | ||
* | * - approve creates a redirect from the QuoteSource value to the target | ||
* | * - edit changes the target before approving | ||
* | * - rewrite rewrites the value itself on every segment that uses it | ||
* - delete removes the value from every segment that uses it | |||
* - done marks a row treated, persisted into the page wikitext | |||
* | * | ||
* | * The "hide treated" control is built here rather than in wikitext: raw | ||
* not | * <label>/<input>/<button> do not survive the wikitext sanitizer. | ||
*/ | */ | ||
( function () { | ( function () { | ||
'use strict'; | 'use strict'; | ||
if ( mw. | var api; | ||
return; | |||
function getApi() { | |||
if ( !api ) { api = new mw.Api(); } | |||
return api; | |||
} | } | ||
$( function () { | $( function () { | ||
var $ | var $tables = $( '.qs-table' ); | ||
if ( !$ | if ( !$tables.length ) { | ||
return; | return; | ||
} | } | ||
function setState( $cell, cls, text ) { | |||
$cell.removeClass( 'qs-ok qs-err qs-busy' ).addClass( cls ).text( text ); | |||
} | |||
/* A treated row's controls are inert. Checked by every handler, so a | |||
* delegated listener cannot fire on a row that has been marked done. */ | |||
function isTreated( $el ) { | |||
return $el.closest( 'tr' ).hasClass( 'qs-treated' ); | |||
} | |||
function setTreated( $row ) { | |||
$row.addClass( 'qs-treated' ); | |||
$row.find( 'button' ).prop( 'disabled', true ); | |||
$row.find( '.qs-value-edit, .qs-value-delete, .qs-target-edit' ) | |||
.addClass( 'qs-disabled' ).attr( 'aria-disabled', 'true' ).removeAttr( 'tabindex' ); | |||
$row.find( '.qs-dismiss' ).text( 'undo' ); | |||
} | |||
function clearTreated( $row ) { | |||
$row.removeClass( 'qs-treated' ); | |||
$row.find( 'button' ).prop( 'disabled', false ); | |||
$row.find( '.qs-value-edit, .qs-value-delete, .qs-target-edit' ) | |||
.removeClass( 'qs-disabled' ).removeAttr( 'aria-disabled' ).attr( 'tabindex', '0' ); | |||
$row.find( '.qs-dismiss' ).text( 'done' ); | |||
} | |||
function countTreated() { | |||
var n = $( 'tr.qs-treated' ).length; | |||
function | $( '.qs-treated-count' ).text( | ||
$ | n ? n + ' of ' + $( '.qs-table tr' ).has( 'td' ).length + ' marked treated' : '' | ||
); | |||
} | } | ||
/* ----------------------------------------------------------------- | |||
* Approving: create the redirect | |||
* ----------------------------------------------------------------- */ | |||
function approve( $cell ) { | function approve( $cell ) { | ||
var from = $cell.data( 'from' ); | var from = $cell.data( 'from' ); | ||
var to = $cell.data( 'to' ); | var to = $cell.closest( 'tr' ).find( '.qs-target-label' ).text() || $cell.data( 'to' ); | ||
if ( !from || !to ) { | if ( !from || !to ) { | ||
setState( $cell, 'qs-err', 'missing data' ); | |||
return; | return; | ||
} | } | ||
setState( $cell, 'qs-busy', 'creating…' ); | |||
setState( $cell, 'qs- | getApi().postWithToken( 'csrf', { | ||
action: 'edit', | action: 'edit', | ||
title: from, | title: from, | ||
text: '#REDIRECT [[' + to + ']]', | text: '#REDIRECT [[' + to + ']]', | ||
summary: ' | summary: 'QuoteSource redirect to [[' + to + ']]', | ||
createonly: 1 | createonly: 1 | ||
} ).done( function () { | } ).done( function () { | ||
setState( $cell, 'qs- | setState( $cell, 'qs-ok', 'created' ); | ||
} ).fail( function ( code, res ) { | } ).fail( function ( code, res ) { | ||
if ( code === 'articleexists' ) { | if ( code === 'articleexists' ) { | ||
setState( $cell, 'qs-ok', 'already exists' ); | |||
} else if ( res && res.error && res.error.info ) { | } else if ( res && res.error && res.error.info ) { | ||
setState( $cell, 'qs-err', res.error.info ); | |||
} else { | |||
setState( $cell, 'qs-err', code ); | |||
} | } | ||
} ); | } ); | ||
} | } | ||
/ | /* ----------------------------------------------------------------- | ||
* Persisting the treated state | |||
* | |||
* The row is marked by editing this page's own wikitext: the `|-` that | |||
* opens the row becomes `|- class="qs-treated"`. The row is found by its | |||
* QuoteSource value, which is unique per row. | |||
* ----------------------------------------------------------------- */ | |||
function markInWikitext( value, add ) { | |||
var page = mw.config.get( 'wgPageName' ); | |||
return getApi().get( { | |||
action: 'query', prop: 'revisions', rvprop: 'content', | |||
rvslots: 'main', titles: page, formatversion: 2 | |||
} ).then( function ( res ) { | |||
var pg = res.query.pages[ 0 ]; | |||
var text = pg.revisions[ 0 ].slots.main.content; | |||
function markInWikitext( value, add | |||
var | |||
rvslots: 'main', titles: | |||
).then( function ( res ) { | |||
var | |||
var text = | |||
var needle = '<code class="qs-value">' + value + '</code>'; | var needle = '<code class="qs-value">' + value + '</code>'; | ||
var at = text.indexOf( needle ); | var at = text.indexOf( needle ); | ||
if ( at === -1 ) { return $.Deferred().reject( 'row not found' ); } | if ( at === -1 ) { | ||
return $.Deferred().reject( 'row not found in wikitext' ); | |||
} | |||
var rowStart = text.lastIndexOf( '|-', at ); | var rowStart = text.lastIndexOf( '|-', at ); | ||
var lineEnd = text.indexOf( '\n', rowStart ); | var lineEnd = text.indexOf( '\n', rowStart ); | ||
var line = text.slice( rowStart, lineEnd ); | var line = text.slice( rowStart, lineEnd ); | ||
| Line 100: | Line 118: | ||
? ( line.indexOf( 'qs-treated' ) === -1 ? '|- class="qs-treated"' : line ) | ? ( line.indexOf( 'qs-treated' ) === -1 ? '|- class="qs-treated"' : line ) | ||
: '|-'; | : '|-'; | ||
var newText = text.slice( 0, rowStart ) + updated + text.slice( lineEnd ); | |||
return getApi().postWithToken( 'csrf', { | |||
return | action: 'edit', title: page, text: newText, | ||
action: 'edit', title: | summary: ( add ? 'mark' : 'unmark' ) + ' QuoteSource row treated: ' + value | ||
summary: ( add ? ' | |||
} ); | } ); | ||
} ); | } ); | ||
} | } | ||
$tables.on( 'click', '.qs-dismiss', function () { | |||
var $tr = $( this ).closest( 'tr' ); | var $tr = $( this ).closest( 'tr' ); | ||
var v = | var v = $tr.find( '.qs-value' ).text(); | ||
var $status = $tr.find( '.qs-rewrite-status' ); | |||
var $status = $( '.qs- | |||
var add = !$tr.hasClass( 'qs-treated' ); | var add = !$tr.hasClass( 'qs-treated' ); | ||
if ( add ) { setTreated( $tr ); } else { clearTreated( $tr ); } | |||
if ( add ) { setTreated( $tr | countTreated(); | ||
markInWikitext( v, add | markInWikitext( v, add ).fail( function ( why ) { | ||
if ( add ) { clearTreated( $tr ); } else { setTreated( $tr ); } | |||
countTreated(); | |||
if ( add ) { clearTreated( $tr ); } else { setTreated( $tr | $status.removeClass( 'qs-busy qs-ok' ).addClass( 'qs-err' ) | ||
$status.text( 'could not save: ' + | .text( 'could not save: ' + why ); | ||
} ); | } ); | ||
} ); | } ); | ||
/* ----------------------------------------------------------------- | |||
* Removing a value entirely | |||
* | |||
* QuoteSource can hold several sources separated by ';', so the value is | |||
* removed by exact match and the separators tidied, never by blanking | |||
* the field. | |||
* ----------------------------------------------------------------- */ | |||
function stripValue( field, value ) { | |||
var parts = field.split( ';' ); | |||
var kept = []; | |||
var i, p; | |||
for ( i = 0; i < parts.length; i++ ) { | |||
p = parts[ i ]; | |||
if ( $.trim( p ) !== $.trim( value ) ) { | |||
kept.push( p ); | |||
} | |||
} | |||
return kept.join( ';' ); | |||
} | |||
function removeOnPage( pages, i, value, $status, done ) { | |||
if ( i >= pages.length ) { | |||
$status.removeClass( 'qs-busy' ).addClass( 'qs-ok' ) | |||
.text( 'removed from ' + pages.length + ' segments' ); | |||
done(); | |||
return; | return; | ||
} | } | ||
$status.text( 'editing ' + ( i + 1 ) + ' of ' + pages.length + '…' ); | |||
getApi().get( { | |||
action: 'query', prop: 'revisions', rvprop: 'content', | |||
rvslots: 'main', titles: pages[ i ], formatversion: 2 | |||
} ).done( function ( res ) { | |||
var pg = res.query.pages[ 0 ]; | |||
if ( !pg || !pg.revisions ) { | |||
removeOnPage( pages, i + 1, value, $status, done ); | |||
return; | |||
} | |||
var text = pg.revisions[ 0 ].slots.main.content; | |||
var updated = text.replace( | |||
/(\|\s*QuoteSource\s*=)([^\n|]*)/g, | |||
function ( all, head, val ) { | |||
return head + stripValue( val, value ); | |||
} | |||
); | |||
if ( updated === text ) { | |||
removeOnPage( pages, i + 1, value, $status, done ); | |||
return; | |||
} | |||
getApi().postWithToken( 'csrf', { | |||
action: 'edit', title: pages[ i ], text: updated, | |||
summary: 'QuoteSource: remove ' + value | |||
} ).done( function () { | |||
removeOnPage( pages, i + 1, value, $status, done ); | |||
} ).fail( function ( code ) { | |||
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code ); | |||
} ); | |||
} ).fail( function ( code ) { | |||
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code ); | |||
} ); | |||
} | |||
$tables.on( 'click', '.qs-value-delete', function () { | |||
var $el = $( this ); | |||
if ( isTreated( $el ) ) { return; } | |||
var $tr = $el.closest( 'tr' ); | |||
var value = $tr.find( '.qs-value' ).text(); | |||
var $status = $tr.find( '.qs-rewrite-status' ); | |||
if ( !window.confirm( | if ( !window.confirm( | ||
' | 'Remove "' + value + '" from every segment that uses it?\n\n' + | ||
' | 'Other sources on the same segment are kept.' | ||
) ) { return; } | |||
) ) { | |||
$status.removeClass( 'qs-ok qs-err' ).addClass( 'qs-busy' ).text( 'finding segments…' ); | |||
function | getApi().get( { | ||
if ( | action: 'ask', | ||
$status. | query: '[[QuoteSource::' + value + ']]|limit=500', | ||
formatversion: 2 | |||
} ).done( function ( res ) { | |||
var results = ( res.query && res.query.results ) || {}; | |||
var pages = Object.keys( results ); | |||
if ( !pages.length ) { | |||
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( 'no segments found' ); | |||
return; | return; | ||
} | } | ||
removeOnPage( pages, 0, value, $status, function () { | |||
setTreated( $tr ); | |||
countTreated(); | |||
markInWikitext( value, true ); | |||
} ); | } ); | ||
} | } ).fail( function ( code ) { | ||
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code ); | |||
} ); | |||
} ); | } ); | ||
$ | /* ----------------------------------------------------------------- | ||
var $ | * Editing the target | ||
$( ' | * ----------------------------------------------------------------- */ | ||
$tables.on( 'click', '.qs-target-edit', function () { | |||
var $el = $( this ); | |||
if ( isTreated( $el ) ) { return; } | |||
var $span = $el.closest( '.qs-target' ); | |||
var $label = $span.find( '.qs-target-label' ); | |||
var current = $label.text(); | |||
var next = window.prompt( 'Redirect target for this value:', current ); | |||
if ( next === null || next === current || !$.trim( next ) ) { return; } | |||
$label.text( $.trim( next ) ); | |||
$span.closest( 'tr' ).find( '.qs-approve' ).data( 'to', $.trim( next ) ); | |||
} ); | } ); | ||
/ | /* ----------------------------------------------------------------- | ||
* Rewriting the QuoteSource value on the segments | |||
* ----------------------------------------------------------------- */ | |||
function rewriteValue( $el ) { | |||
var $tr = $el.closest( 'tr' ); | |||
var from = $tr.find( '.qs-value' ).text(); | |||
var $status = $tr.find( '.qs-rewrite-status' ); | |||
function rewriteValue( $ | |||
var from = $ | |||
var to = window.prompt( | var to = window.prompt( | ||
'Rewrite this QuoteSource value on every segment that uses it | 'Rewrite this QuoteSource value on every segment that uses it:', from | ||
); | ); | ||
if ( to === null ) { | if ( to === null || to === from || !$.trim( to ) ) { return; } | ||
to = $.trim( to ); | to = $.trim( to ); | ||
$status.removeClass( 'qs-ok qs-err' ).addClass( 'qs-busy' ).text( 'finding segments…' ); | |||
getApi().get( { | |||
action: 'ask', | action: 'ask', | ||
query: '[[QuoteSource::' + from | query: '[[QuoteSource::' + from + ']]|limit=500', | ||
formatversion: 2 | |||
} ).done( function ( res ) { | } ).done( function ( res ) { | ||
var results = ( res.query && res.query.results ) || {}; | var results = ( res.query && res.query.results ) || {}; | ||
var pages = Object.keys( results ); | var pages = Object.keys( results ); | ||
if ( !pages.length ) { | if ( !pages.length ) { | ||
$status. | $status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( 'no segments found' ); | ||
return; | return; | ||
} | } | ||
editNext( pages, 0, from, to, $status ); | editNext( pages, 0, from, to, $status ); | ||
} ).fail( function ( code ) { | } ).fail( function ( code ) { | ||
$status. | $status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code ); | ||
} ); | } ); | ||
} | } | ||
function editNext( pages, i, from, to, $status ) { | function editNext( pages, i, from, to, $status ) { | ||
if ( i >= pages.length ) { | if ( i >= pages.length ) { | ||
$status.text( ' | $status.removeClass( 'qs-busy' ).addClass( 'qs-ok' ) | ||
.text( 'rewrote ' + pages.length + ' segments' ); | |||
return; | return; | ||
} | } | ||
$status.text( 'editing ' + ( i + 1 ) + ' of ' + pages.length + '…' ); | $status.text( 'editing ' + ( i + 1 ) + ' of ' + pages.length + '…' ); | ||
getApi().get( { | |||
action: 'query', prop: 'revisions', rvprop: 'content', | |||
rvslots: 'main', titles: pages[ i ] | rvslots: 'main', titles: pages[ i ], formatversion: 2 | ||
).done( function ( res ) { | } ).done( function ( res ) { | ||
var | var pg = res.query.pages[ 0 ]; | ||
if ( !pg || !pg.revisions ) { | |||
if ( ! | editNext( pages, i + 1, from, to, $status ); | ||
return; | return; | ||
} | } | ||
var text = pg.revisions[ 0 ].slots.main.content; | |||
var updated = text.split( from ).join( to ); | |||
if ( updated === text ) { | |||
editNext( pages, i + 1, from, to, $status ); | editNext( pages, i + 1, from, to, $status ); | ||
return; | return; | ||
} | } | ||
getApi().postWithToken( 'csrf', { | |||
action: 'edit', title: pages[ i ], text: updated, | action: 'edit', title: pages[ i ], text: updated, | ||
summary: 'QuoteSource: ' + from + ' → ' + to | summary: 'QuoteSource: ' + from + ' → ' + to | ||
| Line 301: | Line 311: | ||
editNext( pages, i + 1, from, to, $status ); | editNext( pages, i + 1, from, to, $status ); | ||
} ).fail( function ( code ) { | } ).fail( function ( code ) { | ||
$status. | $status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code ); | ||
} ); | } ); | ||
} ).fail( function ( code ) { | } ).fail( function ( code ) { | ||
$status. | $status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code ); | ||
} ); | } ); | ||
} | } | ||
$ | $tables.on( 'click', '.qs-value-edit', function () { | ||
rewriteValue( $( this ). | var $el = $( this ); | ||
if ( isTreated( $el ) ) { return; } | |||
rewriteValue( $el ); | |||
} ); | |||
/* ----------------------------------------------------------------- | |||
* Build the controls | |||
* ----------------------------------------------------------------- */ | |||
$( '.qs-approve' ).each( function () { | |||
var $cell = $( this ); | |||
$cell.append( | |||
$( '<button>' ).addClass( 'qs-btn' ).text( 'approve' ) | |||
.on( 'click', function () { | |||
if ( isTreated( $cell ) ) { return; } | |||
approve( $cell ); | |||
} ) | |||
); | |||
} ); | } ); | ||
$( '.qs-controls' ).each( function () { | |||
var $box = $( this ); | |||
$( '.qs- | var $cb = $( '<input>' ).attr( 'type', 'checkbox' ); | ||
var $ | var $table = $box.nextAll( '.qs-table' ).first(); | ||
var $ | $cb.on( 'change', function () { | ||
var $ | $table.toggleClass( 'qs-hiding', this.checked ); | ||
} ); | |||
$box.append( | |||
' | $( '<label>' ).addClass( 'qs-hide-label' ).append( $cb, ' Hide treated rows' ), | ||
$( '<span>' ).addClass( 'qs-treated-count' ) | |||
); | ); | ||
} ); | } ); | ||
$( 'tr.qs-treated' ).each( function () { setTreated( $( this ) ); } ); | |||
countTreated(); | |||
} ); | } ); | ||
}() ); | }() ); | ||
Revision as of 13:24, 9 September 2026
/**
* QuoteSourceRedirects
*
* Controls for [[BCA:QuoteSource redirects to create]].
*
* - approve creates a redirect from the QuoteSource value to the target
* - edit changes the target before approving
* - rewrite rewrites the value itself on every segment that uses it
* - delete removes the value from every segment that uses it
* - done marks a row treated, persisted into the page wikitext
*
* The "hide treated" control is built here rather than in wikitext: raw
* <label>/<input>/<button> do not survive the wikitext sanitizer.
*/
( function () {
'use strict';
var api;
function getApi() {
if ( !api ) { api = new mw.Api(); }
return api;
}
$( function () {
var $tables = $( '.qs-table' );
if ( !$tables.length ) {
return;
}
function setState( $cell, cls, text ) {
$cell.removeClass( 'qs-ok qs-err qs-busy' ).addClass( cls ).text( text );
}
/* A treated row's controls are inert. Checked by every handler, so a
* delegated listener cannot fire on a row that has been marked done. */
function isTreated( $el ) {
return $el.closest( 'tr' ).hasClass( 'qs-treated' );
}
function setTreated( $row ) {
$row.addClass( 'qs-treated' );
$row.find( 'button' ).prop( 'disabled', true );
$row.find( '.qs-value-edit, .qs-value-delete, .qs-target-edit' )
.addClass( 'qs-disabled' ).attr( 'aria-disabled', 'true' ).removeAttr( 'tabindex' );
$row.find( '.qs-dismiss' ).text( 'undo' );
}
function clearTreated( $row ) {
$row.removeClass( 'qs-treated' );
$row.find( 'button' ).prop( 'disabled', false );
$row.find( '.qs-value-edit, .qs-value-delete, .qs-target-edit' )
.removeClass( 'qs-disabled' ).removeAttr( 'aria-disabled' ).attr( 'tabindex', '0' );
$row.find( '.qs-dismiss' ).text( 'done' );
}
function countTreated() {
var n = $( 'tr.qs-treated' ).length;
$( '.qs-treated-count' ).text(
n ? n + ' of ' + $( '.qs-table tr' ).has( 'td' ).length + ' marked treated' : ''
);
}
/* -----------------------------------------------------------------
* Approving: create the redirect
* ----------------------------------------------------------------- */
function approve( $cell ) {
var from = $cell.data( 'from' );
var to = $cell.closest( 'tr' ).find( '.qs-target-label' ).text() || $cell.data( 'to' );
if ( !from || !to ) {
setState( $cell, 'qs-err', 'missing data' );
return;
}
setState( $cell, 'qs-busy', 'creating…' );
getApi().postWithToken( 'csrf', {
action: 'edit',
title: from,
text: '#REDIRECT [[' + to + ']]',
summary: 'QuoteSource redirect to [[' + to + ']]',
createonly: 1
} ).done( function () {
setState( $cell, 'qs-ok', 'created' );
} ).fail( function ( code, res ) {
if ( code === 'articleexists' ) {
setState( $cell, 'qs-ok', 'already exists' );
} else if ( res && res.error && res.error.info ) {
setState( $cell, 'qs-err', res.error.info );
} else {
setState( $cell, 'qs-err', code );
}
} );
}
/* -----------------------------------------------------------------
* Persisting the treated state
*
* The row is marked by editing this page's own wikitext: the `|-` that
* opens the row becomes `|- class="qs-treated"`. The row is found by its
* QuoteSource value, which is unique per row.
* ----------------------------------------------------------------- */
function markInWikitext( value, add ) {
var page = mw.config.get( 'wgPageName' );
return getApi().get( {
action: 'query', prop: 'revisions', rvprop: 'content',
rvslots: 'main', titles: page, formatversion: 2
} ).then( function ( res ) {
var pg = res.query.pages[ 0 ];
var text = pg.revisions[ 0 ].slots.main.content;
var needle = '<code class="qs-value">' + value + '</code>';
var at = text.indexOf( needle );
if ( at === -1 ) {
return $.Deferred().reject( 'row not found in wikitext' );
}
var rowStart = text.lastIndexOf( '|-', at );
var lineEnd = text.indexOf( '\n', rowStart );
var line = text.slice( rowStart, lineEnd );
var updated = add
? ( line.indexOf( 'qs-treated' ) === -1 ? '|- class="qs-treated"' : line )
: '|-';
var newText = text.slice( 0, rowStart ) + updated + text.slice( lineEnd );
return getApi().postWithToken( 'csrf', {
action: 'edit', title: page, text: newText,
summary: ( add ? 'mark' : 'unmark' ) + ' QuoteSource row treated: ' + value
} );
} );
}
$tables.on( 'click', '.qs-dismiss', function () {
var $tr = $( this ).closest( 'tr' );
var v = $tr.find( '.qs-value' ).text();
var $status = $tr.find( '.qs-rewrite-status' );
var add = !$tr.hasClass( 'qs-treated' );
if ( add ) { setTreated( $tr ); } else { clearTreated( $tr ); }
countTreated();
markInWikitext( v, add ).fail( function ( why ) {
if ( add ) { clearTreated( $tr ); } else { setTreated( $tr ); }
countTreated();
$status.removeClass( 'qs-busy qs-ok' ).addClass( 'qs-err' )
.text( 'could not save: ' + why );
} );
} );
/* -----------------------------------------------------------------
* Removing a value entirely
*
* QuoteSource can hold several sources separated by ';', so the value is
* removed by exact match and the separators tidied, never by blanking
* the field.
* ----------------------------------------------------------------- */
function stripValue( field, value ) {
var parts = field.split( ';' );
var kept = [];
var i, p;
for ( i = 0; i < parts.length; i++ ) {
p = parts[ i ];
if ( $.trim( p ) !== $.trim( value ) ) {
kept.push( p );
}
}
return kept.join( ';' );
}
function removeOnPage( pages, i, value, $status, done ) {
if ( i >= pages.length ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-ok' )
.text( 'removed from ' + pages.length + ' segments' );
done();
return;
}
$status.text( 'editing ' + ( i + 1 ) + ' of ' + pages.length + '…' );
getApi().get( {
action: 'query', prop: 'revisions', rvprop: 'content',
rvslots: 'main', titles: pages[ i ], formatversion: 2
} ).done( function ( res ) {
var pg = res.query.pages[ 0 ];
if ( !pg || !pg.revisions ) {
removeOnPage( pages, i + 1, value, $status, done );
return;
}
var text = pg.revisions[ 0 ].slots.main.content;
var updated = text.replace(
/(\|\s*QuoteSource\s*=)([^\n|]*)/g,
function ( all, head, val ) {
return head + stripValue( val, value );
}
);
if ( updated === text ) {
removeOnPage( pages, i + 1, value, $status, done );
return;
}
getApi().postWithToken( 'csrf', {
action: 'edit', title: pages[ i ], text: updated,
summary: 'QuoteSource: remove ' + value
} ).done( function () {
removeOnPage( pages, i + 1, value, $status, done );
} ).fail( function ( code ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code );
} );
} ).fail( function ( code ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code );
} );
}
$tables.on( 'click', '.qs-value-delete', function () {
var $el = $( this );
if ( isTreated( $el ) ) { return; }
var $tr = $el.closest( 'tr' );
var value = $tr.find( '.qs-value' ).text();
var $status = $tr.find( '.qs-rewrite-status' );
if ( !window.confirm(
'Remove "' + value + '" from every segment that uses it?\n\n' +
'Other sources on the same segment are kept.'
) ) { return; }
$status.removeClass( 'qs-ok qs-err' ).addClass( 'qs-busy' ).text( 'finding segments…' );
getApi().get( {
action: 'ask',
query: '[[QuoteSource::' + value + ']]|limit=500',
formatversion: 2
} ).done( function ( res ) {
var results = ( res.query && res.query.results ) || {};
var pages = Object.keys( results );
if ( !pages.length ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( 'no segments found' );
return;
}
removeOnPage( pages, 0, value, $status, function () {
setTreated( $tr );
countTreated();
markInWikitext( value, true );
} );
} ).fail( function ( code ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code );
} );
} );
/* -----------------------------------------------------------------
* Editing the target
* ----------------------------------------------------------------- */
$tables.on( 'click', '.qs-target-edit', function () {
var $el = $( this );
if ( isTreated( $el ) ) { return; }
var $span = $el.closest( '.qs-target' );
var $label = $span.find( '.qs-target-label' );
var current = $label.text();
var next = window.prompt( 'Redirect target for this value:', current );
if ( next === null || next === current || !$.trim( next ) ) { return; }
$label.text( $.trim( next ) );
$span.closest( 'tr' ).find( '.qs-approve' ).data( 'to', $.trim( next ) );
} );
/* -----------------------------------------------------------------
* Rewriting the QuoteSource value on the segments
* ----------------------------------------------------------------- */
function rewriteValue( $el ) {
var $tr = $el.closest( 'tr' );
var from = $tr.find( '.qs-value' ).text();
var $status = $tr.find( '.qs-rewrite-status' );
var to = window.prompt(
'Rewrite this QuoteSource value on every segment that uses it:', from
);
if ( to === null || to === from || !$.trim( to ) ) { return; }
to = $.trim( to );
$status.removeClass( 'qs-ok qs-err' ).addClass( 'qs-busy' ).text( 'finding segments…' );
getApi().get( {
action: 'ask',
query: '[[QuoteSource::' + from + ']]|limit=500',
formatversion: 2
} ).done( function ( res ) {
var results = ( res.query && res.query.results ) || {};
var pages = Object.keys( results );
if ( !pages.length ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( 'no segments found' );
return;
}
editNext( pages, 0, from, to, $status );
} ).fail( function ( code ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code );
} );
}
function editNext( pages, i, from, to, $status ) {
if ( i >= pages.length ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-ok' )
.text( 'rewrote ' + pages.length + ' segments' );
return;
}
$status.text( 'editing ' + ( i + 1 ) + ' of ' + pages.length + '…' );
getApi().get( {
action: 'query', prop: 'revisions', rvprop: 'content',
rvslots: 'main', titles: pages[ i ], formatversion: 2
} ).done( function ( res ) {
var pg = res.query.pages[ 0 ];
if ( !pg || !pg.revisions ) {
editNext( pages, i + 1, from, to, $status );
return;
}
var text = pg.revisions[ 0 ].slots.main.content;
var updated = text.split( from ).join( to );
if ( updated === text ) {
editNext( pages, i + 1, from, to, $status );
return;
}
getApi().postWithToken( 'csrf', {
action: 'edit', title: pages[ i ], text: updated,
summary: 'QuoteSource: ' + from + ' → ' + to
} ).done( function () {
editNext( pages, i + 1, from, to, $status );
} ).fail( function ( code ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code );
} );
} ).fail( function ( code ) {
$status.removeClass( 'qs-busy' ).addClass( 'qs-err' ).text( code );
} );
}
$tables.on( 'click', '.qs-value-edit', function () {
var $el = $( this );
if ( isTreated( $el ) ) { return; }
rewriteValue( $el );
} );
/* -----------------------------------------------------------------
* Build the controls
* ----------------------------------------------------------------- */
$( '.qs-approve' ).each( function () {
var $cell = $( this );
$cell.append(
$( '<button>' ).addClass( 'qs-btn' ).text( 'approve' )
.on( 'click', function () {
if ( isTreated( $cell ) ) { return; }
approve( $cell );
} )
);
} );
$( '.qs-controls' ).each( function () {
var $box = $( this );
var $cb = $( '<input>' ).attr( 'type', 'checkbox' );
var $table = $box.nextAll( '.qs-table' ).first();
$cb.on( 'change', function () {
$table.toggleClass( 'qs-hiding', this.checked );
} );
$box.append(
$( '<label>' ).addClass( 'qs-hide-label' ).append( $cb, ' Hide treated rows' ),
$( '<span>' ).addClass( 'qs-treated-count' )
);
} );
$( 'tr.qs-treated' ).each( function () { setTreated( $( this ) ); } );
countTreated();
} );
}() );