--- /dev/null
+<!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/lib/jquery/jquery.js"></script>
+ <link rel="stylesheet" href="../../resources/src/jquery/jquery.confirmable.css">
+ <script type="text/javascript" src="../../resources/src/jquery/jquery.confirmable.js"></script>
+ <style>
+ body {
+ font: small sans-serif;
+ }
+ .mw-rollback-link a,
+ .mw-unwatch-link a,
+ .mw-thanks-thank-link a {
+ background: #ccf;
+ }
+ </style>
+</head>
+<body>
+ <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. The enhanced links are highlighted in blue.</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>
+
"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}}",
"hidetoc": "הסתרה",
"collapsible-collapse": "הסתרה",
"collapsible-expand": "הצגה",
+ "confirmable-confirm": "האם {{GENDER:$1|ברצונך}} להמשיך?",
+ "confirmable-yes": "כן",
+ "confirmable-no": "לא",
"thisisdeleted": "לשחזר או להציג $1?",
"viewdeleted": "להציג $1?",
"restorelink": "{{PLURAL:$1|גרסה מחוקה אחת|$1 גרסאות מחוקות}}",
"hidetoc": "This is the link used to hide the table of contents\n\n{{Identical|Hide}}",
"collapsible-collapse": "{{Doc-actionlink}}\nThis is the link used to collapse a collapsible element. (used as plaintext. No wikitext or html is parsed.)\n\nSee also:\n* {{msg-mw|Collapsible-expand}}\n{{Identical|Collapse}}",
"collapsible-expand": "{{Doc-actionlink}}\nThis is the link used to expand a collapsible element (used as plaintext. No wikitext or html is parsed.)\n\nSee also:\n* {{msg-mw|Collapsible-collapse}}\n\nSee the following example:\n{{Identical|Expand}}",
+ "confirmable-confirm": "Question asking the user to confirm a potentially uncancellable action.\n\"Yes\" and \"No\" buttons are displayed beside it.\n\nSee also:\n* {{msg-mw|confirmable-yes}}\n* {{msg-mw|confirmable-no}}\n",
+ "confirmable-yes": "{{Doc-actionlink}}\nText of a button that will confirm triggering of a potentially uncancellable action.\n\nSee also:\n* {{msg-mw|confirmable-confirm}}\n* {{msg-mw|confirmable-no}}",
+ "confirmable-no": "{{Doc-actionlink}}\nText of a button that will cancel triggering of a potentially uncancellable action.\n\nSee also:\n* {{msg-mw|confirmable-confirm}}\n* {{msg-mw|confirmable-yes}}",
"thisisdeleted": "Message shown on a deleted page when the user has the undelete right. Parameters:\n* $1 - a link to [[Special:Undelete]], with {{msg-mw|restorelink}} as the text\nSee also:\n* {{msg-mw|viewdeleted}}",
"viewdeleted": "Message shown on a deleted page when the user does not have the undelete right (but has the deletedhistory right).\n\nParameters:\n* $1 - a link to [[Special:Undelete]], with {{msg-mw|restorelink}} as the text\nSee also:\n* {{msg-mw|thisisdeleted}}",
"restorelink": "This text is always displayed in conjunction with the {{msg-mw|thisisdeleted}} message (View or restore $1?). The user will see\nView or restore <nowiki>{{PLURAL:$1|one deleted edit|$1 deleted edits}}</nowiki>? i.e ''View or restore one deleted edit?'' or\n''View or restore n deleted edits?''",
"../../resources/src/jquery/jquery.checkboxShiftClick.js",
"../../resources/src/jquery/jquery.client.js",
"../../resources/src/jquery/jquery.colorUtil.js",
+ "../../resources/src/jquery/jquery.confirmable.js",
"../../resources/src/jquery/jquery.footHovzer.js",
"../../resources/src/jquery/jquery.getAttrs.js",
"../../resources/src/jquery/jquery.hidpi.js",
'jquery.colorUtil' => array(
'scripts' => 'resources/src/jquery/jquery.colorUtil.js',
),
+ 'jquery.confirmable' => array(
+ 'scripts' => array(
+ 'resources/src/jquery/jquery.confirmable.js',
+ 'resources/src/jquery/jquery.confirmable.mediawiki.js',
+ ),
+ 'messages' => array(
+ 'confirmable-confirm',
+ 'confirmable-yes',
+ 'confirmable-no',
+ ),
+ 'styles' => 'resources/src/jquery/jquery.confirmable.css',
+ 'dependencies' => 'mediawiki.jqueryMsg',
+ ),
// Use mediawiki.cookie in new code, rather than jquery.cookie.
'jquery.cookie' => array(
'scripts' => 'resources/lib/jquery/jquery.cookie.js',
--- /dev/null
+.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);
+}
--- /dev/null
+/**
+ * jQuery confirmable plugin
+ *
+ * Released under the MIT License.
+ *
+ * @author Bartosz Dziewoński
+ *
+ * @class jQuery.plugin.confirmable
+ */
+( function ( $ ) {
+ var identity = function ( data ) {
+ return data;
+ };
+
+ /**
+ * Enable inline confirmation for given clickable element (like `<a />` or `<button />`).
+ *
+ * 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 the `jquery-confirmable-element` class added to it when it's clicked for
+ * the first time, which has `white-space: nowrap;` and `display: inline-block;` defined in CSS.
+ * If the computed values for the element are different when you make it confirmable, you might
+ * encounter unexpected behavior.
+ *
+ * @param {Object} [options]
+ * @param {string} [options.events='click'] Events to hook to.
+ * @param {Function} [options.wrapperCallback] Callback to fire when preparing confirmable
+ * interface. Receives the interface jQuery object as the only parameter.
+ * @param {Function} [options.buttonCallback] Callback to fire when preparing confirmable buttons.
+ * It is fired separately for the 'Yes' and 'No' button. Receives the button jQuery object as
+ * the first parameter and 'yes' or 'no' as the second.
+ * @param {Function} [options.handler] Callback to fire when the action is confirmed (user clicks
+ * the 'Yes' button).
+ * @param {string} [options.i18n] Text to use for interface elements.
+ * @param {string} [options.i18n.confirm] Text to use for the confirmation question.
+ * @param {string} [options.i18n.yes] Text to use for the 'Yes' button.
+ * @param {string} [options.i18n.no] Text to use for the 'No' button.
+ *
+ * @chainable
+ */
+ $.fn.confirmable = function ( options ) {
+ options = $.extend( true, {}, $.fn.confirmable.defaultOptions, options || {} );
+
+ return 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 );
+ } );
+ };
+
+ /**
+ * Default options. Overridable primarily for internationalisation handling.
+ * @property {Object} defaultOptions
+ */
+ $.fn.confirmable.defaultOptions = {
+ events: 'click',
+ wrapperCallback: identity,
+ buttonCallback: identity,
+ handler: null,
+ i18n: {
+ confirm: 'Are you sure?',
+ yes: 'Yes',
+ no: 'No'
+ }
+ };
+}( jQuery ) );
--- /dev/null
+/*!
+ * jQuery confirmable plugin customization for 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 ) );