Link inspector images and descriptions

Fetches images from PageImages extension and descriptions
from Wikibase.

Bug: T93693
Bug: T93694 (partial)
Change-Id: I1bea6b7b57ab951e79468cfa00e9eecddc113d18
This commit is contained in:
suchetag 2014-11-20 12:05:47 -05:00 committed by Ed Sanders
parent 2e96c8c1f3
commit 3963b1daf2
8 changed files with 134 additions and 26 deletions

View file

@ -381,6 +381,8 @@ class VisualEditorHooks {
$vars['wgVisualEditor'] = array(
'pageLanguageCode' => $pageLanguage->getHtmlCode(),
'pageLanguageDir' => $pageLanguage->getDir(),
'usePageImages' => defined( 'PAGE_IMAGES_INSTALLED' ),
'usePageDescriptions' => defined( 'WBC_VERSION' ),
);
return true;

View file

@ -1133,7 +1133,8 @@ $wgResourceModules += array(
'modules/ve-mw/ui/contextitems/ve.ui.MWInternalLinkContextItem.js',
),
'styles' => array(
'modules/ve-mw/ui/styles/widgets/ve.ui.MWLinkTargetInputWidget.css'
'modules/ve-mw/ui/styles/widgets/ve.ui.MWLinkTargetInputWidget.css',
'modules/ve-mw/ui/styles/widgets/ve.ui.MWInternalLinkMenuOptionWidget.css'
),
'skinStyles' => array(
'default' => array(
@ -1550,6 +1551,7 @@ $wgResourceModules += array(
'modules/ve-mw/ui/styles/ve.ui.Icons.css',
),
'dependencies' => array(
'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-advanced',
'oojs-ui.styles.icons-editing-core',
'oojs-ui.styles.icons-editing-styling',

View file

@ -1158,7 +1158,8 @@
"modules/ve-mw/ui/contextitems/ve.ui.MWInternalLinkContextItem.js"
],
"styles": [
"modules/ve-mw/ui/styles/widgets/ve.ui.MWLinkTargetInputWidget.css"
"modules/ve-mw/ui/styles/widgets/ve.ui.MWLinkTargetInputWidget.css",
"modules/ve-mw/ui/styles/widgets/ve.ui.MWInternalLinkMenuOptionWidget.css"
],
"skinStyles": {
"default": [
@ -1577,6 +1578,7 @@
"modules/ve-mw/ui/styles/ve.ui.Icons.css"
],
"dependencies": [
"oojs-ui.styles.icons-content",
"oojs-ui.styles.icons-editing-advanced",
"oojs-ui.styles.icons-editing-core",
"oojs-ui.styles.icons-editing-styling",

View file

@ -79,7 +79,10 @@
ve.init.mw.LinkCache.prototype.getRequestPromise = function ( subqueue ) {
return new mw.Api().get( {
action: 'query',
prop: 'info|pageprops',
gpslimit: 10,
prop: 'info|pageprops|pageimages|pageterms',
pithumbsize: 80,
pilimit: 10,
ppprop: 'disambiguation',
titles: subqueue.join( '|' )
} );
@ -93,7 +96,9 @@
missing: page.missing !== undefined,
redirect: page.redirect !== undefined,
// Disambiguator extension
disambiguation: page.pageprops && page.pageprops.disambiguation !== undefined
disambiguation: page.pageprops && page.pageprops.disambiguation !== undefined,
imageUrl: ve.getProp( page, 'thumbnail', 'source' ),
description: ve.getProp( page, 'terms', 'description' )
};
};
}() );

View file

@ -0,0 +1,58 @@
/*!
* VisualEditor MediaWiki UserInterface MWInternalLinkMenuOptionWidget styles.
*
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
.ve-ui-mwLinkTargetInputWidget-menu-withImages .ve-ui-mwInternalLinkMenuOptionWidget {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
min-height: 3.75em;
margin-left: 3.75em;
}
.ve-ui-mwLinkTargetInputWidget-menu-withImages .ve-ui-mwInternalLinkMenuOptionWidget:not(:last-child) {
margin-bottom: 1px;
}
.ve-ui-mwLinkTargetInputWidget-menu-withImages .oo-ui-iconElement .oo-ui-iconElement-icon {
display: block;
width: 3.75em;
height: 3.75em;
left: -3.75em;
background-color: #ccc;
opacity: 0.4;
}
.ve-ui-mwLinkTargetInputWidget-menu-withImages .oo-ui-iconElement .ve-ui-mwInternalLinkMenuOptionWidget-hasImage {
border: 0;
background-size: cover;
opacity: 1;
}
.ve-ui-mwLinkTargetInputWidget-menu-withImages .ve-ui-mwInternalLinkMenuOptionWidget .oo-ui-labelElement-label {
line-height: 3em;
}
.ve-ui-mwInternalLinkMenuOptionWidget-description {
display: none;
}
.ve-ui-mwLinkTargetInputWidget-menu-withDescriptions .ve-ui-mwInternalLinkMenuOptionWidget .oo-ui-labelElement-label {
line-height: 1.5em;
font-weight: bold;
}
.ve-ui-mwLinkTargetInputWidget-menu-withDescriptions .ve-ui-mwInternalLinkMenuOptionWidget-description {
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.oo-ui-menuOptionWidget:not(.oo-ui-optionWidget-selected) .ve-ui-mwInternalLinkMenuOptionWidget-description,
.oo-ui-menuOptionWidget.oo-ui-optionWidget-highlighted .ve-ui-mwInternalLinkMenuOptionWidget-description {
color: #888;
}

View file

@ -1,7 +1,8 @@
/*!
* VisualEditor UserInterface MWInternalLinkMenuOptionWidget class
* VisualEditor UserInterface MWInternalLinkMenuOptionWidget class.
*
* @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
@ -13,10 +14,12 @@
* @constructor
* @param {Object} [config] Configuration options
* @cfg {string} [pagename] Pagename to return the names of internal pages
* @cfg {string} [imageUrl] Thumbnail image URL with URL encoding
* @cfg {string} [description] Page description
*/
ve.ui.MWInternalLinkMenuOptionWidget = function VeUiMWInternalLinkMenuOptionWidget( config ) {
// Config initialization
config = config || {};
config = ve.extendObject( { icon: 'article' }, config );
// Properties
this.pagename = config.pagename;
@ -26,6 +29,23 @@ ve.ui.MWInternalLinkMenuOptionWidget = function VeUiMWInternalLinkMenuOptionWidg
// Style based on link cache information
ve.init.platform.linkCache.styleElement( this.pagename, this.$link );
// Intialization
this.$element.addClass( 've-ui-mwInternalLinkMenuOptionWidget' );
if ( config.imageUrl ) {
this.$icon
.addClass( 've-ui-mwInternalLinkMenuOptionWidget-hasImage' )
.css( 'background-image', 'url(' + config.imageUrl + ')' );
}
if ( config.description ) {
this.$element.append(
$( '<span>' )
.addClass( 've-ui-mwInternalLinkMenuOptionWidget-description' )
.text( config.description )
);
}
};
/* Inheritance */

View file

@ -1,7 +1,8 @@
/*!
* VisualEditor UserInterface MWLinkMenuOptionWidget class
* VisualEditor UserInterface MWLinkMenuOptionWidget class.
*
* @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org
* @copyright 2011-2015 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
@ -16,7 +17,7 @@
*/
ve.ui.MWLinkMenuOptionWidget = function VeUiMWLinkMenuOptionWidget( config ) {
// Config initialization
config = config || {};
config = $.extend( { icon: null }, config );
// Parent constructor
ve.ui.MWLinkMenuOptionWidget.super.call( this, config );

View file

@ -33,6 +33,12 @@ ve.ui.MWLinkTargetInputWidget = function VeUiMWLinkTargetInputWidget( config ) {
// Initialization
this.$element.addClass( 've-ui-mwLinkTargetInputWidget' );
this.lookupMenu.$element.addClass( 've-ui-mwLinkTargetInputWidget-menu' );
if ( mw.config.get( 'wgVisualEditor' ).usePageImages ) {
this.lookupMenu.$element.addClass( 've-ui-mwLinkTargetInputWidget-menu-withImages' );
}
if ( mw.config.get( 'wgVisualEditor' ).usePageDescriptions ) {
this.lookupMenu.$element.addClass( 've-ui-mwLinkTargetInputWidget-menu-withDescriptions' );
}
this.interwikiPrefixes = [];
this.interwikiPrefixesPromise = new mw.Api().get( {
@ -103,8 +109,8 @@ ve.ui.MWLinkTargetInputWidget.prototype.getLookupRequest = function () {
var req,
widget = this,
promiseAbortObject = { abort: function () {
// Do nothing. This is just so OOUI doesn't break due to abort being undefined.
} };
// Do nothing. This is just so OOUI doesn't break due to abort being undefined.
} };
if ( mw.Title.newFromText( this.value ) ) {
return this.interwikiPrefixesPromise.then( function () {
@ -124,7 +130,10 @@ ve.ui.MWLinkTargetInputWidget.prototype.getLookupRequest = function () {
generator: 'prefixsearch',
gpssearch: widget.value,
gpsnamespace: 0,
prop: 'info|pageprops',
gpslimit: 10,
prop: 'info|pageprops|pageimages|pageterms',
pithumbsize: 80,
pilimit: 10,
ppprop: 'disambiguation'
} );
promiseAbortObject.abort = req.abort.bind( req ); // todo: ew
@ -155,26 +164,30 @@ ve.ui.MWLinkTargetInputWidget.prototype.getLookupCacheDataFromResponse = functio
* @returns {OO.ui.MenuOptionWidget[]} Menu items
*/
ve.ui.MWLinkTargetInputWidget.prototype.getLookupMenuOptionsFromData = function ( data ) {
var i, len, item, pageExistsExact, pageExists, index, matchingPage,
var i, len, item, pageExistsExact, pageExists, index, matchingPage, linkData,
items = [],
existingPages = [],
matchingPages = [],
disambigPages = [],
redirectPages = [],
titleObj = mw.Title.newFromText( this.value ),
linkCacheUpdate = {};
links = {};
for ( index in data ) {
matchingPage = data[index];
linkCacheUpdate[matchingPage.title] = { missing: false, redirect: false, disambiguation: false };
links[matchingPage.title] = {
missing: false, redirect: false, disambiguation: false,
imageUrl: ve.getProp( matchingPage, 'thumbnail', 'source' ),
description: ve.getProp( matchingPage, 'terms', 'description' )
};
existingPages.push( matchingPage.title );
if ( matchingPage.redirect !== undefined ) {
redirectPages.push( matchingPage.title );
linkCacheUpdate[matchingPage.title].redirect = true;
links[matchingPage.title].redirect = true;
} else if ( matchingPage.pageprops !== undefined && matchingPage.pageprops.disambiguation !== undefined ) {
disambigPages.push( matchingPage.title );
linkCacheUpdate[matchingPage.title].disambiguation = true;
links[matchingPage.title].disambiguation = true;
} else {
matchingPages.push( matchingPage.title );
}
@ -188,10 +201,10 @@ ve.ui.MWLinkTargetInputWidget.prototype.getLookupMenuOptionsFromData = function
);
if ( !pageExists ) {
linkCacheUpdate[this.value] = { missing: true, redirect: false, disambiguation: false };
links[this.value] = { missing: true, redirect: false, disambiguation: false };
}
ve.init.platform.linkCache.set( linkCacheUpdate );
ve.init.platform.linkCache.set( links );
// External link
if ( ve.init.platform.getExternalLinkUrlProtocolsRegExp().test( this.value ) ) {
@ -233,17 +246,19 @@ ve.ui.MWLinkTargetInputWidget.prototype.getLookupMenuOptionsFromData = function
if ( matchingPages && matchingPages.length ) {
items.push( new OO.ui.MenuSectionOptionWidget( {
data: 'matchingPages',
label: ve.msg( 'visualeditor-linkinspector-suggest-matching-page',
matchingPages.length )
label: ve.msg( 'visualeditor-linkinspector-suggest-matching-page', matchingPages.length )
} ) );
// Offer the exact text as a suggestion if the page exists
if ( pageExists && !pageExistsExact ) {
matchingPages.unshift( this.value );
}
for ( i = 0, len = matchingPages.length; i < len; i++ ) {
linkData = links[matchingPages[i]] || {};
items.push( new ve.ui.MWInternalLinkMenuOptionWidget( {
data: this.getInternalLinkAnnotationFromTitle( matchingPages[i] ),
pagename: matchingPages[i]
pagename: matchingPages[i],
imageUrl: linkData.imageUrl,
description: linkData.description
} ) );
}
}
@ -255,9 +270,12 @@ ve.ui.MWLinkTargetInputWidget.prototype.getLookupMenuOptionsFromData = function
label: ve.msg( 'visualeditor-linkinspector-suggest-disambig-page', disambigPages.length )
} ) );
for ( i = 0, len = disambigPages.length; i < len; i++ ) {
linkData = links[disambigPages[i]] || {};
items.push( new ve.ui.MWInternalLinkMenuOptionWidget( {
data: this.getInternalLinkAnnotationFromTitle( disambigPages[i] ),
pagename: disambigPages[i]
pagename: disambigPages[i],
imageUrl: linkData.imageUrl,
description: linkData.description
} ) );
}
}
@ -269,10 +287,10 @@ ve.ui.MWLinkTargetInputWidget.prototype.getLookupMenuOptionsFromData = function
label: ve.msg( 'visualeditor-linkinspector-suggest-redirect-page', redirectPages.length )
} ) );
for ( i = 0, len = redirectPages.length; i < len; i++ ) {
items.push( new OO.ui.MenuOptionWidget( {
items.push( new ve.ui.MWInternalLinkMenuOptionWidget( {
data: this.getInternalLinkAnnotationFromTitle( redirectPages[i] ),
rel: 'redirectPage',
label: redirectPages[i]
// TODO: Add description based on redirect target
} ) );
}
}