From d7847099a1a2ce9ae6ff5f6544c9824a465983d3 Mon Sep 17 00:00:00 2001 From: Daniel Friesen Date: Sun, 12 Aug 2012 23:18:07 -0700 Subject: [PATCH] [mw.notification] Introduce a full fledged notification system into core replacing jsMessage. - Support for multiple messages instead of just one is included. - Persistent messages are also supported. - Tagged message replacement is supported so that only one message of a type will show up at once. - A light flood protection is included to stop all the notifications from disappearing at once. - Watchlist uses it's own tag now. - Legacy jsMessage messages still replace each other by using a tag: 'legacy' option - #mw-js-message was an old id used for something with a completely different style. So rather than re-using it that id is being dropped. Skins can now style the new notifications. - Core is no longer using messageBox and no extension uses it so instead of making a compatibility layer for it that module is just being dropped. This is a follow up to I41c70d78. Change-Id: I2b3d9cbce6d1514d754b4403ec878d89ea6af29d --- RELEASE-NOTES-1.20 | 5 +- resources/Resources.php | 19 +- resources/jquery/jquery.messageBox.css | 15 - resources/jquery/jquery.messageBox.js | 109 ---- .../mediawiki.page.watch.ajax.js | 8 +- .../mediawiki/mediawiki.notification.css | 26 + resources/mediawiki/mediawiki.notification.js | 477 ++++++++++++++++++ resources/mediawiki/mediawiki.notify.js | 20 + resources/mediawiki/mediawiki.util.js | 78 +-- skins/common/shared.css | 10 - skins/common/wikibits.js | 5 +- skins/monobook/main.css | 20 +- skins/vector/screen.css | 29 +- 13 files changed, 573 insertions(+), 248 deletions(-) delete mode 100644 resources/jquery/jquery.messageBox.css delete mode 100644 resources/jquery/jquery.messageBox.js create mode 100644 resources/mediawiki/mediawiki.notification.css create mode 100644 resources/mediawiki/mediawiki.notification.js create mode 100644 resources/mediawiki/mediawiki.notify.js diff --git a/RELEASE-NOTES-1.20 b/RELEASE-NOTES-1.20 index adc6a58fc3..9eba4e67c1 100644 --- a/RELEASE-NOTES-1.20 +++ b/RELEASE-NOTES-1.20 @@ -122,7 +122,6 @@ upgrade PHP if you have not done so prior to upgrading MediaWiki. * HTMLForm mutators can now be chained (they return $this) * A new message, "api-error-filetype-banned-type", is available for formatting API upload errors due to the file extension blacklist. -* jsMessage: Redesigned in Vector/Monobook as floating bubble with auto-hide. * New hook 'ParserTestGlobals' allows to set globals before running parser tests. * Allow importing pages as subpage. * Add lang and hreflang attributes to language links on Login page. @@ -138,6 +137,10 @@ upgrade PHP if you have not done so prior to upgrading MediaWiki. * Added ResourceLoader module "jquery.badge". * mw.util.$content now points to the overall content area in the skin rather than just page text content area. If you need the old behaviour please use $( '#mw-content-text'). +* jsMessage has been replaced with a floating bubble notification system complete + with auto-hide, multi-message support, and message replacement tags. +* jquery.messageBox which appears to be unused by both core and extensions has + been removed. === Bug fixes in 1.20 === * (bug 30245) Use the correct way to construct a log page title. diff --git a/resources/Resources.php b/resources/Resources.php index 82ab718b1b..f831072f3d 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -175,10 +175,6 @@ return array( 'styles' => 'resources/jquery/jquery.makeCollapsible.css', 'messages' => array( 'collapsible-expand', 'collapsible-collapse' ), ), - 'jquery.messageBox' => array( - 'scripts' => 'resources/jquery/jquery.messageBox.js', - 'styles' => 'resources/jquery/jquery.messageBox.css', - ), 'jquery.mockjax' => array( 'scripts' => 'resources/jquery/jquery.mockjax.js', ), @@ -606,6 +602,16 @@ return array( 'mediawiki.htmlform' => array( 'scripts' => 'resources/mediawiki/mediawiki.htmlform.js', ), + 'mediawiki.notification' => array( + 'styles' => 'resources/mediawiki/mediawiki.notification.css', + 'scripts' => 'resources/mediawiki/mediawiki.notification.js', + 'dependencies' => array( + 'mediawiki.page.startup', + ), + ), + 'mediawiki.notify' => array( + 'scripts' => 'resources/mediawiki/mediawiki.notify.js', + ), 'mediawiki.Title' => array( 'scripts' => 'resources/mediawiki/mediawiki.Title.js', 'dependencies' => 'mediawiki.util', @@ -625,8 +631,8 @@ return array( 'dependencies' => array( 'jquery.client', 'jquery.cookie', - 'jquery.messageBox', 'jquery.mwExtension', + 'mediawiki.notify', ), 'messages' => array( 'showtoc', 'hidetoc' ), 'position' => 'top', // For $wgPreloadJavaScriptMwUtil @@ -781,7 +787,8 @@ return array( 'dependencies' => array( 'mediawiki.page.startup', 'mediawiki.api.watch', - 'mediawiki.util' + 'mediawiki.util', + 'mediawiki.notify', ), 'messages' => array( 'watch', diff --git a/resources/jquery/jquery.messageBox.css b/resources/jquery/jquery.messageBox.css deleted file mode 100644 index 96332aa588..0000000000 --- a/resources/jquery/jquery.messageBox.css +++ /dev/null @@ -1,15 +0,0 @@ -.js-messagebox { - margin: 1em 5%; - padding: 0.5em 2.5%; - border: 1px solid #ccc; - background-color: #fcfcfc; - font-size: 0.8em; -} -.js-messagebox .js-messagebox-group { - margin: 1px; - padding: 0.5em 2.5%; - border-bottom: 1px solid #ddd; -} -.js-messagebox .js-messagebox-group:last-child { - border-bottom: thin none transparent; -} \ No newline at end of file diff --git a/resources/jquery/jquery.messageBox.js b/resources/jquery/jquery.messageBox.js deleted file mode 100644 index c088bc424c..0000000000 --- a/resources/jquery/jquery.messageBox.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * jQuery messageBox - * - * Function to inform the user of something. Use sparingly (since there's mw.log for - * messages aimed at developers / debuggers). Based on the function in MediaWiki's - * legacy javascript (wikibits.js) by Aryeh Gregor called jsMsg() added in r23233. - * - * @author Krinkle - * - * Dual license: - * @license CC-BY 3.0 - * @license GPL2 - */ -( function ( $ ) { - -/** @return jQuery object of the message box */ -$.messageBoxNew = function ( options ) { - options = $.extend( { - // unique identifier for this message box - id: 'js-messagebox', - - // jQuery/CSS selector - parent: 'body', - - // 'prepend' or 'append' - insert: 'prepend' - }, options ); - var $curBox = $( '#' + options.id ); - // Only create a new box if it doesn't exist already - if ( $curBox.length > 0 ) { - if ( $curBox.hasClass( 'js-messagebox' ) ) { - return $curBox; - } else { - return $curBox.addClass( 'js-messagebox' ); - } - } else { - var $newBox = $( '
', { - 'id': options.id, - 'class': 'js-messagebox', - 'css': { - 'display': 'none' - } - }); - if ( $( options.parent ).length < 1 ) { - options.parent = 'body'; - } - if ( options.insert === 'append' ) { - $newBox.appendTo( options.parent ); - return $newBox; - } else { - $newBox.prependTo( options.parent ); - return $newBox; - } - } -}; - -/** - * Calling with no message or message set to empty string or null will hide the group, - * setting 'replace' to true as well will reset and hide the group entirely. - * If there are no visible groups the main message box is hidden automatically, - * and shown again once there are messages - * @return {jQuery}: jQuery object of message group. - */ -$.messageBox = function ( options ) { - options = $.extend( { - message: '', - group: 'default', - // if replace=true, it replaces any previous message in this group - replace: false, - target: 'js-messagebox' - }, options ); - var $target = $.messageBoxNew( { id: options.target } ); - var groupID = options.target + '-' + options.group; - var $group = $( '#' + groupID ); - // Create group container if not existant - if ( $group.length < 1 ) { - $group = $( '
', { - 'id': groupID, - 'class': 'js-messagebox-group' - }); - $target.prepend( $group ); - } - // Replace ? - if ( options.replace === true ) { - $group.empty(); - } - // Hide it ? - if ( options.message === '' || options.message === null ) { - $group.hide(); - } else { - // Actual message addition - $group.prepend( $( '

