Merge "Great Annotation Refactor of 2013"

This commit is contained in:
jenkins-bot 2013-04-09 11:28:06 +00:00 committed by Gerrit Code Review
commit 5029ee29ea
25 changed files with 905 additions and 453 deletions

View file

@ -15,12 +15,17 @@
"name": "General",
"classes": [
"ve.ce",
"ve.ce.AnnotationFactory",
"ve.ce.NodeFactory",
"ve.ce.Surface",
"ve.ce.SurfaceObserver",
"ve.ce.DomRange"
]
},
{
"name": "Annotations",
"classes": ["ve.ce.*Annotation"]
},
{
"name": "Nodes",
"classes": ["ve.ce.Document", "ve.ce.*Node"]

View file

@ -286,8 +286,10 @@ $wgResourceModules += array(
// ce
've/ce/ve.ce.js',
've/ce/ve.ce.DomRange.js',
've/ce/ve.ce.AnnotationFactory.js',
've/ce/ve.ce.NodeFactory.js',
've/ce/ve.ce.Document.js',
've/ce/ve.ce.Annotation.js',
've/ce/ve.ce.Node.js',
've/ce/ve.ce.BranchNode.js',
've/ce/ve.ce.ContentBranchNode.js',
@ -319,6 +321,11 @@ $wgResourceModules += array(
've/ce/nodes/ve.ce.MWPreformattedNode.js',
've/ce/nodes/ve.ce.MWImageNode.js',
've/ce/annotations/ve.ce.LinkAnnotation.js',
've/ce/annotations/ve.ce.MWExternalLinkAnnotation.js',
've/ce/annotations/ve.ce.MWInternalLinkAnnotation.js',
've/ce/annotations/ve.ce.TextStyleAnnotation.js',
// ui
've/ui/ve.ui.js',
've/ui/ve.ui.Context.js',

View file

@ -168,8 +168,10 @@ $html = file_get_contents( $page );
<script src="../../modules/ve/dm/metaitems/ve.dm.MWLanguageMetaItem.js"></script>
<script src="../../modules/ve/ce/ve.ce.js"></script>
<script src="../../modules/ve/ce/ve.ce.DomRange.js"></script>
<script src="../../modules/ve/ce/ve.ce.AnnotationFactory.js"></script>
<script src="../../modules/ve/ce/ve.ce.NodeFactory.js"></script>
<script src="../../modules/ve/ce/ve.ce.Document.js"></script>
<script src="../../modules/ve/ce/ve.ce.Annotation.js"></script>
<script src="../../modules/ve/ce/ve.ce.Node.js"></script>
<script src="../../modules/ve/ce/ve.ce.BranchNode.js"></script>
<script src="../../modules/ve/ce/ve.ce.ContentBranchNode.js"></script>
@ -199,6 +201,10 @@ $html = file_get_contents( $page );
<script src="../../modules/ve/ce/nodes/ve.ce.MWHeadingNode.js"></script>
<script src="../../modules/ve/ce/nodes/ve.ce.MWPreformattedNode.js"></script>
<script src="../../modules/ve/ce/nodes/ve.ce.MWImageNode.js"></script>
<script src="../../modules/ve/ce/annotations/ve.ce.LinkAnnotation.js"></script>
<script src="../../modules/ve/ce/annotations/ve.ce.MWExternalLinkAnnotation.js"></script>
<script src="../../modules/ve/ce/annotations/ve.ce.MWInternalLinkAnnotation.js"></script>
<script src="../../modules/ve/ce/annotations/ve.ce.TextStyleAnnotation.js"></script>
<script src="../../modules/ve/ui/ve.ui.js"></script>
<script src="../../modules/ve/ui/ve.ui.Context.js"></script>
<script src="../../modules/ve/ui/ve.ui.Frame.js"></script>

View file

@ -0,0 +1,35 @@
/*!
* VisualEditor ContentEditable LinkAnnotation class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable link annotation.
*
* @class
* @extends ve.ce.Annotation
* @constructor
* @param {ve.dm.LinkAnnotation} model Model to observe
*/
ve.ce.LinkAnnotation = function VeCeLinkAnnotation( model ) {
// Parent constructor
ve.ce.Annotation.call( this, model, $( '<a>' ) );
// DOM changes
this.$.addClass( 've-ce-LinkAnnotation' );
this.$.attr( 'href', model.getAttribute( 'href' ) );
};
/* Inheritance */
ve.inheritClass( ve.ce.LinkAnnotation, ve.ce.Annotation );
/* Static Properties */
ve.ce.LinkAnnotation.static.name = 'link';
/* Registration */
ve.ce.annotationFactory.register( ve.ce.LinkAnnotation );

View file

@ -0,0 +1,35 @@
/*!
* VisualEditor ContentEditable MWExternalLinkAnnotation class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable MediaWiki external link annotation.
*
* @class
* @extends ve.ce.LinkAnnotation
* @constructor
* @param {ve.dm.MWExternalLinkAnnotation} model Model to observe
*/
ve.ce.MWExternalLinkAnnotation = function VeCeMWExternalLinkAnnotation( model ) {
// Parent constructor
ve.ce.LinkAnnotation.call( this, model );
// DOM changes
this.$.addClass( 've-ce-MWExternalLinkAnnotation' );
this.$.attr( 'title', model.getAttribute( 'href' ) );
};
/* Inheritance */
ve.inheritClass( ve.ce.MWExternalLinkAnnotation, ve.ce.LinkAnnotation );
/* Static Properties */
ve.ce.MWExternalLinkAnnotation.static.name = 'link/MWexternal';
/* Registration */
ve.ce.annotationFactory.register( ve.ce.MWExternalLinkAnnotation );

View file

@ -0,0 +1,39 @@
/*!
* VisualEditor ContentEditable MWInternalLinkAnnotation class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable MediaWiki internal link annotation.
*
* @class
* @extends ve.ce.LinkAnnotation
* @constructor
* @param {ve.dm.MWInternalLinkAnnotation} model Model to observe
*/
ve.ce.MWInternalLinkAnnotation = function VeCeMWInternalLinkAnnotation( model ) {
var dmRendering;
// Parent constructor
ve.ce.LinkAnnotation.call( this, model );
// DOM changes
this.$.addClass( 've-ce-MWInternalLinkAnnotation' );
this.$.attr( 'title', model.getAttribute( 'title' ) );
// Get href from DM rendering
dmRendering = model.getDomElements()[0];
this.$.attr( 'href', dmRendering.getAttribute( 'href' ) );
};
/* Inheritance */
ve.inheritClass( ve.ce.MWInternalLinkAnnotation, ve.ce.LinkAnnotation );
/* Static Properties */
ve.ce.MWInternalLinkAnnotation.static.name = 'link/MWinternal';
/* Registration */
ve.ce.annotationFactory.register( ve.ce.MWInternalLinkAnnotation );

View file

@ -0,0 +1,213 @@
/*!
* VisualEditor ContentEditable TextStyleAnnotation class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable text style annotation.
*
* @class
* @extends ve.ce.Annotation
* @constructor
* @param {ve.dm.TextStyleAnnotation} model Model to observe
* @param {jQuery} $element jQuery element (required!)
*/
ve.ce.TextStyleAnnotation = function VeCeTextStyleAnnotation( model, $element ) {
// Parent constructor
ve.ce.Annotation.call( this, model, $element );
// DOM changes
this.$.addClass( 've-ce-TextStyleAnnotation' );
};
/* Inheritance */
ve.inheritClass( ve.ce.TextStyleAnnotation, ve.ce.Annotation );
/* Static Properties */
ve.ce.TextStyleAnnotation.static.name = 'textStyle';
/* Registration */
ve.ce.annotationFactory.register( ve.ce.TextStyleAnnotation );
/* Concrete Subclasses */
/**
* ContentEditable bold annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleBoldAnnotation} model
*/
ve.ce.TextStyleBoldAnnotation = function VeCeTextStyleBoldAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<b>' ) );
this.$.addClass( 've-ce-TextStyleBoldAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleBoldAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleBoldAnnotation.static.name = 'textStyle/bold';
ve.ce.annotationFactory.register( ve.ce.TextStyleBoldAnnotation );
/**
* ContentEditable italic annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleItalicAnnotation} model
*/
ve.ce.TextStyleItalicAnnotation = function VeCeTextStyleItalicAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<i>' ) );
this.$.addClass( 've-ce-TextStyleItalicAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleItalicAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleItalicAnnotation.static.name = 'textStyle/italic';
ve.ce.annotationFactory.register( ve.ce.TextStyleItalicAnnotation );
/**
* ContentEditable underline annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleUnderlineAnnotation} model
*/
ve.ce.TextStyleUnderlineAnnotation = function VeCeTextStyleUnderlineAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<u>' ) );
this.$.addClass( 've-ce-TextStyleUnderlineAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleUnderlineAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleUnderlineAnnotation.static.name = 'textStyle/underline';
ve.ce.annotationFactory.register( ve.ce.TextStyleUnderlineAnnotation );
/**
* ContentEditable strike annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleStrikeAnnotation} model
*/
ve.ce.TextStyleStrikeAnnotation = function VeCeTextStyleStrikeAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<s>' ) );
this.$.addClass( 've-ce-TextStyleStrikeAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleStrikeAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleStrikeAnnotation.static.name = 'textStyle/strike';
ve.ce.annotationFactory.register( ve.ce.TextStyleStrikeAnnotation );
/**
* ContentEditable small annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleSmallAnnotation} model
*/
ve.ce.TextStyleSmallAnnotation = function VeCeTextStyleSmallAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<small>' ) );
this.$.addClass( 've-ce-TextStyleSmallAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleSmallAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleSmallAnnotation.static.name = 'textStyle/small';
ve.ce.annotationFactory.register( ve.ce.TextStyleSmallAnnotation );
/**
* ContentEditable big annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleBigAnnotation} model
*/
ve.ce.TextStyleBigAnnotation = function VeCeTextStyleBigAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<big>' ) );
this.$.addClass( 've-ce-TextStyleBigAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleBigAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleBigAnnotation.static.name = 'textStyle/big';
ve.ce.annotationFactory.register( ve.ce.TextStyleBigAnnotation );
/**
* ContentEditable span annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleSpanAnnotation} model
*/
ve.ce.TextStyleSpanAnnotation = function VeCeTextStyleSpanAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<span>' ) );
this.$.addClass( 've-ce-TextStyleSpanAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleSpanAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleSpanAnnotation.static.name = 'textStyle/span';
ve.ce.annotationFactory.register( ve.ce.TextStyleSpanAnnotation );
/**
* ContentEditable strong annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleStrongAnnotation} model
*/
ve.ce.TextStyleStrongAnnotation = function VeCeTextStyleStrongAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<strong>' ) );
this.$.addClass( 've-ce-TextStyleStrongAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleStrongAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleStrongAnnotation.static.name = 'textStyle/strong';
ve.ce.annotationFactory.register( ve.ce.TextStyleStrongAnnotation );
/**
* ContentEditable emphasize annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleEmphasizeAnnotation} model
*/
ve.ce.TextStyleEmphasizeAnnotation = function VeCeTextStyleEmphasizeAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<em>' ) );
this.$.addClass( 've-ce-TextStyleEmphasizeAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleEmphasizeAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleEmphasizeAnnotation.static.name = 'textStyle/emphasize';
ve.ce.annotationFactory.register( ve.ce.TextStyleEmphasizeAnnotation );
/**
* ContentEditable superScript annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleSuperScriptAnnotation} model
*/
ve.ce.TextStyleSuperScriptAnnotation = function VeCeTextStyleSuperScriptAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<sup>' ) );
this.$.addClass( 've-ce-TextStyleSuperScriptAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleSuperScriptAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleSuperScriptAnnotation.static.name = 'textStyle/superScript';
ve.ce.annotationFactory.register( ve.ce.TextStyleSuperScriptAnnotation );
/**
* ContentEditable subScript annotation.
*
* @class
* @extends ve.ce.TextStyleAnnotation
* @constructor
* @param {ve.dm.TextStyleSubScriptAnnotation} model
*/
ve.ce.TextStyleSubScriptAnnotation = function VeCeTextStyleSubScriptAnnotation( model ) {
ve.ce.TextStyleAnnotation.call( this, model, $( '<sub>' ) );
this.$.addClass( 've-ce-TextStyleSubScriptAnnotation' );
};
ve.inheritClass( ve.ce.TextStyleSubScriptAnnotation, ve.ce.TextStyleAnnotation );
ve.ce.TextStyleSubScriptAnnotation.static.name = 'textStyle/subScript';
ve.ce.annotationFactory.register( ve.ce.TextStyleSubScriptAnnotation );

View file

@ -22,9 +22,6 @@ ve.ce.MWEntityNode = function VeCeMWEntityNode( model ) {
// Need CE=false to prevent selection issues
this.$.attr( 'contenteditable', false );
// Properties
this.currentSource = null;
// Events
this.model.addListenerMethod( this, 'update', 'onUpdate' );

View file

@ -0,0 +1,105 @@
/*!
* VisualEditor ContentEditable Annotation class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Generic ContentEditable annotation.
*
* This is an abstract class, annotations should extend this and call this constructor from their
* constructor. You should not instantiate this class directly.
*
* Subclasses of ve.dm.Annotation should have a corresponding subclass here that controls rendering.
*
* @class
* @constructor
* @param {ve.dm.Annotation} model Model to observe
* @param {jQuery} [$element] Element to use as a container
*/
ve.ce.Annotation = function VeCeAnnotation( model, $element ) {
// Properties
this.model = model;
this.$ = $element || $( '<span>' );
this.parent = null;
this.live = false;
// Initialization
this.$.data( 'annotation', this );
ve.setDomAttributes(
this.$[0],
this.model.getAttributes( 'html/0/' ),
this.constructor.static.domAttributeWhitelist
);
};
/* Events */
/**
* @event live
*/
/* Static Members */
// TODO create a single base class for ce.Node and ce.Annotation
ve.ce.Annotation.static = {};
/**
* Allowed attributes for DOM elements.
*
* This list includes attributes that are generally safe to include in HTML loaded from a
* foreign source and displaying it inside the browser. It doesn't include any event attributes,
* for instance, which would allow arbitrary JavaScript execution. This alone is not enough to
* make HTML safe to display, but it helps.
*
* TODO: Rather than use a single global list, set these on a per-annotation basis to something that makes
* sense for that annotation in particular.
*
* @static
* @property static.domAttributeWhitelist
* @inheritable
*/
ve.ce.Annotation.static.domAttributeWhitelist = [
'abbr', 'about', 'align', 'alt', 'axis', 'bgcolor', 'border', 'cellpadding', 'cellspacing',
'char', 'charoff', 'cite', 'class', 'clear', 'color', 'colspan', 'datatype', 'datetime',
'dir', 'face', 'frame', 'headers', 'height', 'href', 'id', 'itemid', 'itemprop', 'itemref',
'itemscope', 'itemtype', 'lang', 'noshade', 'nowrap', 'property', 'rbspan', 'rel',
'resource', 'rev', 'rowspan', 'rules', 'scope', 'size', 'span', 'src', 'start', 'style',
'summary', 'title', 'type', 'typeof', 'valign', 'value', 'width'
];
/* Methods */
/**
* Get the model this CE annotation observes.
*
* @method
* @returns {ve.ce.Annotation} Model
*/
ve.ce.Annotation.prototype.getModel = function () {
return this.model;
};
/**
* Check if the annotation is attached to the live DOM.
*
* @method
* @returns {boolean} Annotation is attached to the live DOM
*/
ve.ce.Annotation.prototype.isLive = function () {
return this.live;
};
/**
* Set live state.
*
* @method
* @param {boolean} live The annotation has been attached to the live DOM (use false on detach)
* @emits live
*/
ve.ce.Annotation.prototype.setLive = function ( live ) {
this.live = live;
this.emit( 'live' );
};

View file

@ -0,0 +1,28 @@
/*!
* VisualEditor ContentEditable AnnotationFactory class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* ContentEditable annotation factory.
*
* @class
* @extends ve.NodeFactory
* @constructor
*/
ve.ce.AnnotationFactory = function VeCeAnnotationFactory() {
// Parent constructor
// FIXME give ve.NodeFactory a more generic name
ve.NodeFactory.call( this );
};
/* Inheritance */
ve.inheritClass( ve.ce.AnnotationFactory, ve.NodeFactory );
/* Initialization */
// TODO: Move instantiation to a different file
ve.ce.annotationFactory = new ve.ce.AnnotationFactory();

View file

@ -49,40 +49,18 @@ ve.ce.ContentBranchNode.prototype.onSplice = function () {
};
/**
* Get an HTML rendering of contents.
* Get an HTML rendering of the contents.
*
* @method
* @returns {string} HTML rendering
* @returns {jQuery}
*/
ve.ce.ContentBranchNode.prototype.getRenderedContents = function () {
var i, j, open, close, startedClosing, arr, annotation, itemAnnotations, itemHtml, $wrapper,
store = this.model.doc.getStore(), html = '',
annotationStack = new ve.dm.AnnotationSet( store ), annotatedHtml = [];
function openAnnotations( annotations ) {
var out = '',
annotation, i, arr, rendered;
arr = annotations.get();
for ( i = 0; i < arr.length; i++ ) {
annotation = arr[i];
rendered = annotation.renderHTML();
out += ve.getOpeningHtmlTag( rendered.tag, rendered.attributes );
annotationStack.push( annotation );
}
return out;
}
function closeAnnotations( annotations ) {
var out = '',
annotation, i, arr;
arr = annotations.get();
for ( i = 0; i < arr.length; i++ ) {
annotation = arr[i];
out += '</' + annotation.renderHTML().tag + '>';
annotationStack.remove( annotation );
}
return out;
}
var i, j, itemHtml, itemAnnotations, startClosingAt, arr, annotation, $ann,
store = this.model.doc.getStore(),
annotationStack = new ve.dm.AnnotationSet( store ),
annotatedHtml = [],
$wrapper = $( '<div>' ),
$current = $wrapper;
// Gather annotated HTML from the child nodes
for ( i = 0; i < this.children.length; i++ ) {
@ -98,60 +76,51 @@ ve.ce.ContentBranchNode.prototype.getRenderedContents = function () {
itemHtml = annotatedHtml[i];
itemAnnotations = new ve.dm.AnnotationSet( store );
}
open = new ve.dm.AnnotationSet( store );
close = new ve.dm.AnnotationSet( store );
// Go through annotationStack from bottom to top (left to right), and
// close all annotations starting at the first one that's in annotationStack but
// not in itemAnnotations. Then reopen the ones that are in itemAnnotations.
startedClosing = false;
// FIXME code largely copied from ve.dm.Converter
// Close annotations as needed
// Go through annotationStack from bottom to top (low to high),
// and find the first annotation that's not in annotations.
startClosingAt = undefined;
arr = annotationStack.get();
for ( j = 0; j < arr.length; j++ ) {
annotation = arr[j];
if (
!startedClosing &&
annotationStack.contains( annotation ) &&
!itemAnnotations.contains( annotation )
) {
startedClosing = true;
if ( !itemAnnotations.contains( annotation ) ) {
startClosingAt = j;
break;
}
if ( startedClosing ) {
// Because we're processing these in reverse order, we need
// to put these in close in reverse order
close.add( annotation, 0 );
if ( itemAnnotations.contains( annotation ) ) {
// open needs to be reversed with respect to close
open.push( annotation );
}
}
if ( startClosingAt !== undefined ) {
// Close all annotations from top to bottom (high to low)
// until we reach startClosingAt
for ( j = annotationStack.getLength() - 1; j >= startClosingAt; j-- ) {
// Traverse up
$current = $current.parent();
// Remove from annotationStack
annotationStack.removeAt( j );
}
}
// Open all annotations that are in right but not in left
open.addSet( itemAnnotations.diffWith( annotationStack ) );
// Open annotations as needed
arr = itemAnnotations.get();
for ( j = 0; j < arr.length; j++ ) {
annotation = arr[j];
if ( !annotationStack.contains( annotation ) ) {
// Create new node and descend into it
$ann = ve.ce.annotationFactory.create( annotation.getType(), annotation ).$;
$current.append( $ann );
$current = $ann;
// Add to annotationStack
annotationStack.push( annotation );
}
}
// Output the annotation closings and openings
html += closeAnnotations( close );
html += openAnnotations( open );
// Output the actual HTML
if ( typeof itemHtml === 'string' ) {
// Output it directly
html += itemHtml;
} else {
// itemHtml is a jQuery object, output a placeholder
html += '<div class="ve-ce-contentBranch-placeholder" rel="' + i + '"></div>';
}
$current.append( itemHtml );
}
// Close all remaining open annotations
html += closeAnnotations( annotationStack.reversed() );
$wrapper = $( '<div>' ).html( html );
// Replace placeholders
$wrapper.find( '.ve-ce-contentBranch-placeholder' ).each( function() {
var $this = $( this ), item = annotatedHtml[$this.attr( 'rel' )];
$this.replaceWith( ve.isArray( item ) ? item[0] : item );
} );
return $wrapper.contents();
};
/**

View file

@ -13,11 +13,11 @@
* @class
* @extends ve.dm.Annotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.LinkAnnotation = function VeDmLinkAnnotation( element ) {
ve.dm.LinkAnnotation = function VeDmLinkAnnotation( linmodAnnotation ) {
// Parent constructor
ve.dm.Annotation.call( this, element );
ve.dm.Annotation.call( this, linmodAnnotation );
};
/* Inheritance */
@ -40,55 +40,19 @@ ve.dm.LinkAnnotation.static.name = 'link';
*/
ve.dm.LinkAnnotation.static.matchTagNames = ['a'];
/* Methods */
/**
* Get annotation data, especially the href of the link.
*
* @method
* @param {HTMLElement} element
* @returns {Object} Annotation data, containing href property
*/
ve.dm.LinkAnnotation.prototype.getAnnotationData = function( element ) {
return { 'href': element.getAttribute( 'href' ) };
};
/**
* Convert to an object with HTML element information.
*
* @method
* @returns {Object} HTML element information, including tag and attributes properties
*/
ve.dm.LinkAnnotation.prototype.toHTML = function () {
var parentResult = ve.dm.Annotation.prototype.toHTML.call( this );
parentResult.tag = 'a';
parentResult.attributes.href = this.data.href;
return parentResult;
};
/**
* Get the hash object of the link annotation.
*
* This extends the basic annotation hash by adding htmlAttributes.rel
* if it present.
*
* This is a custom hash function for ve#getHash.
*
* @method
* @returns {Object} Object to hash
*/
ve.dm.LinkAnnotation.prototype.getHashObject = function () {
var keys = [ 'name', 'data' ], obj = {}, i;
for ( i = 0; i < keys.length; i++ ) {
if ( this[keys[i]] !== undefined ) {
obj[keys[i]] = this[keys[i]];
ve.dm.LinkAnnotation.static.toDataElement = function ( domElements ) {
return {
'type': 'link',
'attributes': {
'href': domElements[0].getAttribute( 'href' )
}
}
if ( this.htmlAttributes && this.htmlAttributes.rel ) {
obj.htmlAttributes = {};
obj.htmlAttributes.rel = this.htmlAttributes.rel;
}
return obj;
};
};
ve.dm.LinkAnnotation.static.toDomElements = function ( dataElement ) {
var domElement = document.createElement( 'a' );
domElement.setAttribute( 'href', dataElement.attributes.href );
return [ domElement ];
};
/* Registration */

View file

@ -13,16 +13,16 @@
* <a rel="mw:ExtLink/Numbered">
* <a rel="mw:ExtLink/URL">
*
* Each example is semantically slightly different, but don't need special treatment (yet).
* Each example is semantically slightly different, but they don't need special treatment (yet).
*
* @class
* @extends ve.dm.LinkAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.MWExternalLinkAnnotation = function VeDmMWExternalLinkAnnotation( element ) {
ve.dm.MWExternalLinkAnnotation = function VeDmMWExternalLinkAnnotation( linmodAnnotation ) {
// Parent constructor
ve.dm.LinkAnnotation.call( this, element );
ve.dm.LinkAnnotation.call( this, linmodAnnotation );
};
/* Inheritance */
@ -47,22 +47,17 @@ ve.dm.MWExternalLinkAnnotation.static.matchRdfaTypes = [
'mw:ExtLink', 'mw:ExtLink/Numbered', 'mw:ExtLink/URL'
];
/**
* Convert to an object with HTML element information.
*
* @method
* @returns {Object} HTML element information, including tag and attributes properties
*/
ve.dm.MWExternalLinkAnnotation.prototype.toHTML = function () {
var parentResult = ve.dm.LinkAnnotation.prototype.toHTML.call( this );
parentResult.attributes.rel = parentResult.attributes.rel || 'mw:ExtLink';
ve.dm.MWExternalLinkAnnotation.static.toDataElement = function ( domElements ) {
var parentResult = ve.dm.LinkAnnotation.static.toDataElement.apply( this, arguments );
parentResult.type = 'link/MWexternal';
parentResult.attributes.rel = domElements[0].getAttribute( 'rel' );
return parentResult;
};
ve.dm.MWExternalLinkAnnotation.prototype.renderHTML = function () {
var result = this.toHTML();
result.attributes.title = this.data.href;
return result;
ve.dm.MWExternalLinkAnnotation.static.toDomElements = function ( dataElement ) {
var parentResult = ve.dm.LinkAnnotation.static.toDomElements( dataElement );
parentResult[0].setAttribute( 'rel', dataElement.attributes.rel || 'mw:ExtLink' );
return parentResult;
};
/* Registration */

View file

@ -14,11 +14,11 @@
* @class
* @extends ve.dm.LinkAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {HTMLElement|Object} linmodAnnotation
*/
ve.dm.MWInternalLinkAnnotation = function VeDmMWInternalLinkAnnotation( element ) {
ve.dm.MWInternalLinkAnnotation = function VeDmMWInternalLinkAnnotation( linmodAnnotation ) {
// Parent constructor
ve.dm.LinkAnnotation.call( this, element );
ve.dm.LinkAnnotation.call( this, linmodAnnotation );
};
/* Inheritance */
@ -41,60 +41,40 @@ ve.dm.MWInternalLinkAnnotation.static.name = 'link/MWinternal';
*/
ve.dm.MWInternalLinkAnnotation.static.matchRdfaTypes = ['mw:WikiLink'];
/* Methods */
/**
* Get annotation data, especially the href of the link.
*
* @method
* @param {HTMLElement} element
* @returns {Object} Annotation data, containing 'hrefPrefix' and 'title' properties
*/
ve.dm.MWInternalLinkAnnotation.prototype.getAnnotationData = function ( element ) {
ve.dm.MWInternalLinkAnnotation.static.toDataElement = function ( domElements ) {
// Get title from href
// The href is simply the title, unless we're dealing with a page that has slashes in its name
// in which case it's preceded by one or more instances of "./" or "../", so strip those
/*jshint regexp:false */
var matches = element.getAttribute( 'href' ).match( /^((?:\.\.?\/)*)(.*)$/ );
var matches = domElements[0].getAttribute( 'href' ).match( /^((?:\.\.?\/)*)(.*)$/ );
return {
// Store the ./ and ../ prefixes so we can restore them on the way out
'hrefPrefix': matches[1],
'title': decodeURIComponent( matches[2] ).replace( /_/g, ' ' ),
'origTitle': matches[2]
'type': 'link/MWinternal',
'attributes': {
'hrefPrefix': matches[1],
'title': decodeURIComponent( matches[2] ).replace( /_/g, ' ' ),
'origTitle': matches[2]
}
};
};
/**
* Convert to an object with HTML element information.
*
* @method
* @returns {Object} HTML element information, including tag and attributes properties
*/
ve.dm.MWInternalLinkAnnotation.prototype.toHTML = function () {
ve.dm.MWInternalLinkAnnotation.static.toDomElements = function ( dataElement ) {
var href,
parentResult = ve.dm.LinkAnnotation.prototype.toHTML.call( this );
if (
this.data.origTitle &&
decodeURIComponent( this.data.origTitle ).replace( /_/g, ' ' ) === this.data.title
) {
domElement = document.createElement( 'a' ),
title = dataElement.attributes.title,
origTitle = dataElement.attributes.origTitle;
if ( origTitle && decodeURIComponent( origTitle ).replace( /_/g, ' ' ) === title ) {
// Restore href from origTitle
href = this.data.origTitle;
href = origTitle;
// Only use hrefPrefix if restoring from origTitle
if ( this.data.hrefPrefix ) {
href = this.data.hrefPrefix + href;
if ( dataElement.attributes.hrefPrefix ) {
href = dataElement.attributes.hrefPrefix + href;
}
} else {
href = encodeURIComponent( this.data.title );
href = encodeURIComponent( title );
}
parentResult.attributes.href = href;
parentResult.attributes.rel = 'mw:WikiLink';
return parentResult;
};
ve.dm.MWInternalLinkAnnotation.prototype.renderHTML = function () {
var result = this.toHTML();
result.attributes.title = this.data.title;
return result;
domElement.setAttribute( 'href', href );
domElement.setAttribute( 'rel', 'mw:WikiLink' );
return [ domElement ];
};
/* Registration */

View file

@ -13,10 +13,10 @@
* @class
* @extends ve.dm.Annotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleAnnotation = function VeDmTextStyleAnnotation( element ) {
ve.dm.Annotation.call( this, element );
ve.dm.TextStyleAnnotation = function VeDmTextStyleAnnotation( linmodAnnotation ) {
ve.dm.Annotation.call( this, linmodAnnotation );
};
/* Inheritance */
@ -39,18 +39,43 @@ ve.dm.TextStyleAnnotation.static.name = 'textStyle';
*/
ve.dm.TextStyleAnnotation.static.matchTagNames = [];
/**
* Convert to an object with HTML element information.
*
* @method
* @returns {Object} HTML element information, including tag and attributes properties
*/
ve.dm.TextStyleAnnotation.prototype.toHTML = function () {
var parentResult = ve.dm.Annotation.prototype.toHTML.call( this );
parentResult.tag = parentResult.tag || this.constructor.static.matchTagNames[0];
return parentResult;
ve.dm.TextStyleAnnotation.static.toDataElement = function ( domElements ) {
var types = {
'b': 'bold',
'i': 'italic',
'u': 'underline',
's': 'strike',
'small': 'small',
'big': 'big',
'span': 'span',
'strong': 'strong',
'em': 'emphasize',
'sup': 'superScript',
'sub': 'subScript'
};
return {
'type': 'textStyle/' + types[domElements[0].nodeName.toLowerCase()]
};
};
ve.dm.TextStyleAnnotation.static.toDomElements = function ( dataElement ) {
var nodeNames = {
'bold': 'b',
'italic': 'i',
'underline': 'u',
'strike': 's',
'small': 'small',
'big': 'big',
'span': 'span',
'strong': 'strong',
'emphasize': 'em',
'superScript': 'sup',
'subScript': 'sub'
};
return [ document.createElement( nodeNames[dataElement.type.substring( 10 )] ) ];
};
/* Registration */
ve.dm.modelRegistry.register( ve.dm.TextStyleAnnotation );
@ -63,10 +88,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleBoldAnnotation = function VeDmTextStyleBoldAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleBoldAnnotation = function VeDmTextStyleBoldAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleBoldAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleBoldAnnotation.static.name = 'textStyle/bold';
@ -79,10 +104,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleBoldAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleItalicAnnotation = function VeDmTextStyleItalicAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleItalicAnnotation = function VeDmTextStyleItalicAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleItalicAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleItalicAnnotation.static.name = 'textStyle/italic';
@ -95,10 +120,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleItalicAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleUnderlineAnnotation = function VeDmTextStyleUnderlineAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleUnderlineAnnotation = function VeDmTextStyleUnderlineAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleUnderlineAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleUnderlineAnnotation.static.name = 'textStyle/underline';
@ -111,10 +136,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleUnderlineAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleStrikeAnnotation = function VeDmTextStyleStrikeAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleStrikeAnnotation = function VeDmTextStyleStrikeAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleStrikeAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleStrikeAnnotation.static.name = 'textStyle/strike';
@ -127,10 +152,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleStrikeAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleSmallAnnotation = function VeDmTextStyleSmallAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleSmallAnnotation = function VeDmTextStyleSmallAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleSmallAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleSmallAnnotation.static.name = 'textStyle/small';
@ -143,10 +168,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleSmallAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleBigAnnotation = function VeDmTextStyleBigAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleBigAnnotation = function VeDmTextStyleBigAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleBigAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleBigAnnotation.static.name = 'textStyle/big';
@ -159,10 +184,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleBigAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleSpanAnnotation = function VeDmTextStyleSpanAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleSpanAnnotation = function VeDmTextStyleSpanAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleSpanAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleSpanAnnotation.static.name = 'textStyle/span';
@ -175,10 +200,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleSpanAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleStrongAnnotation = function VeDmTextStyleStrongAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleStrongAnnotation = function VeDmTextStyleStrongAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleStrongAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleStrongAnnotation.static.name = 'textStyle/strong';
@ -191,10 +216,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleStrongAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleEmphasizeAnnotation = function VeDmTextStyleEmphasizeAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleEmphasizeAnnotation = function VeDmTextStyleEmphasizeAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleEmphasizeAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleEmphasizeAnnotation.static.name = 'textStyle/emphasize';
@ -207,10 +232,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleEmphasizeAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleSuperScriptAnnotation = function VeDmTextStyleSuperScriptAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleSuperScriptAnnotation = function VeDmTextStyleSuperScriptAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleSuperScriptAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleSuperScriptAnnotation.static.name = 'textStyle/superScript';
@ -223,10 +248,10 @@ ve.dm.modelRegistry.register( ve.dm.TextStyleSuperScriptAnnotation );
* @class
* @extends ve.dm.TextStyleAnnotation
* @constructor
* @param {HTMLElement|Object} element
* @param {Object} linmodAnnotation
*/
ve.dm.TextStyleSubScriptAnnotation = function VeDmTextStyleSubScriptAnnotation( element ) {
ve.dm.TextStyleAnnotation.call( this, element );
ve.dm.TextStyleSubScriptAnnotation = function VeDmTextStyleSubScriptAnnotation( linmodAnnotation ) {
ve.dm.TextStyleAnnotation.call( this, linmodAnnotation );
};
ve.inheritClass( ve.dm.TextStyleSubScriptAnnotation, ve.dm.TextStyleAnnotation );
ve.dm.TextStyleSubScriptAnnotation.static.name = 'textStyle/subScript';

View file

@ -11,29 +11,16 @@
* This is an abstract class, annotations should extend this and call this constructor from their
* constructor. You should not instantiate this class directly.
*
* Annotations in the linear model are instances of subclasses of this class. Subclasses are
* required to have a constructor with the same signature.
*
* this.htmlTagName and this.htmlAttributes are private to the base class, subclasses must not
* use them. Any information from the HTML element that is needed later should be extracted into
* this.data by overriding getAnnotationData(). Subclasses can read from this.data but must not
* write to it directly.
* Annotations in the linear model are instances of subclasses of this class. Subclasses should
* only override static properties and functions.
*
* @class
* @constructor
* @param {HTMLElement|Object} [element] HTML element the annotation was converted from, if any, or
* an object to copy into the annotation's data property
* @param {Object} linmodAnnotation Linear model annotation
*/
ve.dm.Annotation = function VeDmAnnotation( element ) {
this.name = this.constructor.static.name; // Needed for proper hashing
this.data = {};
if ( ve.isPlainObject( element ) ) {
this.data = ve.copyObject( element );
} else if ( element && element.nodeType === Node.ELEMENT_NODE ) {
this.htmlTagName = element.nodeName.toLowerCase();
this.htmlAttributes = ve.getDomAttributes( element );
this.data = this.getAnnotationData( element );
}
ve.dm.Annotation = function VeDmAnnotation( linmodAnnotation ) {
this.name = this.constructor.static.name;
this.linmodAnnotation = linmodAnnotation;
};
/* Static properties */
@ -48,6 +35,8 @@ ve.dm.Annotation = function VeDmAnnotation( element ) {
*/
ve.dm.Annotation.static = {};
// TODO create a single base class for dm.Node, dm.Annotation and dm.MetaItem
/**
* Symbolic name for the annotation class.
*
@ -105,74 +94,134 @@ ve.dm.Annotation.static.matchRdfaTypes = null;
*/
ve.dm.Annotation.static.matchFunction = null;
/**
* Static function to convert a DOM element or set of sibling DOM elements to an annotation of
* this type.
*
* This function is only called if this annotation "won" the matching for the first DOM element, so
* domElements[0] will match this item's matching rule. For annotations, there is only one node in
* domElements[].
*
* This function is allowed to return an annotation even if context.expectingContent is false.
* If that happens, the annotation will be put in a wrapper paragraph. If this function returns
* null, the DOM element will be converted to an alien node.
*
* The returned linear model annotation must have a type property set to a registered annotation
* name (usually the annotations's .static.name, but that's not required). It may optionally have an
* attributes property set to an object with key-value pairs. Any other properties are not allowed.
*
* @static
* @method
* @param {HTMLElement[]} domElements DOM elements to convert. Only one element
* @param {Object} context Object describing the current state of the converter
* @param {boolean} context.expectingContent Whether this function is expected to return a content element
* @param {boolean} context.inWrapper Whether this element is in a wrapper paragraph generated by the converter;
* can only be true if context.expectingContent is also true
* @param {boolean} context.canCloseWrapper Whether the current wrapper paragraph can be closed;
* can only be true if context.inWrapper is also true
* @returns {Object|null} Linear model annotation, or null to alienate
*/
ve.dm.Annotation.static.toDataElement = function ( /*domElements, context*/ ) {
throw new Error( 've.dm.Annotation subclass must implement toDataElement' );
};
/**
* Static function to convert a linear model annotation of this type back to a DOM element.
*
* This function returns an array of DOM elements for consistency, but annotations can only return
* one DOM element, so any elements beyond the first are ignored.
*
* @static
* @method
* @param {Object} Linear model annotation with a type property and optionally an attributes property
* @returns {HTMLElement[]} Array of one DOM element
*/
ve.dm.Annotation.static.toDomElements = function ( /*dataElement*/ ) {
throw new Error( 've.dm.Annotation subclass must implement toDomElements' );
};
/**
* About grouping is not supported for annotations; setting this to true has no effect.
*
* @static
* @property {boolean} static.enableAboutGrouping
*/
ve.dm.Annotation.static.enableAboutGrouping = false;
/**
* Whether HTML attributes should be preserved for this annotation type. If true, the HTML attributes
* of the DOM elements will be stored as attributes in the linear model annotation. The attribute
* names will be html/i/attrName, where i is the index of the DOM element in the domElements array,
* and attrName is the name of the attribute.
*
* This should generally be enabled, except for annotation types that store their entire HTML in an
* attribute.
*
* @static
* @property {boolean} static.storeHtmlAttributes
* @inheritable
*/
ve.dm.Annotation.static.storeHtmlAttributes = true;
/* Methods */
/**
* Get annotation data for the linear model.
*
* Called when building a new annotation from an HTML element.
*
* This annotation data object is completely free-form. It's stored in the linear model, it can be
* manipulated by UI widgets, and you access it as this.data in toHTML() on the way out and in
* renderHTML() for rendering. It is also the ONLY data you can reliably use in those contexts, so
* any information from the HTML element that you'll need later should be extracted into the data
* object here.
*
* @method
* @param {HTMLElement} element HTML element the annotation will represent
* @returns {Object} Annotation data
*/
ve.dm.Annotation.prototype.getAnnotationData = function () {
return {};
ve.dm.Annotation.prototype.getType = function () {
return this.name;
};
/**
* Convert the annotation back to HTML for output purposes.
*
* You should only use this.data here, you cannot reliably use any of the other properties.
* The default action is to restore the original HTML element's tag name and attributes (if this
* annotation was created based on an element). If a subclass wants to do this too (this is common),
* it should call its parent's implementation first, then manipulate the return value.
*
* @method
* @returns {Object} Object with 'tag' (tag name) and 'attributes' (object with attribute key/values)
* Get the linear model object for this annotation
* @returns {Object} Linear model annotation
*/
ve.dm.Annotation.prototype.toHTML = function () {
return {
'tag': this.htmlTagName || '',
'attributes': this.htmlAttributes || {}
};
ve.dm.Annotation.prototype.getLinmodAnnotation = function () {
return this.linmodAnnotation;
};
/**
* Convert the annotation to HTML for rendering purposes.
* Get the value of an attribute
*
* By default, this just calls #toHTML, but it may be customized if the rendering should be
* different from the output.
*
* @see #toHTML
* Return value is by reference if array or object
*
* @method
* @returns {Object} Object with 'tag' (tag name) and 'attributes' (object with attribute key/values)
* @param {string} key Name of attribute to get
* @returns {Mixed} Value of attribute, or undefined if no such attribute exists
*/
ve.dm.Annotation.prototype.renderHTML = function () {
return this.toHTML();
ve.dm.Annotation.prototype.getAttribute = function ( key ) {
return this.linmodAnnotation && this.linmodAnnotation.attributes ?
this.linmodAnnotation.attributes[key] : undefined;
};
// FIXME code copied from ve.dm.Node
/**
* Get the hash object of the annotation.
* Get a copy of all attributes.
*
* This is a custom hash function for ve#getHash.
* Values are by reference if array or object, similar to using the getAttribute method.
*
* @method
* @returns {Object} Object to hash
* @param {string} prefix Only return attributes with this prefix, and remove the prefix from them
* @returns {Object} Attributes
*/
ve.dm.Annotation.prototype.getHashObject = function () {
var keys = [ 'name', 'data' ], obj = {}, i;
for ( i = 0; i < keys.length; i++ ) {
if ( this[keys[i]] !== undefined ) {
obj[keys[i]] = this[keys[i]];
ve.dm.Annotation.prototype.getAttributes = function ( prefix ) {
var key, filtered,
attributes = this.element && this.linmodAnnotation.attributes ?
this.linmodAnnotation.attributes : {};
if ( prefix ) {
filtered = {};
for ( key in attributes ) {
if ( key.indexOf( prefix ) === 0 ) {
filtered[key.substr( prefix.length )] = attributes[key];
}
}
return filtered;
}
return obj;
};
return ve.extendObject( {}, attributes );
};
/**
* Convenience wrapper for .toDomElements() on the current annotation
* @method
* @see #toDomElements
*/
ve.dm.Annotation.prototype.getDomElements = function () {
return this.constructor.static.toDomElements( this.linmodAnnotation );
};

View file

@ -98,7 +98,7 @@ ve.dm.Converter.prototype.getDomElementsFromDataElement = function ( dataElement
/**
* Create a data element from a DOM element.
* @param {ve.dm.Node|ve.dm.MetaItem} modelClass Model class to use for conversion
* @param {ve.dm.Node|ve.dm.MetaItem|ve.dm.Annotation} modelClass Model class to use for conversion
* @param {HTMLElement[]} domElements DOM elements to convert
* @param {Object} context Converter context to pass to toDataElement() (will be cloned)
* @returns {Object} Data element
@ -245,7 +245,7 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( store, domElement
path = path || ['document'];
var i, childDomElement, childDomElements, childDataElement, text, childTypes, matches,
wrappingParagraph, prevElement, childAnnotations, modelName, modelClass,
annotation, childIsContent, aboutGroup,
annotation, annotationData, childIsContent, aboutGroup,
data = [],
branchType = path[path.length - 1],
branchHasContent = this.nodeFactory.canNodeContainContent( branchType ),
@ -270,7 +270,10 @@ ve.dm.Converter.prototype.getDataFromDomRecursion = function ( store, domElement
modelName = this.modelRegistry.matchElement( childDomElement );
modelClass = this.modelRegistry.lookup( modelName ) || ve.dm.AlienNode;
if ( modelClass.prototype instanceof ve.dm.Annotation ) {
annotation = this.annotationFactory.create( modelName, childDomElement );
annotationData = this.createDataElement( modelClass, [ childDomElement ], context );
}
if ( modelClass.prototype instanceof ve.dm.Annotation && annotationData ) {
annotation = this.annotationFactory.create( modelName, annotationData );
// Start wrapping if needed
if ( !context.inWrapper && !context.expectingContent ) {
startWrapping();
@ -627,7 +630,9 @@ ve.dm.Converter.prototype.getDomFromData = function ( store, data ) {
text = '';
}
// Create new node and descend into it
annotationElement = this.getDomElementFromDataAnnotation( annotation, doc );
annotationElement = this.getDomElementsFromDataElement(
annotation.getLinmodAnnotation(), doc
)[0];
domElement.appendChild( annotationElement );
domElement = annotationElement;
// Add to annotationStack

View file

@ -491,6 +491,7 @@ ve.dm.Node.prototype.getOffset = function () {
* Return value is by reference if array or object.
*
* @method
* @param {string} key Name of attribute to get
* @returns {Mixed} Value of attribute, or undefined if no such attribute exists
*/
ve.dm.Node.prototype.getAttribute = function ( key ) {

View file

@ -10,9 +10,10 @@ QUnit.module( 've.ce.ContentBranchNode' );
/* Tests */
QUnit.test( 'getRenderedContents', function ( assert ) {
var i, len, doc, $rendered,
var i, len, doc, $rendered, $wrapper,
cases = [
{
'msg': 'Plain text without annotations',
'data': [
{ 'type': 'paragraph' },
'a',
@ -23,6 +24,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': 'abc'
},
{
'msg': 'Bold text',
'data': [
{ 'type': 'paragraph' },
['a', [ { 'type': 'textStyle/bold' } ]],
@ -33,6 +35,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': '<b>abc</b>'
},
{
'msg': 'Bold character, plain character, italic character',
'data': [
{ 'type': 'paragraph' },
['a', [ { 'type': 'textStyle/bold' } ]],
@ -43,6 +46,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': '<b>a</b>b<i>c</i>'
},
{
'msg': 'Bold, italic and underlined text (same order)',
'data': [
{ 'type': 'paragraph' },
['a', [
@ -65,6 +69,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': '<b><i><u>abc</u></i></b>'
},
{
'msg': 'Varying order in consecutive range doesn\'t affect rendering',
'data': [
{ 'type': 'paragraph' },
['a', [
@ -87,6 +92,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': '<b><i><u>abc</u></i></b>'
},
{
'msg': 'Varying order in non-consecutive range does affect rendering',
'data': [
{ 'type': 'paragraph' },
['a', [
@ -105,6 +111,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': '<b><i><u>a</u></i></b>b<u><b><i>c</i></b></u>'
},
{
'msg': 'Text annotated in varying order, surrounded by plain text',
'data': [
{ 'type': 'paragraph' },
'a',
@ -133,6 +140,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': 'abc<b><i><u>def</u></i></b>ghi'
},
{
'msg': 'Out-of-order closings do not produce misnested tags',
'data': [
{ 'type': 'paragraph' },
'a',
@ -160,6 +168,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': 'abc<b><i><u>d</u></i></b><i><u>e<b>f</b></u></i>ghi'
},
{
'msg': 'Additional openings are added inline, even when out of order',
'data': [
{ 'type': 'paragraph' },
'a',
@ -187,6 +196,7 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'html': 'abc<i><u><b>d</b>e<b>f</b></u></i>ghi'
},
{
'msg': 'Out-of-order closings surrounded by plain text',
'data': [
{ 'type': 'paragraph' },
'a',
@ -210,9 +220,10 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
'i',
{ 'type': '/paragraph' }
],
'html': 'abc<i><u><b>d</b></u></i><u><b>ef</b></u>ghi'
'html': 'abc<i><u><b>d</b></u></i><b><u>ef</u></b>ghi'
},
{
'msg': 'Annotation spanning text and inline nodes',
'data': [
{ 'type': 'paragraph' },
'a',
@ -242,6 +253,9 @@ QUnit.test( 'getRenderedContents', function ( assert ) {
for ( i = 0, len = cases.length; i < len; i++ ) {
doc = new ve.dm.Document( ve.dm.example.preprocessAnnotations( cases[i].data ) );
$rendered = ( new ve.ce.ParagraphNode( doc.documentNode.getChildren()[0] ) ).getRenderedContents();
assert.deepEqual( $( '<div>' ).append( $rendered ).html(), cases[i].html );
$wrapper = $( '<div>' ).append( $rendered );
// HACK strip out all the class="ve-ce-TextStyleAnnotation ve-ce-TextStyleBoldAnnotation" crap
$wrapper.find( '.ve-ce-TextStyleAnnotation' ).removeAttr( 'class' );
assert.equalDomElement( $wrapper[0], $( '<div>' ).html( cases[i].html )[0], cases[i].msg );
}
} );

View file

@ -19,7 +19,7 @@ ve.dm.example = {};
* annotation objects, and wraps the result in a ve.dm.ElementLinearData object.
*
* Shorthand notation for annotations is:
* [ 'a', [ { 'type': 'link', 'data': { 'href': '...' }, 'htmlTagName': 'a', 'htmlAttributes': { ... } } ] ]
* [ 'a', [ { 'type': 'link', 'attributes': { 'href': '...' } ] ]
*
* The actual storage format has an instance of ve.dm.LinkAnnotation instead of the plain object,
* and an instance of ve.dm.AnnotationSet instead of the array.
@ -55,18 +55,11 @@ ve.dm.example.preprocessAnnotations = function ( data, store ) {
/**
* Create an annotation object from shorthand notation.
* @method
* @param {Object} annotation Plain object with type, data, htmlTagName and htmlAttributes properties
* @param {Object} annotation Plain object with type and attributes properties
* @return {ve.dm.Annotation} Instance of the right ve.dm.Annotation subclass
*/
ve.dm.example.createAnnotation = function ( annotation ) {
var ann, annKey;
ann = ve.dm.annotationFactory.create( annotation.type );
for ( annKey in annotation ) {
if ( annKey !== 'type' ) {
ann[annKey] = annotation[annKey];
}
}
return ann;
return ve.dm.annotationFactory.create( annotation.type, annotation );
};
/**
@ -88,10 +81,10 @@ ve.dm.example.createAnnotationSet = function ( store, annotations ) {
};
/* Some common annotations in shorthand format */
ve.dm.example.bold = { 'type': 'textStyle/bold', 'htmlTagName': 'b', 'htmlAttributes': {} };
ve.dm.example.italic = { 'type': 'textStyle/italic', 'htmlTagName': 'i', 'htmlAttributes': {} };
ve.dm.example.underline = { 'type': 'textStyle/underline', 'htmlTagName': 'u', 'htmlAttributes': {} };
ve.dm.example.span = { 'type': 'textStyle/span', 'htmlTagName': 'span', 'htmlAttributes': {} };
ve.dm.example.bold = { 'type': 'textStyle/bold' };
ve.dm.example.italic = { 'type': 'textStyle/italic' };
ve.dm.example.underline = { 'type': 'textStyle/underline' };
ve.dm.example.span = { 'type': 'textStyle/span' };
/**
* Creates a document from example data.
@ -1049,16 +1042,13 @@ ve.dm.example.domToDataCases = {
'b',
[ {
'type': 'link/MWinternal',
'data': {
'attributes': {
'title': 'Foo bar',
'origTitle': 'Foo_bar',
'hrefPrefix': ''
},
'htmlTagName': 'a',
'htmlAttributes': {
'data-rt': '{"sHref":"foo bar"}',
'href': 'Foo_bar',
'rel': 'mw:WikiLink'
'hrefPrefix': '',
'html/0/data-rt': '{"sHref":"foo bar"}',
'html/0/href': 'Foo_bar',
'html/0/rel': 'mw:WikiLink'
}
} ]
],
@ -1066,16 +1056,13 @@ ve.dm.example.domToDataCases = {
'a',
[ {
'type': 'link/MWinternal',
'data': {
'attributes': {
'title': 'Foo bar',
'origTitle': 'Foo_bar',
'hrefPrefix': ''
},
'htmlTagName': 'a',
'htmlAttributes': {
'data-rt': '{"sHref":"foo bar"}',
'href': 'Foo_bar',
'rel': 'mw:WikiLink'
'hrefPrefix': '',
'html/0/data-rt': '{"sHref":"foo bar"}',
'html/0/href': 'Foo_bar',
'html/0/rel': 'mw:WikiLink'
}
} ]
],
@ -1083,16 +1070,13 @@ ve.dm.example.domToDataCases = {
'r',
[ {
'type': 'link/MWinternal',
'data': {
'attributes': {
'title': 'Foo bar',
'origTitle': 'Foo_bar',
'hrefPrefix': ''
},
'htmlTagName': 'a',
'htmlAttributes': {
'data-rt': '{"sHref":"foo bar"}',
'href': 'Foo_bar',
'rel': 'mw:WikiLink'
'hrefPrefix': '',
'html/0/data-rt': '{"sHref":"foo bar"}',
'html/0/href': 'Foo_bar',
'html/0/rel': 'mw:WikiLink'
}
} ]
],
@ -1110,15 +1094,12 @@ ve.dm.example.domToDataCases = {
'F',
[ {
'type': 'link/MWinternal',
'data': {
'attributes': {
'title': 'Foo/Bar',
'origTitle': 'Foo/Bar',
'hrefPrefix': './../../../'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': './../../../Foo/Bar',
'rel': 'mw:WikiLink'
'hrefPrefix': './../../../',
'html/0/href': './../../../Foo/Bar',
'html/0/rel': 'mw:WikiLink'
}
} ]
],
@ -1126,15 +1107,12 @@ ve.dm.example.domToDataCases = {
'o',
[ {
'type': 'link/MWinternal',
'data': {
'attributes': {
'title': 'Foo/Bar',
'origTitle': 'Foo/Bar',
'hrefPrefix': './../../../'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': './../../../Foo/Bar',
'rel': 'mw:WikiLink'
'hrefPrefix': './../../../',
'html/0/href': './../../../Foo/Bar',
'html/0/rel': 'mw:WikiLink'
}
} ]
],
@ -1142,15 +1120,12 @@ ve.dm.example.domToDataCases = {
'o',
[ {
'type': 'link/MWinternal',
'data': {
'attributes': {
'title': 'Foo/Bar',
'origTitle': 'Foo/Bar',
'hrefPrefix': './../../../'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': './../../../Foo/Bar',
'rel': 'mw:WikiLink'
'hrefPrefix': './../../../',
'html/0/href': './../../../Foo/Bar',
'html/0/rel': 'mw:WikiLink'
}
} ]
],
@ -1165,13 +1140,11 @@ ve.dm.example.domToDataCases = {
'[',
[ {
'type': 'link/MWexternal',
'data': {
'href': 'http://www.mediawiki.org/'
},
'htmlTagName': 'a',
'htmlAttributes': {
'attributes': {
'href': 'http://www.mediawiki.org/',
'rel': 'mw:ExtLink/Numbered'
'rel': 'mw:ExtLink/Numbered',
'html/0/href': 'http://www.mediawiki.org/',
'html/0/rel': 'mw:ExtLink/Numbered'
}
} ]
],
@ -1179,13 +1152,11 @@ ve.dm.example.domToDataCases = {
'1',
[ {
'type': 'link/MWexternal',
'data': {
'href': 'http://www.mediawiki.org/'
},
'htmlTagName': 'a',
'htmlAttributes': {
'attributes': {
'href': 'http://www.mediawiki.org/',
'rel': 'mw:ExtLink/Numbered'
'rel': 'mw:ExtLink/Numbered',
'html/0/href': 'http://www.mediawiki.org/',
'html/0/rel': 'mw:ExtLink/Numbered'
}
} ]
],
@ -1193,13 +1164,11 @@ ve.dm.example.domToDataCases = {
']',
[ {
'type': 'link/MWexternal',
'data': {
'href': 'http://www.mediawiki.org/'
},
'htmlTagName': 'a',
'htmlAttributes': {
'attributes': {
'href': 'http://www.mediawiki.org/',
'rel': 'mw:ExtLink/Numbered'
'rel': 'mw:ExtLink/Numbered',
'html/0/href': 'http://www.mediawiki.org/',
'html/0/rel': 'mw:ExtLink/Numbered'
}
} ]
],
@ -1214,13 +1183,11 @@ ve.dm.example.domToDataCases = {
'm',
[ {
'type': 'link/MWexternal',
'data': {
'href': 'http://www.mediawiki.org/'
},
'htmlTagName': 'a',
'htmlAttributes': {
'attributes': {
'href': 'http://www.mediawiki.org/',
'rel': 'mw:ExtLink/URL'
'rel': 'mw:ExtLink/URL',
'html/0/href': 'http://www.mediawiki.org/',
'html/0/rel': 'mw:ExtLink/URL'
}
} ]
],
@ -1228,13 +1195,11 @@ ve.dm.example.domToDataCases = {
'w',
[ {
'type': 'link/MWexternal',
'data': {
'href': 'http://www.mediawiki.org/'
},
'htmlTagName': 'a',
'htmlAttributes': {
'attributes': {
'href': 'http://www.mediawiki.org/',
'rel': 'mw:ExtLink/URL'
'rel': 'mw:ExtLink/URL',
'html/0/href': 'http://www.mediawiki.org/',
'html/0/rel': 'mw:ExtLink/URL'
}
} ]
],
@ -1634,15 +1599,12 @@ ve.dm.example.domToDataCases = {
ve.dm.example.bold,
{
'type': 'link/MWinternal',
'data': {
'attributes': {
'hrefPrefix': '',
'origTitle': 'Foo',
'title': 'Foo'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': 'Foo',
'rel': 'mw:WikiLink'
'title': 'Foo',
'html/0/href': 'Foo',
'html/0/rel': 'mw:WikiLink'
}
},
ve.dm.example.italic
@ -1654,15 +1616,12 @@ ve.dm.example.domToDataCases = {
ve.dm.example.bold,
{
'type': 'link/MWinternal',
'data': {
'attributes': {
'hrefPrefix': '',
'origTitle': 'Foo',
'title': 'Foo'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': 'Foo',
'rel': 'mw:WikiLink'
'title': 'Foo',
'html/0/href': 'Foo',
'html/0/rel': 'mw:WikiLink'
}
},
ve.dm.example.italic
@ -1674,15 +1633,12 @@ ve.dm.example.domToDataCases = {
ve.dm.example.bold,
{
'type': 'link/MWinternal',
'data': {
'attributes': {
'hrefPrefix': '',
'origTitle': 'Foo',
'title': 'Foo'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': 'Foo',
'rel': 'mw:WikiLink'
'title': 'Foo',
'html/0/href': 'Foo',
'html/0/rel': 'mw:WikiLink'
}
},
ve.dm.example.italic
@ -1700,15 +1656,12 @@ ve.dm.example.domToDataCases = {
[
{
'type': 'link/MWinternal',
'data': {
'attributes': {
'hrefPrefix': '',
'origTitle': 'Foo',
'title': 'Foo'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': 'Foo',
'rel': 'mw:WikiLink'
'title': 'Foo',
'html/0/href': 'Foo',
'html/0/rel': 'mw:WikiLink'
}
}
]
@ -1718,15 +1671,12 @@ ve.dm.example.domToDataCases = {
[
{
'type': 'link/MWinternal',
'data': {
'attributes': {
'hrefPrefix': '',
'origTitle': 'Foo',
'title': 'Foo'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': 'Foo',
'rel': 'mw:WikiLink'
'title': 'Foo',
'html/0/href': 'Foo',
'html/0/rel': 'mw:WikiLink'
}
},
ve.dm.example.bold
@ -1737,15 +1687,12 @@ ve.dm.example.domToDataCases = {
[
{
'type': 'link/MWinternal',
'data': {
'attributes': {
'hrefPrefix': '',
'origTitle': 'Foo',
'title': 'Foo'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': 'Foo',
'rel': 'mw:WikiLink'
'title': 'Foo',
'html/0/href': 'Foo',
'html/0/rel': 'mw:WikiLink'
}
},
ve.dm.example.bold,
@ -1757,15 +1704,12 @@ ve.dm.example.domToDataCases = {
[
{
'type': 'link/MWinternal',
'data': {
'attributes': {
'hrefPrefix': '',
'origTitle': 'Foo',
'title': 'Foo'
},
'htmlTagName': 'a',
'htmlAttributes': {
'href': 'Foo',
'rel': 'mw:WikiLink'
'title': 'Foo',
'html/0/href': 'Foo',
'html/0/rel': 'mw:WikiLink'
}
},
ve.dm.example.italic

View file

@ -112,8 +112,10 @@
<script src="../../ve/dm/metaitems/ve.dm.MWLanguageMetaItem.js"></script>
<script src="../../ve/ce/ve.ce.js"></script>
<script src="../../ve/ce/ve.ce.DomRange.js"></script>
<script src="../../ve/ce/ve.ce.AnnotationFactory.js"></script>
<script src="../../ve/ce/ve.ce.NodeFactory.js"></script>
<script src="../../ve/ce/ve.ce.Document.js"></script>
<script src="../../ve/ce/ve.ce.Annotation.js"></script>
<script src="../../ve/ce/ve.ce.Node.js"></script>
<script src="../../ve/ce/ve.ce.BranchNode.js"></script>
<script src="../../ve/ce/ve.ce.ContentBranchNode.js"></script>
@ -143,6 +145,10 @@
<script src="../../ve/ce/nodes/ve.ce.MWHeadingNode.js"></script>
<script src="../../ve/ce/nodes/ve.ce.MWPreformattedNode.js"></script>
<script src="../../ve/ce/nodes/ve.ce.MWImageNode.js"></script>
<script src="../../ve/ce/annotations/ve.ce.LinkAnnotation.js"></script>
<script src="../../ve/ce/annotations/ve.ce.MWExternalLinkAnnotation.js"></script>
<script src="../../ve/ce/annotations/ve.ce.MWInternalLinkAnnotation.js"></script>
<script src="../../ve/ce/annotations/ve.ce.TextStyleAnnotation.js"></script>
<script src="../../ve/ui/ve.ui.js"></script>
<script src="../../ve/ui/ve.ui.Context.js"></script>
<script src="../../ve/ui/ve.ui.Frame.js"></script>

View file

@ -195,7 +195,12 @@ ve.ui.LinkInspector.prototype.onClose = function ( remove ) {
* @returns {ve.dm.LinkAnnotation}
*/
ve.ui.LinkInspector.prototype.getAnnotationFromTarget = function ( target ) {
return new ve.dm.LinkAnnotation( { 'href': target } );
return new ve.dm.LinkAnnotation( {
'type': 'link',
'attributes': {
'href': target
}
} );
};
/* Registration */

View file

@ -47,7 +47,12 @@ ve.ui.MWLinkInspector.prototype.getAnnotationFromTarget = function ( target ) {
// Figure out if this is an internal or external link
if ( ve.init.platform.getExternalLinkUrlProtocolsRegExp().test( target ) ) {
// External link
return new ve.dm.MWExternalLinkAnnotation( { 'href': target } );
return new ve.dm.MWExternalLinkAnnotation( {
'type': 'link/MWexternal',
'attributes': {
'href': target
}
} );
} else {
// Internal link
// TODO: In the longer term we'll want to have autocompletion and existence and validity
@ -61,7 +66,12 @@ ve.ui.MWLinkInspector.prototype.getAnnotationFromTarget = function ( target ) {
target = ':' + target;
}
} catch ( e ) { }
return new ve.dm.MWInternalLinkAnnotation( { 'title': target } );
return new ve.dm.MWInternalLinkAnnotation( {
'type': 'link/MWinternal',
'attributes': {
'title': target
}
} );
}
};

View file

@ -45,7 +45,12 @@ ve.ui.LinkTargetInputWidget.prototype.setValue = function ( value ) {
if ( value === '' ) {
this.annotation = null;
} else {
this.setAnnotation( new ve.dm.LinkAnnotation( { 'href': value } ) );
this.setAnnotation( new ve.dm.LinkAnnotation( {
'type': 'link',
'attributes': {
'href': value
}
} ) );
}
// Call parent method
@ -92,7 +97,7 @@ ve.ui.LinkTargetInputWidget.prototype.getAnnotation = function () {
*/
ve.ui.LinkTargetInputWidget.prototype.getTargetFromAnnotation = function ( annotation ) {
if ( annotation instanceof ve.dm.LinkAnnotation ) {
return annotation.data.href;
return annotation.getAttribute( 'href' );
}
return '';
};

View file

@ -257,7 +257,12 @@ ve.ui.MWLinkTargetInputWidget.prototype.getInternalLinkAnnotationFromTitle = fun
target = ':' + target;
}
} catch ( e ) { }
return new ve.dm.MWInternalLinkAnnotation( { 'title': target } );
return new ve.dm.MWInternalLinkAnnotation( {
'type': 'link/MWinternal',
'attributes': {
'title': target
}
} );
};
/**
@ -268,7 +273,12 @@ ve.ui.MWLinkTargetInputWidget.prototype.getInternalLinkAnnotationFromTitle = fun
* @returns {ve.dm.MWExternalLinkAnnotation}
*/
ve.ui.MWLinkTargetInputWidget.prototype.getExternalLinkAnnotationFromUrl = function ( target ) {
return new ve.dm.MWExternalLinkAnnotation( { 'href': target } );
return new ve.dm.MWExternalLinkAnnotation( {
'type': 'link/MWexternal',
'attributes': {
'href': target
}
} );
};
/**
@ -280,9 +290,9 @@ ve.ui.MWLinkTargetInputWidget.prototype.getExternalLinkAnnotationFromUrl = funct
*/
ve.ui.MWLinkTargetInputWidget.prototype.getTargetFromAnnotation = function ( annotation ) {
if ( annotation instanceof ve.dm.MWExternalLinkAnnotation ) {
return annotation.data.href;
return annotation.getAttribute( 'href' );
} else if ( annotation instanceof ve.dm.MWInternalLinkAnnotation ) {
return annotation.data.title;
return annotation.getAttribute( 'title' );
}
return '';
};