User:DannyS712/EFFPRH/sandbox.js
Appearance
< User:DannyS712 | EFFPRH
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:DannyS712/EFFPRH/sandbox. |
// <nowiki>
// 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(
$( '<div>' ).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
* <span> 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 = $( '<a>' )
.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: `
<div class="script-EFFPRH-handler">
<p>Responding to report by {{ reporterName }}.</p>
<p v-if="showDebug">Section {{ sectionNum }}, selected response: {{ selectedResponse }}, comment: {{ commentValue }}.</p>
<!-- Table so that we can align the labels and fields -->
<table><tbody>
<tr>
<td><span>Action:</span></td>
<td><cdx-select v-model:selected="selectedResponse" :menu-items="responseOptions" default-label="Response to report" :disabled="haveSubmitted" /></td>
</tr>
<tr>
<td><span>Comment:</span></td>
<td><cdx-text-input v-model="commentValue" :disabled="haveSubmitted" /></td>
</tr>
</tbody></table>
<br />
<ul v-show="haveSubmitted">
<li>Submitting...</li>
<li v-show="editMade">Success! <a v-on:click="reloadPage"><strong>Reload the page</strong></a></li>
<li v-show="editError">Uh-oh, something went wrong. Please check the console for details.</li>
</ul>
<cdx-button weight="primary" action="progressive" :disabled="!canSubmit" v-on:click="submitHandler">Submit</cdx-button>
<cdx-button weight="primary" action="destructive" :disabled="haveSubmitted" v-on:click="cancelHandler">Cancel</cdx-button>
<cdx-toggle-button v-model="showPreview" :disabled="!canSubmit">{{ previewToggleLabel }}</cdx-toggle-button>
<!-- v-if so that we don't call the api to parse and render a preview when its not needed, do not render with no response template chosen -->
<preview-renderer v-if="showPreview && canSubmit" :wikitext="responseWikiText"></preview-renderer>
</div>`
};
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: `
<div class="script-EFFPRH-preview">
<hr>
<div v-if="haveHtml" v-html="previewHtml"></div>
<div v-else>Loading preview of {{ wikitext }}</div>
</div>`
};
};
/**
* 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();
}
});
// </nowiki>