mediawiki-extensions-Visual.../modules/ve-mw/dm/nodes/ve.dm.MWGalleryImageNode.js
Arlo Breault 0533f49fd5 Support the upcoming mw:File typeof
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
2022-06-10 14:29:31 -04:00

186 lines
6 KiB
JavaScript

/*!
* VisualEditor DataModel MWGalleryImageNode class.
*
* @copyright 2016 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* DataModel MediaWiki gallery image node.
*
* @class
* @extends ve.dm.BranchNode
*
* @constructor
* @param {Object} [element] Reference to element in linear model
* @param {ve.dm.Node[]} [children]
*/
ve.dm.MWGalleryImageNode = function VeDmMWGalleryImageNode() {
// Parent constructor
ve.dm.MWGalleryImageNode.super.apply( this, arguments );
};
/* Inheritance */
OO.inheritClass( ve.dm.MWGalleryImageNode, ve.dm.BranchNode );
/* Static members */
ve.dm.MWGalleryImageNode.static.name = 'mwGalleryImage';
ve.dm.MWGalleryImageNode.static.matchTagNames = [ 'li' ];
ve.dm.MWGalleryImageNode.static.childNodeTypes = [ 'mwGalleryImageCaption' ];
ve.dm.MWGalleryImageNode.static.matchFunction = function ( element ) {
var parentTypeof = ( element.parentNode && element.parentNode.getAttribute( 'typeof' ) ) || '';
return element.getAttribute( 'class' ) === 'gallerybox' &&
parentTypeof.trim().split( /\s+/ ).indexOf( 'mw:Extension/gallery' ) !== -1;
};
ve.dm.MWGalleryImageNode.static.parentNodeTypes = [ 'mwGallery' ];
ve.dm.MWGalleryImageNode.static.toDataElement = function ( domElements, converter ) {
// TODO: Improve handling of missing files. See 'isError' in MWBlockImageNode#toDataElement
var li = domElements[ 0 ];
var img = li.querySelector( 'img,audio,video,span[resource]' );
var container = img.parentNode.parentNode;
// Get caption (may be missing for mode="packed-hover" galleries)
var captionNode = li.querySelector( '.gallerytext' );
if ( captionNode ) {
captionNode = captionNode.cloneNode( true );
// If showFilename is 'yes', the filename is also inside the caption, so throw this out
var filename = captionNode.querySelector( '.galleryfilename' );
if ( filename ) {
filename.remove();
}
}
var caption;
if ( captionNode ) {
caption = converter.getDataFromDomClean( captionNode, { type: 'mwGalleryImageCaption' } );
} else {
caption = [
{ type: 'mwGalleryImageCaption' },
{ type: 'paragraph', internal: { generated: 'wrapper' } },
{ type: '/paragraph' },
{ type: '/mwGalleryImageCaption' }
];
}
var typeofAttrs = container.getAttribute( 'typeof' ).trim().split( /\s+/ );
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' );
if ( isError ) {
typeofAttrs.splice( errorIndex, 1 );
}
var types = ve.dm.MWImageNode.static.rdfaToTypes[ typeofAttrs[ 0 ] ];
var dataElement = {
type: this.name,
attributes: {
mediaClass: types.mediaClass,
mediaTag: img.nodeName.toLowerCase(),
resource: './' + mw.libs.ve.normalizeParsoidResourceName( img.getAttribute( 'resource' ) ),
altText: img.getAttribute( 'alt' ),
// 'src' for images, 'poster' for video/audio
src: img.getAttribute( 'src' ) || img.getAttribute( 'poster' ),
width: width !== null && width !== '' ? +width : null,
height: height !== null && height !== '' ? +height : null,
isError: isError
}
};
return [ dataElement ]
.concat( caption )
.concat( { type: '/' + this.name } );
};
ve.dm.MWGalleryImageNode.static.toDomElements = function ( data, doc ) {
// ImageNode:
// <li> li (gallerybox)
// <div> thumbDiv
// <span> container
// <a> a
// <img> img (or span if error)
var model = data,
attributes = model.attributes,
li = doc.createElement( 'li' ),
thumbDiv = doc.createElement( 'div' ),
container = doc.createElement( 'span' ),
a = doc.createElement( 'a' ),
img = doc.createElement( attributes.isError ? 'span' : ( attributes.mediaTag || 'img' ) ),
alt = attributes.altText;
// FIXME: attributes.mediaTag and attributes.mediaClass aren't set after edit
li.classList.add( 'gallerybox' );
thumbDiv.classList.add( 'thumb' );
container.setAttribute( 'typeof', ve.dm.MWImageNode.static.getRdfa(
( attributes.mediaClass || 'File' ), 'none', attributes.isError
) );
// TODO: Support editing the link
// FIXME: Dropping the href causes Parsoid to mark the node as wrapper modified,
// making the whole gallery subtree edited, preventing selser. When fixing,
// preserving the imgWrapperClassAttr, as in the MW*ImageNodes, will also be
// necessary.
// a.setAttribute( 'href', attributes.src );
img.setAttribute( 'resource', attributes.resource );
if ( attributes.isError ) {
img.classList.add( 'mw-broken-media' );
var filename = mw.libs.ve.normalizeParsoidResourceName( attributes.resource || '' );
img.appendChild( doc.createTextNode( filename ) );
} else {
var srcAttr = ve.dm.MWImageNode.static.tagsToSrcAttrs[ img.nodeName.toLowerCase() ];
img.setAttribute( srcAttr, attributes.src );
}
img.setAttribute( attributes.isError ? 'data-width' : 'width', attributes.width );
img.setAttribute( attributes.isError ? 'data-height' : 'height', attributes.height );
if ( typeof alt === 'string' ) {
img.setAttribute( 'alt', alt );
}
a.appendChild( img );
container.appendChild( a );
thumbDiv.appendChild( container );
li.appendChild( thumbDiv );
return [ li ];
};
ve.dm.MWGalleryImageNode.static.describeChange = function ( key ) {
// These attributes are computed
if ( key === 'src' || key === 'width' || key === 'height' ) {
return null;
}
// Parent method
return ve.dm.MWGalleryImageNode.super.static.describeChange.apply( this, arguments );
};
ve.dm.MWGalleryImageNode.static.isDiffComparable = function ( element, other ) {
// Images with different src's shouldn't be diffed
return element.type === other.type && element.attributes.resource === other.attributes.resource;
};
/* Methods */
/**
* Get the image's caption node.
*
* @return {ve.dm.MWImageCaptionNode|null} Caption node, if present
*/
ve.dm.MWGalleryImageNode.prototype.getCaptionNode = function () {
return this.children.length > 0 ? this.children[ 0 ] : null;
};
/* Registration */
ve.dm.modelRegistry.register( ve.dm.MWGalleryImageNode );