Bartosz Dziewoński has uploaded a new change for review. https://gerrit.wikimedia.org/r/92315
Change subject: jQuery.confirmable: New inline confirmation module [WIP] ...................................................................... jQuery.confirmable: New inline confirmation module [WIP] $().confirmable() can be applied to any inline-block element. It will cause it to expand into "Are you sure? [Yes] [No]" question and buttons when clicked, where the buttons are clones of the original element – [Yes] will carry out the default action (or a different one, if specified), [No] will collapse the interface back. Possible uses include: * Confirmable "rollback" links * Confirmable "unwatch" links on watchlists * Confirmable "thank" links (Echo extension's ones) Added a demo with possible uses on history and watchlist pages. Included Hebrew messages courtesy of Moriel. Change-Id: I2f6e0bd4f6f0a84e1a0d7193cde076738f3cdd25 --- A docs/uidesign/confirmable.html M languages/messages/MessagesEn.php M languages/messages/MessagesHe.php M languages/messages/MessagesQqq.php M maintenance/language/messages.inc M resources/Resources.php A resources/jquery/jquery.confirmable.css A resources/jquery/jquery.confirmable.js A resources/jquery/jquery.confirmable.mediawiki.js 9 files changed, 359 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/15/92315/1 diff --git a/docs/uidesign/confirmable.html b/docs/uidesign/confirmable.html new file mode 100644 index 0000000..855cb77 --- /dev/null +++ b/docs/uidesign/confirmable.html @@ -0,0 +1,137 @@ +<!DOCTYPE html> +<html lang="en" dir="ltr"> +<head> + <meta charset="utf-8"> + <!-- + The jQuery.confirmable module uses some additional modules and files + for internationalization support. These are omitted here for simplicity. + --> + <script type="text/javascript" src="../../resources/jquery/jquery.js"></script> + <link rel="stylesheet" href="../../resources/jquery/jquery.confirmable.css"> + <script type="text/javascript" src="../../resources/jquery/jquery.confirmable.js"></script> +</head> +<body style="font: small sans-serif;"> + <h2>Introduction</h2> + + <p>The jQuery.confirmable module provides a simple inline confirmation script for potentially destructive or uncancellable actions.</p> + + <p>Possible uses include confirmable "rollback" links in histories, confirmable "unwatch" links on watchlists, or confirmable "thanks" links (provided by the Echo extension).</p> + + <p>Shown below is a demo of how each of those could work on history and watchlist entries, in an LTR and RTL language.</p> + + <h2>Examples</h2> + + <h3>LTR (English)</h3> + + <p>Watchlist:</p> + + <ul lang="en" dir="ltr"> + <li class="mw-line-even mw-changeslist-line-not-watched"> + (<a href="#">diff</a> | <a href="#">hist</a>) + <span class="mw-changeslist-separator">. .</span> + <span class="mw-title"><a href="#" class="mw-changeslist-title">Example page</a></span>; <span class="mw-changeslist-date">13:38</span> + <span class="mw-changeslist-separator">. .</span> + <span class="mw-plusminus-neg">(-130)</span> + <span class="mw-changeslist-separator">. .</span> + <a href="#" class="mw-userlink">Example user</a> + <span class="mw-usertoollinks">(<a href="#">Talk</a> | <a href="#">contribs</a> | <a href="#">block</a>)</span> + <span class="comment">(example edit)</span> + <span class="mw-rollback-link">[<a href="https://www.mediawiki.org/wiki/Random_ideas_for_rollback_to_be_shelved_and_forgotten_about">rollback</a>]</span> + (<span class="mw-unwatch-link"><a href="#">unwatch</a></span>) + </li> + </ul> + + <p>History:</p> + + <ul lang="en" dir="ltr"> + <li> + <span class="mw-history-histlinks">(cur | <a href="#">prev</a>)</span> + <input type="radio" style="visibility: hidden;" /><input type="radio" checked /> + <a href="#" class="mw-changeslist-date">13:38, 28 October 2013</a> + <span class='history-user'> + <a href="#" class="mw-userlink">Example user</a> + <span class="mw-usertoollinks">(<a href="#">Talk</a> | <a href="#">contribs</a> | <a href="#">block</a>)</span> + </span> + <span class="mw-changeslist-separator">. .</span> + <span class="history-size">(1,654 bytes)</span> + <span class="mw-plusminus-neg">(-130)</span> + <span class="mw-changeslist-separator">. .</span> + <span class="comment">(example edit)</span> + (<span class="mw-rollback-link"><a href="https://www.mediawiki.org/wiki/Random_ideas_for_rollback_to_be_shelved_and_forgotten_about">rollback 1 edit</a></span> | <span class="mw-history-undo"><a href="#">undo</a></span> | <span class="mw-thanks-thank-link"><a href="#">thank</a></span>) + </li> + </ul> + + <script type="text/javascript"> + $( 'ul[lang="en"] .mw-rollback-link a' ) + .confirmable({ i18n: { confirm: 'Are you sure you want to rollback?' } }); + $( 'ul[lang="en"] .mw-unwatch-link a' ) + .confirmable({ handler: function(){ alert('Unwatched!') } }); + $( 'ul[lang="en"] .mw-thanks-thank-link a' ) + .confirmable({ handler: function(){ alert('Thanked!') } }); + </script> + + <h3>RTL (Hebrew)</h3> + <!-- All of the Hebrew text below has been basically pulled out of my… hat. --> + + <p>Watchlist:</p> + + <ul lang="he" dir="rtl"> + <li class="mw-line-even mw-changeslist-line-not-watched"> + (<a href="#">הבדל</a> | <a href="#">היסטוריה</a>) + <span class="mw-changeslist-separator">. .</span> + <span class="mw-title"><a href="#" class="mw-changeslist-title">דף דוגמה</a></span>; <span class="mw-changeslist-date">13:38</span> + <span class="mw-changeslist-separator">. .</span> + <span class="mw-plusminus-neg">(-57)</span> + <span class="mw-changeslist-separator">. .</span> + <a href="#" class="mw-userlink">דוגמא אדם</a> + <span class="mw-usertoollinks">(<a href="#">שיחה</a> | <a href="#">תרומות</a> | <a href="#">חסימה</a>)</span> + <span class="comment">(עריכה לדוגמה)</span> + <span class="mw-rollback-link">[<a href="https://www.mediawiki.org/wiki/Random_ideas_for_rollback_to_be_shelved_and_forgotten_about">שחזור</a>]</span> + (<span class="mw-unwatch-link"><a href="#">הפסקת מעקב</a></span>) + </li> + </ul> + + <p>History:</p> + + <ul lang="he" dir="rtl"> + <li> + <span class="mw-history-histlinks">(נוכחית | <a href="#">קודמת</a>)</span> + <input type="radio" style="visibility: hidden;" /><input type="radio" checked /> + <a href="#" class="mw-changeslist-date">23:41, 12 במאי 2012</a> + <span class='history-user'> + <a href="#" class="mw-userlink">דוגמא אדם</a> + <span class="mw-usertoollinks">(<a href="#">שיחה</a> | <a href="#">תרומות</a> | <a href="#">חסימה</a>)</span> + </span> + <span class="mw-changeslist-separator">. .</span> + <span class="history-size">(1,762 בתים)</span> + <span class="mw-plusminus-neg">(-57)</span> + <span class="mw-changeslist-separator">. .</span> + <span class="comment">(עריכה לדוגמה)</span> + (<span class="mw-rollback-link"><a href="https://www.mediawiki.org/wiki/Random_ideas_for_rollback_to_be_shelved_and_forgotten_about">שחזור עריכה אחת</a></span> | <span class="mw-history-undo"><a href="#">ביטול</a></span> | <span class="mw-thanks-thank-link"><a href="#">תודה</a></span>) + </li> + </ul> + + <script type="text/javascript"> + var hebrewI18n = { + confirm: 'האם ברצונך להמשיך?', + yes: 'כן', + no: 'לא', + } + + $( 'ul[lang="he"] .mw-rollback-link a' ) + .confirmable({ i18n: $.extend( {}, hebrewI18n, { confirm: 'האם ברצונך לשחזר?' } ) }); + $( 'ul[lang="he"] .mw-unwatch-link a' ) + .confirmable({ i18n: hebrewI18n, handler: function(){ alert('Unwatched!') } }); + $( 'ul[lang="he"] .mw-thanks-thank-link a' ) + .confirmable({ i18n: hebrewI18n, handler: function(){ alert('Thanked!') } }); + </script> + <style type="text/css"> + /* This is normally handled by CSSJanus. */ + ul[dir=rtl] .jquery-confirmable-button { + margin-left: 0; + margin-right: 1ex; + } + </style> +</body> +</html> + diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 6d68d36..7c68945 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -954,6 +954,9 @@ 'hidetoc' => 'hide', 'collapsible-collapse' => 'Collapse', 'collapsible-expand' => 'Expand', +'confirmable-confirm' => 'Are {{GENDER:$1|you}} sure?', +'confirmable-yes' => 'Yes', +'confirmable-no' => 'No', 'thisisdeleted' => 'View or restore $1?', 'viewdeleted' => 'View $1?', 'restorelink' => '{{PLURAL:$1|one deleted edit|$1 deleted edits}}', diff --git a/languages/messages/MessagesHe.php b/languages/messages/MessagesHe.php index ffa2ae2..259ad2d 100644 --- a/languages/messages/MessagesHe.php +++ b/languages/messages/MessagesHe.php @@ -655,6 +655,9 @@ 'hidetoc' => 'הסתרה', 'collapsible-collapse' => 'הסתרה', 'collapsible-expand' => 'הצגה', +'confirmable-confirm' => 'האם {{GENDER:$1|ברצונך}} להמשיך?', +'confirmable-yes' => 'כן', +'confirmable-no' => 'לא', 'thisisdeleted' => 'לשחזר או להציג $1?', 'viewdeleted' => 'להציג $1?', 'restorelink' => '{{PLURAL:$1|גרסה מחוקה אחת|$1 גרסאות מחוקות}}', diff --git a/languages/messages/MessagesQqq.php b/languages/messages/MessagesQqq.php index a8b66b2..b4ce02d 100644 --- a/languages/messages/MessagesQqq.php +++ b/languages/messages/MessagesQqq.php @@ -913,6 +913,24 @@ See the following example: {{Identical|Expand}}', +'confirmable-confirm' => 'Question asking the user to confirm a potentially uncancellable action. +"Yes" and "No" buttons are displayed beside it. + +See also: +* {{msg-mw|confirmable-yes}} +* {{msg-mw|confirmable-no}}', +'confirmable-yes' => '{{Doc-actionlink}} +Text of a button that will confirm triggering of a potentially uncancellable action. + +See also: +* {{msg-mw|confirmable-confirm}} +* {{msg-mw|confirmable-no}}', +'confirmable-no' => '{{Doc-actionlink}} +Text of a button that will cancel triggering of a potentially uncancellable action. + +See also: +* {{msg-mw|confirmable-confirm}} +* {{msg-mw|confirmable-yes}}', 'thisisdeleted' => 'Message shown on a deleted page when the user has the undelete right. Parameters: * $1 - a link to [[Special:Undelete]], with {{msg-mw|restorelink}} as the text See also: diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index a4fc922..c29453d 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -335,6 +335,9 @@ 'hidetoc', 'collapsible-collapse', 'collapsible-expand', + 'confirmable-confirm', + 'confirmable-yes', + 'confirmable-no', 'thisisdeleted', 'viewdeleted', 'restorelink', diff --git a/resources/Resources.php b/resources/Resources.php index 2c02de8..5b6453e 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -191,6 +191,19 @@ 'jquery.colorUtil' => array( 'scripts' => 'resources/jquery/jquery.colorUtil.js', ), + 'jquery.confirmable' => array( + 'scripts' => array( + 'resources/jquery/jquery.confirmable.js', + 'resources/jquery/jquery.confirmable.mediawiki.js', + ), + 'messages' => array( + 'confirmable-confirm', + 'confirmable-yes', + 'confirmable-no', + ), + 'styles' => 'resources/jquery/jquery.confirmable.css', + 'dependencies' => 'mediawiki.jqueryMsg', + ), 'jquery.cookie' => array( 'scripts' => 'resources/jquery/jquery.cookie.js', 'targets' => array( 'desktop', 'mobile' ), diff --git a/resources/jquery/jquery.confirmable.css b/resources/jquery/jquery.confirmable.css new file mode 100644 index 0000000..de69072 --- /dev/null +++ b/resources/jquery/jquery.confirmable.css @@ -0,0 +1,28 @@ +.jquery-confirmable-button { + /* Automatically flipped */ + margin-left: 1ex; +} + +.jquery-confirmable-wrapper { + /* Line breaks within the interface text are unpleasant */ + white-space: nowrap; + /* Hiding the original text when it slides to the left */ + overflow: hidden; +} + +.jquery-confirmable-wrapper, +.jquery-confirmable-element, +.jquery-confirmable-interface { + /* We need inline-block to be able to size the elements and calculate their dimensions */ + display: inline-block; + /* inline-block elements in this context align to baseline by default */ + vertical-align: bottom; +} + +.jquery-confirmable-element { + transition: margin 250ms cubic-bezier(0.2, 0.8, 0.2, 0.8); +} + +.jquery-confirmable-interface { + transition: width 250ms cubic-bezier(0.2, 0.8, 0.2, 0.8); +} diff --git a/resources/jquery/jquery.confirmable.js b/resources/jquery/jquery.confirmable.js new file mode 100644 index 0000000..39682e6 --- /dev/null +++ b/resources/jquery/jquery.confirmable.js @@ -0,0 +1,145 @@ +/** + * jQuery confirmable plugin + * + * Calling .confirmable() on a clickable element (like <a> or <button>) will + * result in an additional inline confirmation step being shown before the + * default action is carried out on click. + * + * Calling .confirmable({ handler: function () { … } }) will fire the handler + * only after the confirmation step. + * + * The element will have class="jquery-confirmable-element" added to it when + * it's clicked for the first time, which among other things means + * white-space: nowrap; and display: inline-block;. If the computed values for + * it are different when you make it confirmable, you might encounter unexpected + * behavior. + * + * @author Bartosz "Matma Rex" Dziewoński, 2013 + * + * @license MIT + */ +( function ( $ ) { + var identity = function ( data ) { + return data; + }; + + $.fn.confirmable = function ( options ) { + options = $.extend( true, {}, $.fn.confirmable.defaultOptions, options || {} ); + + this.on( options.events, function ( e ) { + var $element, $text, $buttonYes, $buttonNo, $wrapper, $interface, $elementClone, + interfaceWidth, elementWidth, rtl, positionOffscreen, positionRestore, sideMargin; + + $element = $( this ); + + if ( $element.data( 'jquery-confirmable-button' ) ) { + // We're running on a clone of this element that represents the 'Yes' or 'No' button. + // (This should never happen for the 'No' case unless calling code does bad things.) + return; + } + + // Only prevent native event handling. Stopping other JavaScript event handlers + // is impossible because they might have already run (we have no control over the order). + e.preventDefault(); + + rtl = $element.css( 'direction' ) === 'rtl'; + if ( rtl ) { + positionOffscreen = { position: 'absolute', right: '-9999px' }; + positionRestore = { position: '', right: '' }; + sideMargin = 'marginRight'; + } else { + positionOffscreen = { position: 'absolute', left: '-9999px' }; + positionRestore = { position: '', left: '' }; + sideMargin = 'marginLeft'; + } + + if ( $element.hasClass( 'jquery-confirmable-element' ) ) { + $wrapper = $element.closest( '.jquery-confirmable-wrapper' ); + $interface = $wrapper.find( '.jquery-confirmable-interface' ); + $text = $interface.find( '.jquery-confirmable-text' ); + $buttonYes = $interface.find( '.jquery-confirmable-button-yes' ); + $buttonNo = $interface.find( '.jquery-confirmable-button-no' ); + + interfaceWidth = $interface.data( 'jquery-confirmable-width' ); + elementWidth = $element.data( 'jquery-confirmable-width' ); + } else { + $elementClone = $element.clone( true ); + $element.addClass( 'jquery-confirmable-element' ); + + elementWidth = $element.width(); + $element.data( 'jquery-confirmable-width', elementWidth ); + + $wrapper = $( '<span>' ) + .addClass( 'jquery-confirmable-wrapper' ); + $element.wrap( $wrapper ); + + // Build the mini-dialog + $text = $( '<span>' ) + .addClass( 'jquery-confirmable-text' ) + .text( options.i18n.confirm ); + + // Clone original element along with event handlers to easily replicate its behavior. + // We could fiddle with .trigger() etc., but that is troublesome especially since + // Safari doesn't implement .click() on <a> links and jQuery follows suit. + $buttonYes = $elementClone.clone( true ) + .addClass( 'jquery-confirmable-button jquery-confirmable-button-yes' ) + .data( 'jquery-confirmable-button', true ) + .text( options.i18n.yes ); + if ( options.handler ) { + $buttonYes.on( options.events, options.handler ); + } + $buttonYes = options.buttonCallback( $buttonYes, 'yes' ); + + // Clone it without any events and prevent default action to represent the 'No' button. + $buttonNo = $elementClone.clone( false ) + .addClass( 'jquery-confirmable-button jquery-confirmable-button-no' ) + .data( 'jquery-confirmable-button', true ) + .text( options.i18n.no ) + .on( options.events, function ( e ) { + $element.css( sideMargin, 0 ); + $interface.css( 'width', 0 ); + e.preventDefault(); + } ); + $buttonNo = options.buttonCallback( $buttonNo, 'no' ); + + // Prevent memory leaks + $elementClone.remove(); + + $interface = $( '<span>' ) + .addClass( 'jquery-confirmable-interface' ) + .append( $text, $buttonYes, $buttonNo ); + $interface = options.wrapperCallback( $interface ); + + // Render offscreen to measure real width + $interface.css( positionOffscreen ); + // Insert it in the correct place while we're at it + $element.after( $interface ); + interfaceWidth = $interface.width(); + $interface.data( 'jquery-confirmable-width', interfaceWidth ); + $interface.css( positionRestore ); + + // Hide to animate the transition later + $interface.css( 'width', 0 ); + } + + // Hide element, show interface. This triggers both transitions. + // In a timeout to trigger the 'width' transition. + setTimeout( function () { + $element.css( sideMargin, -elementWidth ); + $interface.css( 'width', interfaceWidth ); + }, 1 ); + } ); + }; + + $.fn.confirmable.defaultOptions = { + events: 'click', + wrapperCallback: identity, + buttonCallback: identity, + handler: null, + i18n: { + confirm: 'Are you sure?', + yes: 'Yes', + no: 'No' + } + }; +}( jQuery ) ); diff --git a/resources/jquery/jquery.confirmable.mediawiki.js b/resources/jquery/jquery.confirmable.mediawiki.js new file mode 100644 index 0000000..8802418 --- /dev/null +++ b/resources/jquery/jquery.confirmable.mediawiki.js @@ -0,0 +1,9 @@ +/* jQuery.confirmable itself is independent of MediaWiki. + This file serves to inject our localised messages into it. */ +( function ( mw , $ ) { + $.fn.confirmable.defaultOptions.i18n = { + confirm: mw.message( 'confirmable-confirm', mw.user ).text(), + yes: mw.message( 'confirmable-yes' ).text(), + no: mw.message( 'confirmable-no' ).text() + }; +}( mediaWiki, jQuery ) ); -- To view, visit https://gerrit.wikimedia.org/r/92315 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I2f6e0bd4f6f0a84e1a0d7193cde076738f3cdd25 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: Bartosz Dziewoński <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
