mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-24 22:35:41 +00:00
0533f49fd5
The "mediaClass" property now only serves to capture the original class found on the media so that it can be roundtripped without causing dirty diffs. In the 2.4.0 version of Parsoid's output, that will still be the usual Image/Audio/Video. As of 2.5.0, it will always be File and the mediaClass property can be dropped. Parsoid is currently forward compatible with serializing mw:File, so edited or new media can use that type already. The contextmenu item for media has been updated to make use of the "mediaTag" instead of mediaClass to continue distinguishing media types. That was the only place a grep of mediaClass turned up any use. Bug: T273505 Change-Id: If5dc6b794dacd6973d3b2093e6b385591b91d539
239 lines
7.4 KiB
JavaScript
239 lines
7.4 KiB
JavaScript
/*!
|
|
* VisualEditor DataModel MWInlineImage class.
|
|
*
|
|
* @copyright 2011-2020 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* DataModel MediaWiki image node.
|
|
*
|
|
* @class
|
|
* @extends ve.dm.LeafNode
|
|
* @mixins ve.dm.MWImageNode
|
|
*
|
|
* @constructor
|
|
* @param {Object} [element] Reference to element in linear model
|
|
*/
|
|
ve.dm.MWInlineImageNode = function VeDmMWInlineImageNode() {
|
|
// Parent constructor
|
|
ve.dm.MWInlineImageNode.super.apply( this, arguments );
|
|
|
|
// Mixin constructors
|
|
ve.dm.MWImageNode.call( this );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( ve.dm.MWInlineImageNode, ve.dm.LeafNode );
|
|
|
|
// Need to mixin base class as well (T92540)
|
|
OO.mixinClass( ve.dm.MWInlineImageNode, ve.dm.GeneratedContentNode );
|
|
|
|
OO.mixinClass( ve.dm.MWInlineImageNode, ve.dm.MWImageNode );
|
|
|
|
/* Static Properties */
|
|
|
|
ve.dm.MWInlineImageNode.static.isContent = true;
|
|
|
|
ve.dm.MWInlineImageNode.static.name = 'mwInlineImage';
|
|
|
|
ve.dm.MWInlineImageNode.static.preserveHtmlAttributes = function ( attribute ) {
|
|
var attributes = [ 'typeof', 'class', 'src', 'resource', 'width', 'height', 'href', 'data-mw' ];
|
|
return attributes.indexOf( attribute ) === -1;
|
|
};
|
|
|
|
ve.dm.MWInlineImageNode.static.matchTagNames = [ 'span' ];
|
|
|
|
ve.dm.MWInlineImageNode.static.disallowedAnnotationTypes = [ 'link' ];
|
|
|
|
ve.dm.MWInlineImageNode.static.toDataElement = function ( domElements, converter ) {
|
|
var container = domElements[ 0 ]; // <span>
|
|
var imgWrapper = container.children[ 0 ]; // <a> or <span>
|
|
if ( !imgWrapper ) {
|
|
// Malformed figure, alienate (T267282)
|
|
return null;
|
|
}
|
|
var img = imgWrapper.children[ 0 ]; // <img>, <video>, <audio>, or <span> if mw:Error
|
|
var typeofAttrs = ( container.getAttribute( 'typeof' ) || '' ).trim().split( /\s+/ );
|
|
var mwDataJSON = container.getAttribute( 'data-mw' );
|
|
var mwData = mwDataJSON ? JSON.parse( mwDataJSON ) : {};
|
|
var classes = container.getAttribute( 'class' );
|
|
var recognizedClasses = [];
|
|
var errorIndex = typeofAttrs.indexOf( 'mw:Error' );
|
|
var isError = errorIndex !== -1;
|
|
var width = img.getAttribute( isError ? 'data-width' : 'width' );
|
|
var height = img.getAttribute( isError ? 'data-height' : 'height' );
|
|
|
|
var href = imgWrapper.getAttribute( 'href' );
|
|
if ( href ) {
|
|
// Convert absolute URLs to relative if the href refers to a page on this wiki.
|
|
// Otherwise Parsoid generates |link= options for copy-pasted images (T193253).
|
|
var targetData = mw.libs.ve.getTargetDataFromHref( href, converter.getTargetHtmlDocument() );
|
|
if ( targetData.isInternal ) {
|
|
href = './' + targetData.rawTitle;
|
|
}
|
|
}
|
|
|
|
if ( isError ) {
|
|
typeofAttrs.splice( errorIndex, 1 );
|
|
}
|
|
|
|
var types = this.rdfaToTypes[ typeofAttrs[ 0 ] ];
|
|
|
|
var attributes = {
|
|
mediaClass: types.mediaClass,
|
|
mediaTag: img.nodeName.toLowerCase(),
|
|
type: types.frameType,
|
|
src: img.getAttribute( 'src' ) || img.getAttribute( 'poster' ),
|
|
href: href,
|
|
imageClassAttr: img.getAttribute( 'class' ),
|
|
imgWrapperClassAttr: imgWrapper.getAttribute( 'class' ),
|
|
resource: img.getAttribute( 'resource' ),
|
|
originalClasses: classes,
|
|
width: width !== null && width !== '' ? +width : null,
|
|
height: height !== null && height !== '' ? +height : null,
|
|
alt: img.getAttribute( 'alt' ),
|
|
mw: mwData,
|
|
isError: isError
|
|
};
|
|
|
|
// Extract individual classes
|
|
classes = typeof classes === 'string' ? classes.trim().split( /\s+/ ) : [];
|
|
|
|
// Deal with border flag
|
|
if ( classes.indexOf( 'mw-image-border' ) !== -1 ) {
|
|
attributes.borderImage = true;
|
|
recognizedClasses.push( 'mw-image-border' );
|
|
}
|
|
|
|
// Vertical alignment
|
|
attributes.valign = 'default';
|
|
[ 'midde', 'baseline', 'sub', 'super', 'top', 'text-top', 'bottom', 'text-bottom' ].some( function ( valign ) {
|
|
var className = 'mw-valign-' + valign;
|
|
if ( classes.indexOf( className ) !== -1 ) {
|
|
attributes.valign = valign;
|
|
recognizedClasses.push( className );
|
|
return true;
|
|
}
|
|
return false;
|
|
} );
|
|
|
|
// Border
|
|
if ( classes.indexOf( 'mw-image-border' ) !== -1 ) {
|
|
attributes.borderImage = true;
|
|
recognizedClasses.push( 'mw-image-border' );
|
|
}
|
|
|
|
// Default-size
|
|
if ( classes.indexOf( 'mw-default-size' ) !== -1 ) {
|
|
attributes.defaultSize = true;
|
|
recognizedClasses.push( 'mw-default-size' );
|
|
}
|
|
|
|
// Store unrecognized classes so we can restore them on the way out
|
|
attributes.unrecognizedClasses = OO.simpleArrayDifference( classes, recognizedClasses );
|
|
|
|
var dataElement = { type: this.name, attributes: attributes };
|
|
|
|
this.storeGeneratedContents( dataElement, dataElement.attributes.src, converter.getStore() );
|
|
|
|
return dataElement;
|
|
};
|
|
|
|
ve.dm.MWInlineImageNode.static.toDomElements = function ( dataElement, doc, converter ) {
|
|
var attributes = dataElement.attributes,
|
|
container = doc.createElement( 'span' ),
|
|
img = doc.createElement( attributes.isError ? 'span' : attributes.mediaTag ),
|
|
classes = [],
|
|
originalClasses = attributes.originalClasses;
|
|
|
|
ve.setDomAttributes( img, attributes, [ 'resource' ] );
|
|
var width = attributes.width;
|
|
var height = attributes.height;
|
|
if ( width !== null ) {
|
|
img.setAttribute( attributes.isError ? 'data-width' : 'width', width );
|
|
}
|
|
if ( height !== null ) {
|
|
img.setAttribute( attributes.isError ? 'data-width' : 'height', height );
|
|
}
|
|
|
|
var srcAttr = this.tagsToSrcAttrs[ img.nodeName.toLowerCase() ];
|
|
if ( srcAttr && !attributes.isError ) {
|
|
img.setAttribute( srcAttr, attributes.src );
|
|
}
|
|
|
|
// TODO: This does not make sense for broken images (when img is a span node)
|
|
if ( typeof attributes.alt === 'string' ) {
|
|
img.setAttribute( 'alt', attributes.alt );
|
|
}
|
|
|
|
// RDFa type
|
|
container.setAttribute( 'typeof', this.getRdfa( attributes.mediaClass, attributes.type, attributes.isError ) );
|
|
if ( !ve.isEmptyObject( attributes.mw ) ) {
|
|
container.setAttribute( 'data-mw', JSON.stringify( attributes.mw ) );
|
|
}
|
|
|
|
if ( attributes.defaultSize ) {
|
|
classes.push( 'mw-default-size' );
|
|
}
|
|
|
|
if ( attributes.borderImage ) {
|
|
classes.push( 'mw-image-border' );
|
|
}
|
|
|
|
if ( attributes.valign && attributes.valign !== 'default' ) {
|
|
classes.push( 'mw-valign-' + attributes.valign );
|
|
}
|
|
|
|
if ( attributes.unrecognizedClasses ) {
|
|
classes = OO.simpleArrayUnion( classes, attributes.unrecognizedClasses );
|
|
}
|
|
|
|
if (
|
|
originalClasses &&
|
|
ve.compare( originalClasses.trim().split( /\s+/ ).sort(), classes.sort() )
|
|
) {
|
|
// eslint-disable-next-line mediawiki/class-doc
|
|
container.className = originalClasses;
|
|
} else if ( classes.length > 0 ) {
|
|
// eslint-disable-next-line mediawiki/class-doc
|
|
container.className = classes.join( ' ' );
|
|
}
|
|
|
|
var firstChild;
|
|
if ( attributes.href ) {
|
|
firstChild = doc.createElement( 'a' );
|
|
firstChild.setAttribute( 'href', attributes.href );
|
|
if ( attributes.imgWrapperClassAttr ) {
|
|
// eslint-disable-next-line mediawiki/class-doc
|
|
firstChild.className = attributes.imgWrapperClassAttr;
|
|
}
|
|
} else {
|
|
firstChild = doc.createElement( 'span' );
|
|
}
|
|
|
|
if ( attributes.isError ) {
|
|
if ( converter.isForPreview() ) {
|
|
firstChild.classList.add( 'new' );
|
|
}
|
|
var filename = mw.libs.ve.normalizeParsoidResourceName( attributes.resource || '' );
|
|
img.appendChild( doc.createTextNode( filename ) );
|
|
// At the moment, preserving this is only relevant on mw:Error spans
|
|
if ( attributes.imageClassAttr ) {
|
|
// eslint-disable-next-line mediawiki/class-doc
|
|
img.className = attributes.imageClassAttr;
|
|
}
|
|
}
|
|
|
|
container.appendChild( firstChild );
|
|
firstChild.appendChild( img );
|
|
|
|
return [ container ];
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.dm.modelRegistry.unregister( ve.dm.InlineImageNode );
|
|
ve.dm.modelRegistry.register( ve.dm.MWInlineImageNode );
|