User:DannyS712/EFFPRH/sandbox.js

//

// Script to respond to edit filter false positive reports

// @author DannyS712

$(() => {

const EFFPRH = {};

window.EFFPRH = EFFPRH;

EFFPRH.config = {

debug: false,

version: '0-dev'

};

EFFPRH.editSummary = 'Respond to false positive report via User:DannyS712/EFFPRH'

+ ' (v ' + EFFPRH.config.version + ')';

EFFPRH.init = function () {

mw.loader.using(

[ 'vue', '@wikimedia/codex', 'mediawiki.util', 'mediawiki.api' ],

EFFPRH.run

);

};

EFFPRH.run = function () {

EFFPRH.addStyle();

// Add links to each section to open a dialog

$('span.mw-headline').each( function () {

const $editSectionLinks = $( this ).parent().find( '.mw-editsection' );

if ( $editSectionLinks.length === 0 ) {

// Missing links span, nothing to do

return;

}

const sectionNum = EFFPRH.getHeadingSectionNum( $editSectionLinks );

if ( sectionNum === -1 ) {

// Missing link, no idea what section this is

return;

}

// Add a hidden div after the headline that will be where the Vue

// display goes

$( this ).parent().after(

$( '

' ).attr( 'id', 'script-EFFPRH-' + sectionNum )

);

const reporterName = $( this ).text();

EFFPRH.addHandlerLink( $editSectionLinks, reporterName, sectionNum );

} );

};

/**

* Add styles for our interface.

*/

EFFPRH.addStyle = function () {

mw.util.addCSS(`

.script-EFFPRH-handler {

background-color: #e0e0e0;

border: 1px solid black;

margin: 10px 0 10px 0;

}

/* Override normal rules for indenting lists */

.cdx-menu ul {

margin-left: 0px;

}

/* Separate the dropdown and input */

.cdx-menu {

margin-bottom: 10px;

}

/* Reduce vertical space in the dropdown options */

.cdx-menu-item__content {

line-height: 1em;

}

/* Center form elements and labels */

.script-EFFPRH-handler td {

vertical-align: middle;

}

/* Don't use the grey background in the preview */

.script-EFFPRH-preview {

background-color: white;

}

`);

};

/**

* Get the section number for a response, given the jQuery element for the

* with the edit section link. Returns -1 on failure.

*/

EFFPRH.getHeadingSectionNum = function ( $editSectionLinks ) {

const editSectionUrl = $editSectionLinks.find( 'a:first' ).attr( 'href' );

if ( editSectionUrl === undefined ) {

return -1;

}

const sectionMatch = editSectionUrl.match( /§ion=(\d+)(?:$|&)/ );

if ( sectionMatch === null ) {

return -1;

}

return parseInt( sectionMatch[1] );

};

/**

* Add a link next to the edit section link that will launch the report handler.

*/

EFFPRH.addHandlerLink = function ( $editSectionLinks, reporterName, sectionNum ) {

const $handlerLink = $( '' )

.attr( 'id', 'script-EFFPRH-launch-' + sectionNum )

.text( 'Review report' );

$handlerLink.click(

function () {

// Only allow running once per link (until the Vue handler is removed)

if ( $( this ).hasClass( 'script-EFFPRH-disabled' ) ) {

return;

}

$( this ).addClass( 'script-EFFPRH-disabled' );

EFFPRH.showHandler( reporterName, sectionNum );

}

);

// Add before the closing ] of the links

$editSectionLinks.children().last().before(

' | ',

$handlerLink

);

};

// Handler options, see {{EFFP}}

EFFPRH.responseOptions = [

{ value: 'none', label: 'None' },

{ value: 'done', label: 'Done (no change to filter)' },

{ value: 'defm', label: 'Done (may need a change to filter)' },

{ value: 'notdone', label: 'Not Done (filter working properly)' },

{ value: 'ndefm', label: 'Not Done (may need a change to filter)' },

{ value: 'redlink', label: 'Not Done (notable people)' },

{ value: 'alreadydone', label: 'Already Done' },

{ value: 'denied', label: 'Decline (edits are vandalism)' },

{ value: 'checking', label: 'Checking' },

{ value: 'blocked', label: 'User blocked' },

{ value: 'talk', label: 'Request on article talk page' },

{ value: 'fixed', label: 'Fixed filter' },

{ value: 'question', label: 'Question' },

{ value: 'note', label: 'Note' },

{ value: 'private', label: 'Private filter' },

{ value: 'pin', label: 'Pin' },

{ value: 'moot', label: 'Moot (filter working properly)' },

{ value: 'mootefm', label: 'Moot (may need a change to filter)' }

];

/**

* Actually show the handler for a given reporter name and section number.

*/

EFFPRH.showHandler = function ( reporterName, sectionNum ) {

const targetDivId = 'script-EFFPRH-' + sectionNum;

// Need a reference so that it can be unmounted

let vueAppInstance;

// We shouldn't use the mw.loader access directly, but I'm not

// pasing around the `require` function everywhere

const cdx = mw.loader.require( '@wikimedia/codex' );

// Extra component to render wikitext preview

const previewRenderer = EFFPRH.getPreviewComponent();

const handlerApp = {

components: {

CdxButton: cdx.CdxButton,

CdxSelect: cdx.CdxSelect,

CdxTextInput: cdx.CdxTextInput,

CdxToggleButton: cdx.CdxToggleButton,

previewRenderer: previewRenderer

},

data: function () {

return {

reporterName: reporterName,

sectionNum: sectionNum,

responseOptions: EFFPRH.responseOptions,

selectedResponse: 'none',

commentValue: '',

// Debug information of the state

showDebug: EFFPRH.config.debug,

// Preview

showPreview: false,

// Overall state

haveSubmitted: false,

editMade: false,

editError: false

};

},

computed: {

canSubmit: function () {

return !this.haveSubmitted && this.selectedResponse !== 'none';

},

previewToggleLabel: function () {

return ( this.showPreview ? 'Hide preview' : 'Show preview' );

},

responseWikiText: function () {

// Computed here so that we can use it for the api preview,

// does not include the leading newline

let responseText = ': {{EFFP|' + this.selectedResponse + '}}';

if ( this.commentValue ) {

responseText += ' ' + this.commentValue;

}

responseText += ' --~~~~';

return responseText;

}

},

methods: {

reloadPage: function () {

// Needs to be a function instead of using href so that we

// can force the page to reload

location.assign(

mw.util.getUrl( mw.config.get( 'wgPageName' ) + '#' + this.reporterName )

);

location.reload();

},

submitHandler: function () {

this.haveSubmitted = true;

EFFPRH.respondToReport(

this.reporterName,

this.sectionNum,

this.responseWikiText

).then(

// arrow functions to simplify `this`

() => this.editMade = true,

() => this.editError = true

);

},

cancelHandler: function () {

if ( vueAppInstance === undefined ) {

console.log( 'Cannot unmount, no vueAppInstance' );

} else {

vueAppInstance.unmount();

// Restore link

$( '#script-EFFPRH-launch-' + sectionNum ).removeClass(

'script-EFFPRH-disabled'

);

}

}

},

template: `

`

};

vueAppInstance = Vue.createMwApp( handlerApp );

vueAppInstance.mount( '#' + targetDivId );

};

/**

* Extra component: preview of wikitext being added.

*/

EFFPRH.getPreviewComponent = function () {

return {

props: {

wikitext: { type: String, default: '' }

},

data: function () {

return {

previewHtml: '',

haveHtml: false

};

},

methods: {

// Separate from the watcher so that can be called on mounted too

loadPreview: function ( wikitextToPreview ) {

new mw.Api().get( {

action: 'parse',

formatversion: 2,

title: mw.config.get( 'wgPageName' ),

text: wikitextToPreview,

prop: 'text|wikitext',

pst: true,

disablelimitreport: true,

disableeditsection: true,

sectionpreview: true

} ).then(

( res ) => {

console.log( res );

if ( res

&& res.parse

&& res.parse.wikitext === this.wikitext

&& res.parse.text

) {

this.previewHtml = res.parse.text;

this.haveHtml = true;

}

}

);

}

},

watch: {

wikitext: function ( newValue ) {

// Reset when the wikitext to preview changes

this.previewHtml = '';

this.haveHtml = false;

this.loadPreview( newValue );

}

},

mounted: function () {

// Preview starting wikitext

this.loadPreview( this.wikitext );

},

template: `


Loading preview of {{ wikitext }}

`

};

};

/**

* Actually make the page edit to respond to the report. Returns a promise

* for the edit succeeding or not.

*/

EFFPRH.respondToReport = function (

reporterName,

sectionNum,

responseWikiText

) {

return new Promise( function ( resolve, reject ) {

// wikitext is computed in Vue app so that it can have a preview too,

// we just need to add the leading newline

const wikitextToAdd = '\n' + responseWikiText;

const editParams = {

action: 'edit',

title: mw.config.get( 'wgPageName' ),

section: sectionNum,

summary: '/* ' + reporterName + ' */ ' + EFFPRH.editSummary,

notminor: true,

baserevid: mw.config.get( 'wgCurRevisionId' ),

nocreate: true,

appendtext: wikitextToAdd,

assert: 'user',

assertuser: mw.config.get( 'wgUserName' )

};

if ( EFFPRH.config.debug ) {

console.log( { ...editParams } );

}

new mw.Api().postWithEditToken( editParams )

.then(

( res ) => { console.log( res ); resolve(); },

( err ) => { console.log( err ); reject(); }

);

} );

};

});

$( document ).ready( () => {

if (

mw.config.get( 'wgPageName' ) === 'Wikipedia:Edit_filter/False_positives/Reports'

|| mw.config.get( 'wgPageName' ) === 'User:DannyS712/EFFPRH/sandbox'

) {

window.EFFPRH.init();

}

});

//