mirror of
https://github.com/fastmail/Squire.git
synced 2024-12-22 15:23:29 -05:00
Tidy. Make backspace/delete aware of non-editable blocks
This commit is contained in:
parent
f2090b05d0
commit
52a10d463c
2 changed files with 90 additions and 71 deletions
File diff suppressed because one or more lines are too long
141
source/Editor.js
141
source/Editor.js
|
@ -15,19 +15,21 @@
|
||||||
FILTER_ACCEPT = 1, // NodeFilter.FILTER_ACCEPT,
|
FILTER_ACCEPT = 1, // NodeFilter.FILTER_ACCEPT,
|
||||||
FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP;
|
FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP;
|
||||||
|
|
||||||
var win = doc.defaultView,
|
var win = doc.defaultView;
|
||||||
body = doc.body;
|
var body = doc.body;
|
||||||
|
|
||||||
// Browser sniffing. Unfortunately necessary.
|
// Browser sniffing. Unfortunately necessary.
|
||||||
|
var ua = navigator.userAgent;
|
||||||
var isOpera = !!win.opera;
|
var isOpera = !!win.opera;
|
||||||
var isIE = !!win.ie;
|
var isIE = !!win.ie;
|
||||||
var isIE8 = win.ie === 8;
|
var isIE8 = ( win.ie === 8 );
|
||||||
var isGecko = /Gecko\//.test( navigator.userAgent );
|
var isGecko = /Gecko\//.test( ua );
|
||||||
var isWebKit = /WebKit/.test( navigator.userAgent );
|
var isWebKit = /WebKit/.test( ua );
|
||||||
var isIOS = /iP(?:ad|hone|od)/.test( navigator.userAgent );
|
var isIOS = /iP(?:ad|hone|od)/.test( ua );
|
||||||
|
|
||||||
var useTextFixer = isIE || isOpera;
|
var useTextFixer = isIE || isOpera;
|
||||||
var cantFocusEmptyTextNodes = isIE || isWebKit;
|
var cantFocusEmptyTextNodes = isIE || isWebKit;
|
||||||
|
var losesSelectionOnBlur = isIE;
|
||||||
|
|
||||||
// --- DOM Sugar ---
|
// --- DOM Sugar ---
|
||||||
|
|
||||||
|
@ -135,6 +137,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
var sel = win.getSelection();
|
var sel = win.getSelection();
|
||||||
|
var lastSelection = null;
|
||||||
|
|
||||||
var setSelection = function ( range ) {
|
var setSelection = function ( range ) {
|
||||||
if ( range ) {
|
if ( range ) {
|
||||||
|
@ -150,7 +153,6 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var lastSelection = null;
|
|
||||||
var getSelection = function () {
|
var getSelection = function () {
|
||||||
if ( sel.rangeCount ) {
|
if ( sel.rangeCount ) {
|
||||||
lastSelection = sel.getRangeAt( 0 ).cloneRange();
|
lastSelection = sel.getRangeAt( 0 ).cloneRange();
|
||||||
|
@ -167,14 +169,10 @@
|
||||||
|
|
||||||
// IE9 loses selection state of iframe on blur, so make sure we
|
// IE9 loses selection state of iframe on blur, so make sure we
|
||||||
// cache it just before it loses focus.
|
// cache it just before it loses focus.
|
||||||
if ( win.ie ) {
|
if ( losesSelectionOnBlur ) {
|
||||||
win.addEventListener( 'beforedeactivate', getSelection, true );
|
win.addEventListener( 'beforedeactivate', getSelection, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastAnchorNode;
|
|
||||||
var lastFocusNode;
|
|
||||||
var path = '';
|
|
||||||
|
|
||||||
// --- Workaround for browsers that can't focus empty text nodes ---
|
// --- Workaround for browsers that can't focus empty text nodes ---
|
||||||
|
|
||||||
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
||||||
|
@ -222,6 +220,10 @@
|
||||||
|
|
||||||
// --- Path change events ---
|
// --- Path change events ---
|
||||||
|
|
||||||
|
var lastAnchorNode;
|
||||||
|
var lastFocusNode;
|
||||||
|
var path = '';
|
||||||
|
|
||||||
var updatePath = function ( range, force ) {
|
var updatePath = function ( range, force ) {
|
||||||
if ( placeholderTextNode && !force ) {
|
if ( placeholderTextNode && !force ) {
|
||||||
removePlaceholderTextNode( range );
|
removePlaceholderTextNode( range );
|
||||||
|
@ -292,6 +294,8 @@
|
||||||
|
|
||||||
// --- Bookmarking ---
|
// --- Bookmarking ---
|
||||||
|
|
||||||
|
var indexOf = Array.prototype.indexOf;
|
||||||
|
|
||||||
var startSelectionId = 'ss-' + Date.now() + '-' + Math.random();
|
var startSelectionId = 'ss-' + Date.now() + '-' + Math.random();
|
||||||
var endSelectionId = 'es-' + Date.now() + '-' + Math.random();
|
var endSelectionId = 'es-' + Date.now() + '-' + Math.random();
|
||||||
|
|
||||||
|
@ -324,8 +328,6 @@
|
||||||
range.setEndBefore( endNode );
|
range.setEndBefore( endNode );
|
||||||
};
|
};
|
||||||
|
|
||||||
var indexOf = Array.prototype.indexOf;
|
|
||||||
|
|
||||||
var getRangeAndRemoveBookmark = function ( range ) {
|
var getRangeAndRemoveBookmark = function ( range ) {
|
||||||
var start = doc.getElementById( startSelectionId ),
|
var start = doc.getElementById( startSelectionId ),
|
||||||
end = doc.getElementById( endSelectionId );
|
end = doc.getElementById( endSelectionId );
|
||||||
|
@ -374,11 +376,11 @@
|
||||||
|
|
||||||
// These values are initialised in the editor.setHTML method,
|
// These values are initialised in the editor.setHTML method,
|
||||||
// which is always called on initialisation.
|
// which is always called on initialisation.
|
||||||
var undoIndex, // = -1,
|
var undoIndex; // = -1,
|
||||||
undoStack, // = [],
|
var undoStack; // = [],
|
||||||
undoStackLength, // = 0,
|
var undoStackLength; // = 0,
|
||||||
isInUndoState, // = false,
|
var isInUndoState; // = false,
|
||||||
docWasChanged = function () {
|
var docWasChanged = function () {
|
||||||
if ( isInUndoState ) {
|
if ( isInUndoState ) {
|
||||||
isInUndoState = false;
|
isInUndoState = false;
|
||||||
fireEvent( 'undoStateChange', {
|
fireEvent( 'undoStateChange', {
|
||||||
|
@ -398,16 +400,6 @@
|
||||||
if ( !event.ctrlKey && !event.metaKey && !event.altKey &&
|
if ( !event.ctrlKey && !event.metaKey && !event.altKey &&
|
||||||
( code < 16 || code > 20 ) &&
|
( code < 16 || code > 20 ) &&
|
||||||
( code < 33 || code > 45 ) ) {
|
( code < 33 || code > 45 ) ) {
|
||||||
// If you select all in IE8 then type, it makes a P; replace it with
|
|
||||||
// a DIV.
|
|
||||||
var firstChild = body.firstChild;
|
|
||||||
if ( win.ie === 8 && firstChild.nodeName === 'P' ) {
|
|
||||||
saveRangeToBookmark( getSelection() );
|
|
||||||
firstChild.replaceWith( createElement( 'DIV', [
|
|
||||||
firstChild.empty()
|
|
||||||
]) );
|
|
||||||
setSelection( getRangeAndRemoveBookmark() );
|
|
||||||
}
|
|
||||||
docWasChanged();
|
docWasChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -727,6 +719,37 @@
|
||||||
|
|
||||||
// --- Block formatting ---
|
// --- Block formatting ---
|
||||||
|
|
||||||
|
var tagAfterSplit = {
|
||||||
|
DIV: 'DIV',
|
||||||
|
PRE: 'DIV',
|
||||||
|
H1: 'DIV',
|
||||||
|
H2: 'DIV',
|
||||||
|
H3: 'DIV',
|
||||||
|
H4: 'DIV',
|
||||||
|
H5: 'DIV',
|
||||||
|
H6: 'DIV',
|
||||||
|
P: 'DIV',
|
||||||
|
DT: 'DD',
|
||||||
|
DD: 'DT',
|
||||||
|
LI: 'LI'
|
||||||
|
};
|
||||||
|
|
||||||
|
var splitBlock = function ( block, node, offset ) {
|
||||||
|
var splitTag = tagAfterSplit[ block.nodeName ],
|
||||||
|
nodeAfterSplit = node.split( offset, block.parentNode );
|
||||||
|
|
||||||
|
// Make sure the new node is the correct type.
|
||||||
|
if ( nodeAfterSplit.nodeName !== splitTag ) {
|
||||||
|
block = createElement( splitTag );
|
||||||
|
block.className = nodeAfterSplit.dir === 'rtl' ? 'dir-rtl' : '';
|
||||||
|
block.dir = nodeAfterSplit.dir;
|
||||||
|
block.replaces( nodeAfterSplit )
|
||||||
|
.appendChild( nodeAfterSplit.empty() );
|
||||||
|
nodeAfterSplit = block;
|
||||||
|
}
|
||||||
|
return nodeAfterSplit;
|
||||||
|
};
|
||||||
|
|
||||||
var forEachBlock = function ( fn, mutates, range ) {
|
var forEachBlock = function ( fn, mutates, range ) {
|
||||||
if ( !range && !( range = getSelection() ) ) {
|
if ( !range && !( range = getSelection() ) ) {
|
||||||
return;
|
return;
|
||||||
|
@ -912,37 +935,6 @@
|
||||||
return frag;
|
return frag;
|
||||||
};
|
};
|
||||||
|
|
||||||
var tagAfterSplit = {
|
|
||||||
DIV: 'DIV',
|
|
||||||
PRE: 'DIV',
|
|
||||||
H1: 'DIV',
|
|
||||||
H2: 'DIV',
|
|
||||||
H3: 'DIV',
|
|
||||||
H4: 'DIV',
|
|
||||||
H5: 'DIV',
|
|
||||||
H6: 'DIV',
|
|
||||||
P: 'DIV',
|
|
||||||
DT: 'DD',
|
|
||||||
DD: 'DT',
|
|
||||||
LI: 'LI'
|
|
||||||
};
|
|
||||||
|
|
||||||
var splitBlock = function ( block, node, offset ) {
|
|
||||||
var splitTag = tagAfterSplit[ block.nodeName ],
|
|
||||||
nodeAfterSplit = node.split( offset, block.parentNode );
|
|
||||||
|
|
||||||
// Make sure the new node is the correct type.
|
|
||||||
if ( nodeAfterSplit.nodeName !== splitTag ) {
|
|
||||||
block = createElement( splitTag );
|
|
||||||
block.className = nodeAfterSplit.dir === 'rtl' ? 'dir-rtl' : '';
|
|
||||||
block.dir = nodeAfterSplit.dir;
|
|
||||||
block.replaces( nodeAfterSplit )
|
|
||||||
.appendChild( nodeAfterSplit.empty() );
|
|
||||||
nodeAfterSplit = block;
|
|
||||||
}
|
|
||||||
return nodeAfterSplit;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Clean ---
|
// --- Clean ---
|
||||||
|
|
||||||
var linkRegExp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])|(?:[\w\-.%+]+@(?:[\w\-]+\.)+[A-Z]{2,4}))/i;
|
var linkRegExp = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])|(?:[\w\-.%+]+@(?:[\w\-]+\.)+[A-Z]{2,4}))/i;
|
||||||
|
@ -1363,6 +1355,21 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If you select all in IE8 then type, it makes a P; replace it with
|
||||||
|
// a DIV.
|
||||||
|
if ( isIE8 ) {
|
||||||
|
addEventListener( 'keyup', function ( event ) {
|
||||||
|
var firstChild = body.firstChild;
|
||||||
|
if ( firstChild.nodeName === 'P' ) {
|
||||||
|
saveRangeToBookmark( getSelection() );
|
||||||
|
firstChild.replaceWith( createElement( 'DIV', [
|
||||||
|
firstChild.empty()
|
||||||
|
]) );
|
||||||
|
setSelection( getRangeAndRemoveBookmark() );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var keyHandlers = {
|
var keyHandlers = {
|
||||||
enter: function ( event ) {
|
enter: function ( event ) {
|
||||||
// We handle this ourselves
|
// We handle this ourselves
|
||||||
|
@ -1454,7 +1461,7 @@
|
||||||
// Focus cursor
|
// Focus cursor
|
||||||
// If there's a <b>/<i> etc. at the beginning of the split
|
// If there's a <b>/<i> etc. at the beginning of the split
|
||||||
// make sure we focus inside it.
|
// make sure we focus inside it.
|
||||||
while ( nodeAfterSplit.nodeType === ELEMENT_NODE) {
|
while ( nodeAfterSplit.nodeType === ELEMENT_NODE ) {
|
||||||
var child = nodeAfterSplit.firstChild,
|
var child = nodeAfterSplit.firstChild,
|
||||||
next;
|
next;
|
||||||
|
|
||||||
|
@ -1517,6 +1524,12 @@
|
||||||
previous = current.getPreviousBlock();
|
previous = current.getPreviousBlock();
|
||||||
// Must not be at the very beginning of the text area.
|
// Must not be at the very beginning of the text area.
|
||||||
if ( previous ) {
|
if ( previous ) {
|
||||||
|
// If not editable, just delete whole block.
|
||||||
|
if ( !previous.isContentEditable ) {
|
||||||
|
previous.detach();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise merge.
|
||||||
previous.mergeWithBlock( current, range );
|
previous.mergeWithBlock( current, range );
|
||||||
// If deleted line between containers, merge newly adjacent
|
// If deleted line between containers, merge newly adjacent
|
||||||
// containers.
|
// containers.
|
||||||
|
@ -1566,6 +1579,12 @@
|
||||||
next = current.getNextBlock();
|
next = current.getNextBlock();
|
||||||
// Must not be at the very end of the text area.
|
// Must not be at the very end of the text area.
|
||||||
if ( next ) {
|
if ( next ) {
|
||||||
|
// If not editable, just delete whole block.
|
||||||
|
if ( !next.isContentEditable ) {
|
||||||
|
next.detach();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwise merge.
|
||||||
current.mergeWithBlock( next, range );
|
current.mergeWithBlock( next, range );
|
||||||
// If deleted line between containers, merge newly adjacent
|
// If deleted line between containers, merge newly adjacent
|
||||||
// containers.
|
// containers.
|
||||||
|
|
Loading…
Reference in a new issue