Merge branch 'dmrewrite' of ssh://review/mediawiki/extensions/VisualEditor into dmrewrite

This commit is contained in:
Inez Korczynski 2012-06-01 13:51:24 -07:00
commit 8a35e6eafe
39 changed files with 874 additions and 164 deletions

View file

@ -33,10 +33,9 @@ class VisualEditorHooks {
return true;
}
/**
* Allow edits to the namespace only by admins
* Code used from Extension:NamespaceProtection
*
*/
public static function canUserEditPage( &$title, &$user, $action, &$result ){
public static function namespaceProtection( &$title, &$user, $action, &$result ){
global $wgUser, $wgNamespaceProtection;
if ( array_key_exists( $title->mNamespace, $wgNamespaceProtection ) ) {

View file

@ -97,10 +97,10 @@ $wgResourceModules += array(
// ve
'jquery/jquery.json.js',
've2/ve.js',
've2/ve.NodeFactory.js',
've2/ve.EventEmitter.js',
've2/ve.Factory.js',
've2/ve.Position.js',
've2/ve.Range.js',
've2/ve.EventEmitter.js',
've2/ve.Node.js',
've2/ve.BranchNode.js',
've2/ve.LeafNode.js',
@ -110,14 +110,17 @@ $wgResourceModules += array(
// dm
've2/dm/ve.dm.js',
've2/dm/ve.dm.NodeFactory.js',
've2/dm/ve.dm.AnnotationFactory.js',
've2/dm/ve.dm.Node.js',
've2/dm/ve.dm.BranchNode.js',
've2/dm/ve.dm.LeafNode.js',
've2/dm/ve.dm.Annotation.js',
've2/dm/ve.dm.TransactionProcessor.js',
've2/dm/ve.dm.Transaction.js',
've2/dm/ve.dm.Surface.js',
've2/dm/ve.dm.Document.js',
've2/dm/ve.dm.DocumentSynchronizer.js',
've2/dm/ve.dm.Converter.js',
've2/dm/ve.dm.HTMLConverter.js',
've2/dm/nodes/ve.dm.AlienInlineNode.js',
@ -136,6 +139,9 @@ $wgResourceModules += array(
've2/dm/nodes/ve.dm.TableRowNode.js',
've2/dm/nodes/ve.dm.TextNode.js',
've2/dm/annotations/ve.dm.LinkAnnotation.js',
've2/dm/annotations/ve.dm.TextStyleAnnotation.js',
've/dm/serializers/ve.dm.AnnotationSerializer.js',
've/dm/serializers/ve.dm.HtmlSerializer.js',
've/dm/serializers/ve.dm.JsonSerializer.js',
@ -236,7 +242,7 @@ $wgAPIModules['ve-parsoid'] = 'ApiVisualEditor';
// Integration Hooks
$wgAutoloadClasses['VisualEditorHooks'] = $dir . 'VisualEditor.hooks.php';
$wgHooks['BeforePageDisplay'][] = 'VisualEditorHooks::onPageDisplay';
$wgHooks['userCan'][] = 'VisualEditorHooks::canUserEditPage';
$wgHooks['userCan'][] = 'VisualEditorHooks::namespaceProtection';
// API for retrieving wikidom parse results
$wgAutoloadClasses['ApiQueryParseTree'] = $dir . 'api/ApiQueryParseTree.php';

View file

@ -89,10 +89,10 @@ include( '../../modules/sandbox/base.php' );
<script src="../../modules/jquery/jquery.js"></script>
<script src="../../modules/jquery/jquery.json.js"></script>
<script src="../../modules/ve2/ve.js"></script>
<script src="../../modules/ve2/ve.NodeFactory.js"></script>
<script src="../../modules/ve2/ve.EventEmitter.js"></script>
<script src="../../modules/ve2/ve.Factory.js"></script>
<script src="../../modules/ve2/ve.Position.js"></script>
<script src="../../modules/ve2/ve.Range.js"></script>
<script src="../../modules/ve2/ve.EventEmitter.js"></script>
<script src="../../modules/ve2/ve.Node.js"></script>
<script src="../../modules/ve2/ve.BranchNode.js"></script>
<script src="../../modules/ve2/ve.LeafNode.js"></script>
@ -102,9 +102,11 @@ include( '../../modules/sandbox/base.php' );
<!-- dm -->
<script src="../../modules/ve2/dm/ve.dm.js"></script>
<script src="../../modules/ve2/dm/ve.dm.NodeFactory.js"></script>
<script src="../../modules/ve2/dm/ve.dm.AnnotationFactory.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Node.js"></script>
<script src="../../modules/ve2/dm/ve.dm.BranchNode.js"></script>
<script src="../../modules/ve2/dm/ve.dm.LeafNode.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Annotation.js"></script>
<script src="../../modules/ve2/dm/ve.dm.TransactionProcessor.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Transaction.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Surface.js"></script>
@ -112,6 +114,7 @@ include( '../../modules/sandbox/base.php' );
<script src="../../modules/ve2/dm/ve.dm.DocumentSynchronizer.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Transaction.js"></script>
<script src="../../modules/ve2/dm/ve.dm.TransactionProcessor.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Converter.js"></script>
<script src="../../modules/ve2/dm/ve.dm.HTMLConverter.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.AlienInlineNode.js"></script>
@ -130,6 +133,9 @@ include( '../../modules/sandbox/base.php' );
<script src="../../modules/ve2/dm/nodes/ve.dm.TableRowNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.TextNode.js"></script>
<script src="../../modules/ve2/dm/annotations/ve.dm.LinkAnnotation.js"></script>
<script src="../../modules/ve2/dm/annotations/ve.dm.TextStyleAnnotation.js"></script>
<script src="../../modules/ve/dm/serializers/ve.dm.AnnotationSerializer.js"></script>
<script src="../../modules/ve/dm/serializers/ve.dm.HtmlSerializer.js"></script>
<script src="../../modules/ve/dm/serializers/ve.dm.JsonSerializer.js"></script>

View file

@ -15,13 +15,13 @@
padding: 0.25em;
height: 22px;
margin-right: 0.125em;
border: solid 1px transparent;
}
.ve-action-button:before {
content: " ";
position: absolute;
display: block;
height: 22px;
width: 22px;
}
.ve-action-button:hover {
border-color: #eeeeee;
@ -29,9 +29,101 @@
.ve-action-button:active,
.ve-action-button-down {
border-color: #dddddd;
background-position: top left;
background-repeat: repeat-x;
-webkit-box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07);
-moz-box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07);
box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07);
}
/* Save dialog styles */
#ve-saveDialog {
top: 0px;
right: 2.5em;
width: 35em;
}
#ve-saveDialog > .ve-dialog-divider {
border-top:1px solid #dddddd;
padding: 10px 0;
font-size: 12px;
}
#ve-saveDialog br {
clear: both;
}
#ve-saveDialog > .ve-dialog-divider > .ve-dialog-left {
float: left;
}
#ve-saveDialog > .ve-dialog-divider > input[type='text'] {
width: 96%;
font-size: 12px;
padding: 4px;
margin-bottom: 10px;
}
#ve-saveDialog > .ve-dialog-divider > input[type='checkbox'] {
margin-right: 5px;
}
.ve-closeBtn {
width: 22px;
background-image: url(../ve2/ui/styles/images/close.png);
background-position: center center;
background-repeat: no-repeat;
}
/* mini save button */
.ve-saveBtn {
right: .025em;
width: 22px;
background-image: url(../ve2/ui/styles/images/close.png);
background-position: center center;
background-repeat: no-repeat;
}
/* inspector styles */
.es-inspector-savebutton {
padding-right: 24px;
border:1px solid transparent;
border-radius: 0.125em;
-webkit-border-radius: 0.125em;
-moz-border-radius: 0.125em;
-o-border-radius: 0.125em;
/* need new button */
/* @embed */
background-image: url(../ve2/ui/styles/images/save.png);
background-position: center right;
background-repeat: no-repeat;
}
.doSaveBtn {
position: absolute;
border: 1px solid rgb(196,229,154);
margin-top: 10px;
right: 10px;
font-size: 12px;
padding: 5px 10px;
background-image: url(../ve2/ui/styles/images/close.png);
background-position: center right;
background-repeat: no-repeat;
/* Fancy CSS background */
background-image: linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
background-image: -o-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
background-image: -moz-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
background-image: -webkit-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
background-image: -ms-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, rgb(195,229,154)),
color-stop(1, rgb(240,251,225))
);
}
.doSaveBtn > div.doSaveBtnIcon {
height:2em;
width:2em;
float:right;
background: transparent;
background-image: url(../ve2/ui/styles/images/accept.png);
background-position: center right;
background-repeat: no-repeat;
}