' ).append( options.message ) ).show(); - $target.slideDown(); - } - // If the last visible group was just hidden, slide the entire box up - // Othere wise slideDown (if already visible nothing will happen) - if ( $target.find( '> *:visible' ).length === 0 ) { - // to avoid a sudden dissapearance of the last group followed by - // a slide up of only the outline, show it for a second - $group.show(); - $target.slideUp(); - $group.hide(); - } else { - $target.slideDown(); - } - return $group; -}; - -}( jQuery ) ); diff --git a/resources/mediawiki.page/mediawiki.page.watch.ajax.js b/resources/mediawiki.page/mediawiki.page.watch.ajax.js index 14557d41a1..a7e059c419 100644 --- a/resources/mediawiki.page/mediawiki.page.watch.ajax.js +++ b/resources/mediawiki.page/mediawiki.page.watch.ajax.js @@ -134,7 +134,7 @@ otherAction = action === 'watch' ? 'unwatch' : 'watch'; $li = $link.closest( 'li' ); - mw.util.jsMessage( watchResponse.message, 'ajaxwatch' ); + mw.notify( $.parseHTML( watchResponse.message ), { tag: 'watch-self' } ); // Set link to opposite updateWatchLink( $link, otherAction ); @@ -149,7 +149,7 @@ }, // Error function () { - var cleanTitle, html, link; + var cleanTitle, msg, link; // Reset link to non-loading mode updateWatchLink( $link, action ); @@ -162,10 +162,10 @@ title: cleanTitle }, cleanTitle ); - html = mw.msg( 'watcherrortext', link ); + msg = mw.messsage( 'watcherrortext', link ); // Report to user about the error - mw.util.jsMessage( html, 'ajaxwatch' ); + mw.notify( msg, { tag: 'watch-self' } ); } ); diff --git a/resources/mediawiki/mediawiki.notification.css b/resources/mediawiki/mediawiki.notification.css new file mode 100644 index 0000000000..9a7b651d84 --- /dev/null +++ b/resources/mediawiki/mediawiki.notification.css @@ -0,0 +1,26 @@ +/** + * Stylesheet for mediawiki.notification module + */ + +#mw-notification-area { + position: absolute; + top: 1em; + right: 1em; + width: 20em; + line-height: 1.35; + z-index: 10000; +} + +.mw-notification { + padding: 0.25em 1em; + margin-bottom: 0.5em; + border: solid 1px #ddd; + background-color: #fcfcfc; + /* Message hides on-click */ + /* See also mediawiki.notification.js */ + cursor: pointer; +} + +.mw-notification-title { + font-weight: bold; +} diff --git a/resources/mediawiki/mediawiki.notification.js b/resources/mediawiki/mediawiki.notification.js new file mode 100644 index 0000000000..5bcbfcc9ea --- /dev/null +++ b/resources/mediawiki/mediawiki.notification.js @@ -0,0 +1,477 @@ +/** + * Implements mediaWiki.notification library + */ +( function ( mw, $ ) { + 'use strict'; + + var isPageReady = false, + isInitialized = false, + preReadyNotifQueue = [], + /** + * @var {jQuery} + * The #mw-notification-area div that all notifications are contained inside. + */ + $area = null; + + /** + * Creates a Notification object for 1 message. + * Does not insert anything into the document (see .start()). + * + * @constructor + * @see mw.notification.notify + */ + function Notification( message, options ) { + var $notification, $notificationTitle, $notificationContent; + + $notification = $( '

