repo/resources/wikibase.ui.entityViewInit.js
/**
* @license GPL-2.0-or-later
* @author H. Snater < mediawiki@snater.com >
* @author Daniel Werner < daniel.a.r.werner@gmail.com >
* @author Adrian Heine <adrian.heine@wikimedia.de>
*/
( function ( wb, performance ) {
'use strict';
var getExpertsStore = require( './experts/getStore.js' ),
getParserStore = require( './parsers/getStore.js' ),
DefaultViewFactoryFactory = require( '../../view/resources/wikibase/view/ViewFactoryFactory.js' ),
RevisionStore = require( '../../view/resources/wikibase/wikibase.RevisionStore.js' ),
ApiValueFormatterFactory = require( './formatters/ApiValueFormatterFactory.js' ),
StructureEditorFactory = require( '../../view/resources/wikibase/view/StructureEditorFactory.js' ),
CachingEntityStore = require( '../../view/resources/wikibase/store/store.CachingEntityStore.js' ),
ApiEntityStore = require( '../../view/resources/wikibase/store/store.ApiEntityStore.js' ),
dataTypeStore = require( './dataTypes/wikibase.dataTypeStore.js' ),
CachingEntityIdHtmlFormatter = require( '../../view/resources/wikibase/entityIdFormatter/CachingEntityIdHtmlFormatter.js' ),
DataValueBasedEntityIdHtmlFormatter = require( '../../view/resources/wikibase/entityIdFormatter/DataValueBasedEntityIdHtmlFormatter.js' ),
CachingEntityIdPlainFormatter = require( '../../view/resources/wikibase/entityIdFormatter/CachingEntityIdPlainFormatter.js' ),
DataValueBasedEntityIdPlainFormatter = require( '../../view/resources/wikibase/entityIdFormatter/DataValueBasedEntityIdPlainFormatter.js' ),
ToolbarFactory = require( '../../view/resources/wikibase/view/ToolbarFactory.js' ),
PropertyDataTypeStore = require( './wikibase.PropertyDataTypeStore.js' ),
config = require( './config.json' ),
datamodel = require( 'wikibase.datamodel' ),
serialization = require( 'wikibase.serialization' );
/**
* @return {boolean}
*/
function isEditable() {
return mw.config.get( 'wbIsEditView' )
&& mw.config.get( 'wgRelevantPageIsProbablyEditable' );
}
/**
* @param {wikibase.api.RepoApi} repoApi
* @param {string} languageCode The language code of the ui language
* @return {CachingEntityStore}
*/
function buildEntityStore( repoApi, languageCode ) {
return new CachingEntityStore(
new ApiEntityStore(
repoApi,
new serialization.EntityDeserializer(),
[ languageCode ]
)
);
}
/**
* @param {datamodel.Entity} entity
* @param {jQuery} $entityview
* @return {string} The name of the entity view widget class
*
* @throws {Error} if no widget to render the entity exists.
*/
function createEntityView( entity, $entityview ) {
var currentRevision, revisionStore, entityChangersFactory,
viewFactoryArguments, ViewFactoryFactory, viewFactory, entityView,
repoConfig = mw.config.get( 'wbRepo' ),
repoApiUrl = repoConfig.url + repoConfig.scriptPath + '/api.php',
mwApi = wb.api.getLocationAgnosticMwApi( repoApiUrl ),
repoApi = new wb.api.RepoApi( mwApi, mw.config.get( 'wgUserLanguage' ), config.tags ),
userLanguages = wb.getUserLanguages(),
entityStore = buildEntityStore( repoApi, userLanguages[ 0 ] ),
monolingualTextLanguages = wikibase.WikibaseContentLanguages.getMonolingualTextLanguages(),
formatterFactory = new ApiValueFormatterFactory(
new wb.api.FormatValueCaller(
repoApi,
dataTypeStore
),
userLanguages[ 0 ]
),
parserStore = getParserStore( repoApi ),
htmlDataValueEntityIdFormatter = formatterFactory.getFormatter( null, null, 'text/html' ),
plaintextDataValueEntityIdFormatter = formatterFactory.getFormatter( null, null, 'text/plain' ),
entityIdParser = new ( parserStore.getParser( datamodel.EntityId.TYPE ) )( { lang: userLanguages[ 0 ] } ),
toolbarFactory = new ToolbarFactory(),
structureEditorFactory = new StructureEditorFactory( toolbarFactory ),
startEditingCallback = function () {
return $.Deferred().resolve().promise();
},
entityNamespace = entity.getType(),
wbCurRev = mw.config.get( 'wbCurrentRevision' );
if ( wbCurRev === null ) {
currentRevision = mw.config.get( 'wgCurRevisionId' );
} else {
currentRevision = wbCurRev;
}
revisionStore = new RevisionStore( currentRevision );
entityChangersFactory = new wb.entityChangers.EntityChangersFactory(
repoApi,
revisionStore,
entity,
function ( hookName ) {
var hook = mw.hook( hookName );
hook.fire.apply( hook, Array.prototype.slice.call( arguments, 1 ) );
}
);
viewFactoryArguments = [
toolbarFactory,
entityChangersFactory,
structureEditorFactory,
monolingualTextLanguages,
dataTypeStore,
new CachingEntityIdHtmlFormatter(
new DataValueBasedEntityIdHtmlFormatter( entityIdParser, htmlDataValueEntityIdFormatter )
),
new CachingEntityIdPlainFormatter(
new DataValueBasedEntityIdPlainFormatter( entityIdParser, plaintextDataValueEntityIdFormatter )
),
new PropertyDataTypeStore( mw.hook( 'wikibase.entityPage.entityLoaded' ), entityStore ),
getExpertsStore( dataTypeStore ),
formatterFactory,
{
getMessage: function ( key, params ) {
return mw.msg.apply( mw, [ key ].concat( params || [] ) );
}
},
parserStore,
userLanguages,
repoApiUrl,
config.geoShapeStorageApiEndpoint
];
var hookResults = [];
mw.hook( 'wikibase.entityPage.entityView.viewFactoryFactory.required' ).fire(
entityNamespace,
function ( promise ) {
hookResults.push( promise );
}
);
return $.when.apply( $, hookResults ).then( function () {
ViewFactoryFactory = wb[ entityNamespace ] && wb[ entityNamespace ].view
&& wb[ entityNamespace ].view.ViewFactoryFactory
|| DefaultViewFactoryFactory;
viewFactory = ( new ViewFactoryFactory() ).getViewFactory( isEditable(), viewFactoryArguments );
entityView = viewFactory.getEntityView( startEditingCallback, entity, $entityview );
return entityView.widgetName;
} );
}
/**
* @param {jQuery.wikibase.entityview} $entityview
* @param {string} viewName
* @param {string} entityType
*/
function attachAnonymousEditWarningTrigger( $entityview, viewName, entityType ) {
if ( config.tempUserEnabled || !mw.user || !mw.user.isAnon() ) {
return;
}
$entityview.on( viewName + 'afterstartediting', function () {
if ( !$.find( '.mw-notification-content' ).length
&& !mw.cookie.get( 'wikibase-no-anonymouseditwarning' )
) {
var currentPage = mw.config.get( 'wgPageName' );
var userLoginUrl = mw.util.getUrl( 'Special:UserLogin', { returnto: currentPage } );
var createAccountUrl = mw.util.getUrl( 'Special:CreateAccount', { returnto: currentPage } );
var message = mw.message( 'wikibase-anonymouseditwarning', userLoginUrl, createAccountUrl );
mw.notify( message, { autoHide: false, type: 'warn', tag: 'wikibase-anonymouseditpopup' } );
}
} );
}
/**
* Update the state of the watch link if the user has watchdefault enabled.
*/
function attachWatchLinkUpdater( $entityview, viewName ) {
var update;
if ( mw.loader.getState( 'mediawiki.page.watch.ajax' ) !== 'ready' || !mw.user.options.get( 'watchdefault' ) ) {
return;
}
update = require( 'mediawiki.page.watch.ajax' ).updateWatchLink;
function updateWatchLink() {
// All four supported skins are using the same ID, the other selectors
// in mediawiki.page.watch.ajax.js are undocumented and probably legacy stuff
var $link = $( '#ca-watch' ).find( 'a' );
// Skip if page is already watched and there is no "watch this page" link
// Note: The exposed function fails for empty jQuery collections
if ( !$link.length ) {
return;
}
update( $link, 'watch', 'loading' );
var api = new mw.Api();
api.get( {
formatversion: 2,
action: 'query',
prop: 'info',
inprop: 'watched',
pageids: mw.config.get( 'wgArticleId' )
} ).done( function ( data ) {
var watched = data.query && data.query.pages[ 0 ]
&& data.query.pages[ 0 ].watched;
update( $link, watched ? 'unwatch' : 'watch' );
} ).fail( function () {
update( $link, 'watch' );
} );
}
$entityview.on( viewName + 'afterstopediting', function ( event, dropValue ) {
if ( !dropValue ) {
updateWatchLink();
}
} );
}
/**
* @param {jQuery} $entityview
* @param {jQuery} $origin
* @param {string} gravity
* @param {string} viewName
*/
function showCopyrightTooltip( $entityview, $origin, gravity, viewName ) {
if ( !mw.config.exists( 'wbCopyright' ) ) {
return;
}
gravity = gravity || 'nw';
var copyRight = mw.config.get( 'wbCopyright' ),
copyRightVersion = copyRight.version,
copyRightMessageHtml = copyRight.messageHtml,
cookieKey = 'wikibase.acknowledgedcopyrightversion',
optionsKey = 'wb-acknowledgedcopyrightversion';
if ( mw.cookie.get( cookieKey ) === copyRightVersion
|| mw.user.options.get( optionsKey ) === copyRightVersion
) {
return;
}
var $message = $( '<span><p>' + copyRightMessageHtml + '</p></span>' )
.addClass( 'wikibase-copyrightnotification-container' ),
$hideMessage = $( '<a>' )
.text( mw.msg( 'wikibase-copyrighttooltip-acknowledge' ) )
.appendTo( $message ),
editableTemplatedWidget = $origin.data( 'EditableTemplatedWidget' );
// TODO: Use editableTemplatedWidget's notification system for copyright messages on all widgets
if ( editableTemplatedWidget
&& !( editableTemplatedWidget instanceof $.wikibase.statementview )
&& !( editableTemplatedWidget instanceof $.wikibase.entitytermsview )
&& editableTemplatedWidget.widgetName !== 'lexemeformview'
&& editableTemplatedWidget.widgetName !== 'senseview'
) {
editableTemplatedWidget.notification( $message, 'wb-edit' );
$hideMessage.on( 'click', function ( event ) {
event.preventDefault();
editableTemplatedWidget.notification();
if ( mw.user.isAnon() ) {
mw.cookie.set( cookieKey, copyRightVersion, { expires: 3 * 365 * 24 * 60 * 60, path: '/' } );
} else {
var api = new mw.Api();
api.saveOption( optionsKey, copyRightVersion );
}
} );
return;
}
// Tooltip gets its own anchor since other toolbar elements might have their own tooltip.
var $edittoolbarContainer, $tooltipAnchor;
if ( $origin.data( 'edittoolbar' ) ) {
$edittoolbarContainer = $origin.data( 'edittoolbar' ).getContainer();
$tooltipAnchor = $( '<span>' )
.appendTo( $edittoolbarContainer.children( ':wikibase-toolbar' ) );
} else if ( $origin.find( '.lemma-widget_controls' ).length ) {
// HACK: WikibaseLexeme's lemma widget is implemented in Vue, thus doesn't feature a legacy edittoolbar (T343999)
$edittoolbarContainer = $origin.find( '.lemma-widget_controls' );
$tooltipAnchor = $( '<span>' )
.appendTo( $edittoolbarContainer );
} else {
return;
}
var $messageAnchor = $( '<span>' )
.appendTo( document.body )
.toolbaritem()
.wbtooltip( {
content: $message,
permanent: true,
gravity: gravity,
$anchor: $tooltipAnchor
} );
var eventNamespace = '.wbCopyrightTooltip' + Math.random().toString( 36 ).slice( 2 );
// Remove the no longer needed tooltip anchor
$messageAnchor.one( 'wbtooltipafterhide', function () {
$tooltipAnchor.remove();
} );
$hideMessage.on( 'click', function ( event ) {
event.preventDefault();
$messageAnchor.data( 'wbtooltip' ).degrade( true );
$( window ).off( eventNamespace );
if ( mw.user.isAnon() ) {
mw.cookie.set( cookieKey, copyRightVersion, { expires: 3 * 365 * 24 * 60 * 60, path: '/' } );
} else {
var api = new mw.Api();
api.saveOption( optionsKey, copyRightVersion );
}
} );
$messageAnchor.data( 'wbtooltip' ).show();
// Record the initial edit toolbar offset.
var initialToolbarOffset = $edittoolbarContainer.offset().top;
// Destroy tooltip after edit mode gets closed again or if the position of the toolbar changed.
$entityview.on(
`${viewName}afterstopediting${eventNamespace} ${viewName}afterstartediting${eventNamespace}`,
function ( event, origin ) {
var tooltip = $messageAnchor.data( 'wbtooltip' );
// Don't close this copyright notice if we're still in edit mode (another widget left edit mode)
// and the position of the toolbar is unchanged (we don't bother re-positioning the tooltip).
var isInEditMode = event.type === viewName + 'afterstartediting' || ( editableTemplatedWidget && editableTemplatedWidget.isInEditMode() );
if ( $edittoolbarContainer.offset().top === initialToolbarOffset && isInEditMode ) {
return;
}
if ( tooltip ) {
tooltip.degrade( true );
}
$( window ).off( eventNamespace );
}
);
$( window ).one(
`scroll${eventNamespace} touchmove${eventNamespace} resize${eventNamespace}`,
function () {
var tooltip = $messageAnchor.data( 'wbtooltip' );
if ( tooltip ) {
$messageAnchor.data( 'wbtooltip' ).hide();
}
$entityview.off( eventNamespace );
}
);
}
/**
* @param {jQuery} $entityview
* @param {string} viewName
*/
function attachCopyrightTooltip( $entityview, viewName ) {
var startEditingEvents = [
'entitytermsviewafterstartediting',
'sitelinkgroupviewafterstartediting',
'statementviewafterstartediting'
];
if ( viewName === 'lexemeview' ) {
// WikibaseLexeme specific events. These are handled here, as this legacy code can't be nicely extended/ re-used without a bigger refactoring.
startEditingEvents.push(
'senseviewafterstartediting',
'lexemeformviewafterstartediting',
'lexemeheaderafterstartediting'
);
}
$entityview.on(
startEditingEvents.join( ' ' ),
function ( event ) {
var $target = $( event.target ),
gravity = 'sw';
if ( $target.data( 'sitelinkgroupview' ) ) {
gravity = 'nw';
} else if ( $target.data( 'entitytermsview' ) ) {
gravity = 'ne';
} else if ( $target.find( '.lemma-widget_controls' ).length ) {
// WikibaseLexeme's lemma widget
gravity = 'nw';
}
// Break out of stack to make sure this runs only after the editing toolbar is fully initialized.
// This is needed as showCopyrightTooltip manipulates the toolbar's DOM.
setTimeout( function () {
showCopyrightTooltip( $entityview, $target, gravity, viewName );
}, 0 );
}
);
}
mw.hook( 'wikipage.content' ).add( function () {
// This is copied from startup.js in MediaWiki core.
var mwPerformance = window.performance && performance.mark ? performance : {
mark: function () {}
};
mwPerformance.mark( 'wbInitStart' );
var $entityview = $( '.wikibase-entityview' );
var canEdit = isEditable();
wb.EntityInitializer.newFromEntityLoadedHook().getEntity().then( function ( entity ) {
var viewNamePromise = createEntityView( entity, $entityview.first() );
return viewNamePromise.then( function ( viewName ) {
if ( canEdit ) {
attachAnonymousEditWarningTrigger( $entityview, viewName, entity.getType() );
attachWatchLinkUpdater( $entityview, viewName );
attachCopyrightTooltip( $entityview, viewName );
}
mw.hook( 'wikibase.entityPage.entityView.rendered' ).fire();
mwPerformance.mark( 'wbInitEnd' );
} );
} ).catch( mw.log.error );
if ( canEdit ) {
$entityview
.on( 'entitytermsviewchange entitytermsviewafterstopediting', function ( event, lang ) {
var userLanguage = mw.config.get( 'wgUserLanguage' );
var $entitytermsview = $( event.target ),
entitytermsview = $entitytermsview.data( 'entitytermsview' ),
fingerprint = entitytermsview.value(),
label = wb.view.termFallbackResolver.getTerm( fingerprint.getLabels(), userLanguage ),
isEmpty = !label || label.getText() === '';
if ( isEmpty ) {
$( 'h1' ).find( '.wikibase-title' )
.toggleClass( 'wb-empty', true )
.find( '.wikibase-title-label' )
.text( mw.msg( 'wikibase-label-empty' ) );
} else {
var indicator = wb.view.languageFallbackIndicator.getHtml( label, userLanguage );
$( 'h1' ).find( '.wikibase-title' )
.toggleClass( 'wb-empty', false )
.find( '.wikibase-title-label' )
.html( mw.html.escape( label.getText() ) + indicator );
}
if ( event.type === 'entitytermsviewafterstopediting' ) {
$( 'title' ).text( mw.msg( 'pagetitle', isEmpty ? mw.config.get( 'wgTitle' ) : label.getText() ) );
}
} );
}
} );
}(
wikibase,
window.performance
) );