View file

@ -8,19 +8,10 @@
pageName = mw.config.get( 'wgPageName' ),
validNamespace = mw.config.get('wgCanonicalNamespace') === 'VisualEditor' ? true: false;
this.$content = $('#content');
// Store content padding so that it can be restored.
this.contentPadding = this.$content.css('padding');
this.mainEditor = null;
this.$spinner = $('<div />')
.attr({
'id': 've-loader-spinner',
'class': 'mw-ajax-loader'
}).css({
'height': this.$content.height() + 'px',
'width': (this.$content.width() -20 ) + 'px'
});
this.$content = $('#content');
// modify / stash content styles
this.prepareContentStyles();
// On VisualEditor namespace ?
if ( validNamespace ) {
@ -80,39 +71,39 @@
}
}
};
$editor = $('<div id="ve-editor"></div>');
this.$editor = $('<div id="ve-editor"></div>');
this.$spinner.hide();
this.$content
.css('padding', '0px 0px 0px 1px')
.append( $editor );
this.$content.css({
'padding':'0px 0px 0px 1px'
}).append( this.$editor );
this.mainEditor = new ve.Surface( '#ve-editor', $html[0], options );
$editor.find('.es-panes')
this.$editor.find('.es-panes')
.css('padding', this.contentPadding );
// Save BTN
$editor.find('.es-modes')
this.$editor.find('.es-modes')
.append(
$('<div />')
.attr('class', 've-action-button')
.attr('class', 've-action-button es-inspector-savebutton')
.text('Save')
.click(function(){
// show save dialog
_this.save();
_this.showSaveDialog();
// show/hide dialog
_this.$dialog.toggle();
})
).append(
$('<div />')
.attr('class', 've-action-button')
.text('X')
.attr('class', 've-action-button ve-closeBtn')
.click(function(){
// back to read mode
_this.cleanup();
_this.mainEditor = null;
})
);
this.initSaveDialog();
}
};
@ -177,9 +168,9 @@
var _this = this;
_this.showSpinner();
// Save
_this.getParsoidWikitextAndSave( function( content ){
// cleanup
_this.$dialog.toggle();
_this.cleanup();
// load saved page
_this.$content
@ -187,13 +178,107 @@
});
};
veCore.prototype.showSaveDialog = function(){
veCore.prototype.initSaveDialog = function(){
var _this = this;
this.$dialog =
$('<div />')
.attr({
'id': 've-saveDialog',
'class': 'es-inspector'
}).append(
$('<div />')
.attr('class', 'es-inspector-title')
.text('Save your changes')
).append(
$('<div />')
.attr('class', 'es-inspector-button ve-saveBtn')
.click(function(){
_this.$dialog.toggle();
})
).append(
$('<div />')
.attr('class', 've-dialog-divider')
.append(
$('<div />')
.text("Describe what you changed")
).append(
$('<input />')
.attr({
'type':'text'
})
).append(
$('<br />')
).append(
$('<div />')
.attr('class', 've-dialog-left')
.append(
$('<input />')
.attr({
'type': 'checkbox',
'name': 'chkMinorEdit',
'id': 'chkMinorEdit'
})
).append(
$('<label />')
.attr('for', 'chkMinorEdit')
// i18n
.html('This is a <a href="/wiki/Minor_edit">minor edit</a>')
).append(
$('<br />')
).append(
$('<input />')
.attr({
'type': 'checkbox',
'name': 'chkWatchlist',
'id': 'chkWatchlist'
})
).append(
$('<label />')
.attr('for', 'chkWatchlist')
// i18n
.text('Watch this page')
)
).append(
$('<div />')
.attr('class', 've-action-button es-inspector-savebutton doSaveBtn')
.text('Save page')
.click(function(){
_this.save();
})
.append(
$('<div />')
.attr('class', 'doSaveBtnIcon')
)
).append(
$('<br />')
)
).append(
$('<div />')
.attr('class', 've-dialog-divider')
.append(
$("<p />")
// TODO: Complete text and i18n
.text('By editing this page, yadda yadda yadda')
)
);
this.$editor
.find('.es-inspector-savebutton')
.after ( this.$dialog );
};
veCore.prototype.showSpinner = function(){
var _this = this;
//remove it
this.$spinner = $('<div />')
.attr({
'id': 've-loader-spinner',
'class': 'mw-ajax-loader'
}).css({
'height': this.$content.height() + 'px',
'width': (this.$content.width() -20 ) + 'px'
});
_this.$spinner.remove();
//hide all of the #content element children
_this.$content
@ -206,8 +291,35 @@
);
};
/* Make a backup of the #content div styles
In order to tuck the toolbar close under the tabs,
We need to change the padding before inserting the toolbar
Additionally, the transitions must be removed so that
when adjusting the padding, there is no animated transition.
*/
veCore.prototype.prepareContentStyles = function(){
// Store Padding and transitions
this.contentPadding = this.$content.css('padding');
this.contentTransition = {
'transition': this.$content.css('transition'),
'transition-property': this.$content.css('transition-property'),
'-moz-transition': this.$content.css('-moz-transition'),
'-webkit-transition': this.$content.css('-webkit-transition'),
'-o-transition': this.$content.css('-o-transition')
};
// Squash transitions
this.$content.css({
'transition': 'none',
'transition-property': 'none',
'-moz-transition': 'none',
'-webkit-transition': 'none',
'-o-transition': 'color 0 ease-in'
});
};
//back to view
veCore.prototype.cleanup = function(){
this.mainEditor = null;
this.selectTab( 'ca-view' );
this.$content
.find('#mw-content-text, #bodyContent, #firstHeading').show()

View file

@ -56,12 +56,6 @@ ve.ce.TextNode.htmlCharacters = {
* @member
*/
ve.ce.TextNode.annotationRenderers = {
'object/hook': {
'open': function( data ) {
return '<span class="ve-ce-content-format-object">' + data.html;
},
'close': '</span>'
},
'textStyle/bold': {
'open': '<b>',
'close': '</b>'
@ -138,8 +132,14 @@ ve.ce.TextNode.prototype.getHtml = function() {
htmlChars = ve.ce.TextNode.htmlCharacters,
renderers = ve.ce.TextNode.annotationRenderers,
out = '',
i,
j,
hash,
left = '',
right,
open,
close,
index,
leftPlain,
rightPlain,
hashStack = [],
@ -151,9 +151,9 @@ ve.ce.TextNode.prototype.getHtml = function() {
for ( var hash in annotations ) {
annotation = annotations[hash];
out += typeof renderers[annotation.type].open === 'function'
? renderers[annotation.type].open( annotation.data )
: renderers[annotation.type].open;
out += typeof renderers[annotation.type].open === 'function' ?
renderers[annotation.type].open( annotation.data ) :
renderers[annotation.type].open;
hashStack.push( hash );
annotationStack[hash] = annotation;
}
@ -166,9 +166,9 @@ ve.ce.TextNode.prototype.getHtml = function() {
for ( var hash in annotations ) {
annotation = annotations[hash];
out += typeof renderers[annotation.type].close === 'function'
? renderers[annotation.type].close( annotation.data )
: renderers[annotation.type].close;
out += typeof renderers[annotation.type].close === 'function' ?
renderers[annotation.type].close( annotation.data ) :
renderers[annotation.type].close;
// new version
hashStack.pop();
@ -187,18 +187,18 @@ ve.ce.TextNode.prototype.getHtml = function() {
return out;
};
for ( var i = 0; i < data.length; i++ ) {
for ( i = 0; i < data.length; i++ ) {
right = data[i];
leftPlain = typeof left === 'string';
rightPlain = typeof right === 'string';
if ( !leftPlain && rightPlain ) {
// [formatted][plain]
var close = {};
for ( var j = hashStack.length - 1; j >= 0; j-- ) {
close = {};
for ( j = hashStack.length - 1; j >= 0; j-- ) {
close[hashStack[j]] = annotationStack[hashStack[j]];
}
out += closeAnnotations( close );
out += closeAnnotations( close );
} else if ( leftPlain && !rightPlain ) {
// [plain][formatted]
out += openAnnotations( right[1] );
@ -207,24 +207,24 @@ ve.ce.TextNode.prototype.getHtml = function() {
// setting index to undefined is is necessary to it does not use value from
// the previous iteration
var open = {},
index = undefined;
open = {},
index = undefined;
for ( var hash in left[1] ) {
for ( hash in left[1] ) {
if ( !( hash in right[1] ) ) {
index = ( index === undefined )
? hashStack.indexOf( hash )
: Math.min( index, hashStack.indexOf( hash ) );
index = ( index === undefined ) ?
hashStack.indexOf( hash ) :
Math.min( index, hashStack.indexOf( hash ) );
}
}
if ( index !== undefined ) {
var close = {};
for ( var j = hashStack.length - 1; j >= index; j-- ) {
close = {};
for ( j = hashStack.length - 1; j >= index; j-- ) {
close[hashStack[j]] = annotationStack[hashStack[j]];
}
for ( var j = index; j < hashStack.length; j++ ) {
for ( j = index; j < hashStack.length; j++ ) {
if ( hashStack[j] in right[1] && hashStack[j] in left[1] ) {
open[hashStack[j]] = annotationStack[hashStack[j]];
}
@ -232,7 +232,7 @@ ve.ce.TextNode.prototype.getHtml = function() {
out += closeAnnotations( close );
}
for ( var hash in right[1] ) {
for ( hash in right[1] ) {
if ( !( hash in left[1] ) ) {
open[hash] = right[1][hash];
}
@ -246,11 +246,11 @@ ve.ce.TextNode.prototype.getHtml = function() {
left = right;
}
var close = {};
for ( var j = hashStack.length - 1; j >= 0; j-- ) {
close = {};
for ( j = hashStack.length - 1; j >= 0; j-- ) {
close[hashStack[j]] = annotationStack[hashStack[j]];
}
out += closeAnnotations( close );
out += closeAnnotations( close );
return out;
};
@ -261,4 +261,4 @@ ve.ce.nodeFactory.register( 'text', ve.ce.TextNode );
/* Inheritance */
ve.extendClass( ve.ce.TextNode, ve.ce.LeafNode );
ve.extendClass( ve.ce.TextNode, ve.ce.LeafNode );

View file

@ -2,12 +2,12 @@
* ContentEditable node factory.
*
* @class
* @extends {ve.NodeFactory}
* @extends {ve.Factory}
* @constructor
*/
ve.ce.NodeFactory = function() {
// Inheritance
ve.NodeFactory.call( this );
ve.Factory.call( this );
};
/* Methods */
@ -28,7 +28,7 @@ ve.ce.NodeFactory.prototype.canNodeBeSplit = function( type ) {
/* Inheritance */
ve.extendClass( ve.ce.NodeFactory, ve.NodeFactory );
ve.extendClass( ve.ce.NodeFactory, ve.Factory );
/* Initialization */

View file

@ -24,7 +24,7 @@ ve.ce.Surface = function( $container, model ) {
'mouseup': this.proxy( this.onMouseUp ),
'mousemove': this.proxy( this.onMouseMove ),
'cut copy': this.proxy( this.onCutCopy ),
'beforepaste paste': this.proxy( this.onPaste ),
'beforepaste paste': this.proxy( this.onPaste )
} );
// Initialization
@ -51,12 +51,19 @@ ve.ce.Surface.prototype.onKeyDown = function( e ) {
var offset = this.getOffset( rangySel.anchorNode, rangySel.anchorOffset );
console.log( 'onKeyDown', 'offset', offset );
var relativeContentOffset,
relativeStructuralOffset,
relativeStructuralOffsetNode,
hasSlug;
switch ( e.which ) {
case 37: // left arrow key
var relativeContentOffset = this.documentView.model.getRelativeContentOffset( offset, -1 );
var relativeStructuralOffset = this.documentView.model.getRelativeStructuralOffset( offset - 1, -1, true );
var relativeStructuralOffsetNode = this.documentView.documentNode.getNodeFromOffset( relativeStructuralOffset );
var hasSlug = relativeStructuralOffsetNode.hasSlugAtOffset( relativeStructuralOffset );
relativeContentOffset = this.documentView.model.getRelativeContentOffset( offset, -1 );
relativeStructuralOffset =
this.documentView.model.getRelativeStructuralOffset( offset - 1, -1, true );
relativeStructuralOffsetNode =
this.documentView.documentNode.getNodeFromOffset( relativeStructuralOffset );
hasSlug = relativeStructuralOffsetNode.hasSlugAtOffset( relativeStructuralOffset );
if ( hasSlug ) {
if ( relativeContentOffset > offset ) {
this.showCursor( relativeStructuralOffset );
@ -67,12 +74,13 @@ ve.ce.Surface.prototype.onKeyDown = function( e ) {
this.showCursor( relativeContentOffset );
}
return false;
break;
case 39: // right arrow key
var relativeContentOffset = this.documentView.model.getRelativeContentOffset( offset, 1 );
var relativeStructuralOffset = this.documentView.model.getRelativeStructuralOffset( offset + 1, 1, true );
var relativeStructuralOffsetNode = this.documentView.documentNode.getNodeFromOffset( relativeStructuralOffset );
var hasSlug = relativeStructuralOffsetNode.hasSlugAtOffset( relativeStructuralOffset );
relativeContentOffset = this.documentView.model.getRelativeContentOffset( offset, 1 );
relativeStructuralOffset =
this.documentView.model.getRelativeStructuralOffset( offset + 1, 1, true );
relativeStructuralOffsetNode =
this.documentView.documentNode.getNodeFromOffset( relativeStructuralOffset );
hasSlug = relativeStructuralOffsetNode.hasSlugAtOffset( relativeStructuralOffset );
if ( hasSlug ) {
if ( relativeContentOffset < offset ) {
this.showCursor( relativeStructuralOffset );
@ -83,7 +91,6 @@ ve.ce.Surface.prototype.onKeyDown = function( e ) {
this.showCursor( relativeContentOffset );
}
return false;
break;
}
};
@ -108,13 +115,16 @@ ve.ce.Surface.prototype.onMouseDown = function( e ) {
var closestLeafOffset = closestLeafModel.getRoot().getOffsetFromNode( closestLeafModel );
console.log( 'onMouseDown', 'closestLeafOffset', closestLeafOffset );
var nearestContentOffset = this.documentView.model.getNearestContentOffset( closestLeafOffset, -1 );
var nearestContentOffset =
this.documentView.model.getNearestContentOffset( closestLeafOffset, -1 );
console.log( 'onMouseDown', 'nearestContentOffset', nearestContentOffset );
var nearestStructuralOffset = this.documentView.model.getNearestStructuralOffset( closestLeafOffset, 0, true );
var nearestStructuralOffset =
this.documentView.model.getNearestStructuralOffset( closestLeafOffset, 0, true );
console.log( 'onMouseDown', 'nearestStructuralOffset', nearestStructuralOffset );
var nearestStructuralOffsetNode = this.documentView.documentNode.getNodeFromOffset( nearestStructuralOffset );
var nearestStructuralOffsetNode =
this.documentView.documentNode.getNodeFromOffset( nearestStructuralOffset );
console.log( 'onMouseDown', 'nearestStructuralOffsetNode', nearestStructuralOffsetNode );
var hasSlug = nearestStructuralOffsetNode.hasSlugAtOffset( nearestStructuralOffset );
@ -193,7 +203,9 @@ ve.ce.Surface.prototype.onPaste = function( e ) {
pasteData = ( _this.clipboard[key] ) ? _this.clipboard[key] : pasteString.split('');
// transact
var tx = ve.dm.Transaction.newFromInsertion( _this.documentView.model, insertionPoint, pasteData );
var tx = ve.dm.Transaction.newFromInsertion(
_this.documentView.model, insertionPoint, pasteData
);
ve.dm.TransactionProcessor.commit( _this.documentView.model, tx );
// place cursor
@ -280,14 +292,19 @@ ve.ce.Surface.prototype.getOffset = function( DOMnode, DOMoffset ) {
/**
* Based on a given offset returns DOM node and offset that can be used to place a cursor (with rangy)
* Based on a given offset returns DOM node and offset that can be used to place a cursor.
*
* The results of this function are meant to be used with rangy.
*
* @method
* @param offset {Integer} Linear model offset
* @returns {Object} Object containing a node and offset property where node is an HTML element and
* offset is the position within the element
*/
ve.ce.Surface.prototype.getNodeAndOffset = function( offset ) {
var node = this.documentView.getNodeFromOffset( offset ),
startOffset = this.documentView.getDocumentNode().getOffsetFromNode( node ) + ( ( node.isWrapped() ) ? 1 : 0 ),
startOffset = this.documentView.getDocumentNode().getOffsetFromNode( node ) +
( ( node.isWrapped() ) ? 1 : 0 ),
current = [node.$.contents(), 0],
stack = [current],
item,

View file

@ -0,0 +1,42 @@
/**
* DataModel annotation for a link.
*
* @class
* @constructor
* @extends {ve.dm.Annotation}
*/
ve.dm.LinkAnnotation = function() {
// Inheritance
ve.dm.Annotation.call( this );
};
/* Static Members */
/**
* Converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.LinkAnnotation.converters = {
'tags': 'a',
'toHtml': function( subType, annotation ) {
return annotation.type &&
ve.dm.createHtmlElement( 'a', { 'data-type': 'link/' + subType } );
},
'toData': function( tag, element ) {
// FIXME: the parser currently doesn't output this data this way
// Internal links get 'linkType': 'internal' in the data-mw-rt attrib, while external
// links currently get nothing
return { 'type': 'link/' + ( element.getAttribute( 'data-type' ) || 'unknown' ) };
}
};
/* Registration */
ve.dm.annotationFactory.register( 'link', ve.dm.LinkAnnotation );
/* Inheritance */
ve.extendClass( ve.dm.LinkAnnotation, ve.dm.Annotation );

View file

@ -0,0 +1,58 @@
/**
* DataModel annotation for a text style.
*
* @class
* @constructor
* @extends {ve.dm.Annotation}
*/
ve.dm.TextStyleAnnotation = function() {
// Inheritance
ve.dm.Annotation.call( this );
};
/* Static Members */
/**
* Converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.TextStyleAnnotation.converters = {
'tags': ['i', 'b', 'u', 's', 'small', 'big', 'span'],
'toHtml': function( subType, annotation ) {
return annotation.type && ve.dm.createHtmlElement( ( {
'italic': 'i',
'bold': 'b',
'underline': 'u',
'strike': 's',
'small': 'small',
'big': 'big',
'span': 'span'
// TODO: Add other supported HTML inline elements to this list
} )[subType] );
},
'toData': function( tag, element ) {
return {
'type': 'textStyle/' + ( {
'i': 'italic',
'b': 'bold',
'u': 'underline',
's': 'strike',
'small': 'small',
'big': 'big',
'span': 'span'
// TODO: Add other supported HTML inline elements to this list
} )[tag]
};
}
};
/* Registration */
ve.dm.annotationFactory.register( 'textStyle', ve.dm.TextStyleAnnotation );
/* Inheritance */
ve.extendClass( ve.dm.TextStyleAnnotation, ve.dm.Annotation );

View file

@ -29,6 +29,9 @@ ve.dm.AlienBlock.rules = {
'parentNodeTypes': null
};
// This is a special node, no converter registration is required
ve.dm.AlienBlock.converters = null;
/* Registration */
ve.dm.nodeFactory.register( 'alienBlock', ve.dm.AlienBlock );

View file

@ -29,6 +29,9 @@ ve.dm.AlienInline.rules = {
'parentNodeTypes': null
};
// This is a special node, no converter registration is required
ve.dm.AlienInline.converters = null;
/* Registration */
ve.dm.nodeFactory.register( 'alienInline', ve.dm.AlienInline );

View file

@ -29,6 +29,23 @@ ve.dm.DefinitionListItemNode.rules = {
'parentNodeTypes': ['definitionList']
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.DefinitionListItemNode.converters = {
'tags': 'dl',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'dl' );
},
'toData': function( tag, element ) {
return { 'type': 'definitionList' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'definitionListItem', ve.dm.DefinitionListItemNode );

View file

@ -29,6 +29,29 @@ ve.dm.DefinitionListNode.rules = {
'parentNodeTypes': null
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.DefinitionListNode.converters = {
'tags': ['dt', 'dd'],
'toHtml': function( type, element ) {
return element.attributes && ( {
'term': ve.dm.createHtmlElement( 'dt' ),
'definition': ve.dm.createHtmlElement( 'dd' )
} )[element.attributes['style']];
},
'toData': function( tag, element ) {
return ( {
'dt': { 'type': 'definitionList', 'attributes': { 'style': 'term' } },
'dd': { 'type': 'definitionList', 'attributes': { 'style': 'definition' } }
} )[tag];
}
};
/* Registration */
ve.dm.nodeFactory.register( 'definitionList', ve.dm.DefinitionListNode );

View file

@ -29,10 +29,15 @@ ve.dm.DocumentNode.rules = {
'parentNodeTypes': []
};
// This is a special node, no converter registration is required
ve.dm.DocumentNode.converters = null;
/* Registration */
ve.dm.nodeFactory.register( 'document', ve.dm.DocumentNode );
// This is a special node, no converter registration is required
/* Inheritance */
ve.extendClass( ve.dm.DocumentNode, ve.dm.BranchNode );

View file

@ -29,6 +29,37 @@ ve.dm.HeadingNode.rules = {
'parentNodeTypes': null
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.HeadingNode.converters = {
'tags': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
'toHtml': function( type, element ) {
return element.attributes && ( {
1: ve.dm.createHtmlElement( 'h1' ),
2: ve.dm.createHtmlElement( 'h2' ),
3: ve.dm.createHtmlElement( 'h3' ),
4: ve.dm.createHtmlElement( 'h4' ),
5: ve.dm.createHtmlElement( 'h5' ),
6: ve.dm.createHtmlElement( 'h6' )
} )[element.attributes['level']];
},
'toData': function( tag, element ) {
return ( {
'h1': { 'type': 'heading', 'attributes': { 'level': 1 } },
'h2': { 'type': 'heading', 'attributes': { 'level': 2 } },
'h3': { 'type': 'heading', 'attributes': { 'level': 3 } },
'h4': { 'type': 'heading', 'attributes': { 'level': 4 } },
'h5': { 'type': 'heading', 'attributes': { 'level': 5 } },
'h6': { 'type': 'heading', 'attributes': { 'level': 6 } }
} )[tag];
}
};
/* Registration */
ve.dm.nodeFactory.register( 'heading', ve.dm.HeadingNode );

View file

@ -29,6 +29,23 @@ ve.dm.ImageNode.rules = {
'parentNodeTypes': null
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.ImageNode.converters = {
'tags': 'img',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'img' );
},
'toData': function( tag, element ) {
return { 'type': 'image' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'image', ve.dm.ImageNode );

View file

@ -29,6 +29,23 @@ ve.dm.ListItemNode.rules = {
'parentNodeTypes': ['list']
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.ListItemNode.converters = {
'tags': 'li',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'li' );
},
'toData': function( tag, element ) {
return { 'type': 'listItem' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'listItem', ve.dm.ListItemNode );

View file

@ -29,6 +29,29 @@ ve.dm.ListNode.rules = {
'parentNodeTypes': null
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.ListNode.converters = {
'tags': ['ul', 'ol'],
'toHtml': function( type, element ) {
return element.attributes && ( {
'bullet': ve.dm.createHtmlElement( 'ul' ),
'number': ve.dm.createHtmlElement( 'ol' )
} )[element.attributes['style']];
},
'toData': function( tag, element ) {
return ( {
'ul': { 'type': 'list', 'attributes': { 'style': 'bullet' } },
'ol': { 'type': 'list', 'attributes': { 'style': 'number' } }
} )[tag];
}
};
/* Registration */
ve.dm.nodeFactory.register( 'list', ve.dm.ListNode );

View file

@ -29,6 +29,23 @@ ve.dm.ParagraphNode.rules = {
'parentNodeTypes': null
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.ParagraphNode.converters = {
'tags': 'p',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'p' );
},
'toData': function( tag, element ) {
return { 'type': 'paragraph' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'paragraph', ve.dm.ParagraphNode );

View file

@ -29,6 +29,23 @@ ve.dm.PreformattedNode.rules = {
'parentNodeTypes': null
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.PreformattedNode.converters = {
'tags': 'pre',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'pre' );
},
'toData': function( tag, element ) {
return { 'type': 'preformatted' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'preformatted', ve.dm.PreformattedNode );

View file

@ -29,6 +29,23 @@ ve.dm.TableCellNode.rules = {
'parentNodeTypes': ['tableRow']
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.TableCellNode.converters = {
'tags': 'td',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'td' );
},
'toData': function( tag, element ) {
return { 'type': 'tableCell' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'tableCell', ve.dm.TableCellNode );

View file

@ -29,6 +29,23 @@ ve.dm.TableNode.rules = {
'parentNodeTypes': null
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.TableNode.converters = {
'tags': 'table',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'table' );
},
'toData': function( tag, element ) {
return { 'type': 'table' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'table', ve.dm.TableNode );

View file

@ -29,6 +29,23 @@ ve.dm.TableRowNode.rules = {
'parentNodeTypes': ['table']
};
/**
* Node converters.
*
* @see {ve.dm.Converter}
* @static
* @member
*/
ve.dm.TableRowNode.converters = {
'tags': 'tr',
'toHtml': function( type, element ) {
return ve.dm.createHtmlElement( 'tr' );
},
'toData': function( tag, element ) {
return { 'type': 'tableRow' };
}
};
/* Registration */
ve.dm.nodeFactory.register( 'tableRow', ve.dm.TableRowNode );

View file

@ -28,6 +28,9 @@ ve.dm.TextNode.rules = {
'parentNodeTypes': null
};
// This is a special node, no converter registration is required
ve.dm.TextNode.converters = null;
/* Registration */
ve.dm.nodeFactory.register( 'text', ve.dm.TextNode );

View file

@ -0,0 +1,8 @@
/**
* Generic DataModel annotation.
*
* @class
* @constructor
*/
ve.dm.Annotation = function() {
};

View file

@ -0,0 +1,19 @@
/**
* DataModel annotation factory.
*
* @class
* @extends {ve.Factory}
* @constructor
*/
ve.dm.AnnotationFactory = function() {
// Inheritance
ve.Factory.call( this );
};
/* Inheritance */
ve.extendClass( ve.dm.AnnotationFactory, ve.Factory );
/* Initialization */
ve.dm.annotationFactory = new ve.dm.AnnotationFactory();

View file

@ -0,0 +1,83 @@
/**
* Converter between HTML DOM and VisualEditor linear data.
*
* @class
* @constructor
* @param {Object} options Conversion options
*/
ve.dm.Converter = function( nodeFactory, annotationFactory ) {
// Properties
this.nodeFactory = nodeFactory;
this.annotationFactory = annotationFactory;
this.elements = { 'toHtml': {}, 'toData': {}, 'types': {} };
this.annotations = { 'toHtml': {}, 'toData': {} };
// Events
this.nodeFactory.addListenerMethod( this, 'register', 'onNodeRegister' );
this.annotationFactory.addListenerMethod( this, 'register', 'onAnnotationRegister' );
};
/* Methods */
/**
* Responds to register events from the node factory.
*
* If a node is special; such as document, alienInline, alienBlock and text; it's converters data
* should be set to null, as to distinguish it from a new node type that someone has simply
* forgotten to implement converters for.
*
* @method
* @param {String} type Node type
* @param {Function} constructor Node constructor
* @throws 'Missing conversion data in node implementation of {type}'
*/
ve.dm.Converter.prototype.onNodeRegister = function( type, constructor ) {
if ( constructor.converters === undefined ) {
throw 'Missing conversion data in node implementation of ' + type;
} else if ( constructor.converters !== null ) {
var tags = constructor.converters.tags,
toHtml = constructor.converters.toHtml,
toData = constructor.converters.toData;
// Convert tags to an array if needed
if ( !ve.isArray( tags ) ) {
tags = [tags];
}
// Registration
this.elements.toHtml[type] = toHtml;
for ( var i = 0; i < tags.length; i++ ) {
this.elements.toData[tags[i]] = toData;
this.elements.types[tags[i]] = type;
}
}
};
/**
* Responds to register events from the annotation factory.
*
* @method
* @param {String} type Base annotation type
* @param {Function} constructor Annotation constructor
* @throws 'Missing conversion data in annotation implementation of {type}'
*/
ve.dm.Converter.prototype.onAnnotationRegister = function( type, constructor ) {
if ( constructor.converters === undefined ) {
throw 'Missing conversion data in annotation implementation of ' + type;
} else if ( constructor.converters !== null ) {
var tags = constructor.converters.tags,
toHtml = constructor.converters.toHtml,
toData = constructor.converters.toData;
// Convert tags to an array if needed
if ( !ve.isArray( tags ) ) {
tags = [tags];
}
// Registration
this.annotations.toHtml[type] = toHtml;
for ( var i = 0; i < tags.length; i++ ) {
this.annotations.toData[tags[i]] = toData;
}
}
};
/* Initialization */
ve.dm.converter = new ve.dm.Converter( ve.dm.nodeFactory, ve.dm.annotationFactory );

View file

@ -2,12 +2,12 @@
* DataModel node factory.
*
* @class
* @extends {ve.NodeFactory}
* @extends {ve.Factory}
* @constructor
*/
ve.dm.NodeFactory = function() {
// Inheritance
ve.NodeFactory.call( this );
ve.Factory.call( this );
};
/* Methods */
@ -18,7 +18,7 @@ ve.dm.NodeFactory = function() {
* @method
* @param {String} type Node type
* @returns {String[]|null} List of node types allowed as children or null if any type is allowed
* @throws 'Unknown node type'
* @throws 'Unknown node type: {type}'
*/
ve.dm.NodeFactory.prototype.getChildNodeTypes = function( type ) {
if ( type in this.registry ) {
@ -33,7 +33,7 @@ ve.dm.NodeFactory.prototype.getChildNodeTypes = function( type ) {
* @method
* @param {String} type Node type
* @returns {String[]|null} List of node types allowed as parents or null if any type is allowed
* @throws 'Unknown node type'
* @throws 'Unknown node type: {type}'
*/
ve.dm.NodeFactory.prototype.getParentNodeTypes = function( type ) {
if ( type in this.registry ) {
@ -48,7 +48,7 @@ ve.dm.NodeFactory.prototype.getParentNodeTypes = function( type ) {
* @method
* @param {String} type Node type
* @returns {Boolean} The node can have children
* @throws 'Unknown node type'
* @throws 'Unknown node type: {type}'
*/
ve.dm.NodeFactory.prototype.canNodeHaveChildren = function( type ) {
if ( type in this.registry ) {
@ -66,7 +66,7 @@ ve.dm.NodeFactory.prototype.canNodeHaveChildren = function( type ) {
* @method
* @param {String} type Node type
* @returns {Boolean} The node can have grandchildren
* @throws 'Unknown node type'
* @throws 'Unknown node type: {type}'
*/
ve.dm.NodeFactory.prototype.canNodeHaveGrandchildren = function( type ) {
if ( type in this.registry ) {
@ -83,7 +83,7 @@ ve.dm.NodeFactory.prototype.canNodeHaveGrandchildren = function( type ) {
* @method
* @param {String} type Node type
* @returns {Boolean} Whether the node has a wrapping element
* @throws 'Unknown node type'
* @throws 'Unknown node type: {type}'
*/
ve.dm.NodeFactory.prototype.isNodeWrapped = function( type ) {
if ( type in this.registry ) {
@ -98,7 +98,7 @@ ve.dm.NodeFactory.prototype.isNodeWrapped = function( type ) {
* @method
* @param {String} type Node type
* @returns {Boolean} The node contains content
* @throws 'Unknown node type'
* @throws 'Unknown node type: {type}'
*/
ve.dm.NodeFactory.prototype.canNodeContainContent = function( type ) {
if ( type in this.registry ) {
@ -113,7 +113,7 @@ ve.dm.NodeFactory.prototype.canNodeContainContent = function( type ) {
* @method
* @param {String} type Node type
* @returns {Boolean} The node is content
* @throws 'Unknown node type'
* @throws 'Unknown node type: {type}'
*/
ve.dm.NodeFactory.prototype.isNodeContent = function( type ) {
if ( type in this.registry ) {
@ -124,7 +124,7 @@ ve.dm.NodeFactory.prototype.isNodeContent = function( type ) {
/* Inheritance */
ve.extendClass( ve.dm.NodeFactory, ve.NodeFactory );
ve.extendClass( ve.dm.NodeFactory, ve.Factory );
/* Initialization */

View file

@ -5,4 +5,16 @@
*/
ve.dm = {
//'nodeFactory': Initialized in ve.dm.NodeFactory.js
//'converter': Initialized in ve.dm.Converter.js
};
ve.dm.createHtmlElement = function( type, attributes, doc ) {
if ( doc === undefined ) {
doc = document;
}
var element = doc.createElement( type );
for ( var key in attributes ) {
element.setAttribute( key, attributes[key] );
}
return element;
};

View file

@ -1,35 +1,42 @@
/**
* Generic node factory.
* Generic object factory.
*
* @class
* @abstract
* @constructor
* @extends {ve.EventEmitter}
*/
ve.NodeFactory = function() {
ve.Factory = function() {
// Inheritance
ve.EventEmitter.call( this );
// Properties
this.registry = [];
};
/* Methods */
/**
* Register a node type with the factory.
* Register a constructor with the factory.
*
* Arguments will be passed through directly to the constructor.
* @see {ve.NodeFactory.prototype.create}
* @see {ve.Factory.prototype.create}
*
* @method
* @param {String} type Node type
* @param {Function} constructor Node constructor subclassing ve.Node
* @param {String} type Object type
* @param {Function} constructor Constructor to use when creating object
* @throws 'Constructor must be a function, cannot be a string'
*/
ve.NodeFactory.prototype.register = function( type, constructor ) {
ve.Factory.prototype.register = function( type, constructor ) {
if ( typeof constructor !== 'function' ) {
throw 'Constructor must be a function, cannot be a ' + typeof constructor;
}
this.registry[type] = constructor;
this.emit( 'register', type, constructor );
};
/**
* Create a node based on a type.
* Create an object based on a type.
*
* Type is used to look up the constructor to use, while all additional arguments are passed to the
* constructor directly, so leaving one out will pass an undefined to the constructor.
@ -40,25 +47,29 @@ ve.NodeFactory.prototype.register = function( type, constructor ) {
* in adding more.
*
* @method
* @param {String} type Node type
* @param {String} type Object type
* @param {Mixed} [...] Up to 2 additional arguments to pass through to the constructor
* @returns {ve.Node} The new node object
* @throws 'Unknown node type'
* @returns {Object} The new object
* @throws 'Unknown object type'
*/
ve.NodeFactory.prototype.create = function( type, a, b ) {
ve.Factory.prototype.create = function( type, a, b ) {
if ( type in this.registry ) {
return new this.registry[type]( a, b );
}
throw 'Unknown node type: ' + type;
throw 'Unknown object type: ' + type;
};
/**
* Gets a constructor for a given type.
*
* @method
* @param {String} type Node type
* @param {String} type Object type
* @returns {Function|undefined} Constructor for type
*/
ve.NodeFactory.prototype.lookup = function( type ) {
ve.Factory.prototype.lookup = function( type ) {
return this.registry[type];
};
/* Inheritance */
ve.extendClass( ve.Factory, ve.EventEmitter );

View file

@ -16,10 +16,10 @@ ve.ce.NodeFactoryNodeStub.rules = {
test( 'canNodeBeSplit', 2, function() {
var factory = new ve.ce.NodeFactory();
raises( function() {
factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
factory.canNodeBeSplit( 'node-factory-node-stub' );
},
/^Unknown node type: node-factory-node-stub$/,
'throws an exception when getting allowed child nodes of a node of an unregistered type'
'throws an exception when getting split rules for a node of an unregistered type'
);
factory.register( 'node-factory-node-stub', ve.ce.NodeFactoryNodeStub );
strictEqual(

View file

@ -14,6 +14,8 @@ ve.dm.BranchNodeStub.rules = {
'childNodeTypes': null
};
ve.dm.BranchNodeStub.converters = null;
ve.extendClass( ve.dm.BranchNodeStub, ve.dm.BranchNode );
ve.dm.nodeFactory.register( 'branch-stub', ve.dm.BranchNodeStub );

View file

@ -14,6 +14,8 @@ ve.dm.LeafNodeStub.rules = {
'childNodeTypes': []
};
ve.dm.LeafNodeStub.converters = null;
ve.extendClass( ve.dm.LeafNodeStub, ve.dm.LeafNode );
ve.dm.nodeFactory.register( 'leaf-stub', ve.dm.LeafNodeStub );

View file

@ -14,6 +14,8 @@ ve.dm.NodeStub.rules = {
'childNodeTypes': []
};
ve.dm.NodeStub.converters = null;
ve.extendClass( ve.dm.NodeStub, ve.dm.Node );
ve.dm.nodeFactory.register( 'stub', ve.dm.NodeStub );

View file

@ -15,12 +15,16 @@ ve.dm.NodeFactoryNodeStub.rules = {
'parentNodeTypes': null
};
ve.dm.NodeFactoryNodeStub.converters = null;
ve.dm.NodeFactoryNodeStub.converters = null;
/* Tests */
test( 'getChildNodeTypes', 2, function() {
var factory = new ve.dm.NodeFactory();
raises( function() {
factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
factory.getChildNodeTypes( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
},
/^Unknown node type: node-factory-node-stub$/,
'throws an exception when getting allowed child nodes of a node of an unregistered type'
@ -36,7 +40,7 @@ test( 'getChildNodeTypes', 2, function() {
test( 'getParentNodeTypes', 2, function() {
var factory = new ve.dm.NodeFactory();
raises( function() {
factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
factory.getParentNodeTypes( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
},
/^Unknown node type: node-factory-node-stub$/,
'throws an exception when getting allowed parent nodes of a node of an unregistered type'
@ -52,7 +56,7 @@ test( 'getParentNodeTypes', 2, function() {
test( 'canNodeHaveChildren', 2, function() {
var factory = new ve.dm.NodeFactory();
raises( function() {
factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
factory.canNodeHaveChildren( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
},
/^Unknown node type: node-factory-node-stub$/,
'throws an exception when checking if a node of an unregistered type can have children'
@ -68,7 +72,7 @@ test( 'canNodeHaveChildren', 2, function() {
test( 'canNodeHaveGrandchildren', 2, function() {
var factory = new ve.dm.NodeFactory();
raises( function() {
factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
factory.canNodeHaveGrandchildren( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
},
/^Unknown node type: node-factory-node-stub$/,
'throws an exception when checking if a node of an unregistered type can have grandchildren'

View file

@ -23,26 +23,32 @@
<script src="../../modules/ve2/ve.Node.js"></script>
<script src="../../modules/ve2/ve.BranchNode.js"></script>
<script src="../../modules/ve2/ve.LeafNode.js"></script>
<script src="../../modules/ve2/ve.NodeFactory.js"></script>
<script src="../../modules/ve2/ve.Factory.js"></script>
<script src="../../modules/ve2/ve.Document.js"></script>
<script src="../../modules/ve2/ve.Position.js"></script>
<script src="../../modules/ve2/ve.Range.js"></script>
<script src="../../modules/ve2/ve.Surface.js"></script>
<!-- VisualEditor DataModel -->
<script src="../../modules/ve2/dm/ve.dm.js"></script>
<script src="../../modules/ve2/dm/ve.dm.NodeFactory.js"></script>
<script src="../../modules/ve2/dm/ve.dm.AnnotationFactory.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Node.js"></script>
<script src="../../modules/ve2/dm/ve.dm.BranchNode.js"></script>
<script src="../../modules/ve2/dm/ve.dm.LeafNode.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Annotation.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Document.js"></script>
<script src="../../modules/ve2/dm/ve.dm.DocumentSynchronizer.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Transaction.js"></script>
<script src="../../modules/ve2/dm/ve.dm.TransactionProcessor.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Surface.js"></script>
<script src="../../modules/ve2/dm/ve.dm.Converter.js"></script>
<script src="../../modules/ve2/dm/ve.dm.HTMLConverter.js"></script>
<!-- VisualEditor DataModel Annotations -->
<script src="../../modules/ve2/dm/annotations/ve.dm.LinkAnnotation.js"></script>
<script src="../../modules/ve2/dm/annotations/ve.dm.TextStyleAnnotation.js"></script>
<!-- VisualEditor DataModel Nodes -->
<script src="../../modules/ve2/dm/nodes/ve.dm.AlienInlineNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.AlienBlockNode.js"></script>
@ -91,7 +97,7 @@
<script src="ve.Node.test.js"></script>
<script src="ve.BranchNode.test.js"></script>
<script src="ve.LeafNode.test.js"></script>
<script src="ve.NodeFactory.test.js"></script>
<script src="ve.Factory.test.js"></script>
<!-- VisualEditor DataModel Tests -->
<script src="dm/ve.dm.example.js"></script>

View file

@ -0,0 +1,44 @@
module( 've.Factory' );
/* Stubs */
ve.FactoryObjectStub = function( a, b ) {
this.a = a;
this.b = b;
};
/* Tests */
test( 'register', 1, function() {
var factory = new ve.Factory();
raises(
function() {
factory.register( 'factory-object-stub', 'not-a-function' );
},
/^Constructor must be a function, cannot be a string$/,
'throws an exception when trying to register a non-function value as a constructor'
);
} );
test( 'create', 2, function() {
var factory = new ve.Factory();
raises(
function() {
factory.create( 'factory-object-stub', 23, { 'bar': 'baz' } );
},
/^Unknown object type: factory-object-stub$/,
'throws an exception when trying to create a object of an unregistered type'
);
factory.register( 'factory-object-stub', ve.FactoryObjectStub );
deepEqual(
factory.create( 'factory-object-stub', 16, { 'baz': 'quux' } ),
new ve.FactoryObjectStub( 16, { 'baz': 'quux' } ),
'creates objects of a registered type and passes through arguments'
);
} );
test( 'lookup', 1, function() {
var factory = new ve.Factory();
factory.register( 'factory-object-stub', ve.FactoryObjectStub );
strictEqual( factory.lookup( 'factory-object-stub' ), ve.FactoryObjectStub );
} );

View file

@ -1,42 +0,0 @@
module( 've.NodeFactory' );
/* Stubs */
ve.NodeFactoryNodeStub = function( a, b ) {
this.a = a;
this.b = b;
};
/* Tests */
test( 'register', 1, function() {
var factory = new ve.NodeFactory();
raises( function() {
factory.register( 'node-factory-node-stub', 'not-a-function' );
},
/^Constructor must be a function, cannot be a string$/,
'throws an exception when trying to register a non-function value as a constructor'
);
} );
test( 'create', 2, function() {
var factory = new ve.NodeFactory();
raises( function() {
factory.create( 'node-factory-node-stub', 23, { 'bar': 'baz' } );
},
/^Unknown node type: node-factory-node-stub$/,
'throws an exception when trying to create a node of an unregistered type'
);
factory.register( 'node-factory-node-stub', ve.NodeFactoryNodeStub );
deepEqual(
factory.create( 'node-factory-node-stub', 16, { 'baz': 'quux' } ),
new ve.NodeFactoryNodeStub( 16, { 'baz': 'quux' } ),
'creates nodes of a registered type and passes through arguments'
);
} );
test( 'lookup', 1, function() {
var factory = new ve.NodeFactory();
factory.register( 'node-factory-node-stub', ve.NodeFactoryNodeStub );
strictEqual( factory.lookup( 'node-factory-node-stub' ), ve.NodeFactoryNodeStub );
} );