MediaWiki:Gadget-WmfProjectStatusHelper.js
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Internet Explorer或Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
- Opera:按 Ctrl-F5。
/* Project Status Helper rmoen@wikimedia.org psh.js v1.0 TODO: Conflict handling: Edit, Replace, Cleanup */ var testTxt = ''; ( function( mw, $ ) { /* Project status helper class */ Psh = function( project ) { this.project = { date: '', name: '', desc: '' }; if( project !== undefined && project.date !== undefined ) { this.project.date = project.date; } else { this.project.date = ( new Date() ).toJSON().substring( 0, 10 ); } if( project !== undefined && project.name !== undefined ) { this.project.name = project.name; } else { this.project.name = mw.config.get( 'wgTitle' ).replace( '/status', '' ); } var _this = this, todayFormatted = this.project.date.replace( 'monthly', '28' ), isMonthly = ( this.project.date.search( 'monthly' ) > -1 ), $modalElements = $( '<form>' ) .attr( 'id', 'projectStatusHelperForm' ) .append( $( '<div>' ) .attr( 'class', 'mw-ajax-loader' ) .css({ position: 'absolute', width: '100%', height: '100%', top: '0px', left: '0px' }) .hide() ).append( $( '<div>' ).attr( 'id', 'projectStatusHelperInput' ) .append( $( '<div>' ) .append( $( '<label>' ) .attr( 'for', 'projectStatusName' ) .text( 'Project Name:' ) ).append( $( '<br>' ).css( 'clear', 'both' ) ).append( $( '<select>' ) .attr({ name: 'projectStatusName', id: 'projectStatusName' }) .bind( 'change', function() { _this.updateStatusLink(); }).append( $( '<option>' ) .attr( 'value', '' ) .text( '- Please Select Project -' ) ) ) ).append( $( '<div>' ) .append( $( '<span>' ) .text( 'Project Link: ' ) ).append( $( '<a>' ) .attr({ id: 'projectLink', target: '_blank' }).css( 'text-decoration', 'underline' ) ) ).append( $( '<div>' ) .append( $( '<label>' ) .attr( 'for', 'projectStatusDate' ) .text( 'Status Date:' ) ).append( $( '<br>' ).css({'clear': 'both'}) ).append( $( '<input>' ) .attr({ type: 'text', name: 'projectStatusDate', id: 'projectStatusDate' }) .val( todayFormatted ) .bind( 'change', function() { _this.fillDescriptionField(); }) ).append( $( '<small>' ).text( 'eg. YYYY-MM-DD' ) ) ).append( $( '<div>' ) .append( $( '<label>' ) .attr( 'for', 'projectStatusMonthlyFlag' ) .text( 'Include in Monthly report?' ) ).append( $( '<input>' ) .attr({ type: 'checkbox', name: 'projectStatusMonthlyFlag', id: 'projectStatusMonthlyFlag' }) .prop( 'checked', isMonthly ) .bind( 'change', function() { _this.fillDescriptionField(); }) ) ).append( $( '<div>' ) .append( $( '<label>' ) .attr( 'for', 'projectStatusUpdateDescription' ) .text( 'Description:' ) ).append( $( '<textarea>' ) .attr({ name: 'projectStatusUpdateDescription', id: 'projectStatusUpdateDescription', style: 'width:98.5% !important' }).css({ padding: '5px', fontSize: '12px', height: '130px' // TODO: remove this after testing }).html( testTxt ) ) ) ).append( $( '<div>' ) .attr( 'id', 'projectStatusPreview' ) .css({ backgroundColor: '#FDFFE7', border: '1px solid #FCEB92', padding: '5px', height: '310px', overflowY: 'scroll', marginBottom: '5px' }).hide() )/* .append( $('<button>') .attr({'id': 'projectStatusBackBtn'}) .text( 'Back' ) .click( function( e ) { e.preventDefault(); _this.back(); }).hide() ).append( $( '<button>' ) .attr({'id': 'projectStatusPreviewBtn'}) .text( 'Preview' ) .click( function( e ) { e.preventDefault(); _this.preview(); }) ).append( $( '<button>' ) .attr({'id': ''}) .text( 'Publish' ) .click( function( e ) { e.preventDefault(); _this.publish(); }) )*/; this.uneditedDescription = ''; var projectPages = [ 'Wikimedia_Features_engineering', 'Wikimedia_Platform_Engineering', 'Wikimedia_mobile_engineering', 'Analytics', 'Wikimedia_Language_engineering' ]; //{{Wikimedia project index line|TimedMediaHandler}} var lookFor = '{{Wikimedia project index line|'; // Testing getPages this.getPages( projectPages, function( pages ) { for ( var page in pages ) { _this.scrubPage( pages[page], lookFor, buildProjectsObject ); } _this.updateStatusLink(); _this.fillDescriptionField(); } ); function buildProjectsObject( title, names ) { var $projectSel = $modalElements.find( '#projectStatusName' ); var $projectGroup = $( '<optgroup>' ).attr( 'label', title ); var name; for ( var i = 0; i < names.length; i++ ) { name = names[i] .replace( lookFor, '' ) .replace( '}}', '' ); $projectGroup .append( $('<option>') .attr( 'value', name ) .prop( 'selected', name === _this.project.name ) .text( name ) ); } $projectSel.append( $projectGroup ); } $( '#psh-dialog' ).remove(); // add to the DOM $( 'body' ).append( $( '<div>' ).attr({ id: 'psh-dialog', title: 'Project Status Helper' }) .append( $modalElements ) .hide() ); mw.loader.using( ['jquery.ui.dialog', 'jquery.ui.datepicker'], function() { $( '#psh-dialog' ).dialog({ height: 'auto', width: 800, modal: true, buttons: [ { id: 'projectStatusBackBtn', text: 'Back', click: function() { _this.back(); } }, { id: 'projectStatusPreviewBtn', text: 'Preview', click: function() { _this.preview(); } }, { id: 'projectStatusPublishBtn', text: 'Publish', click: function() { _this.publish(); } } ] }); // hide preview back button $( '#projectStatusBackBtn' ).hide(); // datepicker that $( '#projectStatusDate' ) .datepicker({'dateFormat': 'yy-mm-dd'}); }); this.$modal = $( '#psh-dialog' ); return this; }; Psh.prototype.scrubPage = function( page, lookFor, callback ) { var lines, i; var found = []; var content = page.revisions[0]['*']; var title = page.title; if ( content.length > 0 ) { lines = content.split( /\n/ ); // Loop through lines for ( i = 0; i < lines.length; i++ ) { if ( lines[i].indexOf( lookFor ) !== -1 ) { found.push( lines[i] ); } } callback( title, found ); } }; Psh.prototype.addStatus = function() { var _this = this, summary = 'new status update'; // Send the wikitext to the parser for rendering $.ajax({ url: mw.util.wikiScript( 'api' ), data: { action: 'edit', // Project page name... title: this.pageName, //appendtext: this.wikitext, text: this.wikitext, token: mw.user.tokens.get( 'editToken' ), format: 'json', summary: summary, notminor: true }, dataType: 'json', type: 'POST', success: function( data ) { if ( data && data.edit && data.edit.result === 'Success' ) { _this.hideSpinner(); window.location.reload( true ); } }, error: function() {} }); }; Psh.prototype.getPages = function( titles, callback ) { var pagesQuery = $.isArray( titles ) ? titles.join( '|' ) : titles; $.ajax({ url: mw.util.wikiScript( 'api' ), data: { action: 'query', format: 'json', titles: pagesQuery, prop: 'revisions', rvprop: 'content' }, dataType: 'json', type: 'GET', cache: 'false', success: function( data ) { if ( data && data.query && data.query.pages ) { // return pages to callback if( typeof callback === 'function' ) { callback( data.query.pages ); } } }, error: function() { if( typeof callback === 'function' ) { callback( {} ); } } }); }; /* quick and dirty validation form */ Psh.prototype.validate = function() { for ( var prop in this.project ) { if ( this.project[prop] === '' ) { return false; } } return true; }; Psh.prototype.fillDescriptionField = function() { var _this = this; var $descField = _this.$modal.find( '#projectStatusUpdateDescription' ); var currentdesc = $descField.val(); if ( this.uneditedDescription != currentdesc && currentdesc != '' ) { if ( !confirm( 'Replace existing entry with content from date (if available)?' ) ) { return false; } } this.setupProject(); if ( this.project.name == '' ) { _this.pageContent = ''; return false; } this.getPages( this.pageName, function( pages ) { _this.fillStatusPageContent( pages ); var splitEntry = _this.getStatusPageSplitAtDate( _this.project.date ); var desc = _this.stripEntry( splitEntry['middle'] ); $descField.val( desc ); _this.uneditedDescription = desc; }); }; Psh.prototype.stripEntry = function( content ) { // strip the title content = content.replace( /^== *([^= ]*) *==\n\n?/m, '' ); // ...and the begin and end tags content = content.replace( /^\s*<section[\s]+begin[^>]*\/>\s*\n?/mg, '' ); content = content.replace( /<section[\s]+end[^>]*\/>\s*\n?$/mg, '' ); return content; }; Psh.prototype.fillStatusPageContent = function( pages ) { // use only first page in pages object. for ( var page in pages ) { // if page exists the title will be in the returned object if ( pages[page].hasOwnProperty( 'revisions' ) ) { // save wikitext, if page empty pageContent will be '' this.pageContent = pages[page].revisions[0]['*']; } else { // page does not exist this.pageContent = ''; } break; } }; /* query for page */ Psh.prototype.publish = function() { var _this = this; this.showSpinner(); this.setupProject(); //console.log( this.project ); if ( this.validate() === false ) { alert( 'Project name, date, and description are required.' ); this.hideSpinner(); return ; } this.getPages( this.pageName, function( pages ) { _this.fillStatusPageContent( pages ); _this.prepareContentAndUpdate(); }); }; Psh.prototype.setupProject = function() { var $helperForm = this.$modal.find( '#projectStatusHelperForm' ); this.project = { date: $helperForm.find( '#projectStatusDate' ).val(), name: $helperForm.find( '#projectStatusName' ).val(), monthly: $helperForm.find( '#projectStatusMonthlyFlag' ).prop( 'checked' ), desc: $helperForm.find( '#projectStatusUpdateDescription' ).val() }; // if monthly status update if ( this.project.monthly ) { this.project.date = this.project.date.substring( 0, 8 ) + 'monthly'; } this.project.latestUpdate = 'Last update on: <section begin="latest"/>' + this.project.date + '<section end="latest"/>'; this.wikitext = ''; }; Psh.prototype.getStatusPageSplitAtDate = function( splitDate ) { var statusLines = [], lookFor = ''; // for replacement, gather text before and after entry var splitEntry = { 'firstline': '', 'before': '', 'middle': '', 'after': '' }; if ( this.pageContent.length > 0 ) { statusLines = this.pageContent.split( /\n/ ); splitEntry['firstline'] = statusLines[0] + '\n'; var datematch = null; var splitState = 'before'; // skip the first line, populate 'before', 'middle', and 'after' for ( var i = 1; i < statusLines.length; i++ ) { datematch = statusLines[i].match( /^== *([^= ]*) *==$/ ); if ( datematch != null ) { if ( datematch[1] == splitDate ) { splitState = 'middle'; } else if ( splitState == 'middle' ) { // we must be starting a new section splitState = 'after'; } } splitEntry[splitState] += statusLines[i] + '\n'; } } return splitEntry; }; /** * replace this.wikitext with new version containing latest update, * then push it to the wiki. */ Psh.prototype.prepareContentAndUpdate = function() { var splitEntry = this.getStatusPageSplitAtDate( this.project.date ); if( splitEntry['middle'] != '' ) { if ( confirm( 'Replace existing entry?' ) ) { splitstate = 'middle'; } else { //console.log( 'abort' ); this.hideSpinner(); return false; } } this.wikitext = this.project.latestUpdate + '\n'; this.wikitext += splitEntry['before']; if( splitEntry['middle'] == '' ) { this.wikitext = this.wikitext.replace( /\n*$/m, '\n\n' ); } this.wikitext += this.buildWikitext(); this.wikitext += splitEntry['after']; this.addStatus(); return true; }; Psh.prototype.buildWikitext = function() { var retval = ''; retval = '== ' + this.project.date + ' ==\n\n'; retval += '<section begin="'+ this.project.date + '"/>' + this.project.desc + '<section end="' + this.project.date + '"/>'; retval += '\n\n'; return retval; }; Psh.prototype.preview = function() { var _this = this; $( '#projectStatusPreviewBtn' ).hide(); $( '#projectStatusBackBtn' ).show(); this.showSpinner(); this.setupProject(); wikitext = this.buildWikitext(); // Send the wikitext to the parser for rendering $.ajax({ url: mw.util.wikiScript( 'api' ), data: { 'action': 'parse', 'title': this.pageName, 'format': 'json', 'text': wikitext, 'prop': 'text', 'pst': true }, dataType: 'json', type: 'POST', success: function ( data ) { _this.$modal.find( '#projectStatusPreview' ) .html( data.parse.text['*'] ) .show() .prev() .hide(); _this.$modal.find( '.editsection' ).remove(); _this.hideSpinner(); }, error: function() {} }); }; Psh.prototype.back = function() { $( '#projectStatusHelperInput, #projectStatusPreviewBtn' ).show(); $( '#projectStatusPreview, #projectStatusBackBtn' ).hide(); }; Psh.prototype.showSpinner = function() { this.$modal .find( '.mw-ajax-loader' ) .show(); }; Psh.prototype.hideSpinner = function() { this.$modal .find( '.mw-ajax-loader' ) .hide(); }; Psh.prototype.updateStatusLink = function() { var path = mw.config.get( 'wgServer' ) + mw.config.get( 'wgArticlePath' ), $inputElement = this.$modal .find( '#projectStatusName' ); var selectedProject = $inputElement.val(); if( selectedProject != '' ) { this.pageName = $inputElement.val() + '/status'; this.pageURL = path.replace( '$1', this.pageName ); $( '#projectLink' ) .attr( 'href', this.pageURL ) .text( this.pageName ); } }; $( document ).ready( function() { // Add a link to the toolbox var link = mw.util.addPortletLink( 'p-tb', '#', 'Project Status', 't-prettylinkwidget', 'Dialog to help you submit your project updates', null, '#t-projectstatushelper' ); // Setup link click event $( link ).click( function( e ) { e.preventDefault(); var psh = new Psh(); $( '#projectStatusHelperInput div' ).css({ 'margin-top': '10px' }); }); // override edit links on project pages $( '.mw-statushelper-editlink > a' ).each( function( i ) { $( this ).click( function( event ) { event.preventDefault(); var projectObj = {}; projectObj.name = $( '.mw-statushelper-editlink' )[i].getAttribute( 'data-statuspage' ).replace( '/status', '' ); projectObj.date = $( '.mw-statushelper-editlink' )[i].getAttribute( 'data-entrydate' ); var psh = new Psh( projectObj ); $( '#projectStatusHelperInput div' ).css({ 'margin-top': '10px' }); }); }); // override add links on project pages $( '.mw-statushelper-addlink > a' ).each( function( i ) { $( this ).click( function( event ) { event.preventDefault(); var projectObj = {}; projectObj.name = $( '.mw-statushelper-addlink' )[i].getAttribute( 'data-statuspage' ).replace( '/status', '' ); var psh = new Psh( projectObj ); $( '#projectStatusHelperInput div' ).css({ 'margin-top': '10px' }); }); }); }); })( mediaWiki, jQuery );