/**
* VisualEditor Surface class.
*
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Creates an ve.Surface object.
*
* A surface is a top-level object which contains both a surface model and a surface view.
*
* @class
* @constructor
* @param {String} parent Selector of element to attach to
* @param {HTMLElement} html Document html
* @param {Object} options Configuration options
*/
ve.Surface = function ( parent, dom, options ) {
// Create linear model from HTML5 DOM
var data = ve.dm.converter.getDataFromDom( dom );
// Properties
this.parent = parent;
this.currentMode = null;
/* Extend VE configuration recursively */
this.options = ve.extendObject( true, {
// Default options
toolbars: {
top: {
tools: [{ 'name': 'history', 'items' : ['undo', 'redo'] },
{ 'name': 'textStyle', 'items' : ['format'] },
{ 'name': 'textStyle', 'items' : ['bold', 'italic', 'link', 'clear'] },
{ 'name': 'list', 'items' : ['number', 'bullet', 'outdent', 'indent'] }]
}
}
}, options );
// A place to store element references
this.$base = null;
this.$surface = null;
this.toolbarWrapper = {};
// Create document model object with the linear model
this.documentModel = new ve.dm.Document( data );
this.model = new ve.dm.Surface( this.documentModel );
// Setup VE DOM Skeleton
this.setupBaseElements();
this.$surface = $( '
' ).addClass( 'es-editor' );
this.$base.find( '.es-visual' ).append( this.$surface );
/* Instantiate surface layer */
this.view = new ve.ce.Surface( $( '.es-editor' ), this.model );
// Setup toolbars based on this.options
this.setupToolbars();
// Registration
ve.instances.push( this );
// Start tracking changes
this.model.startHistoryTracking();
};
/* Methods */
ve.Surface.prototype.getModel = function () {
return this.model;
};
ve.Surface.prototype.getDocumentModel = function () {
return this.documentModel;
};
ve.Surface.prototype.getView = function () {
return this.view;
};
ve.Surface.prototype.getContext = function () {
return this.context;
};
ve.Surface.prototype.getParent = function () {
return this.parent;
};
ve.Surface.prototype.setupBaseElements = function () {
// Make new base element
this.$base = $( '
' )
.addClass( 'es-base' )
.append(
$( '
' ).addClass( 'es-panes' )
.append( $( '
' ).addClass( 'es-visual' ) )
.append( $( '
' ).addClass( 'es-panels' ) )
.append( $( '
' ).css( 'clear', 'both' ) )
)
.append(
$( '
' ).attr( {
// TODO: make 'paste' in surface stateful and remove this attrib
'id': 'paste',
'class': 'paste',
'contenteditable': 'true'
} )
);
// Attach the base the the parent
$( this.getParent() ).append( this.$base );
};
ve.Surface.prototype.setupToolbars = function () {
var surface = this;
// Build each toolbar
$.each( this.options.toolbars, function ( name, config ) {
if ( config !== null ) {
if( name === 'top' ) {
// Append toolbar wrapper at the top, just above .es-panes
surface.toolbarWrapper[name] = $( '
' )
.addClass( 'es-toolbar-wrapper' )
.append(
$( '
' ).addClass( 'es-toolbar' )
.append(
$( '
' ).addClass( 'es-modes' )
).append(
$( '
' ).css( 'clear', 'both' )
).append(
$( '
' ).addClass( 'es-toolbar-shadow' )
)
);
surface.$base.find( '.es-panes' ).before( surface.toolbarWrapper[name] );
if ( 'float' in config && config.float === true ) {
// Float top toolbar
surface.floatTopToolbar();
}
}
// Instantiate the toolbar
surface['toolbar-' + name] = new ve.ui.Toolbar(
surface.$base.find( '.es-toolbar' ),
surface.view,
config.tools
);
}
} );
};
/*
* This code is responsible for switching toolbar into floating mode when scrolling ( with
* keyboard or mouse ).
* TODO: Determine if this would be better in ui.toolbar vs here.
* TODO: This needs to be refactored so that it only works on the main editor top tool bar.
*/
ve.Surface.prototype.floatTopToolbar = function () {
if ( !this.toolbarWrapper.top ) {
return;
}
var $toolbarWrapper = this.toolbarWrapper.top,
$toolbar = $toolbarWrapper.find( '.es-toolbar' ),
$window = $( window );
$window.scroll( function () {
var left, right,
toolbarWrapperOffset = $toolbarWrapper.offset(),
$editorDocument = $toolbarWrapper.parent().find('.ve-surface .ve-ce-documentNode');
if ( $window.scrollTop() > toolbarWrapperOffset.top ) {
left = toolbarWrapperOffset.left;
right = $window.width() - $toolbarWrapper.outerWidth() - left;
// If not floating, set float
if ( !$toolbarWrapper.hasClass( 'float' ) ) {
$toolbarWrapper
.css( 'height', $toolbarWrapper.height() )
.addClass( 'float' );
$toolbar.css( {
'left': left,
'right': right
} );
} else {
// Toolbar is floated
if (
// Toolbar is at or below the top of last node in the document
$window.scrollTop() + $toolbar.height() >=
$editorDocument.children( '.ve-ce-branchNode:last' ).offset().top
) {
// XXX: Use less generic class names (not "bottom" and "float")
if( !$toolbarWrapper.hasClass( 'bottom' ) ) {
$toolbarWrapper
.removeClass( 'float' )
.addClass( 'bottom' );
$toolbar.css({
'top': $window.scrollTop() + 'px',
'left': left,
'right': right
});
}
} else { // Unattach toolbar
if ( $toolbarWrapper.hasClass( 'bottom' ) ) {
$toolbarWrapper
.removeClass( 'bottom' )
.addClass( 'float' );
$toolbar.css( {
'top': 0,
'left': left,
'right': right
} );
}
}
}
} else { // Return toolbar to top position
if ( $toolbarWrapper.hasClass( 'float' ) || $toolbarWrapper.hasClass( 'bottom' ) ) {
$toolbarWrapper.css( 'height', 'auto' )
.removeClass( 'float' )
.removeClass( 'bottom' );
$toolbar.css( {
'top': 0,
'left': 0,
'right': 0
} );
}
}
} );
};