.
if ( !isBlock( block ) ) {
fixContainer( block );
}
else {
// If it doesn't break a line, just remove it; it's not doing
// anything useful. We'll add it back later if required by the
// browser. If it breaks a line, split the block or leave it as
// appropriate.
if ( brBreaksLine[l] ) {
// If in a
, split, but anywhere else we might change
// the formatting too much (e.g.
-> to two list items!)
// so just play it safe and leave it.
if ( block.nodeName !== 'DIV' ) {
continue;
}
split( br.parentNode, br, block.parentNode );
}
detach( br );
}
}
};
proto._ensureBottomLine = function () {
var body = this._body,
div = body.lastChild;
if ( !div || div.nodeName !== 'DIV' || !isBlock( div ) ) {
body.appendChild( this.createDefaultBlock() );
}
};
// --- Cut and Paste ---
proto._onCut = function () {
// Save undo checkpoint
var range = this.getSelection();
var self = this;
this._recordUndoState( range );
this._getRangeAndRemoveBookmark( range );
this.setSelection( range );
setTimeout( function () {
try {
// If all content removed, ensure div at start of body.
self._ensureBottomLine();
} catch ( error ) {
self.didError( error );
}
}, 0 );
};
proto._onPaste = function ( event ) {
if ( this._awaitingPaste ) { return; }
// Treat image paste as a drop of an image file.
var clipboardData = event.clipboardData,
items = clipboardData && clipboardData.items,
fireDrop = false,
hasImage = false,
l, type;
if ( items ) {
l = items.length;
while ( l-- ) {
type = items[l].type;
if ( type === 'text/html' ) {
hasImage = false;
break;
}
if ( /^image\/.*/.test( type ) ) {
hasImage = true;
}
}
if ( hasImage ) {
event.preventDefault();
this.fireEvent( 'dragover', {
dataTransfer: clipboardData,
/*jshint loopfunc: true */
preventDefault: function () {
fireDrop = true;
}
/*jshint loopfunc: false */
});
if ( fireDrop ) {
this.fireEvent( 'drop', {
dataTransfer: clipboardData
});
}
return;
}
}
this._awaitingPaste = true;
var self = this,
body = this._body,
range = this.getSelection(),
startContainer = range.startContainer,
startOffset = range.startOffset,
endContainer = range.endContainer,
endOffset = range.endOffset,
startBlock = getStartBlockOfRange( range );
// Record undo checkpoint
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
// We need to position the pasteArea in the visible portion of the screen
// to stop the browser auto-scrolling.
var pasteArea = this.createElement( 'DIV', {
style: 'position: absolute; overflow: hidden; top:' +
( body.scrollTop +
( startBlock ? startBlock.getBoundingClientRect().top : 0 ) ) +
'px; left: 0; width: 1px; height: 1px;'
});
body.appendChild( pasteArea );
range.selectNodeContents( pasteArea );
this.setSelection( range );
// A setTimeout of 0 means this is added to the back of the
// single javascript thread, so it will be executed after the
// paste event.
setTimeout( function () {
try {
// Get the pasted content and clean
var frag = empty( detach( pasteArea ) ),
first = frag.firstChild,
range = self._createRange(
startContainer, startOffset, endContainer, endOffset );
// Was anything actually pasted?
if ( first ) {
// Safari and IE like putting extra divs around things.
if ( first === frag.lastChild &&
first.nodeName === 'DIV' ) {
frag.replaceChild( empty( first ), first );
}
frag.normalize();
addLinks( frag );
cleanTree( frag, false );
cleanupBRs( frag );
removeEmptyInlines( frag );
var node = frag,
doPaste = true;
while ( node = getNextBlock( node ) ) {
fixCursor( node );
}
self.fireEvent( 'willPaste', {
fragment: frag,
preventDefault: function () {
doPaste = false;
}
});
// Insert pasted data
if ( doPaste ) {
insertTreeFragmentIntoRange( range, frag );
self._docWasChanged();
range.collapse( false );
self._ensureBottomLine();
}
}
self.setSelection( range );
self._updatePath( range, true );
self._awaitingPaste = false;
} catch ( error ) {
self.didError( error );
}
}, 0 );
};
// --- Keyboard interaction ---
var keys = {
8: 'backspace',
9: 'tab',
13: 'enter',
32: 'space',
37: 'left',
39: 'right',
46: 'delete',
219: '[',
221: ']'
};
var mapKeyTo = function ( method ) {
return function ( self, event ) {
event.preventDefault();
self[ method ]();
};
};
var mapKeyToFormat = function ( tag, remove ) {
remove = remove || null;
return function ( self, event ) {
event.preventDefault();
var range = self.getSelection();
if ( self.hasFormat( tag, null, range ) ) {
self.changeFormat( null, { tag: tag }, range );
} else {
self.changeFormat( { tag: tag }, remove, range );
}
};
};
// If you delete the content inside a span with a font styling, Webkit will
// replace it with a tag (!). If you delete all the text inside a
// link in Opera, it won't delete the link. Let's make things consistent. If
// you delete all text inside an inline tag, remove the inline tag.
var afterDelete = function ( self ) {
try {
var range = self.getSelection(),
node = range.startContainer,
parent;
if ( node.nodeType === TEXT_NODE ) {
node = node.parentNode;
}
// If focussed in empty inline element
if ( isInline( node ) && !node.textContent ) {
do {
parent = node.parentNode;
} while ( isInline( parent ) &&
!parent.textContent && ( node = parent ) );
range.setStart( parent,
indexOf.call( parent.childNodes, node ) );
range.collapse( true );
parent.removeChild( node );
if ( !isBlock( parent ) ) {
parent = getPreviousBlock( parent );
}
fixCursor( parent );
moveRangeBoundariesDownTree( range );
self.setSelection( range );
self._updatePath( range );
}
self._ensureBottomLine();
} catch ( error ) {
self.didError( error );
}
};
// If you select all in IE8 then type, it makes a P; replace it with
// a DIV.
if ( isIE8 ) {
proto._ieSelAllClean = function () {
var firstChild = this._body.firstChild;
if ( firstChild.nodeName === 'P' ) {
this._saveRangeToBookmark( this.getSelection() );
replaceWith( firstChild, this.createDefaultBlock([
empty( firstChild )
]));
this.setSelection( this._getRangeAndRemoveBookmark() );
}
};
}
var keyHandlers = {
enter: function ( self, event ) {
// We handle this ourselves
event.preventDefault();
// Must have some form of selection
var range = self.getSelection(),
block, parent, tag, splitTag, nodeAfterSplit;
if ( !range ) { return; }
// Save undo checkpoint and add any links in the preceding section.
// Remove any zws so we don't think there's content in an empty
// block.
self._recordUndoState( range );
addLinks( range.startContainer );
self._removeZWS();
self._getRangeAndRemoveBookmark( range );
// Selected text is overwritten, therefore delete the contents
// to collapse selection.
if ( !range.collapsed ) {
deleteContentsOfRange( range );
}
block = getStartBlockOfRange( range );
if ( block && ( parent = getNearest( block, 'LI' ) ) ) {
block = parent;
}
tag = block ? block.nodeName : 'DIV';
splitTag = tagAfterSplit[ tag ];
// If this is a malformed bit of document, just play it safe
// and insert a
.
if ( !block ) {
insertNodeInRange( range, self.createElement( 'BR' ) );
range.collapse( false );
self.setSelection( range );
self._updatePath( range, true );
self._docWasChanged();
return;
}
// We need to wrap the contents in divs.
var splitNode = range.startContainer,
splitOffset = range.startOffset,
replacement;
if ( !splitTag ) {
// If the selection point is inside the block, we're going to
// rewrite it so our saved reference points won't be valid.
// Pick a node at a deeper point in the tree to avoid this.
if ( splitNode === block ) {
splitNode = splitOffset ?
splitNode.childNodes[ splitOffset - 1 ] : null;
splitOffset = 0;
if ( splitNode ) {
if ( splitNode.nodeName === 'BR' ) {
splitNode = splitNode.nextSibling;
} else {
splitOffset = getLength( splitNode );
}
if ( !splitNode || splitNode.nodeName === 'BR' ) {
replacement = fixCursor( self.createElement( 'DIV' ) );
if ( splitNode ) {
block.replaceChild( replacement, splitNode );
} else {
block.appendChild( replacement );
}
splitNode = replacement;
}
}
}
fixContainer( block );
splitTag = 'DIV';
if ( !splitNode ) {
splitNode = block.firstChild;
}
range.setStart( splitNode, splitOffset );
range.setEnd( splitNode, splitOffset );
block = getStartBlockOfRange( range );
}
if ( !block.textContent ) {
// Break list
if ( getNearest( block, 'UL' ) || getNearest( block, 'OL' ) ) {
return self.modifyBlocks( decreaseListLevel, range );
}
// Break blockquote
else if ( getNearest( block, 'BLOCKQUOTE' ) ) {
return self.modifyBlocks( removeBlockQuote, range );
}
}
// Otherwise, split at cursor point.
nodeAfterSplit = splitBlock( block, splitNode, splitOffset );
// Focus cursor
// If there's a / etc. at the beginning of the split
// make sure we focus inside it.
while ( nodeAfterSplit.nodeType === ELEMENT_NODE ) {
var child = nodeAfterSplit.firstChild,
next;
// Don't continue links over a block break; unlikely to be the
// desired outcome.
if ( nodeAfterSplit.nodeName === 'A' ) {
replaceWith( nodeAfterSplit, empty( nodeAfterSplit ) );
nodeAfterSplit = child;
continue;
}
while ( child && child.nodeType === TEXT_NODE && !child.data ) {
next = child.nextSibling;
if ( !next || next.nodeName === 'BR' ) {
break;
}
detach( child );
child = next;
}
// 'BR's essentially don't count; they're a browser hack.
// If you try to select the contents of a 'BR', FF will not let
// you type anything!
if ( !child || child.nodeName === 'BR' ||
( child.nodeType === TEXT_NODE && !isOpera ) ) {
break;
}
nodeAfterSplit = child;
}
range = self._createRange( nodeAfterSplit, 0 );
self.setSelection( range );
self._updatePath( range, true );
// Scroll into view
if ( nodeAfterSplit.nodeType === TEXT_NODE ) {
nodeAfterSplit = nodeAfterSplit.parentNode;
}
var doc = self._doc,
body = self._body;
if ( nodeAfterSplit.offsetTop + nodeAfterSplit.offsetHeight >
( doc.documentElement.scrollTop || body.scrollTop ) +
body.offsetHeight ) {
nodeAfterSplit.scrollIntoView( false );
}
// We're not still in an undo state
self._docWasChanged();
},
backspace: function ( self, event ) {
self._removeZWS();
// Record undo checkpoint.
var range = self.getSelection();
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
// If not collapsed, delete contents
if ( !range.collapsed ) {
event.preventDefault();
deleteContentsOfRange( range );
self._ensureBottomLine();
self.setSelection( range );
self._updatePath( range, true );
}
// If at beginning of block, merge with previous
else if ( rangeDoesStartAtBlockBoundary( range ) ) {
event.preventDefault();
var current = getStartBlockOfRange( range ),
previous = current && getPreviousBlock( current );
// Must not be at the very beginning of the text area.
if ( previous ) {
// If not editable, just delete whole block.
if ( !previous.isContentEditable ) {
detach( previous );
return;
}
// Otherwise merge.
mergeWithBlock( previous, current, range );
// If deleted line between containers, merge newly adjacent
// containers.
current = previous.parentNode;
while ( current && !current.nextSibling ) {
current = current.parentNode;
}
if ( current && ( current = current.nextSibling ) ) {
mergeContainers( current );
}
self.setSelection( range );
}
// If at very beginning of text area, allow backspace
// to break lists/blockquote.
else if ( current ) {
// Break list
if ( getNearest( current, 'UL' ) ||
getNearest( current, 'OL' ) ) {
return self.modifyBlocks( decreaseListLevel, range );
}
// Break blockquote
else if ( getNearest( current, 'BLOCKQUOTE' ) ) {
return self.modifyBlocks( decreaseBlockQuoteLevel, range );
}
self.setSelection( range );
self._updatePath( range, true );
}
}
// Otherwise, leave to browser but check afterwards whether it has
// left behind an empty inline tag.
else {
self.setSelection( range );
setTimeout( function () { afterDelete( self ); }, 0 );
}
},
'delete': function ( self, event ) {
self._removeZWS();
// Record undo checkpoint.
var range = self.getSelection();
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
// If not collapsed, delete contents
if ( !range.collapsed ) {
event.preventDefault();
deleteContentsOfRange( range );
self._ensureBottomLine();
self.setSelection( range );
self._updatePath( range, true );
}
// If at end of block, merge next into this block
else if ( rangeDoesEndAtBlockBoundary( range ) ) {
event.preventDefault();
var current = getStartBlockOfRange( range ),
next = current && getNextBlock( current );
// Must not be at the very end of the text area.
if ( next ) {
// If not editable, just delete whole block.
if ( !next.isContentEditable ) {
detach( next );
return;
}
// Otherwise merge.
mergeWithBlock( current, next, range );
// If deleted line between containers, merge newly adjacent
// containers.
next = current.parentNode;
while ( next && !next.nextSibling ) {
next = next.parentNode;
}
if ( next && ( next = next.nextSibling ) ) {
mergeContainers( next );
}
self.setSelection( range );
self._updatePath( range, true );
}
}
// Otherwise, leave to browser but check afterwards whether it has
// left behind an empty inline tag.
else {
self.setSelection( range );
setTimeout( function () { afterDelete( self ); }, 0 );
}
},
tab: function ( self, event ) {
self._removeZWS();
var range = self.getSelection(),
node, parent;
// If no selection and in an empty block
if ( range.collapsed &&
rangeDoesStartAtBlockBoundary( range ) &&
rangeDoesEndAtBlockBoundary( range ) ) {
node = getStartBlockOfRange( range );
// Iterate through the block's parents
while ( parent = node.parentNode ) {
// If we find a UL or OL (so are in a list, node must be an LI)
if ( parent.nodeName === 'UL' || parent.nodeName === 'OL' ) {
// AND the LI is not the first in the list
if ( node.previousSibling ) {
// Then increase the list level
event.preventDefault();
self.modifyBlocks( increaseListLevel, range );
}
break;
}
node = parent;
}
event.preventDefault();
}
},
space: function ( self ) {
var range = self.getSelection();
self._recordUndoState( range );
addLinks( range.startContainer );
self._getRangeAndRemoveBookmark( range );
self.setSelection( range );
},
left: function ( self ) {
self._removeZWS();
},
right: function ( self ) {
self._removeZWS();
}
};
// Firefox incorrectly handles Cmd-left/Cmd-right on Mac:
// it goes back/forward in history! Override to do the right
// thing.
// https://bugzilla.mozilla.org/show_bug.cgi?id=289384
if ( isMac && isGecko && win.getSelection().modify ) {
keyHandlers[ 'meta-left' ] = function ( self, event ) {
event.preventDefault();
self._sel.modify( 'move', 'backward', 'lineboundary' );
};
keyHandlers[ 'meta-right' ] = function ( self, event ) {
event.preventDefault();
self._sel.modify( 'move', 'forward', 'lineboundary' );
};
}
keyHandlers[ ctrlKey + 'b' ] = mapKeyToFormat( 'B' );
keyHandlers[ ctrlKey + 'i' ] = mapKeyToFormat( 'I' );
keyHandlers[ ctrlKey + 'u' ] = mapKeyToFormat( 'U' );
keyHandlers[ ctrlKey + 'shift-7' ] = mapKeyToFormat( 'S' );
keyHandlers[ ctrlKey + 'shift-5' ] = mapKeyToFormat( 'SUB', { tag: 'SUP' } );
keyHandlers[ ctrlKey + 'shift-6' ] = mapKeyToFormat( 'SUP', { tag: 'SUB' } );
keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' );
keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
// Ref: http://unixpapa.com/js/key.html
proto._onKey = function ( event ) {
var code = event.keyCode,
key = keys[ code ],
modifiers = '';
if ( !key ) {
key = String.fromCharCode( code ).toLowerCase();
// Only reliable for letters and numbers
if ( !/^[A-Za-z0-9]$/.test( key ) ) {
key = '';
}
}
// On keypress, delete and '.' both have event.keyCode 46
// Must check event.which to differentiate.
if ( isOpera && event.which === 46 ) {
key = '.';
}
// Function keys
if ( 111 < code && code < 124 ) {
key = 'f' + ( code - 111 );
}
// We need to apply the backspace/delete handlers regardless of modifiers.
if ( key !== 'backspace' && key !== 'delete' ) {
if ( event.altKey ) { modifiers += 'alt-'; }
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
if ( event.metaKey ) { modifiers += 'meta-'; }
if ( event.shiftKey ) { modifiers += 'shift-'; }
key = modifiers + key;
}
if ( keyHandlers[ key ] ) {
keyHandlers[ key ]( this, event );
}
};
// --- Get/Set data ---
proto._getHTML = function () {
return this._body.innerHTML;
};
proto._setHTML = function ( html ) {
var node = this._body;
node.innerHTML = html;
do {
fixCursor( node );
} while ( node = getNextBlock( node ) );
};
proto.getHTML = function ( withBookMark ) {
var brs = [],
node, fixer, html, l, range;
if ( withBookMark && ( range = this.getSelection() ) ) {
this._saveRangeToBookmark( range );
}
if ( useTextFixer ) {
node = this._body;
while ( node = getNextBlock( node ) ) {
if ( !node.textContent && !node.querySelector( 'BR' ) ) {
fixer = this.createElement( 'BR' );
node.appendChild( fixer );
brs.push( fixer );
}
}
}
html = this._getHTML().replace( /\u200B/g, '' );
if ( useTextFixer ) {
l = brs.length;
while ( l-- ) {
detach( brs[l] );
}
}
if ( range ) {
this._getRangeAndRemoveBookmark( range );
}
return html;
};
proto.setHTML = function ( html ) {
var frag = this._doc.createDocumentFragment(),
div = this.createElement( 'DIV' ),
child;
// Parse HTML into DOM tree
div.innerHTML = html;
frag.appendChild( empty( div ) );
cleanTree( frag, true );
cleanupBRs( frag );
fixContainer( frag );
// Fix cursor
var node = frag;
while ( node = getNextBlock( node ) ) {
fixCursor( node );
}
// Remove existing body children
var body = this._body;
while ( child = body.lastChild ) {
body.removeChild( child );
}
// And insert new content
body.appendChild( frag );
fixCursor( body );
// Reset the undo stack
this._undoIndex = -1;
this._undoStack.length = 0;
this._undoStackLength = 0;
this._isInUndoState = false;
// Record undo state
var range = this._getRangeAndRemoveBookmark() ||
this._createRange( body.firstChild, 0 );
this._recordUndoState( range );
this._getRangeAndRemoveBookmark( range );
// IE will also set focus when selecting text so don't use
// setSelection. Instead, just store it in lastSelection, so if
// anything calls getSelection before first focus, we have a range
// to return.
if ( losesSelectionOnBlur ) {
this._lastSelection = range;
} else {
this.setSelection( range );
}
this._updatePath( range, true );
return this;
};
proto.insertElement = function ( el, range ) {
if ( !range ) { range = this.getSelection(); }
range.collapse( true );
if ( isInline( el ) ) {
insertNodeInRange( range, el );
range.setStartAfter( el );
} else {
// Get containing block node.
var body = this._body,
splitNode = getStartBlockOfRange( range ) || body,
parent, nodeAfterSplit;
// While at end of container node, move up DOM tree.
while ( splitNode !== body && !splitNode.nextSibling ) {
splitNode = splitNode.parentNode;
}
// If in the middle of a container node, split up to body.
if ( splitNode !== body ) {
parent = splitNode.parentNode;
nodeAfterSplit = split( parent, splitNode.nextSibling, body );
}
if ( nodeAfterSplit ) {
body.insertBefore( el, nodeAfterSplit );
range.setStart( nodeAfterSplit, 0 );
range.setStart( nodeAfterSplit, 0 );
moveRangeBoundariesDownTree( range );
} else {
body.appendChild( el );
// Insert blank line below block.
body.appendChild( this.createDefaultBlock() );
range.setStart( el, 0 );
range.setEnd( el, 0 );
}
this.focus();
this.setSelection( range );
this._updatePath( range );
}
return this;
};
proto.insertImage = function ( src ) {
var img = this.createElement( 'IMG', {
src: src
});
this.insertElement( img );
return img;
};
// --- Formatting ---
var command = function ( method, arg, arg2 ) {
return function () {
this[ method ]( arg, arg2 );
return this.focus();
};
};
proto.addStyles = function ( styles ) {
if ( styles ) {
var head = this._doc.documentElement.firstChild,
style = this.createElement( 'STYLE', {
type: 'text/css'
});
if ( style.styleSheet ) {
// IE8: must append to document BEFORE adding styles
// or you get the IE7 CSS parser!
head.appendChild( style );
style.styleSheet.cssText = styles;
} else {
// Everyone else
style.appendChild( this._doc.createTextNode( styles ) );
head.appendChild( style );
}
}
return this;
};
proto.bold = command( 'changeFormat', { tag: 'B' } );
proto.italic = command( 'changeFormat', { tag: 'I' } );
proto.underline = command( 'changeFormat', { tag: 'U' } );
proto.strikethrough = command( 'changeFormat', { tag: 'S' } );
proto.subscript = command( 'changeFormat', { tag: 'SUB' }, { tag: 'SUP' } );
proto.superscript = command( 'changeFormat', { tag: 'SUP' }, { tag: 'SUB' } );
proto.removeBold = command( 'changeFormat', null, { tag: 'B' } );
proto.removeItalic = command( 'changeFormat', null, { tag: 'I' } );
proto.removeUnderline = command( 'changeFormat', null, { tag: 'U' } );
proto.removeStrikethrough = command( 'changeFormat', null, { tag: 'S' } );
proto.removeSubscript = command( 'changeFormat', null, { tag: 'SUB' } );
proto.removeSuperscript = command( 'changeFormat', null, { tag: 'SUP' } );
proto.makeLink = function ( url ) {
var range = this.getSelection();
if ( range.collapsed ) {
var protocolEnd = url.indexOf( ':' ) + 1;
if ( protocolEnd ) {
while ( url[ protocolEnd ] === '/' ) { protocolEnd += 1; }
}
insertNodeInRange(
range,
this._doc.createTextNode( url.slice( protocolEnd ) )
);
}
this.changeFormat({
tag: 'A',
attributes: {
href: url
}
}, {
tag: 'A'
}, range );
return this.focus();
};
proto.removeLink = function () {
this.changeFormat( null, {
tag: 'A'
}, this.getSelection(), true );
return this.focus();
};
proto.setFontFace = function ( name ) {
this.changeFormat({
tag: 'SPAN',
attributes: {
'class': 'font',
style: 'font-family: ' + name + ', sans-serif;'
}
}, {
tag: 'SPAN',
attributes: { 'class': 'font' }
});
return this.focus();
};
proto.setFontSize = function ( size ) {
this.changeFormat({
tag: 'SPAN',
attributes: {
'class': 'size',
style: 'font-size: ' +
( typeof size === 'number' ? size + 'px' : size )
}
}, {
tag: 'SPAN',
attributes: { 'class': 'size' }
});
return this.focus();
};
proto.setTextColour = function ( colour ) {
this.changeFormat({
tag: 'SPAN',
attributes: {
'class': 'colour',
style: 'color: ' + colour
}
}, {
tag: 'SPAN',
attributes: { 'class': 'colour' }
});
return this.focus();
};
proto.setHighlightColour = function ( colour ) {
this.changeFormat({
tag: 'SPAN',
attributes: {
'class': 'highlight',
style: 'background-color: ' + colour
}
}, {
tag: 'SPAN',
attributes: { 'class': 'highlight' }
});
return this.focus();
};
proto.setTextAlignment = function ( alignment ) {
this.forEachBlock( function ( block ) {
block.className = ( block.className
.split( /\s+/ )
.filter( function ( klass ) {
return !( /align/.test( klass ) );
})
.join( ' ' ) +
' align-' + alignment ).trim();
block.style.textAlign = alignment;
}, true );
return this.focus();
};
proto.setTextDirection = function ( direction ) {
this.forEachBlock( function ( block ) {
block.className = ( block.className
.split( /\s+/ )
.filter( function ( klass ) {
return !( /dir/.test( klass ) );
})
.join( ' ' ) +
' dir-' + direction ).trim();
block.dir = direction;
}, true );
return this.focus();
};
proto.increaseQuoteLevel = command( 'modifyBlocks', increaseBlockQuoteLevel );
proto.decreaseQuoteLevel = command( 'modifyBlocks', decreaseBlockQuoteLevel );
proto.makeUnorderedList = command( 'modifyBlocks', makeUnorderedList );
proto.makeOrderedList = command( 'modifyBlocks', makeOrderedList );
proto.removeList = command( 'modifyBlocks', removeList );
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );