Skip to content

Commit

Permalink
Add "More" link (to show more categories) to Related Images widget
Browse files Browse the repository at this point in the history
  • Loading branch information
edwardspec committed Oct 20, 2024
1 parent eefa69c commit 89b850d
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 73 deletions.
16 changes: 16 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"root": true,
"extends": [
"wikimedia/client",
"wikimedia/jquery",
"wikimedia/mediawiki"
],
"globals": {
"require": "readonly"
},
"rules": {
"no-jquery/no-event-shorthand": "off",
"no-jquery/no-global-selector": "off",
"no-console": "off"
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
vendor
node_modules
.eslintcache

6 changes: 6 additions & 0 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "stylelint-config-wikimedia",
"rules": {
"selector-max-id": null
}
}
30 changes: 30 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-env node */
module.exports = function ( grunt ) {
var conf = grunt.file.readJSON( 'extension.json' );

grunt.loadNpmTasks( 'grunt-banana-checker' );
grunt.loadNpmTasks( 'grunt-eslint' );
grunt.loadNpmTasks( 'grunt-stylelint' );

grunt.initConfig( {
eslint: {
options: {
cache: true
},
all: [
'**/*.{js,json}',
'!{vendor,node_modules}/**'
]
},
banana: conf.MessagesDirs,
stylelint: {
all: [
'**/*.css',
'!{vendor,node_modules}/**'
]
}
} );

grunt.registerTask( 'test', [ 'eslint', 'banana', 'stylelint' ] );
grunt.registerTask( 'default', 'test' );
};
16 changes: 12 additions & 4 deletions extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,27 @@
},
"config": {
"RelatedImagesMaxCategories": {
"value": 3,
"value": 3,
"description": "How many categories to show in Related Images recommendations."
},
"RelatedImagesMaxImagesPerCategory": {
"value": 3,
"value": 3,
"description": "How many images to show for each category."
},
"RelatedImagesMoreCategoriesPerClick": {
"value": 2,
"description": "How many additional categories to show when user clicks More link at the bottom of Related Images recommendations."
},
"RelatedImagesIgnoredCategories": {
"value": [],
"description": "List of categories that are too generic to be useful when searching for related images. Category names should start with a capital letter and use underscore (_) instead of spaces."
},
"RelatedImagesThumbnailWidth": {
"value": 50,
"value": 50,
"description": "Maximum width of thumbnails of recommended images."
},
"RelatedImagesThumbnailHeight": {
"value": 50,
"value": 50,
"description": "Maximum height of thumbnails of recommended images."
},
"RelatedImagesBoxExtraCssClass": {
Expand Down Expand Up @@ -80,6 +84,10 @@
],
"dependencies": [
"mediawiki.api"
],
"messages": [
"relatedimages-more",
"relatedimages-more-loading"
]
},
"ext.relatedimages.css": {
Expand Down
5 changes: 3 additions & 2 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
},

"relatedimages-desc": "Displays \"Related images\" navigation box on File pages.",
"relatedimages-header": "Related media"
"relatedimages-header": "Related media",
"relatedimages-more": "Show more",
"relatedimages-more-loading": "Loading..."
}

12 changes: 12 additions & 0 deletions i18n/qqq.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"@metadata": {
"authors": [
"Edward Chernenko"
]
},

