ThumbnailInfo: support multi lingual SVG

Bug: T208564
Change-Id: Icf082ca0dc94bc4739fd31b795de76f8a0083b70
This commit is contained in:
Simon Legner 2024-05-28 11:07:27 +02:00 committed by Derk-Jan Hartman
parent 4e1c2e841f
commit 4672f2eb57
9 changed files with 98 additions and 59 deletions

View file

@ -2,7 +2,7 @@
"modules": [
{
"resourceModule": "mmv",
"maxSize": "26.6 kB"
"maxSize": "26.9 kB"
},
{
"resourceModule": "mmv.ui.restriction",

View file

@ -206,7 +206,7 @@ class MultimediaViewer {
this.currentIndex = image.index;
this.currentImageFileTitle = image.filePageTitle;
this.currentImage = image;
if ( !this.isOpen ) {
$( document ).trigger( $.Event( 'mmv-setup-overlay' ) );
@ -632,9 +632,9 @@ class MultimediaViewer {
guessing = true;
thumbnailPromise = this.guessedThumbnailInfoProvider.get(
fileTitle, sampleUrl, width, originalWidth, originalHeight
).then( null, () => this.thumbnailInfoProvider.get( fileTitle, width ) );
).then( null, () => this.thumbnailInfoProvider.get( fileTitle, sampleUrl, width ) );
} else {
thumbnailPromise = this.thumbnailInfoProvider.get( fileTitle, width );
thumbnailPromise = this.thumbnailInfoProvider.get( fileTitle, sampleUrl, width );
}
imagePromise = thumbnailPromise.then( ( thumbnail ) => this.imageProvider.get( thumbnail.url ) );
@ -644,7 +644,7 @@ class MultimediaViewer {
// As a side effect this introduces an extra (harmless) retry of a failed thumbnailInfoProvider.get call
// because thumbnailInfoProvider.get is already called above when guessedThumbnailInfoProvider.get fails.
imagePromise = imagePromise
.then( null, () => this.thumbnailInfoProvider.get( fileTitle, width )
.then( null, () => this.thumbnailInfoProvider.get( fileTitle, sampleUrl, width )
.then( ( thumbnail ) => this.imageProvider.get( thumbnail.url ) ) );
}
@ -748,7 +748,7 @@ class MultimediaViewer {
*/
setTitle() {
// update title after route change, see T225387
document.title = this.createDocumentTitle( this.currentImageFileTitle );
document.title = this.createDocumentTitle( this.currentImage && this.currentImage.filePageTitle );
}
/**
@ -813,13 +813,13 @@ class MultimediaViewer {
} ).on( 'mmv-resize-end.mmvp', () => {
this.resize( this.ui );
} ).on( 'mmv-request-thumbnail.mmvp', ( e, size ) => {
if ( this.currentImageFileTitle ) {
return this.thumbnailInfoProvider.get( this.currentImageFileTitle, size );
if ( this.currentImage && this.currentImage.filePageTitle ) {
return this.thumbnailInfoProvider.get( this.currentImage.filePageTitle, this.currentImage.src, size );
} else {
return $.Deferred().reject();
}
} ).on( 'mmv-viewfile.mmvp', () => {
this.imageInfoProvider.get( this.currentImageFileTitle ).done( ( imageInfo ) => {
this.imageInfoProvider.get( this.currentImage.filePageTitle ).done( ( imageInfo ) => {
document.location = imageInfo.url;
} );
} );

View file

@ -42,12 +42,15 @@ class ThumbnailInfo extends Api {
* is smaller).
*
* @param {mw.Title} file
* @param {string} sampleUrl a thumbnail URL for the same file (but with different size).
* @param {number} width thumbnail width in pixels
* @param {number} height thumbnail height in pixels
* @return {jQuery.Promise.<Thumbnail>}
*/
get( file, width, height ) {
const cacheKey = `${ file.getPrefixedDb() }|${ width || '' }|${ height || '' }`;
get( file, sampleUrl, width, height ) {
const match = sampleUrl.match( /(lang|page)([\d\-a-z]+)-(\d+)px/ ); // multi lingual SVG or PDF page
const iiurlparam = match ? `${ match[ 1 ] }${ match[ 2 ] }-${ width }px` : undefined;
const cacheKey = [ file.getPrefixedDb(), width || '', height || '', iiurlparam || '' ].join();
return this.getCachedPromise( cacheKey, () => this.apiGetWithMaxAge( {
formatversion: 2,
@ -55,6 +58,7 @@ class ThumbnailInfo extends Api {
prop: 'imageinfo',
titles: file.getPrefixedDb(),
iiprop: 'url',
iiurlparam,
iiurlwidth: width,
iiurlheight: height
} ).then( ( data ) => this.getQueryPage( data ) ).then( ( page ) => {

View file

@ -739,7 +739,7 @@ class MetadataPanel extends UiElement {
this.$datetimeUpdatedLi.removeClass( 'empty' );
}
this.buttons.set( imageData );
this.buttons.set( image, imageData );
this.description.set( imageData.description, image.caption );
this.setLicense( imageData.license, imageData.descriptionUrl );

View file

@ -44,16 +44,29 @@ class StripeButtons extends UiElement {
/**
* @inheritdoc
* @param {LightboxImage} image
* @param {ImageModel} imageInfo
*/
set( imageInfo ) {
set( image, imageInfo ) {
const match = image && image.src ?
image.src.match( /(lang|page)([\d\-a-z]+)-(\d+)px/ ) : // multi lingual SVG or PDF page
null;
const params = {};
if ( match ) {
params[ match[ 1 ] ] = match[ 2 ];
}
let descriptionUrl = imageInfo.descriptionUrl;
let isCommons = String( descriptionUrl ).includes( '//commons.wikimedia.org/' );
if ( imageInfo.pageID ) {
// The file has a local description page, override the description URL
descriptionUrl = imageInfo.title.getUrl();
descriptionUrl = imageInfo.title.getUrl( params );
isCommons = false;
} else {
const parsedUrl = new mw.Uri( descriptionUrl );
parsedUrl.extend( params );
descriptionUrl = parsedUrl.toString();
}
this.$descriptionPage.empty()

View file

@ -460,7 +460,7 @@ const { MultimediaViewerBootstrap } = require( 'mmv.bootstrap' );
const originalWidth = 50;
const viewer = getMultimediaViewer();
viewer.thumbnailInfoProvider.get = function ( fileTitle, width ) {
viewer.thumbnailInfoProvider.get = function ( fileTitle, sampleUrl, width ) {
assert.strictEqual( width, expectedWidth );
return $.Deferred().reject();
};
@ -603,7 +603,7 @@ const { MultimediaViewerBootstrap } = require( 'mmv.bootstrap' );
const oldDocumentTitle = document.title;
this.sandbox.stub( mw.loader, 'using' ).returns( $.Deferred().resolve( viewer ) );
viewer.currentImageFileTitle = title;
viewer.currentImage = { filePageTitle: title };
bootstrap.setupEventHandlers();
viewer.setTitle();

View file

@ -84,55 +84,76 @@ const { ThumbnailInfo } = require( 'mmv' );
assert.true( thumbnailInfoProvider instanceof ThumbnailInfo );
} );
QUnit.test( 'ThumbnailInfo get test', ( assert ) => {
let apiCallCount = 0;
const api = { get: function () {
apiCallCount++;
return $.Deferred().resolve( {
query: {
pages: [
{
ns: 6,
title: 'File:Stuff.jpg',
missing: true,
imagerepository: 'shared',
imageinfo: [
{
thumburl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Stuff.jpg/51px-Stuff.jpg',
thumbwidth: 95,
thumbheight: 200,
url: 'https://upload.wikimedia.org/wikipedia/commons/1/19/Stuff.jpg',
descriptionurl: 'https://commons.wikimedia.org/wiki/File:Stuff.jpg'
}
]
}
]
}
} );
} };
QUnit.test( 'ThumbnailInfo get test', function ( assert ) {
const api = { get: this.sandbox.stub().returns( $.Deferred().resolve( {
query: {
pages: [
{
ns: 6,
title: 'File:Stuff.jpg',
missing: true,
imagerepository: 'shared',
imageinfo: [
{
thumburl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Stuff.jpg/51px-Stuff.jpg',
thumbwidth: 95,
thumbheight: 200,
url: 'https://upload.wikimedia.org/wikipedia/commons/1/19/Stuff.jpg',
descriptionurl: 'https://commons.wikimedia.org/wiki/File:Stuff.jpg'
}
]
}
]
}
} ) ) };
const file = new mw.Title( 'File:Stuff.jpg' );
const thumbnailInfoProvider = new ThumbnailInfo( api );
return thumbnailInfoProvider.get( file, 100 ).then( ( thumbnail ) => {
return thumbnailInfoProvider.get( file, '', 100 ).then( ( thumbnail ) => {
assert.strictEqual( thumbnail.url,
'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Stuff.jpg/51px-Stuff.jpg',
'URL is set correctly' );
assert.strictEqual( thumbnail.width, 95, 'actual width is set correctly' );
assert.strictEqual( thumbnail.height, 200, 'actual height is set correctly' );
} ).then( () => {
assert.strictEqual( apiCallCount, 1 );
assert.strictEqual( api.get.calledOnce, true );
assert.deepEqual( api.get.getCall( 0 ).args[ 0 ], {
formatversion: 2,
action: 'query',
prop: 'imageinfo',
titles: 'File:Stuff.jpg',
iiprop: 'url',
iiurlheight: undefined,
iiurlparam: undefined,
iiurlwidth: 100
} );
// call the data provider a second time to check caching
return thumbnailInfoProvider.get( file, 100 );
return thumbnailInfoProvider.get( file, '', 100 );
} ).then( () => {
assert.strictEqual( apiCallCount, 1 );
assert.strictEqual( api.get.calledOnce, true );
// call a third time with different size to check caching
return thumbnailInfoProvider.get( file, 110 );
return thumbnailInfoProvider.get( file, '', 110 );
} ).then( () => {
assert.strictEqual( apiCallCount, 2 );
assert.strictEqual( api.get.calledTwice, true );
// call it again, with a height specified, to check caching
return thumbnailInfoProvider.get( file, 110, 100 );
return thumbnailInfoProvider.get( file, '', 110, 100 );
} ).then( () => {
assert.strictEqual( apiCallCount, 3 );
assert.strictEqual( api.get.calledThrice, true );
// call it again, with multi lingual SVG
const file2 = new mw.Title( 'File:Multilingual_SVG_example.svg' );
const sampleUrl = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/Multilingual_SVG_example.svg/langit-120px-Multilingual_SVG_example.svg.png';
return thumbnailInfoProvider.get( file2, sampleUrl, 100 );
} ).then( () => {
assert.deepEqual( api.get.getCall( api.get.callCount - 1 ).args[ 0 ], {
action: 'query',
formatversion: 2,
iiprop: 'url',
iiurlheight: undefined,
iiurlparam: 'langit-100px',
iiurlwidth: 100,
prop: 'imageinfo',
titles: 'File:Multilingual_SVG_example.svg'
} );
} );
} );
@ -144,7 +165,7 @@ const { ThumbnailInfo } = require( 'mmv' );
const done = assert.async();
const thumbnailInfoProvider = new ThumbnailInfo( api );
thumbnailInfoProvider.get( file, 100 ).fail( () => {
thumbnailInfoProvider.get( file, '', 100 ).fail( () => {
assert.true( true, 'promise rejected when no data is returned' );
done();
} );
@ -166,7 +187,7 @@ const { ThumbnailInfo } = require( 'mmv' );
const done = assert.async();
const thumbnailInfoProvider = new ThumbnailInfo( api );
thumbnailInfoProvider.get( file, 100 ).fail( () => {
thumbnailInfoProvider.get( file, '', 100 ).fail( () => {
assert.true( true, 'promise rejected when imageinfo is missing' );
done();
} );
@ -190,7 +211,7 @@ const { ThumbnailInfo } = require( 'mmv' );
const done = assert.async();
const thumbnailInfoProvider = new ThumbnailInfo( api );
thumbnailInfoProvider.get( file ).fail( ( errorMessage ) => {
thumbnailInfoProvider.get( file, '' ).fail( ( errorMessage ) => {
assert.strictEqual( errorMessage, 'file does not exist: File:Stuff.jpg',
'error message is set correctly for missing file' );
done();
@ -216,7 +237,7 @@ const { ThumbnailInfo } = require( 'mmv' );
const done = assert.async();
const thumbnailInfoProvider = new ThumbnailInfo( api );
thumbnailInfoProvider.get( file, 100 ).fail( () => {
thumbnailInfoProvider.get( file, '', 100 ).fail( () => {
assert.true( true, 'promise rejected when thumbnail info is missing' );
done();
} );

View file

@ -112,7 +112,8 @@ QUnit.test( '.setImageInfo()', function ( assert ) {
);
const title = 'Foo bar';
const image = {
filePageTitle: mw.Title.newFromText( 'File:' + title + '.jpg' )
filePageTitle: mw.Title.newFromText( 'File:' + title + '.jpg' ),
src: 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg'
};
const imageData = {
title: image.filePageTitle,

View file

@ -48,7 +48,7 @@ const { StripeButtons } = require( 'mmv' );
const buttons = createStripeButtons();
const fakeImageInfo = { descriptionUrl: '//commons.wikimedia.org/wiki/File:Foo.jpg' };
buttons.set( fakeImageInfo );
buttons.set( null, fakeImageInfo );
buttons.empty();
assert.true( true, 'No error on set()/empty().' );
@ -62,16 +62,16 @@ const { StripeButtons } = require( 'mmv' );
const descriptionUrlCommons = 'https://commons.wikimedia.org/desc';
const descriptionUrl2 = 'http://example.com/different-desc';
buttons.set( { descriptionUrl } );
buttons.set( null, { descriptionUrl } );
assert.strictEqual( $button.hasClass( 'mw-mmv-repo-button-commons' ), false, 'Button does not have commons class non-Commons files' );
assert.strictEqual( $button.find( 'a' ).addBack().filter( 'a' ).attr( 'href' ), descriptionUrl, 'Description page link is correct' );
buttons.set( { descriptionUrl: descriptionUrlCommons } );
buttons.set( null, { descriptionUrl: descriptionUrlCommons } );
assert.strictEqual( $button.hasClass( 'mw-mmv-repo-button-commons' ), true, 'Button commons class for Commons files' );
buttons.set( {
buttons.set( null, {
descriptionUrl,
pageID: 1,
title: { getUrl: () => descriptionUrl2 }