' ) + .data( 'mw.notification', this ) + .addClass( options.autoHide ? 'mw-notification-autohide' : 'mw-notification-noautohide' ); + + if ( options.tag ) { + // Sanitize options.tag before it is used by any code. (Including Notification class methods) + options.tag = options.tag.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' ); + if ( options.tag ) { + $notification.addClass( 'mw-notification-tag-' + options.tag ); + } else { + delete options.tag; + } + } + + if ( options.title ) { + $notificationTitle = $( '
' ) + .text( options.title ) + .appendTo( $notification ); + } + + $notificationContent = $( '
' ); + + if ( typeof message === 'object' ) { + // Handle mw.Message objects separately from DOM nodes and jQuery objects + if ( message instanceof mw.Message ) { + $notificationContent.html( message.parse() ); + } else { + $notificationContent.append( message ); + } + } else { + $notificationContent.text( message ); + } + + $notificationContent.appendTo( $notification ); + + // Private state parameters, meant for internal use only + // isOpen: Set to true after .start() is called to avoid double calls. + // Set back to false after .close() to avoid duplicating the close animation. + // isPaused: false after .resume(), true after .pause(). Avoids duplicating or breaking the hide timeouts. + // Set to true initially so .start() can call .resume(). + // message: The message passed to the notification. Unused now but may be used in the future + // to stop replacement of a tagged notification with another notification using the same message. + // options: The options passed to the notification with a little sanitization. Used by various methods. + // $notification: jQuery object containing the notification DOM node. + this.isOpen = false; + this.isPaused = true; + this.message = message; + this.options = options; + this.$notification = $notification; + } + + /** + * Start the notification. + * This inserts it into the page, closes any matching tagged notifications, + * handles the fadeIn animations and repacement transitions, and starts autoHide timers. + */ + Notification.prototype.start = function () { + var + // Local references + $notification, options, + // Original opacity so that we can animate back to it later + opacity, + // Other notification elements matching the same tag + $tagMatches, + outerHeight, + placeholderHeight; + + if ( this.isOpen ) { + return; + } + + this.isOpen = true; + + options = this.options; + $notification = this.$notification; + + opacity = this.$notification.css( 'opacity' ); + + // Set the opacity to 0 so we can fade in later. + $notification.css( 'opacity', 0 ); + + if ( options.tag ) { + // Check to see if there are any tagged notifications with the same tag as the new one + $tagMatches = $area.find( '.mw-notification-tag-' + options.tag ); + } + + // If we found a tagged notification use the replacement pattern instead of the new + // notification fade-in pattern. + if ( options.tag && $tagMatches.length ) { + + // Iterate over the tag matches to find the outerHeight we should use + // for the placeholder. + outerHeight = 0; + $tagMatches.each( function () { + var notif = $( this ).data( 'mw.notification' ); + if ( notif ) { + // Use the notification's height + padding + border + margins + // as the placeholder height. + outerHeight = notif.$notification.outerHeight( true ); + if ( notif.$replacementPlaceholder ) { + // Grab the height of a placeholder that has not finished animating. + placeholderHeight = notif.$replacementPlaceholder.height(); + // Remove any placeholders added by a previous tagged + // notification that was in the middle of replacing another. + // This also makes sure that we only grab the placeholderHeight + // for the most recent notification. + notif.$replacementPlaceholder.remove(); + delete notif.$replacementPlaceholder; + } + // Close the previous tagged notification + // Since we're replacing it do this with a fast speed and don't output a placeholder + // since we're taking care of that transition ourselves. + notif.close( { speed: 'fast', placeholder: false } ); + } + } ); + if ( placeholderHeight !== undefined ) { + // If the other tagged notification was in the middle of replacing another + // tagged notification, continue from the placeholder's height instead of + // using the outerHeight of the notification. + outerHeight = placeholderHeight; + } + + $notification + // Insert the new notification before the tagged notification(s) + .insertBefore( $tagMatches.first() ) + .css( { + // Use an absolute position so that we can use a placeholder to gracefully push other notifications + // into the right spot. + position: 'absolute', + width: $notification.width() + } ) + // Fade-in the notification + .animate( { opacity: opacity }, + { + duration: 'slow', + complete: function () { + // After we've faded in clear the opacity and let css take over + $( this ).css( { opacity: '' } ); + } + } ); + + // Create a clear placeholder we can use to make the notifications around the notification that is being + // replaced expand or contract gracefully to fit the height of the new notification. + var self = this; + self.$replacementPlaceholder = $( '
' ) + // Set the height to the space the previous notification or placeholder took + .css( 'height', outerHeight ) + // Make sure that this placeholder is at the very end of this tagged notification group + .insertAfter( $tagMatches.eq( -1 ) ) + // Animate the placeholder height to the space that this new notification will take up + .animate( { height: $notification.outerHeight( true ) }, + { + // Do space animations fast + speed: 'fast', + complete: function () { + // Reset the notification position after we've finished the space animation + // However do not do it if the placeholder was removed because another tagged + // notification went and closed this one. + if ( self.$replacementPlaceholder ) { + $notification.css( 'position', '' ); + } + // Finally, remove the placeholder from the DOM + $( this ).remove(); + } + } ); + } else { + // Append to the notification area and fade in to the original opacity. + $notification + .appendTo( $area ) + .animate( { opacity: opacity }, + { + duration: 'fast', + complete: function () { + // After we've faded in clear the opacity and let css take over + $( this ).css( 'opacity', '' ); + } + } + ); + } + + // By default a notification is paused. + // If this notification is within the first {autoHideLimit} notifications then + // start the auto-hide timer as soon as it's created. + var autohideCount = $area.find( '.mw-notification-autohide' ).length; + if ( autohideCount <= notification.autoHideLimit ) { + this.resume(); + } + }; + + /** + * Pause any running auto-hide timer for this notification + */ + Notification.prototype.pause = function () { + if ( this.isPaused ) { + return; + } + this.isPaused = true; + + if ( this.timeout ) { + clearTimeout( this.timeout ); + delete this.timeout; + } + }; + + /** + * Start autoHide timer if not already started. + * Does nothing if autoHide is disabled. + * Either to resume from pause or to make the first start. + */ + Notification.prototype.resume = function () { + var notif = this; + if ( !notif.isPaused ) { + return; + } + // Start any autoHide timeouts + if ( notif.options.autoHide ) { + notif.isPaused = false; + notif.timeout = setTimeout( function () { + // Already finished, so don't try to re-clear it + delete notif.timeout; + notif.close(); + }, notification.autoHideSeconds * 1000 ); + } + }; + + /** + * Close/hide the notification. + * + * @param {Object} options An object containing options for the closing of the notification. + * These are typically only used internally. + * - speed: Use a close speed different than the default 'slow'. + * - placeholder: Set to false to disable the placeholder transition. + */ + Notification.prototype.close = function ( options ) { + if ( !this.isOpen ) { + return; + } + this.isOpen = false; + // Clear any remaining timeout on close + this.pause(); + + options = $.extend( { + speed: 'slow', + placeholder: true + }, options ); + + // Remove the mw-notification-autohide class from the notification to avoid + // having a half-closed notification counted as a notification to resume + // when handling {autoHideLimit}. + this.$notification.removeClass( 'mw-notification-autohide' ); + + // Now that a notification is being closed. Start auto-hide timers for any + // notification that has now become one of the first {autoHideLimit} notifications. + notification.resume(); + + this.$notification + .css( { + // Don't trigger any mouse events while fading out, just in case the cursor + // happens to be right above us when we transition upwards. + pointerEvents: 'none', + // Set an absolute position so we can move upwards in the animation. + // Notification replacement doesn't look right unless we use an animation like this. + position: 'absolute', + // We must fix the width to avoid it shrinking horizontally. + width: this.$notification.width() + } ) + // Fix the top/left position to the current computed position from which we + // can animate upwards. + .css( this.$notification.position() ) + // Animate opacity and top to create fade upwards animation for notification closing + .animate( { + opacity: 0, + top: '-=35' + }, { + duration: options.speed, + complete: function () { + // Remove the notification + $( this ).remove(); + if ( options.placeholder ) { + // Use a fast slide up animation after closing to make it look like the notifications + // below slide up into place when the notification disappears + $placeholder.slideUp( 'fast', function () { + // Remove the placeholder + $( this ).remove(); + } ); + } + } + } ); + + if ( options.placeholder ) { + // Insert a placeholder with a height equal to the height of the + // notification plus it's vertical margins in place of the notification + var $placeholder = $( '
' ) + .css( 'height', this.$notification.outerHeight( true ) ) + .insertBefore( this.$notification ); + } + }; + + /** + * Helper function, take a list of notification divs and call + * a function on the Notification instance attached to them + * + * @param {jQuery} $notifications A jQuery object containing notification divs + * @param {string} fn The name of the function to call on the Notification instance + */ + function callEachNotification( $notifications, fn ) { + $notifications.each( function () { + var notif = $( this ).data( 'mw.notification' ); + if ( notif ) { + notif[fn](); + } + } ); + } + + /** + * Initialisation + * (don't call before document ready) + */ + function init() { + if ( !isInitialized ) { + isInitialized = true; + $area = $( '
' ) + // Pause auto-hide timers when the mouse is in the notification area. + .on( { + mouseenter: notification.pause, + mouseleave: notification.resume + } ) + // When clicking on a notification close it. + .on( 'click', '.mw-notification', function () { + var notif = $( this ).data( 'mw.notification' ); + if ( notif ) { + notif.close(); + } + } ) + // Stop click events from tags from propogating to prevent clicking. + // on links from hiding a notification. + .on( 'click', 'a', function ( e ) { + e.stopPropagation(); + } ); + + // Prepend the notification area to the content area and save it's object. + mw.util.$content.prepend( $area ); + } + } + + var notification = { + /** + * Pause auto-hide timers for all notifications. + * Notifications will not auto-hide until resume is called. + */ + pause: function () { + callEachNotification( + $area.children( '.mw-notification' ), + 'pause' + ); + }, + + /** + * Resume any paused auto-hide timers from the beginning. + * Only the first {autoHideLimit} timers will be resumed. + */ + resume: function () { + callEachNotification( + // Only call resume on the first {autoHideLimit} notifications. + // Exclude noautohide notifications to avoid bugs where {autoHideLimit} + // { autoHide: false } notifications are at the start preventing any + // auto-hide notifications from being autohidden. + $area.children( '.mw-notification-autohide' ).slice( 0, notification.autoHideLimit ), + 'resume' + ); + }, + + /** + * Display a notification message to the user. + * + * @param {mixed} message The DOM-element, jQuery object, mw.Message instance, + * or plaintext string to be used as the message. + * @param {Object} options The options to use for the notification. + * See mw.notification.defaults for details. + */ + notify: function ( message, options ) { + var notif; + options = $.extend( {}, notification.defaults, options ); + + notif = new Notification( message, options ); + + if ( isPageReady ) { + notif.start(); + } else { + preReadyNotifQueue.push( notif ); + } + }, + + /** + * @var {Object} + * The defaults for mw.notification.notify's options parameter + * autoHide: + * A boolean indicating whether the notifification should automatically + * be hidden after shown. Or if it should persist. + * + * tag: + * An optional string. When a notification is tagged only one message + * with that tag will be displayed. Trying to display a new notification + * with the same tag as one already being displayed will cause the other + * notification to be closed and this new notification to open up inside + * the same place as the previous notification. + * + * title: + * An optional title for the notification. Will be displayed above the + * content. Usually in bold. + */ + defaults: { + autoHide: true, + tag: false, + title: undefined + }, + + /** + * @var {number} + * Number of seconds to wait before auto-hiding notifications. + */ + autoHideSeconds: 5, + + /** + * @var {number} + * Maximum number of notifications to count down auto-hide timers for. + * Only the first {autoHideLimit} notifications being displayed will + * auto-hide. Any notifications further down in the list will only start + * counting down to auto-hide after the first few messages have closed. + * + * This basically represents the number of notifications the user should + * be able to process in {autoHideSeconds} time. + */ + autoHideLimit: 3 + }; + + $( function () { + var notif; + + init(); + + // Handle pre-ready queue. + isPageReady = true; + while ( preReadyNotifQueue.length ) { + notif = preReadyNotifQueue.shift(); + notif.start(); + } + } ); + + mw.notification = notification; + +}( mediaWiki, jQuery ) ); diff --git a/resources/mediawiki/mediawiki.notify.js b/resources/mediawiki/mediawiki.notify.js new file mode 100644 index 0000000000..3bf2a89634 --- /dev/null +++ b/resources/mediawiki/mediawiki.notify.js @@ -0,0 +1,20 @@ +/** + * Implements mediaWiki.notify function + */ +( function ( mw ) { + 'use strict'; + + /** + * @see mw.notification.notify + */ + mw.notify = function ( message, options ) { + // Don't bother loading the whole notification system if we never use it. + mw.loader.using( 'mediawiki.notification', function () { + // Don't bother calling mw.loader.using a second time after we've already loaded mw.notification. + mw.notify = mw.notification.notify; + // Call notify with the notification the user requested of us. + mw.notify( message, options ); + } ); + }; + +}( mediaWiki ) ); \ No newline at end of file diff --git a/resources/mediawiki/mediawiki.util.js b/resources/mediawiki/mediawiki.util.js index e7e35beea5..2928438497 100644 --- a/resources/mediawiki/mediawiki.util.js +++ b/resources/mediawiki/mediawiki.util.js @@ -5,9 +5,7 @@ 'use strict'; // Local cache and alias - var hideMessageTimeout, - messageBoxEvents = false, - util = { + var util = { /** * Initialisation @@ -16,12 +14,6 @@ init: function () { var profile, $tocTitle, $tocToggleLink, hideTocCookie; - /* Set up $.messageBox */ - $.messageBoxNew( { - id: 'mw-js-message', - parent: '#content' - } ); - /* Set tooltipAccessKeyPrefix */ profile = $.client.profile(); @@ -464,60 +456,18 @@ * Calling with no arguments, with an empty string or null will hide the message * * @param message {mixed} The DOM-element, jQuery object or HTML-string to be put inside the message box. - * @param className {String} Used in adding a class; should be different for each call * to allow CSS/JS to hide different boxes. null = no class used. - * @return {Boolean} True on success, false on failure. + * @depreceated Use mw.notify */ - jsMessage: function ( message, className ) { - var $messageDiv = $( '#mw-js-message' ); - + jsMessage: function ( message ) { if ( !arguments.length || message === '' || message === null ) { - $messageDiv.empty().hide(); - stopHideMessageTimeout(); - return true; // Emptying and hiding message is intended behaviour, return true - } else { - // We special-case skin structures provided by the software. Skins that - // choose to abandon or significantly modify our formatting can just define - // an mw-js-message div to start with. - if ( !$messageDiv.length ) { - $messageDiv = $( '
' ); - if ( util.$content.parent().length ) { - util.$content.parent().prepend( $messageDiv ); - } else { - return false; - } - } - - if ( !messageBoxEvents ) { - messageBoxEvents = true; - $messageDiv - .on( { - 'mouseenter': stopHideMessageTimeout, - 'mouseleave': startHideMessageTimeout, - 'click': hideMessage - } ) - .on( 'click', 'a', function ( e ) { - // Prevent links, even those that don't exist yet, from causing the - // message box to close when clicked - e.stopPropagation(); - } ); - } - - if ( className ) { - $messageDiv.prop( 'className', 'mw-js-message-' + className ); - } - - if ( typeof message === 'object' ) { - $messageDiv.empty(); - $messageDiv.append( message ); - } else { - $messageDiv.html( message ); - } - - $messageDiv.slideDown(); - startHideMessageTimeout(); return true; } + if ( typeof message !== 'object' ) { + message = $.parseHTML( message ); + } + mw.notify( message, { autoHide: true, tag: 'legacy' } ); + return true; }, /** @@ -653,18 +603,6 @@ } }; - // Message auto-hide helpers - function hideMessage() { - $( '#mw-js-message' ).fadeOut( 'slow' ); - } - function stopHideMessageTimeout() { - clearTimeout( hideMessageTimeout ); - } - function startHideMessageTimeout() { - clearTimeout( hideMessageTimeout ); - hideMessageTimeout = setTimeout( hideMessage, 5000 ); - } - mw.util = util; }( mediaWiki, jQuery ) ); diff --git a/skins/common/shared.css b/skins/common/shared.css index 8c35626233..c45948b209 100644 --- a/skins/common/shared.css +++ b/skins/common/shared.css @@ -121,16 +121,6 @@ span.texhtml { cursor: pointer; } -div#mw-js-message { - margin: 1em 5%; - padding: 0.5em 2.5%; - border: solid 1px #ddd; - background-color: #fcfcfc; - /* Message hides on-click */ - /* See also mw.util.jsMessage */ - cursor: pointer; -} - /* Edit section links */ /* Correct directionality when page dir is different from site/user dir */ /* @noflip */ diff --git a/skins/common/wikibits.js b/skins/common/wikibits.js index 6526e8b438..563bd74015 100644 --- a/skins/common/wikibits.js +++ b/skins/common/wikibits.js @@ -515,11 +515,8 @@ window.redirectToFragment = function( fragment ) { * Add a cute little box at the top of the screen to inform the user of * something, replacing any preexisting message. * - * @deprecated since 1.17 Use mw.util.jsMessage instead. + * @deprecated since 1.17 Use mw.util.notify instead. * @param {String|HTMLElement} message To be put inside the message box. - * @param {String} className Used in adding a class; Can be used to selectively - * apply CSS to a certain category of messages. null = no class used. - * @return {Boolean} True on success, false on failure */ window.jsMsg = function () { return mw.util.jsMessage.apply( mw.util, arguments ); diff --git a/skins/monobook/main.css b/skins/monobook/main.css index 5c7ad82c8e..5866d97d04 100644 --- a/skins/monobook/main.css +++ b/skins/monobook/main.css @@ -840,7 +840,7 @@ div#searchTargetHide { } #powersearch p { - margin-top: 0px; + margin-top: 0; } div.multipageimagenavbox { @@ -914,17 +914,9 @@ div.mw-lag-warn-high { font-size: 127%; } -/* jsMessage */ - -div#mw-js-message { - position: absolute; - margin: 0; - padding: 0.25em 1em; - right: 1em; - top: 1em; - width: 20em; - z-index: 10000; - -moz-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.125); - -webkit-box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.125); - box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.125); +/* mediawiki.notification */ +.skin-monobook .mw-notification { + -webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.125); + -moz-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.125); + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.125); } diff --git a/skins/vector/screen.css b/skins/vector/screen.css index bab8822b90..4069d9e47e 100644 --- a/skins/vector/screen.css +++ b/skins/vector/screen.css @@ -784,25 +784,24 @@ div#content a.external[href *=".pdf?"], div#content a.external[href *=".PDF?"], position: relative; width: 100%; } -div#mw-js-message { +div#bodyContent { + line-height: 1.5em; +} + +/* mediawiki.notification */ +.skin-vector #mw-notification-area { + top: 7em; + font-size: 0.8em; +} +.skin-vector .mw-notification { background-color: #fff; background-color: rgba(255, 255, 255, 0.93); - font-size: 0.8em; - position: absolute; - margin: 0; - padding: 1em 2em; - right: 1em; - top: 7em; - width: 20em; - z-index: 10000; + padding: 0.75em 1.5em; border: solid 1px #a7d7f9; border-radius: 0.75em; - -moz-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.125); - -webkit-box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.125); - box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.125); -} -div#bodyContent { - line-height: 1.5em; + -webkit-box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.125); + -moz-box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.125); + box-shadow: 0 2px 10px 0 rgba(0, 0, 0, 0.125); } /* Watch/Unwatch Icon Styling */ -- 2.20.1