mirror of
https://github.com/fastmail/Squire.git
synced 2024-12-22 23:40:35 -05:00
Improve copy/paste
* In browsers that support it, we now tell it to copy exactly what was selected in the DOM, and not to add extra gunk which browsers do to preserve exact styling if pasted into another document. * Don't use the clipboard APIs with MS Edge, since it only supports plain text. If we let it fallback to the browser implementation it will insert HTML.
This commit is contained in:
parent
6ed3b93900
commit
6413034884
6 changed files with 96 additions and 34 deletions
|
@ -28,7 +28,8 @@ var isMac = /Mac OS X/.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 isWebKit = /WebKit\//.test( ua );
|
var isEdge = /Edge\//.test( ua );
|
||||||
|
var isWebKit = !isEdge && /WebKit\//.test( ua );
|
||||||
|
|
||||||
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
||||||
|
|
||||||
|
@ -777,7 +778,7 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
( isInline( endBlock ) || isBlock( endBlock ) );
|
( isInline( endBlock ) || isBlock( endBlock ) );
|
||||||
|
|
||||||
// Remove selected range
|
// Remove selected range
|
||||||
extractContentsOfRange( range );
|
var frag = extractContentsOfRange( range );
|
||||||
|
|
||||||
// Move boundaries back down tree so that they are inside the blocks.
|
// Move boundaries back down tree so that they are inside the blocks.
|
||||||
// If we don't do this, the range may be collapsed to a point between
|
// If we don't do this, the range may be collapsed to a point between
|
||||||
|
@ -807,6 +808,7 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
} else {
|
} else {
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
}
|
}
|
||||||
|
return frag;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
@ -1979,21 +1981,48 @@ var cleanupBRs = function ( root ) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onCut = function () {
|
var onCut = function ( event ) {
|
||||||
// Save undo checkpoint
|
var clipboardData = event.clipboardData;
|
||||||
var range = this.getSelection();
|
var range = this.getSelection();
|
||||||
|
var node = this.createElement( 'div' );
|
||||||
|
var body = this._body;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Save undo checkpoint
|
||||||
this._recordUndoState( range );
|
this._recordUndoState( range );
|
||||||
|
|
||||||
|
// Edge only seems to support setting plain text as of 2016-03-11.
|
||||||
|
if ( !isEdge && clipboardData ) {
|
||||||
|
moveRangeBoundariesUpTree( range, body );
|
||||||
|
node.appendChild( deleteContentsOfRange( range, body ) );
|
||||||
|
clipboardData.setData( 'text/html', node.innerHTML );
|
||||||
|
event.preventDefault();
|
||||||
|
} else {
|
||||||
|
setTimeout( function () {
|
||||||
|
try {
|
||||||
|
// If all content removed, ensure div at start of body.
|
||||||
|
self._ensureBottomLine();
|
||||||
|
} catch ( error ) {
|
||||||
|
self.didError( error );
|
||||||
|
}
|
||||||
|
}, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
this._getRangeAndRemoveBookmark( range );
|
this._getRangeAndRemoveBookmark( range );
|
||||||
this.setSelection( range );
|
this.setSelection( range );
|
||||||
setTimeout( function () {
|
};
|
||||||
try {
|
|
||||||
// If all content removed, ensure div at start of body.
|
var onCopy = function ( event ) {
|
||||||
self._ensureBottomLine();
|
var clipboardData = event.clipboardData;
|
||||||
} catch ( error ) {
|
var range = this.getSelection();
|
||||||
self.didError( error );
|
var node = this.createElement( 'div' );
|
||||||
}
|
|
||||||
}, 0 );
|
// Edge only seems to support setting plain text as of 2016-03-11.
|
||||||
|
if ( !isEdge && clipboardData ) {
|
||||||
|
node.appendChild( range.cloneContents() );
|
||||||
|
clipboardData.setData( 'text/html', node.innerHTML );
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onPaste = function ( event ) {
|
var onPaste = function ( event ) {
|
||||||
|
@ -2009,7 +2038,8 @@ var onPaste = function ( event ) {
|
||||||
// ---------------------------------
|
// ---------------------------------
|
||||||
// https://html.spec.whatwg.org/multipage/interaction.html
|
// https://html.spec.whatwg.org/multipage/interaction.html
|
||||||
|
|
||||||
if ( items ) {
|
// Edge only provides access to plain text as of 2016-03-11.
|
||||||
|
if ( !isEdge && items ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
l = items.length;
|
l = items.length;
|
||||||
while ( l-- ) {
|
while ( l-- ) {
|
||||||
|
@ -2066,7 +2096,7 @@ var onPaste = function ( event ) {
|
||||||
// let the browser insert the content. I've filed
|
// let the browser insert the content. I've filed
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1254028
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1254028
|
||||||
types = clipboardData && clipboardData.types;
|
types = clipboardData && clipboardData.types;
|
||||||
if ( types && (
|
if ( !isEdge && types && (
|
||||||
indexOf.call( types, 'text/html' ) > -1 || (
|
indexOf.call( types, 'text/html' ) > -1 || (
|
||||||
!isGecko &&
|
!isGecko &&
|
||||||
indexOf.call( types, 'text/plain' ) > -1 &&
|
indexOf.call( types, 'text/plain' ) > -1 &&
|
||||||
|
@ -2084,8 +2114,8 @@ var onPaste = function ( event ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No interface :(
|
// No interface. Includes all versions of IE :(
|
||||||
// ---------------
|
// --------------------------------------------
|
||||||
|
|
||||||
this._awaitingPaste = true;
|
this._awaitingPaste = true;
|
||||||
|
|
||||||
|
@ -2232,6 +2262,7 @@ function Squire ( doc, config ) {
|
||||||
// again before our after paste function is called.
|
// again before our after paste function is called.
|
||||||
this._awaitingPaste = false;
|
this._awaitingPaste = false;
|
||||||
this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
|
this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
|
||||||
|
this.addEventListener( 'copy', onCopy );
|
||||||
this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
|
this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
|
||||||
|
|
||||||
// Opera does not fire keydown repeatedly.
|
// Opera does not fire keydown repeatedly.
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,20 +1,47 @@
|
||||||
/*jshint strict:false, undef:false, unused:false */
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
|
|
||||||
var onCut = function () {
|
var onCut = function ( event ) {
|
||||||
// Save undo checkpoint
|
var clipboardData = event.clipboardData;
|
||||||
var range = this.getSelection();
|
var range = this.getSelection();
|
||||||
|
var node = this.createElement( 'div' );
|
||||||
|
var body = this._body;
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
// Save undo checkpoint
|
||||||
this._recordUndoState( range );
|
this._recordUndoState( range );
|
||||||
|
|
||||||
|
// Edge only seems to support setting plain text as of 2016-03-11.
|
||||||
|
if ( !isEdge && clipboardData ) {
|
||||||
|
moveRangeBoundariesUpTree( range, body );
|
||||||
|
node.appendChild( deleteContentsOfRange( range, body ) );
|
||||||
|
clipboardData.setData( 'text/html', node.innerHTML );
|
||||||
|
event.preventDefault();
|
||||||
|
} else {
|
||||||
|
setTimeout( function () {
|
||||||
|
try {
|
||||||
|
// If all content removed, ensure div at start of body.
|
||||||
|
self._ensureBottomLine();
|
||||||
|
} catch ( error ) {
|
||||||
|
self.didError( error );
|
||||||
|
}
|
||||||
|
}, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
this._getRangeAndRemoveBookmark( range );
|
this._getRangeAndRemoveBookmark( range );
|
||||||
this.setSelection( range );
|
this.setSelection( range );
|
||||||
setTimeout( function () {
|
};
|
||||||
try {
|
|
||||||
// If all content removed, ensure div at start of body.
|
var onCopy = function ( event ) {
|
||||||
self._ensureBottomLine();
|
var clipboardData = event.clipboardData;
|
||||||
} catch ( error ) {
|
var range = this.getSelection();
|
||||||
self.didError( error );
|
var node = this.createElement( 'div' );
|
||||||
}
|
|
||||||
}, 0 );
|
// Edge only seems to support setting plain text as of 2016-03-11.
|
||||||
|
if ( !isEdge && clipboardData ) {
|
||||||
|
node.appendChild( range.cloneContents() );
|
||||||
|
clipboardData.setData( 'text/html', node.innerHTML );
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onPaste = function ( event ) {
|
var onPaste = function ( event ) {
|
||||||
|
@ -30,7 +57,8 @@ var onPaste = function ( event ) {
|
||||||
// ---------------------------------
|
// ---------------------------------
|
||||||
// https://html.spec.whatwg.org/multipage/interaction.html
|
// https://html.spec.whatwg.org/multipage/interaction.html
|
||||||
|
|
||||||
if ( items ) {
|
// Edge only provides access to plain text as of 2016-03-11.
|
||||||
|
if ( !isEdge && items ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
l = items.length;
|
l = items.length;
|
||||||
while ( l-- ) {
|
while ( l-- ) {
|
||||||
|
@ -87,7 +115,7 @@ var onPaste = function ( event ) {
|
||||||
// let the browser insert the content. I've filed
|
// let the browser insert the content. I've filed
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1254028
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1254028
|
||||||
types = clipboardData && clipboardData.types;
|
types = clipboardData && clipboardData.types;
|
||||||
if ( types && (
|
if ( !isEdge && types && (
|
||||||
indexOf.call( types, 'text/html' ) > -1 || (
|
indexOf.call( types, 'text/html' ) > -1 || (
|
||||||
!isGecko &&
|
!isGecko &&
|
||||||
indexOf.call( types, 'text/plain' ) > -1 &&
|
indexOf.call( types, 'text/plain' ) > -1 &&
|
||||||
|
@ -105,8 +133,8 @@ var onPaste = function ( event ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No interface :(
|
// No interface. Includes all versions of IE :(
|
||||||
// ---------------
|
// --------------------------------------------
|
||||||
|
|
||||||
this._awaitingPaste = true;
|
this._awaitingPaste = true;
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,8 @@ var isMac = /Mac OS X/.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 isWebKit = /WebKit\//.test( ua );
|
var isEdge = /Edge\//.test( ua );
|
||||||
|
var isWebKit = !isEdge && /WebKit\//.test( ua );
|
||||||
|
|
||||||
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ function Squire ( doc, config ) {
|
||||||
// again before our after paste function is called.
|
// again before our after paste function is called.
|
||||||
this._awaitingPaste = false;
|
this._awaitingPaste = false;
|
||||||
this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
|
this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
|
||||||
|
this.addEventListener( 'copy', onCopy );
|
||||||
this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
|
this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
|
||||||
|
|
||||||
// Opera does not fire keydown repeatedly.
|
// Opera does not fire keydown repeatedly.
|
||||||
|
|
|
@ -145,7 +145,7 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
( isInline( endBlock ) || isBlock( endBlock ) );
|
( isInline( endBlock ) || isBlock( endBlock ) );
|
||||||
|
|
||||||
// Remove selected range
|
// Remove selected range
|
||||||
extractContentsOfRange( range );
|
var frag = extractContentsOfRange( range );
|
||||||
|
|
||||||
// Move boundaries back down tree so that they are inside the blocks.
|
// Move boundaries back down tree so that they are inside the blocks.
|
||||||
// If we don't do this, the range may be collapsed to a point between
|
// If we don't do this, the range may be collapsed to a point between
|
||||||
|
@ -175,6 +175,7 @@ var deleteContentsOfRange = function ( range ) {
|
||||||
} else {
|
} else {
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
}
|
}
|
||||||
|
return frag;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
Loading…
Reference in a new issue