mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-02-24 14:57:26 +08:00
Improve Squire edit history stack
This commit is contained in:
parent
ed1dc4dbc1
commit
d4f55a02a4
1 changed files with 141 additions and 150 deletions
291
vendors/squire/build/squire-raw.js
vendored
291
vendors/squire/build/squire-raw.js
vendored
|
@ -17,6 +17,7 @@ const
|
|||
DOCUMENT_FRAGMENT_NODE = 11, // Node.DOCUMENT_FRAGMENT_NODE,
|
||||
SHOW_ELEMENT = 1, // NodeFilter.SHOW_ELEMENT,
|
||||
SHOW_TEXT = 4, // NodeFilter.SHOW_TEXT,
|
||||
SHOW_ELEMENT_OR_TEXT = 5,
|
||||
|
||||
START_TO_START = 0, // Range.START_TO_START
|
||||
START_TO_END = 1, // Range.START_TO_END
|
||||
|
@ -1034,7 +1035,7 @@ const
|
|||
},
|
||||
|
||||
newContentWalker = root => createTreeWalker( root,
|
||||
SHOW_TEXT|SHOW_ELEMENT,
|
||||
SHOW_ELEMENT_OR_TEXT,
|
||||
node => node.nodeType === TEXT_NODE ? notWS.test( node.data ) : node.nodeName === 'IMG'
|
||||
),
|
||||
|
||||
|
@ -1206,7 +1207,7 @@ const
|
|||
// 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 );
|
||||
self._recordUndoState( range, false );
|
||||
if ( self._config.addLinks ) {
|
||||
addLinks( range.startContainer, root, self );
|
||||
}
|
||||
|
@ -1505,7 +1506,7 @@ const
|
|||
while ( isInline( nonInlineParent ) ) {
|
||||
nonInlineParent = nonInlineParent.parentNode;
|
||||
}
|
||||
let walker = createTreeWalker( nonInlineParent, SHOW_TEXT|SHOW_ELEMENT );
|
||||
let walker = createTreeWalker( nonInlineParent, SHOW_ELEMENT_OR_TEXT );
|
||||
|
||||
for ( i = 0, l = children.length; i < l; ++i ) {
|
||||
child = children[i];
|
||||
|
@ -1612,7 +1613,7 @@ const
|
|||
while ( isInline( block ) ) {
|
||||
block = block.parentNode;
|
||||
}
|
||||
walker = createTreeWalker( block, SHOW_ELEMENT|SHOW_TEXT, notWSTextNode );
|
||||
walker = createTreeWalker( block, SHOW_ELEMENT_OR_TEXT, notWSTextNode );
|
||||
walker.currentNode = br;
|
||||
return !!walker.nextNode() ||
|
||||
( isLBIfEmptyBlock && !walker.previousNode() );
|
||||
|
@ -1929,7 +1930,7 @@ let contentWalker,
|
|||
TreeWalker.prototype.previousPONode = function () {
|
||||
let current = this.currentNode,
|
||||
root = this.root,
|
||||
nodeType = this.nodeType,
|
||||
nodeType = this.nodeType, // whatToShow?
|
||||
filter = this.filter,
|
||||
node;
|
||||
while ( current ) {
|
||||
|
@ -2405,7 +2406,7 @@ let keyHandlers = {
|
|||
space: ( self, _, range ) => {
|
||||
let node;
|
||||
let root = self._root;
|
||||
self._recordUndoState( range );
|
||||
self._recordUndoState( range, false );
|
||||
if ( self._config.addLinks ) {
|
||||
addLinks( range.startContainer, root, self );
|
||||
}
|
||||
|
@ -2470,6 +2471,123 @@ keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
|||
keyHandlers[ 'undo' ] = mapKeyTo( 'undo' );
|
||||
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
||||
|
||||
class EditStack extends Array
|
||||
{
|
||||
constructor(squire) {
|
||||
super();
|
||||
this.squire = squire;
|
||||
this.index = -1;
|
||||
this.inUndoState = false;
|
||||
|
||||
this.threshold = -1; // -1 means no threshold
|
||||
this.limit = -1; // -1 means no limit
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.index = -1;
|
||||
this.length = 0;
|
||||
}
|
||||
|
||||
stateChanged ( /*canUndo, canRedo*/ ) {
|
||||
this.squire.fireEvent( 'undoStateChange', {
|
||||
canUndo: this.index > 0,
|
||||
canRedo: this.index + 1 < this.length
|
||||
});
|
||||
this.squire.fireEvent( 'input' );
|
||||
}
|
||||
|
||||
docWasChanged () {
|
||||
if ( this.inUndoState ) {
|
||||
this.inUndoState = false;
|
||||
this.stateChanged ( /*true, false*/ );
|
||||
} else
|
||||
this.squire.fireEvent( 'input' );
|
||||
}
|
||||
|
||||
// Leaves bookmark
|
||||
recordUndoState ( range, replace ) {
|
||||
replace = replace !== false && this.inUndoState;
|
||||
// Don't record if we're already in an undo state
|
||||
if ( !this.inUndoState || replace ) {
|
||||
// Advance pointer to new position
|
||||
let undoIndex = this.index;
|
||||
let undoThreshold = this.threshold;
|
||||
let undoLimit = this.limit;
|
||||
let squire = this.squire;
|
||||
let html;
|
||||
|
||||
if ( !replace ) {
|
||||
++undoIndex;
|
||||
}
|
||||
undoIndex = Math.max(0, undoIndex);
|
||||
|
||||
// Truncate stack if longer (i.e. if has been previously undone)
|
||||
this.length = Math.min(undoIndex + 1, this.length);
|
||||
|
||||
// Get data
|
||||
if ( range ) {
|
||||
squire._saveRangeToBookmark( range );
|
||||
}
|
||||
html = squire._getHTML();
|
||||
|
||||
// If this document is above the configured size threshold,
|
||||
// limit the number of saved undo states.
|
||||
// Threshold is in bytes, JS uses 2 bytes per character
|
||||
if ( undoThreshold > -1 && html.length * 2 > undoThreshold
|
||||
&& undoLimit > -1 && undoIndex > undoLimit ) {
|
||||
this.splice( 0, undoIndex - undoLimit );
|
||||
}
|
||||
|
||||
// Save data
|
||||
this[ undoIndex ] = html;
|
||||
this.index = undoIndex;
|
||||
this.inUndoState = true;
|
||||
}
|
||||
}
|
||||
|
||||
saveUndoState ( range ) {
|
||||
let squire = this.squire;
|
||||
if ( range === undefined ) {
|
||||
range = squire.getSelection();
|
||||
}
|
||||
this.recordUndoState( range );
|
||||
squire._getRangeAndRemoveBookmark( range );
|
||||
}
|
||||
|
||||
undo () {
|
||||
let squire = this.squire,
|
||||
undoIndex = this.index - 1;
|
||||
// Sanity check: must not be at beginning of the history stack
|
||||
if ( undoIndex >= 0 || !this.inUndoState ) {
|
||||
// Make sure any changes since last checkpoint are saved.
|
||||
this.recordUndoState( squire.getSelection(), false );
|
||||
this.index = undoIndex;
|
||||
squire._setHTML( this[ undoIndex ] );
|
||||
let range = squire._getRangeAndRemoveBookmark();
|
||||
if ( range ) {
|
||||
squire.setSelection( range );
|
||||
}
|
||||
this.inUndoState = true;
|
||||
this.stateChanged ( /*undoIndex > 0, true*/ );
|
||||
}
|
||||
}
|
||||
|
||||
redo () {
|
||||
// Sanity check: must not be at end of stack and must be in an undo state.
|
||||
let squire = this.squire,
|
||||
undoIndex = this.index + 1;
|
||||
if ( undoIndex < this.length && this.inUndoState ) {
|
||||
this.index = undoIndex;
|
||||
squire._setHTML( this[ undoIndex ] );
|
||||
let range = squire._getRangeAndRemoveBookmark();
|
||||
if ( range ) {
|
||||
squire.setSelection( range );
|
||||
}
|
||||
this.stateChanged ( /*true, undoIndex + 1 < this.length*/ );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Squire
|
||||
{
|
||||
constructor (root, config) {
|
||||
|
@ -2501,10 +2619,7 @@ class Squire
|
|||
};
|
||||
this.addEventListener('selectstart', () => this.addEventListener('selectionchange', selectionchange));
|
||||
|
||||
this._undoIndex = -1;
|
||||
this._undoStack = [];
|
||||
this._undoStackLength = 0;
|
||||
this._isInUndoState = false;
|
||||
this.editStack = new EditStack(this);
|
||||
this._ignoreChange = false;
|
||||
this._ignoreAllChanges = false;
|
||||
|
||||
|
@ -2558,10 +2673,6 @@ class Squire
|
|||
setConfig ( config ) {
|
||||
config = mergeObjects({
|
||||
blockTag: 'DIV',
|
||||
undo: {
|
||||
documentSizeThreshold: -1, // -1 means no threshold
|
||||
undoLimit: -1 // -1 means no limit
|
||||
},
|
||||
addLinks: true
|
||||
}, config, true );
|
||||
|
||||
|
@ -2663,24 +2774,6 @@ class Squire
|
|||
return this;
|
||||
}
|
||||
|
||||
destroy () {
|
||||
let events = this._events;
|
||||
let type;
|
||||
|
||||
for ( type in events ) {
|
||||
this.removeEventListener( type );
|
||||
}
|
||||
if ( this._mutation ) {
|
||||
this._mutation.disconnect();
|
||||
}
|
||||
delete this._root.__squire__;
|
||||
|
||||
// Destroy undo stack
|
||||
this._undoIndex = -1;
|
||||
this._undoStack = [];
|
||||
this._undoStackLength = 0;
|
||||
}
|
||||
|
||||
handleEvent ( event ) {
|
||||
this.fireEvent( event.type, event );
|
||||
}
|
||||
|
@ -2849,7 +2942,7 @@ class Squire
|
|||
}
|
||||
let walker = createTreeWalker(
|
||||
range.commonAncestorContainer,
|
||||
SHOW_TEXT|SHOW_ELEMENT,
|
||||
SHOW_ELEMENT_OR_TEXT,
|
||||
node => isNodeContainedInRange( range, node )
|
||||
);
|
||||
let startContainer = range.startContainer;
|
||||
|
@ -3024,121 +3117,29 @@ class Squire
|
|||
|
||||
_docWasChanged () {
|
||||
nodeCategoryCache = new WeakMap();
|
||||
if ( this._ignoreAllChanges ) {
|
||||
return;
|
||||
if ( !this._ignoreAllChanges ) {
|
||||
if ( this._ignoreChange ) {
|
||||
this._ignoreChange = false;
|
||||
} else {
|
||||
this.editStack.docWasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
if ( this._ignoreChange ) {
|
||||
this._ignoreChange = false;
|
||||
return;
|
||||
}
|
||||
if ( this._isInUndoState ) {
|
||||
this._isInUndoState = false;
|
||||
this.fireEvent( 'undoStateChange', {
|
||||
canUndo: true,
|
||||
canRedo: false
|
||||
});
|
||||
}
|
||||
this.fireEvent( 'input' );
|
||||
}
|
||||
|
||||
// Leaves bookmark
|
||||
_recordUndoState ( range, replace ) {
|
||||
// Don't record if we're already in an undo state
|
||||
if ( !this._isInUndoState|| replace ) {
|
||||
// Advance pointer to new position
|
||||
let undoIndex = this._undoIndex;
|
||||
let undoStack = this._undoStack;
|
||||
let undoConfig = this._config.undo;
|
||||
let undoThreshold = undoConfig.documentSizeThreshold;
|
||||
let undoLimit = undoConfig.undoLimit;
|
||||
let html;
|
||||
|
||||
if ( !replace ) {
|
||||
++undoIndex;
|
||||
}
|
||||
|
||||
// Truncate stack if longer (i.e. if has been previously undone)
|
||||
if ( undoIndex < this._undoStackLength ) {
|
||||
undoStack.length = this._undoStackLength = undoIndex;
|
||||
}
|
||||
|
||||
// Get data
|
||||
if ( range ) {
|
||||
this._saveRangeToBookmark( range );
|
||||
}
|
||||
html = this._getHTML();
|
||||
|
||||
// If this document is above the configured size threshold,
|
||||
// limit the number of saved undo states.
|
||||
// Threshold is in bytes, JS uses 2 bytes per character
|
||||
if ( undoThreshold > -1 && html.length * 2 > undoThreshold ) {
|
||||
if ( undoLimit > -1 && undoIndex > undoLimit ) {
|
||||
undoStack.splice( 0, undoIndex - undoLimit );
|
||||
undoIndex = undoLimit;
|
||||
this._undoStackLength = undoLimit;
|
||||
}
|
||||
}
|
||||
|
||||
// Save data
|
||||
undoStack[ undoIndex ] = html;
|
||||
this._undoIndex = undoIndex;
|
||||
++this._undoStackLength;
|
||||
this._isInUndoState = true;
|
||||
}
|
||||
this.editStack.recordUndoState( range, replace );
|
||||
}
|
||||
|
||||
saveUndoState ( range ) {
|
||||
if ( range === undefined ) {
|
||||
range = this.getSelection();
|
||||
}
|
||||
this._recordUndoState( range, this._isInUndoState );
|
||||
this._getRangeAndRemoveBookmark( range );
|
||||
|
||||
return this;
|
||||
this.editStack.saveUndoState( range );
|
||||
}
|
||||
|
||||
undo () {
|
||||
// Sanity check: must not be at beginning of the history stack
|
||||
if ( this._undoIndex !== 0 || !this._isInUndoState ) {
|
||||
// Make sure any changes since last checkpoint are saved.
|
||||
this._recordUndoState( this.getSelection(), false );
|
||||
|
||||
--this._undoIndex;
|
||||
this._setHTML( this._undoStack[ this._undoIndex ] );
|
||||
let range = this._getRangeAndRemoveBookmark();
|
||||
if ( range ) {
|
||||
this.setSelection( range );
|
||||
}
|
||||
this._isInUndoState = true;
|
||||
this.fireEvent( 'undoStateChange', {
|
||||
canUndo: this._undoIndex !== 0,
|
||||
canRedo: true
|
||||
});
|
||||
this.fireEvent( 'input' );
|
||||
}
|
||||
return this;
|
||||
this.editStack.undo();
|
||||
}
|
||||
|
||||
redo () {
|
||||
// Sanity check: must not be at end of stack and must be in an undo
|
||||
// state.
|
||||
let undoIndex = this._undoIndex,
|
||||
undoStackLength = this._undoStackLength;
|
||||
if ( undoIndex + 1 < undoStackLength && this._isInUndoState ) {
|
||||
++this._undoIndex;
|
||||
this._setHTML( this._undoStack[ this._undoIndex ] );
|
||||
let range = this._getRangeAndRemoveBookmark();
|
||||
if ( range ) {
|
||||
this.setSelection( range );
|
||||
}
|
||||
this.fireEvent( 'undoStateChange', {
|
||||
canUndo: true,
|
||||
canRedo: undoIndex + 2 < undoStackLength
|
||||
});
|
||||
this.fireEvent( 'input' );
|
||||
}
|
||||
return this;
|
||||
this.editStack.redo();
|
||||
}
|
||||
|
||||
// --- Inline formatting ---
|
||||
|
@ -3282,7 +3283,7 @@ class Squire
|
|||
// them, and adding other styles is harmless.
|
||||
walker = createTreeWalker(
|
||||
range.commonAncestorContainer,
|
||||
SHOW_TEXT|SHOW_ELEMENT,
|
||||
SHOW_ELEMENT_OR_TEXT,
|
||||
node => ( node.nodeType === TEXT_NODE ||
|
||||
node.nodeName === 'BR' ||
|
||||
node.nodeName === 'IMG'
|
||||
|
@ -3515,7 +3516,7 @@ class Squire
|
|||
}
|
||||
|
||||
// 1. Save undo checkpoint and bookmark selection
|
||||
this._recordUndoState( range, this._isInUndoState );
|
||||
this._recordUndoState( range );
|
||||
|
||||
let root = this._root;
|
||||
let frag;
|
||||
|
@ -3563,7 +3564,7 @@ class Squire
|
|||
}
|
||||
|
||||
// Save undo checkpoint and bookmark selection
|
||||
this._recordUndoState( range, this._isInUndoState );
|
||||
this._recordUndoState( range );
|
||||
|
||||
// Increase list depth
|
||||
let type = list.nodeName;
|
||||
|
@ -3613,7 +3614,7 @@ class Squire
|
|||
}
|
||||
|
||||
// Save undo checkpoint and bookmark selection
|
||||
this._recordUndoState( range, this._isInUndoState );
|
||||
this._recordUndoState( range );
|
||||
|
||||
if ( startLi ) {
|
||||
// Find the new parent list node
|
||||
|
@ -3670,13 +3671,6 @@ class Squire
|
|||
}
|
||||
}
|
||||
|
||||
// --- Keyboard interaction ---
|
||||
|
||||
setKeyHandler ( key, fn ) {
|
||||
this._keyHandlers[ key ] = fn;
|
||||
return this;
|
||||
}
|
||||
|
||||
// --- Get/Set data ---
|
||||
|
||||
_getHTML () {
|
||||
|
@ -3751,10 +3745,7 @@ class Squire
|
|||
fixCursor( root, root );
|
||||
|
||||
// Reset the undo stack
|
||||
this._undoIndex = -1;
|
||||
this._undoStack.length = 0;
|
||||
this._undoStackLength = 0;
|
||||
this._isInUndoState = false;
|
||||
this.editStack.clear();
|
||||
|
||||
// Record undo state
|
||||
let range = this._getRangeAndRemoveBookmark() ||
|
||||
|
|
Loading…
Reference in a new issue