From 29f68565a11a428cc051cbe648fc24527d4dda37 Mon Sep 17 00:00:00 2001 From: theopolisme Date: Fri, 23 May 2014 18:13:54 -0500 Subject: [PATCH] Add preferences interface! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lots going on here, I contemplated summarizing it, but… To be continued. This implements the interface and adds a couple of prefs, more still need to be decided and added. --- src/less/general.less | 62 ++++++++ src/less/submissions.less | 66 +-------- src/modules/core.js | 224 +++++++++++++++++++++++++++-- src/modules/submissions.js | 51 ++----- src/templates/tpl-preferences.html | 20 +++ src/templates/tpl-submissions.html | 5 +- 6 files changed, 312 insertions(+), 116 deletions(-) create mode 100644 src/templates/tpl-preferences.html diff --git a/src/less/general.less b/src/less/general.less index 05a2105..b417c63 100644 --- a/src/less/general.less +++ b/src/less/general.less @@ -70,3 +70,65 @@ div.gradient-button, .gradient-button { } } +/* Form styling */ +.afch-form { + .afch-option { + display: block; + padding: 5px; + } + + .afch-label { + padding: 5px; + } + + select .afch-input { + vertical-align: baseline; + } + + .afch-textfield { + -webkit-appearance: none; + border: 1px solid @lightgray; + border-collapse: collapse; + box-sizing: border-box; + cursor: auto; + display: inline-block; + font-size: 14px; + height: 30px; + margin: 0px; + padding-left: 5px; + vertical-align: middle; + width: 25%; + min-width: 150px; + + &:focus { + border: 1px solid @gray; + outline: 0; + } + } + + .afch-textarea { + height: 150px; + width: 50%; + margin-top: 5px; + margin-left: auto; + margin-right: auto; + } + + label, input { + display: inline-block; + } + + label { + text-align: left; + min-width: 200px; + } + + label + input[type=text] { + width: 30%; + min-width: 250px; + } + + input + input { + float: right; + } +} diff --git a/src/less/submissions.less b/src/less/submissions.less index 6a76ce2..5f3c4a1 100644 --- a/src/less/submissions.less +++ b/src/less/submissions.less @@ -94,51 +94,6 @@ padding: 10px; } - /* Label/input styling */ - - .afch-option { - display: block; - padding: 5px; - } - - .afch-label { - font-size: 0.75em; - padding: 5px; - } - - select .afch-input { - vertical-align: baseline; - } - - .afch-textfield { - -webkit-appearance: none; - border: 1px solid @lightgray; - border-collapse: collapse; - box-sizing: border-box; - cursor: auto; - display: inline-block; - font-size: 14px; - height: 30px; - margin: 0px; - padding-left: 5px; - vertical-align: middle; - width: 25%; - min-width: 150px; - - &:focus { - border: 1px solid @gray; - outline: 0; - } - } - - .afch-textarea { - height: 150px; - width: 50%; - margin-top: 5px; - margin-left: auto; - margin-right: auto; - } - /* MAIN (+SPLASH) */ .initial, @@ -158,10 +113,6 @@ font-size: 15px; padding-bottom: 4px; } - - .feedback-link { - padding-left: 5px; - } } .warnings { @@ -281,23 +232,10 @@ .accept { .ThemedPanel(@green); - label, input { - display: inline-block; - } - - label { + .afch-label { + font-size: 0.75em; margin-top: 12px; width: 15%; - text-align: left; - min-width: 150px; - } - - label + input[type=text] { - width: 30%; - } - - input + input { - float: right; } select#lifeStatus.afch-input { diff --git a/src/modules/core.js b/src/modules/core.js index 06a9673..17688f6 100644 --- a/src/modules/core.js +++ b/src/modules/core.js @@ -60,10 +60,9 @@ AFCH.api = new mw.Api(); - // FIXME: Add real preferences code! - AFCH.prefs = { - summaryAd: ' ([[WP:AFCHRW|afch-rewrite]] ' + AFCH.consts.version + ')' - }; + // Set up the preferences interface + AFCH.preferences = new AFCH.Preferences(); + AFCH.prefs = AFCH.preferences.prefStore; // Add more constants -- don't overwrite those already set, though AFCH.consts = $.extend( {}, { @@ -78,8 +77,8 @@ nullstatus: { update: function () { return; } }, // Current user user: mw.user.getName(), - // Wiki id/database name, e.g. "enwiki" - wikiId: mw.config.get( 'wgDBname' ), + // Edit summary ad + summaryAd: ' ([[WP:AFCHRW|afch-rewrite]] ' + AFCH.consts.version + ')', // Require users to be on whitelist to use the script whitelistRequired: true, // Name of the whitelist page for reviewers @@ -143,6 +142,7 @@ // jquery resources 'jquery.chosen', 'jquery.spinner', + 'jquery.ui.dialog', // mediawiki.api 'mediawiki.api', @@ -169,8 +169,8 @@ initFeedback: function ( $element, type, linkText ) { var feedback = new mw.Feedback( { title: new mw.Title( 'Wikipedia talk:WikiProject Articles for creation/Helper script/Rewrite' ), - bugsLink: 'https://github.com/WPAFC/afch-rewrite/issues/new', - bugsListLink: 'https://github.com/WPAFC/afch-rewrite/issues?state=open' + bugsLink: 'https://en.wikipedia.org/w/index.php?title=Wikipedia_talk:WikiProject_Articles_for_creation/Helper_script/Rewrite&action=edit§ion=new', + bugsListLink: 'https://en.wikipedia.org/w/index.php?title=Wikipedia_talk:WikiProject_Articles_for_creation/Helper_script/Rewrite' } ); $( '' ) .text( linkText || 'Give feedback!' ) @@ -553,7 +553,7 @@ action: 'edit', text: options.contents, title: pagename, - summary: options.summary + AFCH.prefs.summaryAd + summary: options.summary + AFCH.consts.summaryAd }; // Depending on mode, set appendtext=text or prependtext=text, @@ -636,7 +636,7 @@ action: 'move', from: oldTitle, to: newTitle, - reason: reason + AFCH.prefs.summaryAd + reason: reason + AFCH.consts.summaryAd }, additionalParameters ); if ( AFCH.consts.mockItUp ) { @@ -709,6 +709,11 @@ logPage = new AFCH.Page( 'User:' + mw.config.get( 'wgUserName' ) + '/' + ( window.Twinkle && window.Twinkle.getPref( 'speedyLogPageName' ) || 'CSD log' ) ); + // Abort if user disabled in preferences + if ( !AFCH.prefs.logCsd ) { + return; + } + logPage.getText().done( function ( logText ) { var status, date = new Date(), @@ -984,11 +989,160 @@ return JSON.parse( cached ); } + // Prevent JSON.parse from exploding on the `undefined` + if ( !fallback ) { + fallback = false; + } + // Otherwise just use mw.user.options return JSON.parse( mw.user.options.get( fullKey, JSON.stringify( fallback ) ) ); } }, + /** + * AFCH.Preferences is a mechanism for accessing and altering user + * preferences in regards to the script. + * + * Preferences are edited by the user via a jquery.ui.dialog and are + * saved and persist for the user using AFCH.userData. + * + * Typical usage: + * AFCH.preferences = new AFCH.Preferences(); + * AFCH.preferences.initLink( $( '.put-prefs-link-here' ) ); + * + * @type {object} + */ + Preferences: function () { + var prefs = this; + + /** + * Default values for user preferences; details for each preference can be + * found inline in `templates/tpl-preferences.html`. + * @type {object} + */ + this.prefDefaults = { + autoOpen: false, + logCsd: true + }; + + /** + * Current user's preferences + * @type {object} + */ + this.prefStore = AFCH.userData.get( 'preferences', this.prefDefaults ); + + /** + * Initializes the preferences modification dialog + */ + this.initDialog = function () { + var $spinner = $.createSpinner( { + size: 'large', + type: 'block' + } ).css( 'padding', '20px' ); + + if ( !this.$dialog ) { + // Initialize the $dialog div + this.$dialog = $( '
' ); + } + + // Until we finish lazy-loading the prefs interface, + // show a spinner in its place. + this.$dialog.empty().append( $spinner ); + + this.$dialog.dialog( { + width: 500, + autoOpen: false, + title: 'AFCH Preferences', + modal: true, + buttons: [ + { + text: 'Cancel', + click: function () { + prefs.$dialog.dialog( 'close' ); + } + }, + { + text: 'Save preferences', + click: function () { + prefs.save(); + prefs.$dialog.empty().append( $spinner ); + } + } + ] + } ); + + // If we've already fetched the template, render immediately + if ( this.views ) { + this.renderMain(); + } else { + // Otherwise, load the template file and *then* render + $.ajax( { + type: 'GET', + url: AFCH.consts.baseurl + '/tpl-preferences.js', + dataType: 'text' + } ).done( function ( data ) { + prefs.views = new AFCH.Views( data ); + prefs.renderMain(); + } ); + } + }; + + /** + * Renders the main preferences menu in the $dialog + */ + this.renderMain = function () { + if ( !( this.views && this.$dialog ) ) { + return; + } + + // Empty the dialog and render the preferences view. Provides the values of all + // of the preferences as variables, as well as an additional few used in other locations. + this.$dialog.empty().append( + this.views.renderView( 'preferences', $.extend( {}, this.prefStore, { + version: AFCH.consts.version, + versionName: AFCH.consts.versionName + } ) ) + ); + }; + + /** + * Updates prefs based on data in the dialog which + * is created in AFCH.preferences.init(). + */ + this.save = function () { + // First, hide the buttons so the user won't start multiple actions + this.$dialog.dialog( { buttons: [] } ); + + // Now update the prefStore + $.extend( this.prefStore, AFCH.getFormValues( this.$dialog.find( '.afch-input' ) ) ); + + // Set the new userData value + AFCH.userData.set( 'preferences', this.prefStore ).done( function () { + // When we're done, close the dialog and notify the user + prefs.$dialog.dialog( 'close' ); + mw.notify( 'AFCH: Preferences saved successfully! They will take effect when the current page is ' + + 'reloaded or when you browse to another page.' ); + } ); + }; + + /** + * Adds a link to launch the preferences modification dialog + * + * @param {jQuery} $element element to append the link to + * @param {string} linkText text to display in the link + */ + this.initLink = function ( $element, linkText ) { + $( '' ) + .text( linkText || 'Update preferences' ) + .addClass( 'preferences-link link' ) + .appendTo( $element ) + .click( function () { + prefs.initDialog(); + prefs.$dialog.dialog( 'open' ); + } ); + }; + }, + /** * Represents a series of "views", aka templateable thingamajigs. * When creating a set of views, they are loaded from a given piece of @@ -1082,6 +1236,56 @@ } }, + /** + * Gets the values of all elements matched by a selector, including + * converting checkboxes to bools, providing textual values of select + * elements, ignoring placeholder elements, and more. + * + * @param {jQuery} $selector elements to get values from + * @return {object} object of values, with the ids as keys + */ + getFormValues: function ( $selector ) { + var data = {}; + + $selector.each( function ( _, element ) { + var value, allTexts, + $element = $( element ); + + if ( element.type === 'checkbox' ) { + value = element.checked; + } else { + value = $element.val(); + + // Ignore placeholder text + if ( value === $element.attr( 'placeholder' ) ) { + value = ''; + } + + // For . + // Primary use for this is the edit summary in handleDecline(). + if ( element.nodeName.toLowerCase() === 'select' ) { + allTexts = []; + + $element.find( 'option:selected' ).each( function () { + allTexts.push( $( this ).text() ); + } ); + + data[element.id + 'Texts'] = allTexts; + } + } + + data[element.id] = value; + } ); + + return data; + }, + /** * Creates an element that links to a given page * @param {string} pagename diff --git a/src/modules/submissions.js b/src/modules/submissions.js index 27ebb5d..a2af64b 100644 --- a/src/modules/submissions.js +++ b/src/modules/submissions.js @@ -662,9 +662,13 @@ $afchLaunchLink = $( mw.util.addPortletLink( 'p-cactions', '#', 'Review (AFCH)', 'ca-afch', 'Review submission using afch-rewrite', '1' ) ); - // When clicked, launch AFCH (only once to prevent serious - // befuddlement, both for the user and for the script...) - $afchLaunchLink.one( 'click', createAFCHInstance ); + if ( AFCH.prefs.autoOpen ) { + // Launch the script immediately + createAFCHInstance(); + } else { + // Otherwise, wait for a click (`one` to prevent spawning multiple panels) + $afchLaunchLink.one( 'click', createAFCHInstance ); + } // Mark launch link for the old helper script as "old" if present on page $( '#p-cactions #ca-afcHelper > a' ).append( ' (old)' ); @@ -805,8 +809,9 @@ } } ); - // Add the feedback link to the header - AFCH.initFeedback( $afch.find( '.initial-header > span' ), '[your topic here]', '(Give feedback!)' ); + // Add feedback and preferences links + AFCH.initFeedback( $afch.find( 'span.feedback-wrapper' ), '[your topic here]', 'give feedback' ); + AFCH.preferences.initLink( $afch.find( 'span.preferences-wrapper' ), 'preferences' ); // Set up click handlers $afch.find( '#afchAccept' ).click( function () { spinnerAndRun( showAcceptOptions ); } ); @@ -1196,41 +1201,7 @@ data.afchText = new AFCH.Text( text ); // Also provide the values for each afch-input element - $afch.find( '.afch-input' ).each( function ( _, element ) { - var value, allTexts, - $element = $( element ); - - if ( element.type === 'checkbox' ) { - value = element.checked; - } else { - value = $element.val(); - - // Ignore placeholder text - if ( value === $element.attr( 'placeholder' ) ) { - value = ''; - } - - // For . - // Primary use for this is the edit summary in handleDecline(). - if ( element.nodeName.toLowerCase() === 'select' ) { - allTexts = []; - - $element.find( 'option:selected' ).each( function () { - allTexts.push( $( this ).text() ); - } ); - - data[element.id + 'Texts'] = allTexts; - } - } - - data[element.id] = value; - } ); + $.extend( data, AFCH.getFormValues( $afch.find( '.afch-input' ) ) ); prepareForProcessing(); diff --git a/src/templates/tpl-preferences.html b/src/templates/tpl-preferences.html new file mode 100644 index 0000000..4dec839 --- /dev/null +++ b/src/templates/tpl-preferences.html @@ -0,0 +1,20 @@ +// + + +
+
+ AFCH v{{version}} / {{versionName}} +
+
+
+ + +
+
+ + +
+
+ + +//
diff --git a/src/templates/tpl-submissions.html b/src/templates/tpl-submissions.html index 6dbd2ff..5c22d93 100644 --- a/src/templates/tpl-submissions.html +++ b/src/templates/tpl-submissions.html @@ -3,7 +3,8 @@
- AFCH v{{version}} + AFCH v{{version}}  + ( | ) Reviewing "{{title}}"
@@ -57,7 +58,7 @@
Accepting...
-
+