mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-12-18 17:21:25 +00:00
79120fc16c
Parsoid added a class and, without it, we get selser complaining about wrappers being modified, similar to T214649. The "image" class is removed since Parsoid never added it (although it now has "mw-file-description" for a similar purpose) and the legacy parser doesn't apply it indiscriminately. It doesn't seem like VE supports editing the |link= media option; it just tries to roundtrip what's there and drops it on edit. The patch here works with that limitation. Galleries are found to drop href's, breaking selser, and should be fixed in a follow up. Bug: T292657 Bug: T303469 Change-Id: I92359048b42d32fe8a0f2cb79cd348cf5f2c56cc
236 lines
7.3 KiB
JavaScript
236 lines
7.3 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;
|
|
};
|
|
|
|
// For a while, Parsoid switched to <figure-inline> for inline images, but
|
|
// then decided to switch back to <span> in T266143.
|
|
ve.dm.MWInlineImageNode.static.matchTagNames = [ 'span', 'figure-inline' ];
|
|
|
|
ve.dm.MWInlineImageNode.static.disallowedAnnotationTypes = [ 'link' ];
|
|
|
|
ve.dm.MWInlineImageNode.static.toDataElement = function ( domElements, converter ) {
|
|
var container = domElements[ 0 ]; // <span> or <figure-inline>
|
|
var imgWrapper = container.children[ 0 ]; // <a> or <span>
|
|
if ( !imgWrapper ) {
|
|
// Malformed figure, alienate (T267282)
|
|
return null;
|
|
}
|
|
var img = imgWrapper.children[ 0 ]; // <img>, <video> or <audio>
|
|
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,
|
|
type: types.frameType,
|
|
src: img.getAttribute( 'src' ) || img.getAttribute( 'poster' ),
|
|
href: href,
|
|
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,
|
|
tagName: container.nodeName.toLowerCase()
|
|
};
|
|
|
|
// 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,
|
|
mediaClass = attributes.mediaClass,
|
|
container = doc.createElement( attributes.tagName || 'span' ),
|
|
img = doc.createElement( attributes.isError ? 'span' : this.typesToTags[ mediaClass ] ),
|
|
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.typesToSrcAttrs[ mediaClass ];
|
|
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( 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 ) );
|
|
}
|
|
|
|
container.appendChild( firstChild );
|
|
firstChild.appendChild( img );
|
|
|
|
return [ container ];
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.dm.modelRegistry.unregister( ve.dm.InlineImageNode );
|
|
ve.dm.modelRegistry.register( ve.dm.MWInlineImageNode );
|