0
Fork 0
mirror of https://github.com/fastmail/Squire.git synced 2025-01-03 05:00:13 -05:00

New (increase|decrease)ListLevel algorithms

Fixes #287
This commit is contained in:
Neil Jenkins 2017-08-15 11:11:48 +10:00
parent 6842cb94eb
commit 833d7dfdbd
4 changed files with 298 additions and 148 deletions

View file

@ -1451,7 +1451,7 @@ var keyHandlers = {
// Break list // Break list
if ( getNearest( block, root, 'UL' ) || if ( getNearest( block, root, 'UL' ) ||
getNearest( block, root, 'OL' ) ) { getNearest( block, root, 'OL' ) ) {
return self.modifyBlocks( decreaseListLevel, range ); return self.decreaseListLevel( range );
} }
// Break blockquote // Break blockquote
else if ( getNearest( block, root, 'BLOCKQUOTE' ) ) { else if ( getNearest( block, root, 'BLOCKQUOTE' ) ) {
@ -1558,7 +1558,7 @@ var keyHandlers = {
// Break list // Break list
if ( getNearest( current, root, 'UL' ) || if ( getNearest( current, root, 'UL' ) ||
getNearest( current, root, 'OL' ) ) { getNearest( current, root, 'OL' ) ) {
return self.modifyBlocks( decreaseListLevel, range ); return self.decreaseListLevel( range );
} }
// Break blockquote // Break blockquote
else if ( getNearest( current, root, 'BLOCKQUOTE' ) ) { else if ( getNearest( current, root, 'BLOCKQUOTE' ) ) {
@ -1653,12 +1653,12 @@ var keyHandlers = {
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) { if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) {
node = getStartBlockOfRange( range, root ); node = getStartBlockOfRange( range, root );
// Iterate through the block's parents // Iterate through the block's parents
while ( parent = node.parentNode ) { while ( ( parent = node.parentNode ) ) {
// If we find a UL or OL (so are in a list, node must be an LI) // If we find a UL or OL (so are in a list, node must be an LI)
if ( parent.nodeName === 'UL' || parent.nodeName === 'OL' ) { if ( parent.nodeName === 'UL' || parent.nodeName === 'OL' ) {
// Then increase the list level // Then increase the list level
event.preventDefault(); event.preventDefault();
self.modifyBlocks( increaseListLevel, range ); self.increaseListLevel( range );
break; break;
} }
node = parent; node = parent;
@ -1676,7 +1676,7 @@ var keyHandlers = {
if ( getNearest( node, root, 'UL' ) || if ( getNearest( node, root, 'UL' ) ||
getNearest( node, root, 'OL' ) ) { getNearest( node, root, 'OL' ) ) {
event.preventDefault(); event.preventDefault();
self.modifyBlocks( decreaseListLevel, range ); self.decreaseListLevel( range );
} }
} }
}, },
@ -3888,77 +3888,155 @@ var removeList = function ( frag ) {
return frag; return frag;
}; };
var increaseListLevel = function ( frag ) { var getListSelection = function ( range, root ) {
var items = frag.querySelectorAll( 'LI' ), // Get start+end li in single common ancestor
i, l, item, var list = range.commonAncestorContainer;
type, newParent, var startLi = range.startContainer;
tagAttributes = this._config.tagAttributes, var endLi = range.endContainer;
listAttrs; while ( list && list !== root && !/^[OU]L$/.test( list.nodeName ) ) {
for ( i = 0, l = items.length; i < l; i += 1 ) { list = list.parentNode;
item = items[i];
if ( !isContainer( item.firstChild ) ) {
// type => 'UL' or 'OL'
type = item.parentNode.nodeName;
newParent = item.previousSibling;
if ( !newParent || !( newParent = newParent.lastChild ) ||
newParent.nodeName !== type ) {
listAttrs = tagAttributes[ type.toLowerCase() ];
newParent = this.createElement( type, listAttrs );
replaceWith(
item,
newParent
);
}
newParent.appendChild( item );
}
} }
return frag; if ( !list || list === root ) {
return null;
}
if ( startLi === list ) {
startLi = startLi.childNodes[ range.startOffset ];
}
if ( endLi === list ) {
endLi = endLi.childNodes[ range.endOffset ];
}
while ( startLi && startLi.parentNode !== list ) {
startLi = startLi.parentNode;
}
while ( endLi && endLi.parentNode !== list ) {
endLi = endLi.parentNode;
}
return [ list, startLi, endLi ];
}; };
var decreaseListLevel = function ( frag ) { proto.increaseListLevel = function ( range ) {
var root = this._root; if ( !range && !( range = this.getSelection() ) ) {
var items = frag.querySelectorAll( 'LI' ); return this.focus();
Array.prototype.filter.call( items, function ( el ) { }
return !isContainer( el.firstChild );
}).forEach( function ( item ) {
var parent = item.parentNode,
newParent = parent.parentNode,
first = item.firstChild,
node = first,
next;
if ( item.previousSibling ) {
parent = split( parent, item, newParent, root );
}
// if the new parent is another list then we simply move the node var listSelection = getListSelection( range, root );
// e.g. `ul > ul > li` becomes `ul > li` if ( !listSelection ) {
if ( /^[OU]L$/.test( newParent.nodeName ) ) { return this.focus();
newParent.insertBefore( item, parent ); }
if ( !parent.firstChild ) {
newParent.removeChild( parent ); var list = listSelection[0];
} var startLi = listSelection[1];
} else { var endLi = listSelection[2];
while ( node ) { if ( !startLi || startLi === list.firstChild ) {
next = node.nextSibling; return this.focus();
if ( isContainer( node ) ) { }
break;
} // Save undo checkpoint and bookmark selection
newParent.insertBefore( node, parent ); this._recordUndoState( range, this._isInUndoState );
node = next;
} // Increase list depth
var type = list.nodeName;
var newParent = startLi.previousSibling;
var listAttrs, next;
if ( newParent.nodeName !== type ) {
listAttrs = this._config.tagAttributes[ type.toLowerCase() ];
newParent = this.createElement( type, listAttrs );
list.insertBefore( newParent, startLi );
}
do {
next = startLi === endLi ? null : startLi.nextSibling;
newParent.appendChild( startLi );
} while ( ( startLi = next ) );
next = newParent.nextSibling;
if ( next ) {
mergeContainers( next, this._root );
}
// Restore selection
this._getRangeAndRemoveBookmark( range );
this.setSelection( range );
this._updatePath( range, true );
// We're not still in an undo state
if ( !canObserveMutations ) {
this._docWasChanged();
}
return this.focus();
};
proto.decreaseListLevel = function ( range ) {
if ( !range && !( range = this.getSelection() ) ) {
return this.focus();
}
var root = this._root;
var listSelection = getListSelection( range, root );
if ( !listSelection ) {
return this.focus();
}
var list = listSelection[0];
var startLi = listSelection[1];
var endLi = listSelection[2];
if ( !startLi ) {
startLi = list.firstChild;
}
if ( !endLi ) {
endLi = list.lastChild;
}
// Save undo checkpoint and bookmark selection
this._recordUndoState( range, this._isInUndoState );
// Find the new parent list node
var newParent = list.parentNode;
var next;
// Split list if necesary
var insertBefore = !endLi.nextSibling ?
list.nextSibling :
split( list, endLi.nextSibling, newParent, root );
if ( newParent !== root && newParent.nodeName === 'LI' ) {
newParent = newParent.parentNode;
while ( insertBefore ) {
next = insertBefore.nextSibling;
endLi.appendChild( insertBefore );
insertBefore = next;
} }
if ( newParent.nodeName === 'LI' && first.previousSibling ) { insertBefore = list.parentNode.nextSibling;
split( newParent, first, newParent.parentNode, root ); }
var makeNotList = !/^[OU]L$/.test( newParent.nodeName );
do {
next = startLi === endLi ? null : startLi.nextSibling;
list.removeChild( startLi );
if ( makeNotList && startLi.nodeName === 'LI' ) {
startLi = this.createDefaultBlock([ empty( startLi ) ]);
} }
while ( item !== frag && !item.childNodes.length ) { newParent.insertBefore( startLi, insertBefore );
parent = item.parentNode; } while ( ( startLi = next ) );
parent.removeChild( item );
item = parent; if ( !list.firstChild ) {
} detach( list );
}, this ); }
fixContainer( frag, root );
return frag; if ( insertBefore ) {
mergeContainers( insertBefore, root );
}
// Restore selection
this._getRangeAndRemoveBookmark( range );
this.setSelection( range );
this._updatePath( range, true );
// We're not still in an undo state
if ( !canObserveMutations ) {
this._docWasChanged();
}
return this.focus();
}; };
proto._ensureBottomLine = function () { proto._ensureBottomLine = function () {
@ -4554,9 +4632,6 @@ proto.makeUnorderedList = command( 'modifyBlocks', makeUnorderedList );
proto.makeOrderedList = command( 'modifyBlocks', makeOrderedList ); proto.makeOrderedList = command( 'modifyBlocks', makeOrderedList );
proto.removeList = command( 'modifyBlocks', removeList ); proto.removeList = command( 'modifyBlocks', removeList );
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );
// Node.js exports // Node.js exports
Squire.isInline = isInline; Squire.isInline = isInline;
Squire.isBlock = isBlock; Squire.isBlock = isBlock;

File diff suppressed because one or more lines are too long

View file

@ -1454,77 +1454,155 @@ var removeList = function ( frag ) {
return frag; return frag;
}; };
var increaseListLevel = function ( frag ) { var getListSelection = function ( range, root ) {
var items = frag.querySelectorAll( 'LI' ), // Get start+end li in single common ancestor
i, l, item, var list = range.commonAncestorContainer;
type, newParent, var startLi = range.startContainer;
tagAttributes = this._config.tagAttributes, var endLi = range.endContainer;
listAttrs; while ( list && list !== root && !/^[OU]L$/.test( list.nodeName ) ) {
for ( i = 0, l = items.length; i < l; i += 1 ) { list = list.parentNode;
item = items[i];
if ( !isContainer( item.firstChild ) ) {
// type => 'UL' or 'OL'
type = item.parentNode.nodeName;
newParent = item.previousSibling;
if ( !newParent || !( newParent = newParent.lastChild ) ||
newParent.nodeName !== type ) {
listAttrs = tagAttributes[ type.toLowerCase() ];
newParent = this.createElement( type, listAttrs );
replaceWith(
item,
newParent
);
}
newParent.appendChild( item );
}
} }
return frag; if ( !list || list === root ) {
return null;
}
if ( startLi === list ) {
startLi = startLi.childNodes[ range.startOffset ];
}
if ( endLi === list ) {
endLi = endLi.childNodes[ range.endOffset ];
}
while ( startLi && startLi.parentNode !== list ) {
startLi = startLi.parentNode;
}
while ( endLi && endLi.parentNode !== list ) {
endLi = endLi.parentNode;
}
return [ list, startLi, endLi ];
}; };
var decreaseListLevel = function ( frag ) { proto.increaseListLevel = function ( range ) {
var root = this._root; if ( !range && !( range = this.getSelection() ) ) {
var items = frag.querySelectorAll( 'LI' ); return this.focus();
Array.prototype.filter.call( items, function ( el ) { }
return !isContainer( el.firstChild );
}).forEach( function ( item ) {
var parent = item.parentNode,
newParent = parent.parentNode,
first = item.firstChild,
node = first,
next;
if ( item.previousSibling ) {
parent = split( parent, item, newParent, root );
}
// if the new parent is another list then we simply move the node var listSelection = getListSelection( range, root );
// e.g. `ul > ul > li` becomes `ul > li` if ( !listSelection ) {
if ( /^[OU]L$/.test( newParent.nodeName ) ) { return this.focus();
newParent.insertBefore( item, parent ); }
if ( !parent.firstChild ) {
newParent.removeChild( parent ); var list = listSelection[0];
} var startLi = listSelection[1];
} else { var endLi = listSelection[2];
while ( node ) { if ( !startLi || startLi === list.firstChild ) {
next = node.nextSibling; return this.focus();
if ( isContainer( node ) ) { }
break;
} // Save undo checkpoint and bookmark selection
newParent.insertBefore( node, parent ); this._recordUndoState( range, this._isInUndoState );
node = next;
} // Increase list depth
var type = list.nodeName;
var newParent = startLi.previousSibling;
var listAttrs, next;
if ( newParent.nodeName !== type ) {
listAttrs = this._config.tagAttributes[ type.toLowerCase() ];
newParent = this.createElement( type, listAttrs );
list.insertBefore( newParent, startLi );
}
do {
next = startLi === endLi ? null : startLi.nextSibling;
newParent.appendChild( startLi );
} while ( ( startLi = next ) );
next = newParent.nextSibling;
if ( next ) {
mergeContainers( next, this._root );
}
// Restore selection
this._getRangeAndRemoveBookmark( range );
this.setSelection( range );
this._updatePath( range, true );
// We're not still in an undo state
if ( !canObserveMutations ) {
this._docWasChanged();
}
return this.focus();
};
proto.decreaseListLevel = function ( range ) {
if ( !range && !( range = this.getSelection() ) ) {
return this.focus();
}
var root = this._root;
var listSelection = getListSelection( range, root );
if ( !listSelection ) {
return this.focus();
}
var list = listSelection[0];
var startLi = listSelection[1];
var endLi = listSelection[2];
if ( !startLi ) {
startLi = list.firstChild;
}
if ( !endLi ) {
endLi = list.lastChild;
}
// Save undo checkpoint and bookmark selection
this._recordUndoState( range, this._isInUndoState );
// Find the new parent list node
var newParent = list.parentNode;
var next;
// Split list if necesary
var insertBefore = !endLi.nextSibling ?
list.nextSibling :
split( list, endLi.nextSibling, newParent, root );
if ( newParent !== root && newParent.nodeName === 'LI' ) {
newParent = newParent.parentNode;
while ( insertBefore ) {
next = insertBefore.nextSibling;
endLi.appendChild( insertBefore );
insertBefore = next;
} }
if ( newParent.nodeName === 'LI' && first.previousSibling ) { insertBefore = list.parentNode.nextSibling;
split( newParent, first, newParent.parentNode, root ); }
var makeNotList = !/^[OU]L$/.test( newParent.nodeName );
do {
next = startLi === endLi ? null : startLi.nextSibling;
list.removeChild( startLi );
if ( makeNotList && startLi.nodeName === 'LI' ) {
startLi = this.createDefaultBlock([ empty( startLi ) ]);
} }
while ( item !== frag && !item.childNodes.length ) { newParent.insertBefore( startLi, insertBefore );
parent = item.parentNode; } while ( ( startLi = next ) );
parent.removeChild( item );
item = parent; if ( !list.firstChild ) {
} detach( list );
}, this ); }
fixContainer( frag, root );
return frag; if ( insertBefore ) {
mergeContainers( insertBefore, root );
}
// Restore selection
this._getRangeAndRemoveBookmark( range );
this.setSelection( range );
this._updatePath( range, true );
// We're not still in an undo state
if ( !canObserveMutations ) {
this._docWasChanged();
}
return this.focus();
}; };
proto._ensureBottomLine = function () { proto._ensureBottomLine = function () {
@ -2119,6 +2197,3 @@ proto.decreaseQuoteLevel = command( 'modifyBlocks', decreaseBlockQuoteLevel );
proto.makeUnorderedList = command( 'modifyBlocks', makeUnorderedList ); proto.makeUnorderedList = command( 'modifyBlocks', makeUnorderedList );
proto.makeOrderedList = command( 'modifyBlocks', makeOrderedList ); proto.makeOrderedList = command( 'modifyBlocks', makeOrderedList );
proto.removeList = command( 'modifyBlocks', removeList ); proto.removeList = command( 'modifyBlocks', removeList );
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );

View file

@ -192,7 +192,7 @@ var keyHandlers = {
// Break list // Break list
if ( getNearest( block, root, 'UL' ) || if ( getNearest( block, root, 'UL' ) ||
getNearest( block, root, 'OL' ) ) { getNearest( block, root, 'OL' ) ) {
return self.modifyBlocks( decreaseListLevel, range ); return self.decreaseListLevel( range );
} }
// Break blockquote // Break blockquote
else if ( getNearest( block, root, 'BLOCKQUOTE' ) ) { else if ( getNearest( block, root, 'BLOCKQUOTE' ) ) {
@ -299,7 +299,7 @@ var keyHandlers = {
// Break list // Break list
if ( getNearest( current, root, 'UL' ) || if ( getNearest( current, root, 'UL' ) ||
getNearest( current, root, 'OL' ) ) { getNearest( current, root, 'OL' ) ) {
return self.modifyBlocks( decreaseListLevel, range ); return self.decreaseListLevel( range );
} }
// Break blockquote // Break blockquote
else if ( getNearest( current, root, 'BLOCKQUOTE' ) ) { else if ( getNearest( current, root, 'BLOCKQUOTE' ) ) {
@ -394,12 +394,12 @@ var keyHandlers = {
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) { if ( range.collapsed && rangeDoesStartAtBlockBoundary( range, root ) ) {
node = getStartBlockOfRange( range, root ); node = getStartBlockOfRange( range, root );
// Iterate through the block's parents // Iterate through the block's parents
while ( parent = node.parentNode ) { while ( ( parent = node.parentNode ) ) {
// If we find a UL or OL (so are in a list, node must be an LI) // If we find a UL or OL (so are in a list, node must be an LI)
if ( parent.nodeName === 'UL' || parent.nodeName === 'OL' ) { if ( parent.nodeName === 'UL' || parent.nodeName === 'OL' ) {
// Then increase the list level // Then increase the list level
event.preventDefault(); event.preventDefault();
self.modifyBlocks( increaseListLevel, range ); self.increaseListLevel( range );
break; break;
} }
node = parent; node = parent;
@ -417,7 +417,7 @@ var keyHandlers = {
if ( getNearest( node, root, 'UL' ) || if ( getNearest( node, root, 'UL' ) ||
getNearest( node, root, 'OL' ) ) { getNearest( node, root, 'OL' ) ) {
event.preventDefault(); event.preventDefault();
self.modifyBlocks( decreaseListLevel, range ); self.decreaseListLevel( range );
} }
} }
}, },