mirror of
https://github.com/fastmail/Squire.git
synced 2025-03-14 16:41:34 -05:00
Update to latest Squire
This commit is contained in:
parent
4ec6f03d68
commit
06ef2f41fb
2 changed files with 387 additions and 188 deletions
|
@ -17,6 +17,11 @@ var START_TO_END = 1; // Range.START_TO_END
|
||||||
var END_TO_END = 2; // Range.END_TO_END
|
var END_TO_END = 2; // Range.END_TO_END
|
||||||
var END_TO_START = 3; // Range.END_TO_START
|
var END_TO_START = 3; // Range.END_TO_START
|
||||||
|
|
||||||
|
var HIGHLIGHT_CLASS = 'highlight';
|
||||||
|
var COLOUR_CLASS = 'colour';
|
||||||
|
var FONT_FAMILY_CLASS = 'font';
|
||||||
|
var FONT_SIZE_CLASS = 'size';
|
||||||
|
|
||||||
var ZWS = '\u200B';
|
var ZWS = '\u200B';
|
||||||
|
|
||||||
var win = doc.defaultView;
|
var win = doc.defaultView;
|
||||||
|
@ -26,11 +31,14 @@ var ua = navigator.userAgent;
|
||||||
var isIOS = /iP(?:ad|hone|od)/.test( ua );
|
var isIOS = /iP(?:ad|hone|od)/.test( ua );
|
||||||
var isMac = /Mac OS X/.test( ua );
|
var isMac = /Mac OS X/.test( ua );
|
||||||
|
|
||||||
|
var isAndroid = /Android/.test( ua );
|
||||||
|
|
||||||
var isGecko = /Gecko\//.test( ua );
|
var isGecko = /Gecko\//.test( ua );
|
||||||
var isIElt11 = /Trident\/[456]\./.test( ua );
|
var isIElt11 = /Trident\/[456]\./.test( ua );
|
||||||
var isPresto = !!win.opera;
|
var isPresto = !!win.opera;
|
||||||
var isEdge = /Edge\//.test( ua );
|
var isEdge = /Edge\//.test( ua );
|
||||||
var isWebKit = !isEdge && /WebKit\//.test( ua );
|
var isWebKit = !isEdge && /WebKit\//.test( ua );
|
||||||
|
var isIE = /Trident\/[4567]\./.test( ua );
|
||||||
|
|
||||||
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
||||||
|
|
||||||
|
@ -171,7 +179,7 @@ TreeWalker.prototype.previousPONode = function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|U|VAR|WBR)$/;
|
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;
|
||||||
|
|
||||||
var leafNodeNames = {
|
var leafNodeNames = {
|
||||||
BR: 1,
|
BR: 1,
|
||||||
|
@ -284,6 +292,23 @@ function getPath ( node, root ) {
|
||||||
if ( dir = node.dir ) {
|
if ( dir = node.dir ) {
|
||||||
path += '[dir=' + dir + ']';
|
path += '[dir=' + dir + ']';
|
||||||
}
|
}
|
||||||
|
if ( classNames ) {
|
||||||
|
if ( indexOf.call( classNames, HIGHLIGHT_CLASS ) > -1 ) {
|
||||||
|
path += '[backgroundColor=' +
|
||||||
|
node.style.backgroundColor.replace( / /g,'' ) + ']';
|
||||||
|
}
|
||||||
|
if ( indexOf.call( classNames, COLOUR_CLASS ) > -1 ) {
|
||||||
|
path += '[color=' +
|
||||||
|
node.style.color.replace( / /g,'' ) + ']';
|
||||||
|
}
|
||||||
|
if ( indexOf.call( classNames, FONT_FAMILY_CLASS ) > -1 ) {
|
||||||
|
path += '[fontFamily=' +
|
||||||
|
node.style.fontFamily.replace( / /g,'' ) + ']';
|
||||||
|
}
|
||||||
|
if ( indexOf.call( classNames, FONT_SIZE_CLASS ) > -1 ) {
|
||||||
|
path += '[fontSize=' + node.style.fontSize + ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
@ -522,10 +547,7 @@ function split ( node, offset, stopNode, root ) {
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeInlines ( node, range ) {
|
function _mergeInlines ( node, fakeRange ) {
|
||||||
if ( node.nodeType !== ELEMENT_NODE ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var children = node.childNodes,
|
var children = node.childNodes,
|
||||||
l = children.length,
|
l = children.length,
|
||||||
frags = [],
|
frags = [],
|
||||||
|
@ -535,30 +557,30 @@ function mergeInlines ( node, range ) {
|
||||||
prev = l && children[ l - 1 ];
|
prev = l && children[ l - 1 ];
|
||||||
if ( l && isInline( child ) && areAlike( child, prev ) &&
|
if ( l && isInline( child ) && areAlike( child, prev ) &&
|
||||||
!leafNodeNames[ child.nodeName ] ) {
|
!leafNodeNames[ child.nodeName ] ) {
|
||||||
if ( range.startContainer === child ) {
|
if ( fakeRange.startContainer === child ) {
|
||||||
range.startContainer = prev;
|
fakeRange.startContainer = prev;
|
||||||
range.startOffset += getLength( prev );
|
fakeRange.startOffset += getLength( prev );
|
||||||
}
|
}
|
||||||
if ( range.endContainer === child ) {
|
if ( fakeRange.endContainer === child ) {
|
||||||
range.endContainer = prev;
|
fakeRange.endContainer = prev;
|
||||||
range.endOffset += getLength( prev );
|
fakeRange.endOffset += getLength( prev );
|
||||||
}
|
}
|
||||||
if ( range.startContainer === node ) {
|
if ( fakeRange.startContainer === node ) {
|
||||||
if ( range.startOffset > l ) {
|
if ( fakeRange.startOffset > l ) {
|
||||||
range.startOffset -= 1;
|
fakeRange.startOffset -= 1;
|
||||||
}
|
}
|
||||||
else if ( range.startOffset === l ) {
|
else if ( fakeRange.startOffset === l ) {
|
||||||
range.startContainer = prev;
|
fakeRange.startContainer = prev;
|
||||||
range.startOffset = getLength( prev );
|
fakeRange.startOffset = getLength( prev );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( range.endContainer === node ) {
|
if ( fakeRange.endContainer === node ) {
|
||||||
if ( range.endOffset > l ) {
|
if ( fakeRange.endOffset > l ) {
|
||||||
range.endOffset -= 1;
|
fakeRange.endOffset -= 1;
|
||||||
}
|
}
|
||||||
else if ( range.endOffset === l ) {
|
else if ( fakeRange.endOffset === l ) {
|
||||||
range.endContainer = prev;
|
fakeRange.endContainer = prev;
|
||||||
range.endOffset = getLength( prev );
|
fakeRange.endOffset = getLength( prev );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
detach( child );
|
detach( child );
|
||||||
|
@ -574,14 +596,31 @@ function mergeInlines ( node, range ) {
|
||||||
while ( len-- ) {
|
while ( len-- ) {
|
||||||
child.appendChild( frags.pop() );
|
child.appendChild( frags.pop() );
|
||||||
}
|
}
|
||||||
mergeInlines( child, range );
|
_mergeInlines( child, fakeRange );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeInlines ( node, range ) {
|
||||||
|
if ( node.nodeType === TEXT_NODE ) {
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
if ( node.nodeType === ELEMENT_NODE ) {
|
||||||
|
var fakeRange = {
|
||||||
|
startContainer: range.startContainer,
|
||||||
|
startOffset: range.startOffset,
|
||||||
|
endContainer: range.endContainer,
|
||||||
|
endOffset: range.endOffset
|
||||||
|
};
|
||||||
|
_mergeInlines( node, fakeRange );
|
||||||
|
range.setStart( fakeRange.startContainer, fakeRange.startOffset );
|
||||||
|
range.setEnd( fakeRange.endContainer, fakeRange.endOffset );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function mergeWithBlock ( block, next, range ) {
|
function mergeWithBlock ( block, next, range ) {
|
||||||
var container = next,
|
var container = next,
|
||||||
last, offset, _range;
|
last, offset;
|
||||||
while ( container.parentNode.childNodes.length === 1 ) {
|
while ( container.parentNode.childNodes.length === 1 ) {
|
||||||
container = container.parentNode;
|
container = container.parentNode;
|
||||||
}
|
}
|
||||||
|
@ -596,18 +635,11 @@ function mergeWithBlock ( block, next, range ) {
|
||||||
offset -= 1;
|
offset -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_range = {
|
|
||||||
startContainer: block,
|
|
||||||
startOffset: offset,
|
|
||||||
endContainer: block,
|
|
||||||
endOffset: offset
|
|
||||||
};
|
|
||||||
|
|
||||||
block.appendChild( empty( next ) );
|
block.appendChild( empty( next ) );
|
||||||
mergeInlines( block, _range );
|
|
||||||
|
|
||||||
range.setStart( _range.startContainer, _range.startOffset );
|
range.setStart( block, offset );
|
||||||
range.collapse( true );
|
range.collapse( true );
|
||||||
|
mergeInlines( block, range );
|
||||||
|
|
||||||
// Opera inserts a BR if you delete the last piece of text
|
// Opera inserts a BR if you delete the last piece of text
|
||||||
// in a block-level element. Unfortunately, it then gets
|
// in a block-level element. Unfortunately, it then gets
|
||||||
|
@ -862,6 +894,10 @@ var insertTreeFragmentIntoRange = function ( range, frag, root ) {
|
||||||
if ( allInline ) {
|
if ( allInline ) {
|
||||||
// If inline, just insert at the current position.
|
// If inline, just insert at the current position.
|
||||||
insertNodeInRange( range, frag );
|
insertNodeInRange( range, frag );
|
||||||
|
if ( range.startContainer !== range.endContainer ) {
|
||||||
|
mergeInlines( range.endContainer, range );
|
||||||
|
}
|
||||||
|
mergeInlines( range.startContainer, range );
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
} else {
|
} else {
|
||||||
// Otherwise...
|
// Otherwise...
|
||||||
|
@ -1300,7 +1336,7 @@ var afterDelete = function ( self, range ) {
|
||||||
node = parent;
|
node = parent;
|
||||||
parent = node.parentNode;
|
parent = node.parentNode;
|
||||||
}
|
}
|
||||||
// If focussed in empty inline element
|
// If focused in empty inline element
|
||||||
if ( node !== parent ) {
|
if ( node !== parent ) {
|
||||||
// Move focus to just before empty inline(s)
|
// Move focus to just before empty inline(s)
|
||||||
range.setStart( parent,
|
range.setStart( parent,
|
||||||
|
@ -1622,6 +1658,13 @@ var keyHandlers = {
|
||||||
!node.nextSibling && range.endOffset === getLength( node ) ) {
|
!node.nextSibling && range.endOffset === getLength( node ) ) {
|
||||||
range.setStartAfter( parent );
|
range.setStartAfter( parent );
|
||||||
}
|
}
|
||||||
|
// Delete the selection if not collapsed
|
||||||
|
else if ( !range.collapsed ) {
|
||||||
|
deleteContentsOfRange( range, self._root );
|
||||||
|
self._ensureBottomLine();
|
||||||
|
self.setSelection( range );
|
||||||
|
self._updatePath( range, true );
|
||||||
|
}
|
||||||
|
|
||||||
self.setSelection( range );
|
self.setSelection( range );
|
||||||
},
|
},
|
||||||
|
@ -1690,12 +1733,12 @@ var fontSizes = {
|
||||||
7: 48
|
7: 48
|
||||||
};
|
};
|
||||||
|
|
||||||
var spanToSemantic = {
|
var styleToSemantic = {
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
regexp: notWS,
|
regexp: notWS,
|
||||||
replace: function ( doc, colour ) {
|
replace: function ( doc, colour ) {
|
||||||
return createElement( doc, 'SPAN', {
|
return createElement( doc, 'SPAN', {
|
||||||
'class': 'highlight',
|
'class': HIGHLIGHT_CLASS,
|
||||||
style: 'background-color:' + colour
|
style: 'background-color:' + colour
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1704,7 +1747,7 @@ var spanToSemantic = {
|
||||||
regexp: notWS,
|
regexp: notWS,
|
||||||
replace: function ( doc, colour ) {
|
replace: function ( doc, colour ) {
|
||||||
return createElement( doc, 'SPAN', {
|
return createElement( doc, 'SPAN', {
|
||||||
'class': 'colour',
|
'class': COLOUR_CLASS,
|
||||||
style: 'color:' + colour
|
style: 'color:' + colour
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1725,7 +1768,7 @@ var spanToSemantic = {
|
||||||
regexp: notWS,
|
regexp: notWS,
|
||||||
replace: function ( doc, family ) {
|
replace: function ( doc, family ) {
|
||||||
return createElement( doc, 'SPAN', {
|
return createElement( doc, 'SPAN', {
|
||||||
'class': 'font',
|
'class': FONT_FAMILY_CLASS,
|
||||||
style: 'font-family:' + family
|
style: 'font-family:' + family
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1734,10 +1777,16 @@ var spanToSemantic = {
|
||||||
regexp: notWS,
|
regexp: notWS,
|
||||||
replace: function ( doc, size ) {
|
replace: function ( doc, size ) {
|
||||||
return createElement( doc, 'SPAN', {
|
return createElement( doc, 'SPAN', {
|
||||||
'class': 'size',
|
'class': FONT_SIZE_CLASS,
|
||||||
style: 'font-size:' + size
|
style: 'font-size:' + size
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
textDecoration: {
|
||||||
|
regexp: /^underline/i,
|
||||||
|
replace: function ( doc ) {
|
||||||
|
return createElement( doc, 'U' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1750,36 +1799,45 @@ var replaceWithTag = function ( tag ) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var stylesRewriters = {
|
var replaceStyles = function ( node, parent ) {
|
||||||
SPAN: function ( span, parent ) {
|
var style = node.style;
|
||||||
var style = span.style,
|
var doc = node.ownerDocument;
|
||||||
doc = span.ownerDocument,
|
var attr, converter, css, newTreeBottom, newTreeTop, el;
|
||||||
attr, converter, css, newTreeBottom, newTreeTop, el;
|
|
||||||
|
|
||||||
for ( attr in spanToSemantic ) {
|
for ( attr in styleToSemantic ) {
|
||||||
converter = spanToSemantic[ attr ];
|
converter = styleToSemantic[ attr ];
|
||||||
css = style[ attr ];
|
css = style[ attr ];
|
||||||
if ( css && converter.regexp.test( css ) ) {
|
if ( css && converter.regexp.test( css ) ) {
|
||||||
el = converter.replace( doc, css );
|
el = converter.replace( doc, css );
|
||||||
|
if ( !newTreeTop ) {
|
||||||
|
newTreeTop = el;
|
||||||
|
}
|
||||||
if ( newTreeBottom ) {
|
if ( newTreeBottom ) {
|
||||||
newTreeBottom.appendChild( el );
|
newTreeBottom.appendChild( el );
|
||||||
}
|
}
|
||||||
newTreeBottom = el;
|
newTreeBottom = el;
|
||||||
if ( !newTreeTop ) {
|
node.style[ attr ] = '';
|
||||||
newTreeTop = el;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( newTreeTop ) {
|
if ( newTreeTop ) {
|
||||||
newTreeBottom.appendChild( empty( span ) );
|
newTreeBottom.appendChild( empty( node ) );
|
||||||
parent.replaceChild( newTreeTop, span );
|
if ( node.nodeName === 'SPAN' ) {
|
||||||
|
parent.replaceChild( newTreeTop, node );
|
||||||
|
} else {
|
||||||
|
node.appendChild( newTreeTop );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newTreeBottom || span;
|
return newTreeBottom || node;
|
||||||
},
|
};
|
||||||
|
|
||||||
|
var stylesRewriters = {
|
||||||
|
P: replaceStyles,
|
||||||
|
SPAN: replaceStyles,
|
||||||
STRONG: replaceWithTag( 'B' ),
|
STRONG: replaceWithTag( 'B' ),
|
||||||
EM: replaceWithTag( 'I' ),
|
EM: replaceWithTag( 'I' ),
|
||||||
|
INS: replaceWithTag( 'U' ),
|
||||||
STRIKE: replaceWithTag( 'S' ),
|
STRIKE: replaceWithTag( 'S' ),
|
||||||
FONT: function ( node, parent ) {
|
FONT: function ( node, parent ) {
|
||||||
var face = node.face,
|
var face = node.face,
|
||||||
|
@ -2059,12 +2117,32 @@ var onCopy = function ( event ) {
|
||||||
var clipboardData = event.clipboardData;
|
var clipboardData = event.clipboardData;
|
||||||
var range = this.getSelection();
|
var range = this.getSelection();
|
||||||
var node = this.createElement( 'div' );
|
var node = this.createElement( 'div' );
|
||||||
|
var root = this._root;
|
||||||
|
var startBlock, contents, parent, newContents;
|
||||||
|
|
||||||
// Edge only seems to support setting plain text as of 2016-03-11.
|
// Edge only seems to support setting plain text as of 2016-03-11.
|
||||||
// Mobile Safari flat out doesn't work:
|
// Mobile Safari flat out doesn't work:
|
||||||
// https://bugs.webkit.org/show_bug.cgi?id=143776
|
// https://bugs.webkit.org/show_bug.cgi?id=143776
|
||||||
if ( !isEdge && !isIOS && clipboardData ) {
|
if ( !isEdge && !isIOS && clipboardData ) {
|
||||||
node.appendChild( range.cloneContents() );
|
range = range.cloneRange();
|
||||||
|
startBlock = getStartBlockOfRange( range, root );
|
||||||
|
if ( startBlock === getEndBlockOfRange( range, root ) ) {
|
||||||
|
// Copy all inline formatting, but that's it.
|
||||||
|
moveRangeBoundariesDownTree( range );
|
||||||
|
moveRangeBoundariesUpTree( range, startBlock );
|
||||||
|
contents = range.cloneContents();
|
||||||
|
} else {
|
||||||
|
moveRangeBoundariesUpTree( range, root );
|
||||||
|
contents = range.cloneContents();
|
||||||
|
parent = range.commonAncestorContainer;
|
||||||
|
while ( parent && parent !== root ) {
|
||||||
|
newContents = parent.cloneNode( false );
|
||||||
|
newContents.appendChild( contents );
|
||||||
|
contents = newContents;
|
||||||
|
parent = parent.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.appendChild( contents );
|
||||||
clipboardData.setData( 'text/html', node.innerHTML );
|
clipboardData.setData( 'text/html', node.innerHTML );
|
||||||
clipboardData.setData( 'text/plain',
|
clipboardData.setData( 'text/plain',
|
||||||
node.innerText || node.textContent );
|
node.innerText || node.textContent );
|
||||||
|
@ -2072,14 +2150,21 @@ var onCopy = function ( event ) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Need to monitor for shift key like this, as event.shiftKey is not available
|
||||||
|
// in paste event.
|
||||||
|
function monitorShiftKey ( event ) {
|
||||||
|
this.isShiftDown = event.shiftKey;
|
||||||
|
}
|
||||||
|
|
||||||
var onPaste = function ( event ) {
|
var onPaste = function ( event ) {
|
||||||
var clipboardData = event.clipboardData,
|
var clipboardData = event.clipboardData;
|
||||||
items = clipboardData && clipboardData.items,
|
var items = clipboardData && clipboardData.items;
|
||||||
fireDrop = false,
|
var choosePlain = this.isShiftDown;
|
||||||
hasImage = false,
|
var fireDrop = false;
|
||||||
plainItem = null,
|
var hasImage = false;
|
||||||
self = this,
|
var plainItem = null;
|
||||||
l, item, type, types, data;
|
var self = this;
|
||||||
|
var l, item, type, types, data;
|
||||||
|
|
||||||
// Current HTML5 Clipboard interface
|
// Current HTML5 Clipboard interface
|
||||||
// ---------------------------------
|
// ---------------------------------
|
||||||
|
@ -2092,7 +2177,7 @@ var onPaste = function ( event ) {
|
||||||
while ( l-- ) {
|
while ( l-- ) {
|
||||||
item = items[l];
|
item = items[l];
|
||||||
type = item.type;
|
type = item.type;
|
||||||
if ( type === 'text/html' ) {
|
if ( !choosePlain && type === 'text/html' ) {
|
||||||
/*jshint loopfunc: true */
|
/*jshint loopfunc: true */
|
||||||
item.getAsString( function ( html ) {
|
item.getAsString( function ( html ) {
|
||||||
self.insertHTML( html, true );
|
self.insertHTML( html, true );
|
||||||
|
@ -2103,7 +2188,7 @@ var onPaste = function ( event ) {
|
||||||
if ( type === 'text/plain' ) {
|
if ( type === 'text/plain' ) {
|
||||||
plainItem = item;
|
plainItem = item;
|
||||||
}
|
}
|
||||||
if ( /^image\/.*/.test( type ) ) {
|
if ( !choosePlain && /^image\/.*/.test( type ) ) {
|
||||||
hasImage = true;
|
hasImage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2155,7 +2240,7 @@ var onPaste = function ( event ) {
|
||||||
// insert plain text instead. On iOS, Facebook (and possibly other
|
// insert plain text instead. On iOS, Facebook (and possibly other
|
||||||
// apps?) copy links as type text/uri-list, but also insert a **blank**
|
// apps?) copy links as type text/uri-list, but also insert a **blank**
|
||||||
// text/plain item onto the clipboard. Why? Who knows.
|
// text/plain item onto the clipboard. Why? Who knows.
|
||||||
if (( data = clipboardData.getData( 'text/html' ) )) {
|
if ( !choosePlain && ( data = clipboardData.getData( 'text/html' ) ) ) {
|
||||||
this.insertHTML( data, true );
|
this.insertHTML( data, true );
|
||||||
} else if (
|
} else if (
|
||||||
( data = clipboardData.getData( 'text/plain' ) ) ||
|
( data = clipboardData.getData( 'text/plain' ) ) ||
|
||||||
|
@ -2267,17 +2352,21 @@ function getSquireInstance ( doc ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeObjects ( base, extras ) {
|
function mergeObjects ( base, extras, mayOverride ) {
|
||||||
var prop, value;
|
var prop, value;
|
||||||
if ( !base ) {
|
if ( !base ) {
|
||||||
base = {};
|
base = {};
|
||||||
}
|
}
|
||||||
|
if ( extras ) {
|
||||||
for ( prop in extras ) {
|
for ( prop in extras ) {
|
||||||
|
if ( mayOverride || !( prop in base ) ) {
|
||||||
value = extras[ prop ];
|
value = extras[ prop ];
|
||||||
base[ prop ] = ( value && value.constructor === Object ) ?
|
base[ prop ] = ( value && value.constructor === Object ) ?
|
||||||
mergeObjects( base[ prop ], value ) :
|
mergeObjects( base[ prop ], value, mayOverride ) :
|
||||||
value;
|
value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2295,6 +2384,7 @@ function Squire ( root, config ) {
|
||||||
|
|
||||||
this._events = {};
|
this._events = {};
|
||||||
|
|
||||||
|
this._isFocused = false;
|
||||||
this._lastSelection = null;
|
this._lastSelection = null;
|
||||||
|
|
||||||
// IE loses selection state of iframe on blur, so make sure we
|
// IE loses selection state of iframe on blur, so make sure we
|
||||||
|
@ -2308,6 +2398,7 @@ function Squire ( root, config ) {
|
||||||
this._lastAnchorNode = null;
|
this._lastAnchorNode = null;
|
||||||
this._lastFocusNode = null;
|
this._lastFocusNode = null;
|
||||||
this._path = '';
|
this._path = '';
|
||||||
|
this._willUpdatePath = false;
|
||||||
|
|
||||||
if ( 'onselectionchange' in doc ) {
|
if ( 'onselectionchange' in doc ) {
|
||||||
this.addEventListener( 'selectionchange', this._updatePathOnEvent );
|
this.addEventListener( 'selectionchange', this._updatePathOnEvent );
|
||||||
|
@ -2336,13 +2427,11 @@ function Squire ( root, config ) {
|
||||||
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
||||||
}
|
}
|
||||||
|
|
||||||
// On blur, restore focus except if there is any change to the content, or
|
// On blur, restore focus except if the user taps or clicks to focus a
|
||||||
// the user taps or clicks to focus a specific point. Can't actually use
|
// specific point. Can't actually use click event because focus happens
|
||||||
// click event because focus happens before click, so use
|
// before click, so use mousedown/touchstart
|
||||||
// mousedown/touchstart
|
|
||||||
this._restoreSelection = false;
|
this._restoreSelection = false;
|
||||||
this.addEventListener( 'blur', enableRestoreSelection );
|
this.addEventListener( 'blur', enableRestoreSelection );
|
||||||
this.addEventListener( 'input', disableRestoreSelection );
|
|
||||||
this.addEventListener( 'mousedown', disableRestoreSelection );
|
this.addEventListener( 'mousedown', disableRestoreSelection );
|
||||||
this.addEventListener( 'touchstart', disableRestoreSelection );
|
this.addEventListener( 'touchstart', disableRestoreSelection );
|
||||||
this.addEventListener( 'focus', restoreSelection );
|
this.addEventListener( 'focus', restoreSelection );
|
||||||
|
@ -2352,6 +2441,8 @@ function Squire ( root, config ) {
|
||||||
this._awaitingPaste = false;
|
this._awaitingPaste = false;
|
||||||
this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
|
this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
|
||||||
this.addEventListener( 'copy', onCopy );
|
this.addEventListener( 'copy', onCopy );
|
||||||
|
this.addEventListener( 'keydown', monitorShiftKey );
|
||||||
|
this.addEventListener( 'keyup', monitorShiftKey );
|
||||||
this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
|
this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
|
||||||
this.addEventListener( 'drop', onDrop );
|
this.addEventListener( 'drop', onDrop );
|
||||||
|
|
||||||
|
@ -2420,8 +2511,13 @@ proto.setConfig = function ( config ) {
|
||||||
ol: null,
|
ol: null,
|
||||||
li: null,
|
li: null,
|
||||||
a: null
|
a: null
|
||||||
|
},
|
||||||
|
leafNodeNames: leafNodeNames,
|
||||||
|
undo: {
|
||||||
|
documentSizeThreshold: -1, // -1 means no threshold
|
||||||
|
undoLimit: -1 // -1 means no limit
|
||||||
}
|
}
|
||||||
}, config );
|
}, config, true );
|
||||||
|
|
||||||
// Users may specify block tag in lower case
|
// Users may specify block tag in lower case
|
||||||
config.blockTag = config.blockTag.toUpperCase();
|
config.blockTag = config.blockTag.toUpperCase();
|
||||||
|
@ -2483,8 +2579,26 @@ var customEvents = {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.fireEvent = function ( type, event ) {
|
proto.fireEvent = function ( type, event ) {
|
||||||
var handlers = this._events[ type ],
|
var handlers = this._events[ type ];
|
||||||
l, obj;
|
var isFocused, l, obj;
|
||||||
|
// UI code, especially modal views, may be monitoring for focus events and
|
||||||
|
// immediately removing focus. In certain conditions, this can cause the
|
||||||
|
// focus event to fire after the blur event, which can cause an infinite
|
||||||
|
// loop. So we detect whether we're actually focused/blurred before firing.
|
||||||
|
if ( /^(?:focus|blur)/.test( type ) ) {
|
||||||
|
isFocused = isOrContains( this._root, this._doc.activeElement );
|
||||||
|
if ( type === 'focus' ) {
|
||||||
|
if ( !isFocused || this._isFocused ) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this._isFocused = true;
|
||||||
|
} else {
|
||||||
|
if ( isFocused || !this._isFocused ) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this._isFocused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ( handlers ) {
|
if ( handlers ) {
|
||||||
if ( !event ) {
|
if ( !event ) {
|
||||||
event = {};
|
event = {};
|
||||||
|
@ -2516,6 +2630,7 @@ proto.destroy = function () {
|
||||||
var l = instances.length;
|
var l = instances.length;
|
||||||
var events = this._events;
|
var events = this._events;
|
||||||
var type;
|
var type;
|
||||||
|
|
||||||
for ( type in events ) {
|
for ( type in events ) {
|
||||||
this.removeEventListener( type );
|
this.removeEventListener( type );
|
||||||
}
|
}
|
||||||
|
@ -2527,6 +2642,11 @@ proto.destroy = function () {
|
||||||
instances.splice( l, 1 );
|
instances.splice( l, 1 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy undo stack
|
||||||
|
this._undoIndex = -1;
|
||||||
|
this._undoStack = [];
|
||||||
|
this._undoStackLength = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.handleEvent = function ( event ) {
|
proto.handleEvent = function ( event ) {
|
||||||
|
@ -2617,12 +2737,7 @@ proto.getCursorPosition = function ( range ) {
|
||||||
rect = node.getBoundingClientRect();
|
rect = node.getBoundingClientRect();
|
||||||
parent = node.parentNode;
|
parent = node.parentNode;
|
||||||
parent.removeChild( node );
|
parent.removeChild( node );
|
||||||
mergeInlines( parent, {
|
mergeInlines( parent, range );
|
||||||
startContainer: range.startContainer,
|
|
||||||
endContainer: range.endContainer,
|
|
||||||
startOffset: range.startOffset,
|
|
||||||
endOffset: range.endOffset
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return rect;
|
return rect;
|
||||||
};
|
};
|
||||||
|
@ -2647,9 +2762,22 @@ var getWindowSelection = function ( self ) {
|
||||||
|
|
||||||
proto.setSelection = function ( range ) {
|
proto.setSelection = function ( range ) {
|
||||||
if ( range ) {
|
if ( range ) {
|
||||||
// If we're setting selection, that automatically, and synchronously, // triggers a focus event. Don't want a reentrant call to setSelection.
|
|
||||||
this._restoreSelection = false;
|
|
||||||
this._lastSelection = range;
|
this._lastSelection = range;
|
||||||
|
// If we're setting selection, that automatically, and synchronously, // triggers a focus event. So just store the selection and mark it as
|
||||||
|
// needing restore on focus.
|
||||||
|
if ( !this._isFocused ) {
|
||||||
|
enableRestoreSelection.call( this );
|
||||||
|
} else if ( isAndroid && !this._restoreSelection ) {
|
||||||
|
// Android closes the keyboard on removeAllRanges() and doesn't
|
||||||
|
// open it again when addRange() is called, sigh.
|
||||||
|
// Since Android doesn't trigger a focus event in setSelection(),
|
||||||
|
// use a blur/focus dance to work around this by letting the
|
||||||
|
// selection be restored on focus.
|
||||||
|
// Need to check for !this._restoreSelection to avoid infinite loop
|
||||||
|
enableRestoreSelection.call( this );
|
||||||
|
this.blur();
|
||||||
|
this.focus();
|
||||||
|
} else {
|
||||||
// iOS bug: if you don't focus the iframe before setting the
|
// iOS bug: if you don't focus the iframe before setting the
|
||||||
// selection, you can end up in a state where you type but the input
|
// selection, you can end up in a state where you type but the input
|
||||||
// doesn't get directed into the contenteditable area but is instead
|
// doesn't get directed into the contenteditable area but is instead
|
||||||
|
@ -2663,6 +2791,7 @@ proto.setSelection = function ( range ) {
|
||||||
sel.addRange( range );
|
sel.addRange( range );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2758,13 +2887,18 @@ proto.getPath = function () {
|
||||||
|
|
||||||
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
||||||
|
|
||||||
var removeZWS = function ( root ) {
|
// Walk down the tree starting at the root and remove any ZWS. If the node only
|
||||||
|
// contained ZWS space then remove it too. We may want to keep one ZWS node at
|
||||||
|
// the bottom of the tree so the block can be selected. Define that node as the
|
||||||
|
// keepNode.
|
||||||
|
var removeZWS = function ( root, keepNode ) {
|
||||||
var walker = new TreeWalker( root, SHOW_TEXT, function () {
|
var walker = new TreeWalker( root, SHOW_TEXT, function () {
|
||||||
return true;
|
return true;
|
||||||
}, false ),
|
}, false ),
|
||||||
parent, node, index;
|
parent, node, index;
|
||||||
while ( node = walker.nextNode() ) {
|
while ( node = walker.nextNode() ) {
|
||||||
while ( ( index = node.data.indexOf( ZWS ) ) > -1 ) {
|
while ( ( index = node.data.indexOf( ZWS ) ) > -1 &&
|
||||||
|
( !keepNode || node.parentNode !== keepNode ) ) {
|
||||||
if ( node.length === 1 ) {
|
if ( node.length === 1 ) {
|
||||||
do {
|
do {
|
||||||
parent = node.parentNode;
|
parent = node.parentNode;
|
||||||
|
@ -2813,19 +2947,39 @@ proto._updatePath = function ( range, force ) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// selectionchange is fired synchronously in IE when removing current selection
|
||||||
|
// and when setting new selection; keyup/mouseup may have processing we want
|
||||||
|
// to do first. Either way, send to next event loop.
|
||||||
proto._updatePathOnEvent = function () {
|
proto._updatePathOnEvent = function () {
|
||||||
this._updatePath( this.getSelection() );
|
var self = this;
|
||||||
|
if ( !self._willUpdatePath ) {
|
||||||
|
self._willUpdatePath = true;
|
||||||
|
setTimeout( function () {
|
||||||
|
self._willUpdatePath = false;
|
||||||
|
self._updatePath( self.getSelection() );
|
||||||
|
}, 0 );
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Focus ---
|
// --- Focus ---
|
||||||
|
|
||||||
proto.focus = function () {
|
proto.focus = function () {
|
||||||
this._root.focus();
|
this._root.focus();
|
||||||
|
|
||||||
|
if ( isIE ) {
|
||||||
|
this.fireEvent( 'focus' );
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.blur = function () {
|
proto.blur = function () {
|
||||||
this._root.blur();
|
this._root.blur();
|
||||||
|
|
||||||
|
if ( isIE ) {
|
||||||
|
this.fireEvent( 'blur' );
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2871,38 +3025,31 @@ proto._getRangeAndRemoveBookmark = function ( range ) {
|
||||||
if ( start && end ) {
|
if ( start && end ) {
|
||||||
var startContainer = start.parentNode,
|
var startContainer = start.parentNode,
|
||||||
endContainer = end.parentNode,
|
endContainer = end.parentNode,
|
||||||
collapsed;
|
startOffset = indexOf.call( startContainer.childNodes, start ),
|
||||||
|
endOffset = indexOf.call( endContainer.childNodes, end );
|
||||||
var _range = {
|
|
||||||
startContainer: startContainer,
|
|
||||||
endContainer: endContainer,
|
|
||||||
startOffset: indexOf.call( startContainer.childNodes, start ),
|
|
||||||
endOffset: indexOf.call( endContainer.childNodes, end )
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( startContainer === endContainer ) {
|
if ( startContainer === endContainer ) {
|
||||||
_range.endOffset -= 1;
|
endOffset -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
detach( start );
|
detach( start );
|
||||||
detach( end );
|
detach( end );
|
||||||
|
|
||||||
// Merge any text nodes we split
|
|
||||||
mergeInlines( startContainer, _range );
|
|
||||||
if ( startContainer !== endContainer ) {
|
|
||||||
mergeInlines( endContainer, _range );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !range ) {
|
if ( !range ) {
|
||||||
range = this._doc.createRange();
|
range = this._doc.createRange();
|
||||||
}
|
}
|
||||||
range.setStart( _range.startContainer, _range.startOffset );
|
range.setStart( startContainer, startOffset );
|
||||||
range.setEnd( _range.endContainer, _range.endOffset );
|
range.setEnd( endContainer, endOffset );
|
||||||
collapsed = range.collapsed;
|
|
||||||
|
// Merge any text nodes we split
|
||||||
|
mergeInlines( startContainer, range );
|
||||||
|
if ( startContainer !== endContainer ) {
|
||||||
|
mergeInlines( endContainer, range );
|
||||||
|
}
|
||||||
|
|
||||||
// If we didn't split a text node, we should move into any adjacent
|
// If we didn't split a text node, we should move into any adjacent
|
||||||
// text node to current selection point
|
// text node to current selection point
|
||||||
if ( collapsed ) {
|
if ( range.collapsed ) {
|
||||||
startContainer = range.startContainer;
|
startContainer = range.startContainer;
|
||||||
if ( startContainer.nodeType === TEXT_NODE ) {
|
if ( startContainer.nodeType === TEXT_NODE ) {
|
||||||
endContainer = startContainer.childNodes[ range.startOffset ];
|
endContainer = startContainer.childNodes[ range.startOffset ];
|
||||||
|
@ -2959,19 +3106,37 @@ proto._recordUndoState = function ( range ) {
|
||||||
// Don't record if we're already in an undo state
|
// Don't record if we're already in an undo state
|
||||||
if ( !this._isInUndoState ) {
|
if ( !this._isInUndoState ) {
|
||||||
// Advance pointer to new position
|
// Advance pointer to new position
|
||||||
var undoIndex = this._undoIndex += 1,
|
var undoIndex = this._undoIndex += 1;
|
||||||
undoStack = this._undoStack;
|
var undoStack = this._undoStack;
|
||||||
|
var undoConfig = this._config.undo;
|
||||||
|
var undoThreshold = undoConfig.documentSizeThreshold;
|
||||||
|
var undoLimit = undoConfig.undoLimit;
|
||||||
|
var html;
|
||||||
|
|
||||||
// Truncate stack if longer (i.e. if has been previously undone)
|
// Truncate stack if longer (i.e. if has been previously undone)
|
||||||
if ( undoIndex < this._undoStackLength ) {
|
if ( undoIndex < this._undoStackLength ) {
|
||||||
undoStack.length = this._undoStackLength = undoIndex;
|
undoStack.length = this._undoStackLength = undoIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write out data
|
// Get data
|
||||||
if ( range ) {
|
if ( range ) {
|
||||||
this._saveRangeToBookmark( range );
|
this._saveRangeToBookmark( range );
|
||||||
}
|
}
|
||||||
undoStack[ undoIndex ] = this._getHTML();
|
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 = this._undoIndex = undoLimit;
|
||||||
|
this._undoStackLength = undoLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save data
|
||||||
|
undoStack[ undoIndex ] = html;
|
||||||
this._undoStackLength += 1;
|
this._undoStackLength += 1;
|
||||||
this._isInUndoState = true;
|
this._isInUndoState = true;
|
||||||
}
|
}
|
||||||
|
@ -3141,13 +3306,21 @@ proto._addFormat = function ( tag, attributes, range ) {
|
||||||
// it round the range and focus it.
|
// it round the range and focus it.
|
||||||
var root = this._root;
|
var root = this._root;
|
||||||
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
||||||
node, needsFormat;
|
node, needsFormat, block;
|
||||||
|
|
||||||
if ( range.collapsed ) {
|
if ( range.collapsed ) {
|
||||||
el = fixCursor( this.createElement( tag, attributes ), root );
|
el = fixCursor( this.createElement( tag, attributes ), root );
|
||||||
insertNodeInRange( range, el );
|
insertNodeInRange( range, el );
|
||||||
range.setStart( el.firstChild, el.firstChild.length );
|
range.setStart( el.firstChild, el.firstChild.length );
|
||||||
range.collapse( true );
|
range.collapse( true );
|
||||||
|
|
||||||
|
// Clean up any previous formats that may have been set on this block
|
||||||
|
// that are unused.
|
||||||
|
block = el;
|
||||||
|
while ( isInline( block ) ) {
|
||||||
|
block = block.parentNode;
|
||||||
|
}
|
||||||
|
removeZWS( block, el );
|
||||||
}
|
}
|
||||||
// Otherwise we find all the textnodes in the range (splitting
|
// Otherwise we find all the textnodes in the range (splitting
|
||||||
// partially selected nodes) and if they're not already formatted
|
// partially selected nodes) and if they're not already formatted
|
||||||
|
@ -3162,8 +3335,8 @@ proto._addFormat = function ( tag, attributes, range ) {
|
||||||
// to apply when the user types something in the block, which is
|
// to apply when the user types something in the block, which is
|
||||||
// presumably what was intended.
|
// presumably what was intended.
|
||||||
//
|
//
|
||||||
// IMG tags are included because we may want to create a link around them,
|
// IMG tags are included because we may want to create a link around
|
||||||
// and adding other styles is harmless.
|
// them, and adding other styles is harmless.
|
||||||
walker = new TreeWalker(
|
walker = new TreeWalker(
|
||||||
range.commonAncestorContainer,
|
range.commonAncestorContainer,
|
||||||
SHOW_TEXT|SHOW_ELEMENT,
|
SHOW_TEXT|SHOW_ELEMENT,
|
||||||
|
@ -3342,15 +3515,7 @@ proto._removeFormat = function ( tag, attributes, range, partial ) {
|
||||||
if ( fixer ) {
|
if ( fixer ) {
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
}
|
}
|
||||||
var _range = {
|
mergeInlines( root, range );
|
||||||
startContainer: range.startContainer,
|
|
||||||
startOffset: range.startOffset,
|
|
||||||
endContainer: range.endContainer,
|
|
||||||
endOffset: range.endOffset
|
|
||||||
};
|
|
||||||
mergeInlines( root, _range );
|
|
||||||
range.setStart( _range.startContainer, _range.startOffset );
|
|
||||||
range.setEnd( _range.endContainer, _range.endOffset );
|
|
||||||
|
|
||||||
return range;
|
return range;
|
||||||
};
|
};
|
||||||
|
@ -3358,7 +3523,7 @@ proto._removeFormat = function ( tag, attributes, range, partial ) {
|
||||||
proto.changeFormat = function ( add, remove, range, partial ) {
|
proto.changeFormat = function ( add, remove, range, partial ) {
|
||||||
// Normalise the arguments and get selection
|
// Normalise the arguments and get selection
|
||||||
if ( !range && !( range = this.getSelection() ) ) {
|
if ( !range && !( range = this.getSelection() ) ) {
|
||||||
return;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save undo checkpoint
|
// Save undo checkpoint
|
||||||
|
@ -3769,6 +3934,7 @@ proto.setHTML = function ( html ) {
|
||||||
// anything calls getSelection before first focus, we have a range
|
// anything calls getSelection before first focus, we have a range
|
||||||
// to return.
|
// to return.
|
||||||
this._lastSelection = range;
|
this._lastSelection = range;
|
||||||
|
enableRestoreSelection.call( this );
|
||||||
this._updatePath( range, true );
|
this._updatePath( range, true );
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -3820,7 +3986,7 @@ proto.insertElement = function ( el, range ) {
|
||||||
proto.insertImage = function ( src, attributes ) {
|
proto.insertImage = function ( src, attributes ) {
|
||||||
var img = this.createElement( 'IMG', mergeObjects({
|
var img = this.createElement( 'IMG', mergeObjects({
|
||||||
src: src
|
src: src
|
||||||
}, attributes ));
|
}, attributes, true ));
|
||||||
this.insertElement( img );
|
this.insertElement( img );
|
||||||
return img;
|
return img;
|
||||||
};
|
};
|
||||||
|
@ -3851,7 +4017,7 @@ var addLinks = function ( frag, root, self ) {
|
||||||
match[1] :
|
match[1] :
|
||||||
'http://' + match[1] :
|
'http://' + match[1] :
|
||||||
'mailto:' + match[2]
|
'mailto:' + match[2]
|
||||||
}, defaultAttributes ));
|
}, defaultAttributes, false ));
|
||||||
child.textContent = data.slice( index, endIndex );
|
child.textContent = data.slice( index, endIndex );
|
||||||
parent.insertBefore( child, node );
|
parent.insertBefore( child, node );
|
||||||
node.data = data = data.slice( endIndex );
|
node.data = data = data.slice( endIndex );
|
||||||
|
@ -3864,15 +4030,21 @@ var addLinks = function ( frag, root, self ) {
|
||||||
// by the html being inserted.
|
// by the html being inserted.
|
||||||
proto.insertHTML = function ( html, isPaste ) {
|
proto.insertHTML = function ( html, isPaste ) {
|
||||||
var range = this.getSelection();
|
var range = this.getSelection();
|
||||||
var frag = this._doc.createDocumentFragment();
|
var doc = this._doc;
|
||||||
var div = this.createElement( 'DIV' );
|
|
||||||
var startFragmentIndex, endFragmentIndex;
|
var startFragmentIndex, endFragmentIndex;
|
||||||
var root, node, event;
|
var div, frag, root, node, event;
|
||||||
|
|
||||||
// Edge doesn't just copy the fragment, but includes the surrounding guff
|
// Edge doesn't just copy the fragment, but includes the surrounding guff
|
||||||
// including the full <head> of the page. Need to strip this out. In the
|
// including the full <head> of the page. Need to strip this out. If
|
||||||
// future should probably run all pastes through DOMPurify, but this will
|
// available use DOMPurify to parse and sanitise.
|
||||||
// do for now
|
if ( typeof DOMPurify !== 'undefined' && DOMPurify.isSupported ) {
|
||||||
|
frag = DOMPurify.sanitize( html, {
|
||||||
|
WHOLE_DOCUMENT: false,
|
||||||
|
RETURN_DOM: true,
|
||||||
|
RETURN_DOM_FRAGMENT: true
|
||||||
|
});
|
||||||
|
frag = doc.importNode( frag, true );
|
||||||
|
} else {
|
||||||
if ( isPaste ) {
|
if ( isPaste ) {
|
||||||
startFragmentIndex = html.indexOf( '<!--StartFragment-->' );
|
startFragmentIndex = html.indexOf( '<!--StartFragment-->' );
|
||||||
endFragmentIndex = html.lastIndexOf( '<!--EndFragment-->' );
|
endFragmentIndex = html.lastIndexOf( '<!--EndFragment-->' );
|
||||||
|
@ -3880,10 +4052,12 @@ proto.insertHTML = function ( html, isPaste ) {
|
||||||
html = html.slice( startFragmentIndex + 20, endFragmentIndex );
|
html = html.slice( startFragmentIndex + 20, endFragmentIndex );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse HTML into DOM tree
|
// Parse HTML into DOM tree
|
||||||
|
div = this.createElement( 'DIV' );
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
|
frag = doc.createDocumentFragment();
|
||||||
frag.appendChild( empty( div ) );
|
frag.appendChild( empty( div ) );
|
||||||
|
}
|
||||||
|
|
||||||
// Record undo checkpoint
|
// Record undo checkpoint
|
||||||
this.saveUndoState( range );
|
this.saveUndoState( range );
|
||||||
|
@ -3924,6 +4098,10 @@ proto.insertHTML = function ( html, isPaste ) {
|
||||||
|
|
||||||
this.setSelection( range );
|
this.setSelection( range );
|
||||||
this._updatePath( range, true );
|
this._updatePath( range, true );
|
||||||
|
// Safari sometimes loses focus after paste. Weird.
|
||||||
|
if ( isPaste ) {
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
this.didError( error );
|
this.didError( error );
|
||||||
}
|
}
|
||||||
|
@ -4012,12 +4190,13 @@ proto.makeLink = function ( url, attributes ) {
|
||||||
this._doc.createTextNode( url.slice( protocolEnd ) )
|
this._doc.createTextNode( url.slice( protocolEnd ) )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
attributes = mergeObjects(
|
||||||
if ( !attributes ) {
|
mergeObjects({
|
||||||
attributes = {};
|
href: url
|
||||||
}
|
}, attributes, true ),
|
||||||
mergeObjects( attributes, this._config.tagAttributes.a );
|
this._config.tagAttributes.a,
|
||||||
attributes.href = url;
|
false
|
||||||
|
);
|
||||||
|
|
||||||
this.changeFormat({
|
this.changeFormat({
|
||||||
tag: 'A',
|
tag: 'A',
|
||||||
|
@ -4035,27 +4214,27 @@ proto.removeLink = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.setFontFace = function ( name ) {
|
proto.setFontFace = function ( name ) {
|
||||||
this.changeFormat({
|
this.changeFormat( name ? {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: {
|
attributes: {
|
||||||
'class': 'font',
|
'class': 'font',
|
||||||
style: 'font-family: ' + name + ', sans-serif;'
|
style: 'font-family: ' + name + ', sans-serif;'
|
||||||
}
|
}
|
||||||
}, {
|
} : null, {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: { 'class': 'font' }
|
attributes: { 'class': 'font' }
|
||||||
});
|
});
|
||||||
return this.focus();
|
return this.focus();
|
||||||
};
|
};
|
||||||
proto.setFontSize = function ( size ) {
|
proto.setFontSize = function ( size ) {
|
||||||
this.changeFormat({
|
this.changeFormat( size ? {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: {
|
attributes: {
|
||||||
'class': 'size',
|
'class': 'size',
|
||||||
style: 'font-size: ' +
|
style: 'font-size: ' +
|
||||||
( typeof size === 'number' ? size + 'px' : size )
|
( typeof size === 'number' ? size + 'px' : size )
|
||||||
}
|
}
|
||||||
}, {
|
} : null, {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: { 'class': 'size' }
|
attributes: { 'class': 'size' }
|
||||||
});
|
});
|
||||||
|
@ -4063,13 +4242,13 @@ proto.setFontSize = function ( size ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.setTextColour = function ( colour ) {
|
proto.setTextColour = function ( colour ) {
|
||||||
this.changeFormat({
|
this.changeFormat( colour ? {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: {
|
attributes: {
|
||||||
'class': 'colour',
|
'class': 'colour',
|
||||||
style: 'color:' + colour
|
style: 'color:' + colour
|
||||||
}
|
}
|
||||||
}, {
|
} : null, {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: { 'class': 'colour' }
|
attributes: { 'class': 'colour' }
|
||||||
});
|
});
|
||||||
|
@ -4077,13 +4256,13 @@ proto.setTextColour = function ( colour ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.setHighlightColour = function ( colour ) {
|
proto.setHighlightColour = function ( colour ) {
|
||||||
this.changeFormat({
|
this.changeFormat( colour ? {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: {
|
attributes: {
|
||||||
'class': 'highlight',
|
'class': 'highlight',
|
||||||
style: 'background-color:' + colour
|
style: 'background-color:' + colour
|
||||||
}
|
}
|
||||||
}, {
|
} : colour, {
|
||||||
tag: 'SPAN',
|
tag: 'SPAN',
|
||||||
attributes: { 'class': 'highlight' }
|
attributes: { 'class': 'highlight' }
|
||||||
});
|
});
|
||||||
|
@ -4170,7 +4349,7 @@ proto.removeAllFormatting = function ( range ) {
|
||||||
var cleanNodes = doc.createDocumentFragment();
|
var cleanNodes = doc.createDocumentFragment();
|
||||||
var nodeAfterSplit = split( endContainer, endOffset, stopNode, root );
|
var nodeAfterSplit = split( endContainer, endOffset, stopNode, root );
|
||||||
var nodeInSplit = split( startContainer, startOffset, stopNode, root );
|
var nodeInSplit = split( startContainer, startOffset, stopNode, root );
|
||||||
var nextNode, _range, childNodes;
|
var nextNode, childNodes;
|
||||||
|
|
||||||
// Then replace contents in split with a cleaned version of the same:
|
// Then replace contents in split with a cleaned version of the same:
|
||||||
// blocks become default blocks, text and leaf nodes survive, everything
|
// blocks become default blocks, text and leaf nodes survive, everything
|
||||||
|
@ -4197,15 +4376,9 @@ proto.removeAllFormatting = function ( range ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge text nodes at edges, if possible
|
// Merge text nodes at edges, if possible
|
||||||
_range = {
|
range.setStart( stopNode, startOffset );
|
||||||
startContainer: stopNode,
|
range.setEnd( stopNode, endOffset );
|
||||||
startOffset: startOffset,
|
mergeInlines( stopNode, range );
|
||||||
endContainer: stopNode,
|
|
||||||
endOffset: endOffset
|
|
||||||
};
|
|
||||||
mergeInlines( stopNode, _range );
|
|
||||||
range.setStart( _range.startContainer, _range.startOffset );
|
|
||||||
range.setEnd( _range.endContainer, _range.endOffset );
|
|
||||||
|
|
||||||
// And move back down the tree
|
// And move back down the tree
|
||||||
moveRangeBoundariesDownTree( range );
|
moveRangeBoundariesDownTree( range );
|
||||||
|
@ -4226,6 +4399,32 @@ proto.removeList = command( 'modifyBlocks', removeList );
|
||||||
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
|
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
|
||||||
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );
|
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );
|
||||||
|
|
||||||
|
// Range.js exports
|
||||||
|
Squire.getNodeBefore = getNodeBefore;
|
||||||
|
Squire.getNodeAfter = getNodeAfter;
|
||||||
|
Squire.insertNodeInRange = insertNodeInRange;
|
||||||
|
Squire.extractContentsOfRange = extractContentsOfRange;
|
||||||
|
Squire.deleteContentsOfRange = deleteContentsOfRange;
|
||||||
|
Squire.insertTreeFragmentIntoRange = insertTreeFragmentIntoRange;
|
||||||
|
Squire.isNodeContainedInRange = isNodeContainedInRange;
|
||||||
|
Squire.moveRangeBoundariesDownTree = moveRangeBoundariesDownTree;
|
||||||
|
Squire.moveRangeBoundariesUpTree = moveRangeBoundariesUpTree;
|
||||||
|
Squire.getStartBlockOfRange = getStartBlockOfRange;
|
||||||
|
Squire.getEndBlockOfRange = getEndBlockOfRange;
|
||||||
|
Squire.contentWalker = contentWalker;
|
||||||
|
Squire.rangeDoesStartAtBlockBoundary = rangeDoesStartAtBlockBoundary;
|
||||||
|
Squire.rangeDoesEndAtBlockBoundary = rangeDoesEndAtBlockBoundary;
|
||||||
|
Squire.expandRangeToBlockBoundaries = expandRangeToBlockBoundaries;
|
||||||
|
|
||||||
|
// Clipboard.js exports
|
||||||
|
Squire.onPaste = onPaste;
|
||||||
|
|
||||||
|
// Editor.js exports
|
||||||
|
Squire.addLinks = addLinks;
|
||||||
|
Squire.splitBlock = splitBlock;
|
||||||
|
Squire.startSelectionId = startSelectionId;
|
||||||
|
Squire.endSelectionId = endSelectionId;
|
||||||
|
|
||||||
if ( typeof exports === 'object' ) {
|
if ( typeof exports === 'object' ) {
|
||||||
module.exports = Squire;
|
module.exports = Squire;
|
||||||
} else if ( typeof define === 'function' && define.amd ) {
|
} else if ( typeof define === 'function' && define.amd ) {
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue