From: Brian Wolff Date: Sat, 8 Jun 2013 04:47:07 +0000 (-0300) Subject: New more slick gallery display X-Git-Tag: 1.31.0-rc.0~18969 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22upgrade%22%2C%22reinstall=non%22%29%20.%20%22?a=commitdiff_plain;h=14b3f8767bdcfe7f5d346f4b4b3e55cc2d32044a;p=lhc%2Fweb%2Fwiklou.git New more slick gallery display This extension adds a "mode" parameter to the gallery tag, allowing different formats for the gallery tag (galleries in the ui can be controlled by a global) The added modes are: *traditional - The original gallery *nolines - Like the original, no borders, less padding *packed - All images aligned by having same height. JS also justifies the images. (I think this one is the one that will go over best with users.) *packed-overlay - like packed, but caption goes over top the image in a transloucent box. *packed-hover - like packed-overlay, but caption only visible on hover. Degrades gracefully on screen readers, and falls back to packed-overlay if you are using a touch screen. I kind of like this mode when the caption is not that important (ex a category where its just the file name). This also adds a hook to allow people to make their own gallery version. I believe there would be interest in this, as different people have done different experiments. For example: * Wikia: http://community.wikia.com/wiki/Help:Galleries,_Slideshows,_and_Sliders/wikitext * Wikinews: https://en.wikinews.org/wiki/Template:Picture_select What I would like to see for this patch, is first it gets enabled, with the default still "traditional". After about a month or two we consult with users. If feedback is positive, we change the default mode to one of the others (probably "packed"). Adds a "mode" parameter to gallery for different mode, including one 'height-constrained-overlay' which looks much more like other modern websites. Note: This makes one change to the old gallery format. It makes Nonexistent files be rendered like thumbnails (i.e. they are rendered with a little grey border). One thing I'm slightly worried about with this patch, is that I added an option to MediaTransformOutput::toHtml to override the width attribute. I'm not sure if that is the best approach, and would appreciate thoughts on that. This should be merged at the same time as Ie82c1548 Change-Id: I33462a8b52502ed76aeb163b66e3704c8618ba23 --- diff --git a/RELEASE-NOTES-1.22 b/RELEASE-NOTES-1.22 index cfe553ebad..780feb9ad0 100644 --- a/RELEASE-NOTES-1.22 +++ b/RELEASE-NOTES-1.22 @@ -43,6 +43,9 @@ production. an entry for 'bar.com' will still match 'foo.bar.com' but not 'foobar.com'. * $wgCopyUploadTimeout and $wgCopyUploadAsyncTimeout added to change the timeout times for fetching the file during upload by url. +* New key added to $wgGalleryOptions - $wgGalleryOptions['mode'] to set + default gallery mode. +* New hook 'GalleryGetModes' to allow extensions to make new gallery modes. === New features in 1.22 === * (bug 44525) mediawiki.jqueryMsg can now parse (whitelisted) HTML elements and attributes. @@ -173,6 +176,7 @@ production. * (bug 30713) New mw.hook "wikipage.content". * (bug 40430) jquery.placeholder gets a new parameter to set the attribute value to be used. +<<<<<<< .merge_file_rX8GYk * $wgHTCPMulticastRouting renamed $wgHTCPRouting since it accepts unicast. * $wgHTCPRouting rules can now be passed an array of hosts/ports to send purge too. Can be used whenever several multicast group could be interested by a @@ -184,6 +188,10 @@ production. setcookie() or setrawcookie() should begin using this instead. * New hook WebResponseSetCookie, called from WebResponse::setcookie(). * New hook ResetSessionID, called when the session id is reset. +======= +* Add a mode parameter to tag with potential options of "traditional", + "nolines", "packed", "packed-overlay", or "packed-hover". +>>>>>>> .merge_file_gNstr1 === Bug fixes in 1.22 === * Disable Special:PasswordReset when $wgEnableEmail is false. Previously one diff --git a/docs/hooks.txt b/docs/hooks.txt index 1f25b47ec5..9d3da1c83c 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -1117,6 +1117,12 @@ $reason: reason $title: An optional title object used to links to sections. Can be null. $local: Boolean indicating whether section links should refer to local page. +'GalleryGetModes': Get list of classes that can render different modes of a + gallery +$modeArray: An associative array mapping mode names to classes that implement + that mode. It is expected all registered classes are a subclass of + ImageGalleryBase. + 'GetAutoPromoteGroups': When determining which autopromote groups a user is entitled to be in. &$user: user to promote. diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 42d7d8869a..45ad16835e 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -136,7 +136,6 @@ $wgAutoloadLocalClasses = array( 'ICacheHelper' => 'includes/CacheHelper.php', 'IcuCollation' => 'includes/Collation.php', 'IdentityCollation' => 'includes/Collation.php', - 'ImageGallery' => 'includes/ImageGallery.php', 'ImageHistoryList' => 'includes/ImagePage.php', 'ImageHistoryPseudoPager' => 'includes/ImagePage.php', 'ImagePage' => 'includes/ImagePage.php', @@ -721,6 +720,16 @@ $wgAutoloadLocalClasses = array( 'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php', 'RightsLogFormatter' => 'includes/logging/RightsLogFormatter.php', + # Image gallery + + 'ImageGallery' => 'includes/gallery/TraditionalImageGallery.php', + 'ImageGalleryBase' => 'includes/gallery/ImageGalleryBase.php', + 'NolinesImageGallery' => 'includes/gallery/NolinesImageGallery.php', + 'TraditionalImageGallery' => 'includes/gallery/TraditionalImageGallery.php', + 'PackedImageGallery' => 'includes/gallery/PackedImageGallery.php', + 'PackedHoverImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', + 'PackedOverlayImageGallery' => 'includes/gallery/PackedOverlayImageGallery.php', + # includes/media 'BitmapHandler' => 'includes/media/Bitmap.php', 'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php', diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php index a98f792416..562ca0c197 100644 --- a/includes/CategoryViewer.php +++ b/includes/CategoryViewer.php @@ -141,8 +141,9 @@ class CategoryViewer extends ContextSource { $this->children = array(); $this->children_start_char = array(); if ( $this->showGallery ) { - $this->gallery = new ImageGallery(); + $this->gallery = ImageGalleryBase::factory(); $this->gallery->setHideBadImages(); + $this->gallery->setContext( $this->getContext() ); } else { $this->imgsNoGallery = array(); $this->imgsNoGallery_start_char = array(); diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 0266659f3b..87c1b6b6a6 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1203,6 +1203,7 @@ $wgGalleryOptions = array( 'imageHeight' => 120, // Height of the cells containing images in galleries (in "px") 'captionLength' => 25, // Length of caption to truncate (in characters) 'showBytes' => true, // Show the filesize in bytes in categories + 'mode' => 'traditional', ); /** diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php deleted file mode 100644 index b4ef90459c..0000000000 --- a/includes/ImageGallery.php +++ /dev/null @@ -1,425 +0,0 @@ -mImages = array(); - $this->mShowBytes = $wgGalleryOptions['showBytes']; - $this->mShowFilename = true; - $this->mParser = false; - $this->mHideBadImages = false; - $this->mPerRow = $wgGalleryOptions['imagesPerRow']; - $this->mWidths = $wgGalleryOptions['imageWidth']; - $this->mHeights = $wgGalleryOptions['imageHeight']; - $this->mCaptionLength = $wgGalleryOptions['captionLength']; - } - - /** - * Register a parser object - * - * @param $parser Parser - */ - function setParser( $parser ) { - $this->mParser = $parser; - } - - /** - * Set bad image flag - */ - function setHideBadImages( $flag = true ) { - $this->mHideBadImages = $flag; - } - - /** - * Set the caption (as plain text) - * - * @param string $caption Caption - */ - function setCaption( $caption ) { - $this->mCaption = htmlspecialchars( $caption ); - } - - /** - * Set the caption (as HTML) - * - * @param string $caption Caption - */ - public function setCaptionHtml( $caption ) { - $this->mCaption = $caption; - } - - /** - * Set how many images will be displayed per row. - * - * @param $num Integer >= 0; If perrow=0 the gallery layout will adapt to screensize - * invalid numbers will be rejected - */ - public function setPerRow( $num ) { - if ( $num >= 0 ) { - $this->mPerRow = (int)$num; - } - } - - /** - * Set how wide each image will be, in pixels. - * - * @param $num Integer > 0; invalid numbers will be ignored - */ - public function setWidths( $num ) { - if ( $num > 0 ) { - $this->mWidths = (int)$num; - } - } - - /** - * Set how high each image will be, in pixels. - * - * @param $num Integer > 0; invalid numbers will be ignored - */ - public function setHeights( $num ) { - if ( $num > 0 ) { - $this->mHeights = (int)$num; - } - } - - /** - * Instruct the class to use a specific skin for rendering - * - * @param $skin Skin object - * @deprecated since 1.18 Not used anymore - */ - function useSkin( $skin ) { - wfDeprecated( __METHOD__, '1.18' ); - /* no op */ - } - - /** - * Add an image to the gallery. - * - * @param $title Title object of the image that is added to the gallery - * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown. - * @param $alt String: Alt text for the image - * @param $link String: Override image link (optional) - * @param $handlerOpts Array: Array of options for image handler (aka page number) - */ - function add( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) { - if ( $title instanceof File ) { - // Old calling convention - $title = $title->getTitle(); - } - $this->mImages[] = array( $title, $html, $alt, $link, $handlerOpts ); - wfDebug( 'ImageGallery::add ' . $title->getText() . "\n" ); - } - - /** - * Add an image at the beginning of the gallery. - * - * @param $title Title object of the image that is added to the gallery - * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown. - * @param $alt String: Alt text for the image - * @param $link String: Override image link (optional) - * @param $handlerOpts Array: Array of options for image handler (aka page number) - */ - function insert( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) { - if ( $title instanceof File ) { - // Old calling convention - $title = $title->getTitle(); - } - array_unshift( $this->mImages, array( &$title, $html, $alt, $link, $handlerOpts ) ); - } - - /** - * isEmpty() returns true if the gallery contains no images - * @return bool - */ - function isEmpty() { - return empty( $this->mImages ); - } - - /** - * Enable/Disable showing of the file size of an image in the gallery. - * Enabled by default. - * - * @param $f Boolean: set to false to disable. - */ - function setShowBytes( $f ) { - $this->mShowBytes = (bool)$f; - } - - /** - * Enable/Disable showing of the filename of an image in the gallery. - * Enabled by default. - * - * @param $f Boolean: set to false to disable. - */ - function setShowFilename( $f ) { - $this->mShowFilename = (bool)$f; - } - - /** - * Set arbitrary attributes to go on the HTML gallery output element. - * Should be suitable for a "; - - return $output; - } - - /** - * @return Integer: number of images in the gallery - */ - public function count() { - return count( $this->mImages ); - } - - /** - * Set the contextual title - * - * @param $title Title: contextual title - */ - public function setContextTitle( $title ) { - $this->contextTitle = $title; - } - - /** - * Get the contextual title, if applicable - * - * @return mixed Title or false - */ - public function getContextTitle() { - return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title - ? $this->contextTitle - : false; - } - - /** - * Determines the correct language to be used for this image gallery - * @return Language object - */ - private function getLang() { - global $wgLang; - return $this->mParser - ? $this->mParser->getTargetLanguage() - : $wgLang; - } - -} //class diff --git a/includes/ImageQueryPage.php b/includes/ImageQueryPage.php index eeec5cd624..75f7ba640b 100644 --- a/includes/ImageQueryPage.php +++ b/includes/ImageQueryPage.php @@ -42,7 +42,8 @@ abstract class ImageQueryPage extends QueryPage { */ protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { if ( $num > 0 ) { - $gallery = new ImageGallery(); + $gallery = ImageGalleryBase::factory(); + $gallery->setContext( $this->getContext() ); # $res might contain the whole 1,000 rows, so we read up to # $num [should update this to use a Pager] diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php index 8829cd92e4..ed96d446d5 100644 --- a/includes/filerepo/file/ForeignAPIFile.php +++ b/includes/filerepo/file/ForeignAPIFile.php @@ -86,7 +86,7 @@ class ForeignAPIFile extends File { * @return string */ static function getProps() { - return 'timestamp|user|comment|url|size|sha1|metadata|mime'; + return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype'; } // Dummy functions... @@ -245,10 +245,12 @@ class ForeignAPIFile extends File { } /** - * @todo FIXME: May guess wrong on file types that can be eg audio or video * @return int|string */ function getMediaType() { + if ( isset( $this->mInfo['mediatype'] ) ) { + return $this->mInfo['mediatype']; + } $magic = MimeMagic::singleton(); return $magic->getMediaType( null, $this->getMimeType() ); } diff --git a/includes/gallery/ImageGalleryBase.php b/includes/gallery/ImageGalleryBase.php new file mode 100644 index 0000000000..f8b8c505b1 --- /dev/null +++ b/includes/gallery/ImageGalleryBase.php @@ -0,0 +1,331 @@ +lc( $mode ); + + if ( isset( self::$modeMapping[$mode] ) ) { + return new self::$modeMapping[$mode]( $mode ); + } else { + throw new MWException( "No gallery class registered for mode $mode" ); + } + } + + static private function loadModes() { + if ( self::$modeMapping === false ) { + self::$modeMapping = array( + 'traditional' => 'TraditionalImageGallery', + 'nolines' => 'NolinesImageGallery', + 'packed' => 'PackedImageGallery', + 'packed-hover' => 'PackedHoverImageGallery', + 'packed-overlay' => 'PackedOverlayImageGallery', + ); + // Allow extensions to make a new gallery format. + wfRunHooks( 'GalleryGetModes', self::$modeMapping ); + } + } + + /** + * Create a new image gallery object. + * + * You should not call this directly, but instead use + * ImageGalleryBase::factory(). + */ + function __construct( $mode = 'traditional' ) { + global $wgGalleryOptions; + $this->mImages = array(); + $this->mShowBytes = $wgGalleryOptions['showBytes']; + $this->mShowFilename = true; + $this->mParser = false; + $this->mHideBadImages = false; + $this->mPerRow = $wgGalleryOptions['imagesPerRow']; + $this->mWidths = $wgGalleryOptions['imageWidth']; + $this->mHeights = $wgGalleryOptions['imageHeight']; + $this->mCaptionLength = $wgGalleryOptions['captionLength']; + $this->mMode = $mode; + } + + /** + * Register a parser object. If you do not set this + * and the output of this gallery ends up in parser + * cache, the javascript will break! + * + * @note This also triggers using the page's target + * language instead of the user language. + * + * @param $parser Parser + */ + function setParser( $parser ) { + $this->mParser = $parser; + } + + /** + * Set bad image flag + */ + function setHideBadImages( $flag = true ) { + $this->mHideBadImages = $flag; + } + + /** + * Set the caption (as plain text) + * + * @param string $caption Caption + */ + function setCaption( $caption ) { + $this->mCaption = htmlspecialchars( $caption ); + } + + /** + * Set the caption (as HTML) + * + * @param string $caption Caption + */ + public function setCaptionHtml( $caption ) { + $this->mCaption = $caption; + } + + /** + * Set how many images will be displayed per row. + * + * @param $num Integer >= 0; If perrow=0 the gallery layout will adapt to screensize + * invalid numbers will be rejected + */ + public function setPerRow( $num ) { + if ( $num >= 0 ) { + $this->mPerRow = (int)$num; + } + } + + /** + * Set how wide each image will be, in pixels. + * + * @param $num Integer > 0; invalid numbers will be ignored + */ + public function setWidths( $num ) { + if ( $num > 0 ) { + $this->mWidths = (int)$num; + } + } + + /** + * Set how high each image will be, in pixels. + * + * @param $num Integer > 0; invalid numbers will be ignored + */ + public function setHeights( $num ) { + if ( $num > 0 ) { + $this->mHeights = (int)$num; + } + } + + /** + * Allow setting additional options. This is meant + * to allow extensions to add additional parameters to + * parser tag. + * + * @param Array $options Attributes of gallery tag. + */ + public function setAdditionalOptions( $options ) { } + + /** + * Instruct the class to use a specific skin for rendering + * + * @param $skin Skin object + * @deprecated since 1.18 Not used anymore + */ + function useSkin( $skin ) { + wfDeprecated( __METHOD__, '1.18' ); + /* no op */ + } + + /** + * Add an image to the gallery. + * + * @param $title Title object of the image that is added to the gallery + * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown. + * @param $alt String: Alt text for the image + * @param $link String: Override image link (optional) + * @param $handlerOpts Array: Array of options for image handler (aka page number) + */ + function add( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) { + if ( $title instanceof File ) { + // Old calling convention + $title = $title->getTitle(); + } + $this->mImages[] = array( $title, $html, $alt, $link, $handlerOpts ); + wfDebug( 'ImageGallery::add ' . $title->getText() . "\n" ); + } + + /** + * Add an image at the beginning of the gallery. + * + * @param $title Title object of the image that is added to the gallery + * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown. + * @param $alt String: Alt text for the image + * @param $link String: Override image link (optional) + * @param $handlerOpts Array: Array of options for image handler (aka page number) + */ + function insert( $title, $html = '', $alt = '', $link = '', $handlerOpts = array() ) { + if ( $title instanceof File ) { + // Old calling convention + $title = $title->getTitle(); + } + array_unshift( $this->mImages, array( &$title, $html, $alt, $link, $handlerOpts ) ); + } + + /** + * isEmpty() returns true if the gallery contains no images + * @return bool + */ + function isEmpty() { + return empty( $this->mImages ); + } + + /** + * Enable/Disable showing of the file size of an image in the gallery. + * Enabled by default. + * + * @param $f Boolean: set to false to disable. + */ + function setShowBytes( $f ) { + $this->mShowBytes = (bool)$f; + } + + /** + * Enable/Disable showing of the filename of an image in the gallery. + * Enabled by default. + * + * @param $f Boolean: set to false to disable. + */ + function setShowFilename( $f ) { + $this->mShowFilename = (bool)$f; + } + + /** + * Set arbitrary attributes to go on the HTML gallery output element. + * Should be suitable for a
    element. + * + * Note -- if taking from user input, you should probably run through + * Sanitizer::validateAttributes() first. + * + * @param array $attribs of HTML attribute pairs + */ + function setAttributes( $attribs ) { + $this->mAttribs = $attribs; + } + + /** + * Display an html representation of the gallery + * + * @return String The html + */ + abstract public function toHTML(); + + /** + * @return Integer: number of images in the gallery + */ + public function count() { + return count( $this->mImages ); + } + + /** + * Set the contextual title + * + * @param $title Title: contextual title + */ + public function setContextTitle( $title ) { + $this->contextTitle = $title; + } + + /** + * Get the contextual title, if applicable + * + * @return mixed Title or false + */ + public function getContextTitle() { + return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title + ? $this->contextTitle + : false; + } + + /** + * Determines the correct language to be used for this image gallery + * @return Language object + */ + protected function getRenderLang() { + return $this->mParser + ? $this->mParser->getTargetLanguage() + : $this->getLanguage(); + } + + /* Old constants no longer used. + const THUMB_PADDING = 30; + const GB_PADDING = 5; + const GB_BORDERS = 8; + */ + +} + diff --git a/includes/gallery/NolinesImageGallery.php b/includes/gallery/NolinesImageGallery.php new file mode 100644 index 0000000000..9e0a494047 --- /dev/null +++ b/includes/gallery/NolinesImageGallery.php @@ -0,0 +1,37 @@ +getThumbPadding() + $boxHeight - $thumbHeight/ self::SCALE_FACTOR ) / 2; + } + + protected function getThumbPadding() { + return 0; + } + + protected function getGBPadding() { + return 2; + } + + /** + * @param File $img The file being transformed. May be false + */ + protected function getThumbParams( $img ) { + if ( $img && $img->getMediaType() === MEDIATYPE_AUDIO ) { + $width = $this->mWidths; + } else { + // We want the width not to be the constraining + // factor, so use random big number. + $width = $this->mHeights * 10 + 100; + } + // self::SCALE_FACTOR so the js has some room to manipulate sizes. + return array( + 'width' => $width * self::SCALE_FACTOR, + 'height' => $this->mHeights * self::SCALE_FACTOR, + ); + } + + protected function getThumbDivWidth( $thumbWidth ) { + // Require at least 60px wide, so caption is wide enough to work. + if ( $thumbWidth < 60 * self::SCALE_FACTOR ) { + $thumbWidth = 60 * self::SCALE_FACTOR; + } + return $thumbWidth / self::SCALE_FACTOR + $this->getThumbPadding(); + } + + /** + * @param MediaTransformOutput|bool $thumb the thumbnail, or false if no thumb (which can happen) + */ + protected function getGBWidth( $thumb ) { + $thumbWidth = $thumb ? $thumb->getWidth() : $this->mWidths * self::SCALE_FACTOR; + return $this->getThumbDivWidth( $thumbWidth ) + $this->getGBPadding(); + } + + protected function adjustImageParameters( $thumb, &$imageParameters ) { + // Re-adjust back to normal size. + $imageParameters['override-width'] = ceil( $thumb->getWidth() / self::SCALE_FACTOR ); + $imageParameters['override-height'] = ceil( $thumb->getHeight() / self::SCALE_FACTOR ); + } + + /** + * Add javascript which auto-justifies the rows by manipulating the image sizes. + * Also ensures that the hover version of this degrades gracefully. + */ + protected function getModules() { + return array( 'mediawiki.page.gallery' ); + } +} diff --git a/includes/gallery/PackedOverlayImageGallery.php b/includes/gallery/PackedOverlayImageGallery.php new file mode 100644 index 0000000000..bba06fcfe5 --- /dev/null +++ b/includes/gallery/PackedOverlayImageGallery.php @@ -0,0 +1,60 @@ + is needed to accommodate htmltidy which + # in version 4.8.6 generated crackpot html in its absence, see: + # http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar + + $thumbWidth = $this->getGBWidth( $thumb ) - $this->getThumbPadding() - $this->getGBPadding(); + $captionWidth = ceil( $thumbWidth - 20 ); + + $outerWrapper = '
    '; + return "\n\t\t\t" . $outerWrapper . '
    ' . "\n" + . $galleryText + . "\n\t\t\t
    "; + } +} + +/** + * Same as Packed except different CSS is applied to make the + * caption only show up on hover. If a touch screen is detected, + * falls back to PackedHoverGallery. Degrades gracefully for + * screen readers. + */ +class PackedHoverImageGallery extends PackedOverlayImageGallery { } diff --git a/includes/gallery/TraditionalImageGallery.php b/includes/gallery/TraditionalImageGallery.php new file mode 100644 index 0000000000..223fb0799e --- /dev/null +++ b/includes/gallery/TraditionalImageGallery.php @@ -0,0 +1,328 @@ +mPerRow > 0 ) { + $maxwidth = $this->mPerRow * ( $this->mWidths + $this->getAllPadding() ); + $oldStyle = isset( $this->mAttribs['style'] ) ? $this->mAttribs['style'] : ''; + # _width is ignored by any sane browser. IE6 doesn't know max-width so it uses _width instead + $this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" . $oldStyle; + } + + $attribs = Sanitizer::mergeAttributes( + array( 'class' => 'gallery mw-gallery-' . $this->mMode ), $this->mAttribs ); + + $modules = $this->getModules(); + + if ( $this->mParser ) { + $this->mParser->getOutput()->addModules( $modules ); + } else { + $this->getOutput()->addModules( $modules ); + } + $output = Xml::openElement( 'ul', $attribs ); + if ( $this->mCaption ) { + $output .= "\n\t
  • {$this->mCaption}
  • "; + } + + $lang = $this->getRenderLang(); + # Output each image... + foreach ( $this->mImages as $pair ) { + $nt = $pair[0]; + $text = $pair[1]; # "text" means "caption" here + $alt = $pair[2]; + $link = $pair[3]; + + $descQuery = false; + if ( $nt->getNamespace() === NS_FILE ) { + # Get the file... + if ( $this->mParser instanceof Parser ) { + # Give extensions a chance to select the file revision for us + $options = array(); + wfRunHooks( 'BeforeParserFetchFileAndTitle', + array( $this->mParser, $nt, &$options, &$descQuery ) ); + # Fetch and register the file (file title may be different via hooks) + list( $img, $nt ) = $this->mParser->fetchFileAndTitle( $nt, $options ); + } else { + $img = wfFindFile( $nt ); + } + } else { + $img = false; + } + + $params = $this->getThumbParams( $img ); + // $pair[4] is per image handler options + $transformOptions = $params + $pair[4]; + + $thumb = false; + + if ( !$img ) { + # We're dealing with a non-image, spit out the name and be done with it. + $thumbhtml = "\n\t\t\t" . '
    ' + . htmlspecialchars( $nt->getText() ) . '
    '; + + if ( $this->mParser instanceof Parser ) { + $this->mParser->addTrackingCategory( 'broken-file-category' ); + } + } elseif ( $this->mHideBadImages && wfIsBadImage( $nt->getDBkey(), $this->getContextTitle() ) ) { + # The image is blacklisted, just show it as a text link. + $thumbhtml = "\n\t\t\t" . '
    ' . + Linker::link( + $nt, + htmlspecialchars( $nt->getText() ), + array(), + array(), + array( 'known', 'noclasses' ) + ) . + '
    '; + } elseif ( !( $thumb = $img->transform( $transformOptions ) ) ) { + # Error generating thumbnail. + $thumbhtml = "\n\t\t\t" . '
    ' + . htmlspecialchars( $img->getLastError() ) . '
    '; + } else { + $vpad = $this->getVPad( $this->mHeights, $thumb->getHeight() ); + + $imageParameters = array( + 'desc-link' => true, + 'desc-query' => $descQuery, + 'alt' => $alt, + 'custom-url-link' => $link + ); + # In the absence of both alt text and caption, fall back on providing screen readers with the filename as alt text + if ( $alt == '' && $text == '' ) { + $imageParameters['alt'] = $nt->getText(); + } + + $this->adjustImageParameters( $thumb, $imageParameters ); + + # Set both fixed width and min-height. + $thumbhtml = "\n\t\t\t" . + '
    ' + # Auto-margin centering for block-level elements. Needed now that we have video + # handlers since they may emit block-level elements as opposed to simple tags. + # ref http://css-discuss.incutio.com/?page=CenteringBlockElement + . '
    ' + . $thumb->toHtml( $imageParameters ) . '
    '; + + // Call parser transform hook + if ( $this->mParser && $img->getHandler() ) { + $img->getHandler()->parserTransformHook( $this->mParser, $img ); + } + } + + //TODO + // $linkTarget = Title::newFromText( $wgContLang->getNsText( MWNamespace::getUser() ) . ":{$ut}" ); + // $ul = Linker::link( $linkTarget, $ut ); + + if ( $this->mShowBytes ) { + if ( $img ) { + $fileSize = htmlspecialchars( $lang->formatSize( $img->getSize() ) ); + } else { + $fileSize = $this->msg( 'filemissing' )->escaped(); + } + $fileSize = "$fileSize
    \n"; + } else { + $fileSize = ''; + } + + $textlink = $this->mShowFilename ? + Linker::link( + $nt, + htmlspecialchars( $lang->truncate( $nt->getText(), $this->mCaptionLength ) ), + array(), + array(), + array( 'known', 'noclasses' ) + ) . "
    \n" : + ''; + + + $galleryText = $textlink . $text . $fileSize; + $galleryText = $this->wrapGalleryText( $galleryText, $thumb ); + + # Weird double wrapping (the extra div inside the li) needed due to FF2 bug + # Can be safely removed if FF2 falls completely out of existence + $output .= + "\n\t\t" . '
  • ' + . '
    ' + . $thumbhtml + . $galleryText + . "\n\t\t
  • "; + } + $output .= "\n
"; + + return $output; + } + + + /** + * Add the wrapper html around the thumb's caption + * + * @param String $galleryText The caption + * @param MediaTransformOutput|boolean $thumb The thumb this caption is for or false for bad image. + */ + protected function wrapGalleryText( $galleryText, $thumb ) { + # ATTENTION: The newline after
is needed to accommodate htmltidy which + # in version 4.8.6 generated crackpot html in its absence, see: + # http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar + + return "\n\t\t\t" . '
' . "\n" + . $galleryText + . "\n\t\t\t
"; + } + + /** + * How much padding such the thumb have between image and inner div that + * that contains the border. This is both for verical and horizontal + * padding. (However, it is cut in half in the vertical direction). + * @return int + */ + protected function getThumbPadding() { + return 30; + } + + /** + * + * @note GB stands for gallerybox (as in the
  • element) + * + * @return int + */ + protected function getGBPadding() { + return 5; + } + + /** + * Get how much extra space the borders around the image takes up. + * + * For this mode, it is 2px borders on each side + 2px implied padding on + * each side from the stylesheet, giving us 2*2+2*2 = 8. + * @return int + */ + protected function getGBBorders() { + return 8; + } + + /** + * Get total padding. + * + * @return int How many pixels of whitespace surround the thumbnail. + */ + protected function getAllPadding() { + return $this->getThumbPadding() + $this->getGBPadding() + $this->getGBBorders(); + } + + /** + * Get vertical padding for a thumbnail + * + * Generally this is the total height minus how high the thumb is. + * + * @param int $boxHeight How high we want the box to be. + * @param int $thumbHeight How high the thumbnail is. + * @return int How many vertical padding to add on each side. + */ + protected function getVPad( $boxHeight, $thumbHeight ) { + return ( $this->getThumbPadding() + $boxHeight - $thumbHeight ) / 2; + } + + /** + * Get the transform parameters for a thumbnail. + * + * @param File $img The file in question. May be false for invalid image + */ + protected function getThumbParams( $img ) { + return array( + 'width' => $this->mWidths, + 'height' => $this->mHeights + ); + } + + /** + * Get the width of the inner div that contains the thumbnail in + * question. This is the div with the class of "thumb". + * + * @param int $thumbWidth The width of the thumbnail. + * @return int Width of inner thumb div. + */ + protected function getThumbDivWidth( $thumbWidth ) { + return $this->mWidths + $this->getThumbPadding(); + } + + /** + * Width of gallerybox
  • . + * + * Generally is the width of the image, plus padding on image + * plus padding on gallerybox. + * + * @note Important: parameter will be false if no thumb used. + * @param Mixed $thumb MediaTransformObject object or false. + * @return int width of gallerybox element + */ + protected function getGBWidth( $thumb ) { + return $this->mWidths + $this->getThumbPadding() + $this->getGBPadding(); + } + + /** + * Get a list of modules to include in the page. + * + * Primarily intended for subclasses. + * + * @return Array modules to include + */ + protected function getModules() { + return array(); + } + + /** + * Adjust the image parameters for a thumbnail. + * + * Used by a subclass to insert extra high resolution images. + * @param MediaTransformOutput $thumb The thumbnail + * @param Array $imageParameters Array of options + */ + protected function adjustImageParameters( $thumb, &$imageParameters ) { } +} + +/** + * Backwards compatibility. This always uses traditional mode + * if called the old way, for extensions that may expect traditional + * mode. + * + * @deprecated 1.22 Use ImageGalleryBase::factory instead. + */ +class ImageGallery extends TraditionalImageGallery { + function __construct( $mode = 'traditional' ) { + wfDeprecated( __METHOD__, '1.22' ); + parent::__construct( $mode ); + } +} diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php index 86323993fd..fde38bb279 100644 --- a/includes/media/MediaTransformOutput.php +++ b/includes/media/MediaTransformOutput.php @@ -298,6 +298,8 @@ class ThumbnailImage extends MediaTransformOutput { * valign vertical-align property, if the output is an inline element * img-class Class applied to the \ tag, if there is such a tag * desc-query String, description link query params + * override-width Override width attribute. Should generally not set + * override-height Override height attribute. Should generally not set * custom-url-link Custom URL to link to * custom-title-link Custom Title object to link to * custom target-link Value of the target attribute, for custom-target-link @@ -359,6 +361,12 @@ class ThumbnailImage extends MediaTransformOutput { if ( !empty( $options['img-class'] ) ) { $attribs['class'] = $options['img-class']; } + if ( isset( $options['override-height'] ) ) { + $attribs['height'] = $options['override-height']; + } + if ( isset( $options['override-width'] ) ) { + $attribs['width'] = $options['override-width']; + } // Additional densities for responsive images, if specified. if ( !empty( $this->responsiveUrls ) ) { diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 8fdf4072c0..2b6363e7d0 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -5047,7 +5047,19 @@ class Parser { */ function renderImageGallery( $text, $params ) { wfProfileIn( __METHOD__ ); - $ig = new ImageGallery(); + + $mode = false; + if ( isset( $params['mode'] ) ) { + $mode = $params['mode']; + } + + try { + $ig = ImageGalleryBase::factory( $mode ); + } catch ( MWException $e ) { + // If invalid type set, fallback to default. + $ig = ImageGalleryBase::factory( false ); + } + $ig->setContextTitle( $this->mTitle ); $ig->setShowBytes( false ); $ig->setShowFilename( false ); @@ -5075,6 +5087,7 @@ class Parser { if ( isset( $params['heights'] ) ) { $ig->setHeights( $params['heights'] ); } + $ig->setAdditionalOptions( $params ); wfRunHooks( 'BeforeParserrenderImageGallery', array( &$this, &$ig ) ); diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index 8e92e4ae10..cf5a01ec0f 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -117,7 +117,8 @@ class NewFilesPager extends ReverseChronologicalPager { function getStartBody() { if ( !$this->gallery ) { - $this->gallery = new ImageGallery(); + $this->gallery = ImageGalleryBase::factory(); + $this->gallery->setContext( $this->getContext ); } return ''; diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index efde5cb213..3a057eaafb 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -682,7 +682,8 @@ class SpecialUpload extends SpecialPage { return ''; } - $gallery = new ImageGallery; + $gallery = ImageGalleryBase::factory(); + $gallery->setContext( $this->getContext() ); $gallery->setShowBytes( false ); foreach ( $dupes as $file ) { $gallery->add( $file->getTitle() ); diff --git a/resources/Resources.php b/resources/Resources.php index 6f0d3beafe..1b0e33c3c0 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -851,6 +851,9 @@ return array( /* MediaWiki Page */ + 'mediawiki.page.gallery' => array( + 'scripts' => 'resources/mediawiki.page/mediawiki.page.gallery.js', + ), 'mediawiki.page.ready' => array( 'scripts' => 'resources/mediawiki.page/mediawiki.page.ready.js', 'dependencies' => array( diff --git a/resources/mediawiki.page/mediawiki.page.gallery.js b/resources/mediawiki.page/mediawiki.page.gallery.js new file mode 100644 index 0000000000..fd2af40752 --- /dev/null +++ b/resources/mediawiki.page/mediawiki.page.gallery.js @@ -0,0 +1,216 @@ +/** + * Show gallery captions when focused. Copied directly from jquery.mw-jump.js. + * Also Dynamically resize images to justify them. + */ +( function ( $, mw ) { + $( function () { + var isTouchScreen, + gettingFocus, + galleries = 'ul.mw-gallery-packed-overlay, ul.mw-gallery-packed-hover, ul.mw-gallery-packed'; + + // Is there a better way to detect a touchscreen? Current check taken from stack overflow. + isTouchScreen = !!( window.ontouchstart !== undefined || window.DocumentTouch !== undefined && document instanceof window.DocumentTouch ); + + if ( isTouchScreen ) { + // Always show the caption for a touch screen. + $( 'ul.mw-gallery-packed-hover' ) + .addClass( 'mw-gallery-packed-overlay' ) + .removeClass( 'mw-gallery-packed-hover' ); + } else { + // Note use of just "a", not a.image, since we want this to trigger if a link in + // the caption receives focus + $( 'ul.mw-gallery-packed-hover li.gallerybox' ).on( 'focus blur', 'a', function ( e ) { + // Confusingly jQuery leaves e.type as focusout for delegated blur events + gettingFocus = e.type !== 'blur' && e.type !== 'focusout'; + $( this ).closest( 'li.gallerybox' ).toggleClass( 'mw-gallery-focused', gettingFocus ); + } ); + } + + // Now on to justification. + // We may still get ragged edges if someone resizes their window. Could bind to + // that event, otoh do we really want to constantly be resizing galleries? + $( galleries ).each( function() { + var lastTop, + $img, + imgWidth, + imgHeight, + rows = [], + $gallery = $( this ); + + $gallery.children( 'li' ).each( function() { + // Math.floor to be paranoid if things are off by 0.00000000001 + var top = Math.floor( $(this ).position().top ), + $this = $( this ); + + if ( top !== lastTop ) { + rows[rows.length] = []; + lastTop = top; + } + + $img = $this.find( 'div.thumb a.image img' ); + if ( $img.length && $img[0].height ) { + imgHeight = $img[0].height; + imgWidth = $img[0].width; + } else { + // If we don't have a real image, get the containing divs width/height. + // Note that if we do have a real image, using this method will generally + // give the same answer, but can be different in the case of a very + // narrow image where extra padding is added. + imgHeight = $this.children().children( 'div:first' ).height(); + imgWidth = $this.children().children( 'div:first' ).width(); + } + + // Hack to make an edge case work ok + if ( imgHeight < 30 ) { + // Don't try and resize this item. + imgHeight = 0; + } + + rows[rows.length-1][rows[rows.length-1].length] = { + $elm: $this, + width: $this.outerWidth(), + imgWidth: imgWidth, + aspect: imgWidth / imgHeight, // XXX: can divide by 0 ever happen? + captionWidth: $this.children().children( 'div.gallerytextwrapper' ).width(), + height: imgHeight + }; + }); + + (function () { + var maxWidth, + combinedAspect, + combinedPadding, + curRow, + curRowHeight, + wantedWidth, + preferredHeight, + newWidth, + padding, + $outerDiv, + $innerDiv, + $imageDiv, + $imageElm, + imageElm, + $caption, + hookInfo, + i, + j; + + for ( i = 0; i < rows.length; i++ ) { + maxWidth = $gallery.width(); + combinedAspect = 0; + combinedPadding = 0; + curRow = rows[i]; + curRowHeight = 0; + + for ( j = 0; j < curRow.length; j++ ) { + if ( curRowHeight === 0 ) { + if ( isFinite( curRow[j].height ) ) { + // Get the height of this row, by taking the first + // non-out of bounds height + curRowHeight = curRow[j].height; + } + } + + if ( curRow[j].aspect === 0 || !isFinite( curRow[j].aspect ) ) { + mw.log( 'Skipping item ' + j + ' due to aspect: ' + curRow[j].aspect ); + // One of the dimensions are 0. Probably should + // not try to resize. + combinedPadding += curRow[j].width; + } else { + combinedAspect += curRow[j].aspect; + combinedPadding += curRow[j].width - curRow[j].imgWidth; + } + } + + // Add some padding for inter-element spacing. + combinedPadding += 5 * curRow.length; + wantedWidth = maxWidth - combinedPadding; + preferredHeight = wantedWidth / combinedAspect; + + if ( preferredHeight > curRowHeight * 1.5 ) { + // Only expand at most 1.5 times current size + // As that's as high a resolution as we have. + // Also on the off chance there is a bug in this + // code, would prevent accidentally expanding to + // be 10 billion pixels wide. + mw.log( 'mw.page.gallery: Cannot fit row, aspect is ' + preferredHeight/curRowHeight ); + preferredHeight = 1.5 * curRowHeight; + } + if ( !isFinite( preferredHeight ) ) { + // This *definitely* should not happen. + mw.log( 'mw.page.gallery: Trying to resize row ' + i + ' to ' + preferredHeight + '?!' ); + // Skip this row. + continue; + } + if ( preferredHeight < 5 ) { + // Well something clearly went wrong... + mw.log( {maxWidth: maxWidth, combinedPadding: combinedPadding, combinedAspect: combinedAspect, wantedWidth: wantedWidth } ); + mw.log( 'mw.page.gallery: [BUG!] Fitting row ' + i + ' to too small a size: ' + preferredHeight ); + // Skip this row. + continue; + } + for ( j = 0; j < curRow.length; j++ ) { + newWidth = preferredHeight * curRow[j].aspect; + padding = curRow[j].width - curRow[j].imgWidth; + $outerDiv = curRow[j].$elm; + $innerDiv = $outerDiv.children( 'div' ).first(); + $imageDiv = $innerDiv.children( 'div.thumb' ); + $imageElm = $imageDiv.find( 'img' ).first(); + imageElm = $imageElm.length ? $imageElm[0] : null; + $caption = $outerDiv.find( 'div.gallerytextwrapper' ); + + + // Since we are going to re-adjust the height, the vertical + // centering margins need to be reset. + $imageDiv.children( 'div' ).css( 'margin', '0px auto' ); + + if ( newWidth < 60 || !isFinite( newWidth ) ) { + // Making something skinnier than this will mess up captions, + mw.log( 'mw.page.gallery: Tried to make image ' + newWidth + 'px wide but too narrow.' ); + if ( newWidth < 1 || !isFinite( newWidth ) ) { + $innerDiv.height( preferredHeight ); + // Don't even try and touch the image size if it could mean + // making it disappear. + continue; + } + } else { + $outerDiv.width( newWidth + padding ); + $innerDiv.width( newWidth + padding ); + $imageDiv.width( newWidth ); + $caption.width( curRow[j].captionWidth + (newWidth - curRow[j].imgWidth ) ); + } + + hookInfo = { + fullWidth: newWidth + padding, + imgWidth: newWidth, + imgHeight: preferredHeight, + $innerDiv: $innerDiv, + $imageDiv: $imageDiv, + $outerDiv: $outerDiv, + resolved: false /* Did the hook take action */ + }; + // Allow other media handlers to hook in. + // If your hook resizes an image, it is expected it will + // set resolved to true. Additionally you should load + // your module in position top to ensure it is registered + // before this runs (FIXME: there must be a better way?) + // See TimedMediaHandler for an example. + mw.hook( 'mediawiki.page.gallery.resize' ).fire( hookInfo ); + + if ( !hookInfo.resolved ) { + if ( imageElm ) { + // We don't always have an img, e.g. in the case of an invalid file. + imageElm.width = newWidth; + imageElm.height = preferredHeight; + } else { + // Not a file box. + $imageDiv.height( preferredHeight ); + } + } + } + } + } )(); + } ); + } ); +} )( jQuery, mediaWiki ); diff --git a/skins/common/shared.css b/skins/common/shared.css index 4c684014e7..a9b9d2beec 100644 --- a/skins/common/shared.css +++ b/skins/common/shared.css @@ -832,6 +832,69 @@ div.gallerytext { word-wrap: break-word; } +/* new gallery stuff */ +ul.mw-gallery-nolines li.gallerybox div.thumb { + background-color: transparent; + border: none; +} +ul.mw-gallery-nolines li.gallerybox div.thumb img { + margin: 0; +} + + +/* height constrained gallery */ + +ul.mw-gallery-packed li.gallerybox div.thumb, +ul.mw-gallery-packed-overlay li.gallerybox div.thumb, +ul.mw-gallery-packed-hover li.gallerybox div.thumb { + background-color: transparent; + border: none; +} +ul.mw-gallery-packed li.gallerybox div.thumb img, +ul.mw-gallery-packed-overlay li.gallerybox div.thumb img, +ul.mw-gallery-packed-hover li.gallerybox div.thumb img { + margin: 0; +} + +ul.mw-gallery-packed-hover li.gallerybox, +ul.mw-gallery-packed-overlay li.gallerybox { + position:relative; +} + +ul.mw-gallery-packed-hover div.gallerytextwrapper { + overflow: hidden; + height: 0; +} + +ul.mw-gallery-packed-hover li.gallerybox:hover div.gallerytextwrapper, +ul.mw-gallery-packed-overlay li.gallerybox div.gallerytextwrapper, +ul.mw-gallery-packed-hover li.gallerybox.mw-gallery-focused div.gallerytextwrapper { + position:absolute; + opacity:.8; + filter:alpha(opacity=80); + zoom: 1; + background-color:white; + padding: 5px 10px; + bottom: 0; + left: 0; /* Needed for IE */ + height: auto; + font-weight: bold; + margin: 2px; /* correspond to style on div.thumb */ +} + +ul.mw-gallery-packed-hover, +ul.mw-gallery-packed-overlay, +ul.mw-gallery-packed { + text-align: center; +} + +ul.mw-gallery-packed-hover div.gallerytext, +ul.mw-gallery-packed-overlay div.gallerytext { + opacity: 1; + position: relative; /* Resets opacity in old IE */ +} + + .mw-ajax-loader { /* @embed */ background-image: url(images/ajax-loader.gif); diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index 57c5e1b085..91fabb751f 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -12766,40 +12766,40 @@ image4 |300px| centre * image6 !! result -