"relatedimages-desc": "{{desc|name=RelatedImages|url=https://www.mediawiki.org/wiki/Extension:RelatedImages}}",
"relatedimages-header": "Header above the Related Images widget",
"relatedimages-more": "Link that shows more recommended categories",
"relatedimages-more-loading": "Temporarily shown while waiting for more recommendations to be loaded."
}
52 changes: 36 additions & 16 deletions includes/Hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public function onImagePageAfterImageLinks( $imagePage, &$html ) {
global $wgRelatedImagesIgnoredCategories,
$wgRelatedImagesMaxCategories,
$wgRelatedImagesMaxImagesPerCategory,
$wgRelatedImagesMoreCategoriesPerClick,
$wgRelatedImagesBoxExtraCssClass,
$wgRelatedImagesExperimentalPregenerateThumbnails,
$wgRelatedImagesDisableForExtensions,
Expand Down Expand Up @@ -184,14 +185,19 @@ public function onImagePageAfterImageLinks( $imagePage, &$html ) {
);

// Generate HTML of RelatedImages widget.
$widgetWikitext = '__NOTOC__' .
Xml::tags( 'p',
[ 'class' => 'mw-related-images-header' ],
wfMessage( 'relatedimages-header' )->plain()
);
$widgetWikitext = Xml::tags( 'p',
[ 'class' => 'mw-related-images-header' ],
wfMessage( 'relatedimages-header' )->plain()
);
$thumbsize = $this->getThumbnailSize();

$numCategoriesCount = 0;
$numCategoriesOnThisTab = 0;
$tabs = [];
$tabWikitext = '';

$limitOnThisTab = max( 1, $wgRelatedImagesMaxCategories );
$limitForExtraTabs = max( 1, $wgRelatedImagesMoreCategoriesPerClick );

foreach ( $filenamesPerCategory as $category => $filenames ) {
if ( !$filenames ) {
continue;
Expand All @@ -204,33 +210,47 @@ public function onImagePageAfterImageLinks( $imagePage, &$html ) {
}

$categoryName = strtr( $category, '_', ' ' );
$widgetWikitext .= "\n===== [[:Category:$categoryName|$categoryName]] =====\n";
$tabWikitext .= "\n===== [[:Category:$categoryName|$categoryName]] =====\n";

foreach ( $filenames as $filename ) {
if ( isset( $found[$filename] ) ) {
$widgetWikitext .= "[[File:$filename|$thumbsize]]";
$tabWikitext .= "[[File:$filename|$thumbsize]]";
}
}

if ( ++$numCategoriesCount >= $wgRelatedImagesMaxCategories ) {
break;
if ( ++$numCategoriesOnThisTab >= $limitOnThisTab ) {
/*
Widget consists of several "tabs". The first tab is displayed immediately,
and the other tabs are added when user clicks "More" link at the bottom.
*/
$tabs[] = $tabWikitext;
$tabWikitext = '';

$numCategoriesOnThisTab = 0;
$limitOnThisTab = $limitForExtraTabs;
}
}
if ( $numCategoriesCount === 0 ) {
if ( $tabWikitext ) {
$tabs[] = $tabWikitext;
}

if ( !$tabs ) {
// No files found.
return;
}

foreach ( $tabs as $tabWikitext ) {
// This wikitext will later be rendered by Javascript (using api.php?action=parse).
// @phan-suppress-next-line SecurityCheck-DoubleEscaped
$widgetWikitext .= Xml::element( 'pre', null, $tabWikitext );
}

$wrapperClass = 'mw-related-images';
if ( $wgRelatedImagesBoxExtraCssClass ) {
$wrapperClass .= ' ' . $wgRelatedImagesBoxExtraCssClass;
}

// This wikitext will later be rendered by Javascript (using api.php?action=parse).
// @phan-suppress-next-line SecurityCheck-DoubleEscaped
$widgetHtml = Xml::element( 'pre', null, $widgetWikitext );

$html .= Xml::tags( 'div', [ 'class' => $wrapperClass ], $widgetHtml );
$html .= Xml::tags( 'div', [ 'class' => $wrapperClass ], $widgetWikitext );
$out = RequestContext::getMain()->getOutput();
$out->addModuleStyles( [ 'ext.relatedimages.css' ] );
$out->addModules( [ 'ext.relatedimages' ] );
Expand Down
5 changes: 4 additions & 1 deletion modules/ext.relatedimages.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.mw-related-images {
border: 1px solid black;
border: 1px solid #000;
margin: 10px;
padding: 10px;
}

.mw-related-images,
.mw-related-images pre {
/* This widget is not ready to be shown, it will be prepared and unhidden by JavaScript */
display: none;
}
Expand Down
124 changes: 74 additions & 50 deletions modules/ext.relatedimages.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,85 @@
Script for [[File:Something.png]] pages.
*/

var api = new mw.Api();

// Asynchronously parse wikitext of RelatedImages widget (generating thumbnails),
// display it after they are generated.
$( function () {
var $widget = $( '.mw-related-images' );
if ( !$widget.length ) {
return;
}

var $src = $widget.find( 'pre' );
if ( !$src.length ) {
return;
}
( function () {
var api = new mw.Api();

var wikitext = $src.text();
var q = {
action: 'parse',
contentmodel: 'wikitext',
text: wikitext,
prop: 'text',
disableeditsection: '',
disablelimitreport: ''
};

api.post( q ).done( function ( ret ) {
if ( !ret.parse || !ret.parse.text ) {
console.log( 'RelatedImages: no HTML received from action=parse: ', JSON.stringify( ret ) );
// Asynchronously parse wikitext of RelatedImages widget (generating thumbnails),
// display it after they are generated.
function loadTab( $tab ) {
if ( !$tab.length ) {
return;
}

// Unhide the widget (now that we have HTML to populate it).
$src.replaceWith( ret.parse.text['*'] );
$widget.show();
} )
.fail( function ( code, ret ) {
console.log( 'RelatedImages: ajax error: ', JSON.stringify( ret ) );
} );
} );
var wikitext = $tab.text(),
$widget = $tab.parent(),
$nextTab = $tab.next(),
$loading = $( '<div>' )
.attr( 'class', 'mw-relatedimages-loading' )
.append( mw.msg( 'relatedimages-more-loading' ) );

// If the screen is large enough, move $( '.mw-related-images' ) to the right from the main image.
if ( matchMedia( '(min-width: 800px)' ).matches ) {
$( function () {
var $widget = $( '.mw-related-images' );
if ( !$widget.length ) {
return;
}
$tab.replaceWith( $loading );

var q = {
action: 'parse',
contentmodel: 'wikitext',
text: wikitext,
prop: 'text',
disableeditsection: '',
disablelimitreport: '',
disabletoc: ''
};
api.post( q ).done( function ( ret ) {
$tab.empty();

if ( !ret.parse || !ret.parse.text ) {
console.log( 'RelatedImages: no HTML received from action=parse: ', JSON.stringify( ret ) );
return;
}

var $table = $( '<table/>' )
.attr( 'id', 'mw-related-images-table' )
.append( $( '<tr/>' ).append(
$( '<td/>' ).append( $( '#file' ) ),
$( '<td/>' ).attr( 'id', 'mw-related-images-wrapper' ).append( $widget )
) );
// Unhide the widget (now that we have HTML to populate it).
var $parsed = $( '<div>' ).append( ret.parse.text[ '*' ] );

$( '#filetoc' ).after( $table );
if ( $nextTab.length ) {
// Add "More" link to load 1 more tab.
$parsed.append( $( '<a>' )
.append( mw.msg( 'relatedimages-more' ) )
.click( function () {
$( this ).remove();
loadTab( $nextTab );
} )
);
}

$loading.replaceWith( $parsed );
$widget.show();
} ).fail( function ( code, ret ) {
console.log( 'RelatedImages: ajax error: ', JSON.stringify( ret ) );
} );
}

$( function () {
loadTab( $( '.mw-related-images pre' ).first() );
} );
}

// If the screen is large enough, move $( '.mw-related-images' ) to the right
// from the main image.
if ( matchMedia( '(min-width: 800px)' ).matches ) {
$( function () {
var $widget = $( '.mw-related-images' );
if ( !$widget.length ) {
return;
}

var $table = $( '<table>' )
.attr( 'id', 'mw-related-images-table' )
.append( $( '<tr>' ).append(
$( '<td>' ).append( $( '#file' ) ),
$( '<td>' ).attr( 'id', 'mw-related-images-wrapper' ).append( $widget )
) );

$( '#filetoc' ).after( $table );
} );
}

}() );
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"private": true,
"scripts": {
"test": "grunt test"
},
"devDependencies": {
"eslint-config-wikimedia": "0.22.1",
"grunt": "1.5.3",
"grunt-banana-checker": "0.9.00",
"grunt-eslint": "24.0.0",
"grunt-stylelint": "0.18.0",
"stylelint-config-wikimedia": "0.13.0"
}
}

0 comments on commit 89b850d

Please sign in to comment.