0
Fork 0
mirror of https://github.com/fastmail/Squire.git synced 2024-12-22 15:23:29 -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:
Neil Jenkins 2016-03-11 15:22:49 +11:00
parent 6ed3b93900
commit 6413034884
6 changed files with 96 additions and 34 deletions

View file

@ -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

View file

@ -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;

View file

@ -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-';

View file

@ -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.

View file

@ -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;
}; };
// --- // ---