0
Fork 0
mirror of https://github.com/fastmail/Squire.git synced 2024-12-22 15:23:29 -05:00
Squire/source/Clipboard.js
Neil Jenkins 6413034884 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.
2016-03-11 15:22:49 +11:00

200 lines
6.9 KiB
JavaScript

/*jshint strict:false, undef:false, unused:false */
var onCut = function ( event ) {
var clipboardData = event.clipboardData;
var range = this.getSelection();
var node = this.createElement( 'div' );
var body = this._body;
var self = this;
// Save undo checkpoint
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.setSelection( range );
};
var onCopy = function ( event ) {
var clipboardData = event.clipboardData;
var range = this.getSelection();
var node = this.createElement( 'div' );
// 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 clipboardData = event.clipboardData,
items = clipboardData && clipboardData.items,
fireDrop = false,
hasImage = false,
plainItem = null,
self = this,
l, item, type, types, data;
// Current HTML5 Clipboard interface
// ---------------------------------
// https://html.spec.whatwg.org/multipage/interaction.html
// Edge only provides access to plain text as of 2016-03-11.
if ( !isEdge && items ) {
event.preventDefault();
l = items.length;
while ( l-- ) {
item = items[l];
type = item.type;
if ( type === 'text/html' ) {
/*jshint loopfunc: true */
item.getAsString( function ( html ) {
self.insertHTML( html, true );
});
/*jshint loopfunc: false */
return;
}
if ( type === 'text/plain' ) {
plainItem = item;
}
if ( /^image\/.*/.test( type ) ) {
hasImage = true;
}
}
// Treat image paste as a drop of an image file.
if ( hasImage ) {
this.fireEvent( 'dragover', {
dataTransfer: clipboardData,
/*jshint loopfunc: true */
preventDefault: function () {
fireDrop = true;
}
/*jshint loopfunc: false */
});
if ( fireDrop ) {
this.fireEvent( 'drop', {
dataTransfer: clipboardData
});
}
} else if ( plainItem ) {
item.getAsString( function ( text ) {
self.insertPlainText( text, true );
});
}
return;
}
// Old interface
// -------------
// Safari (and indeed many other OS X apps) copies stuff as text/rtf
// rather than text/html; even from a webpage in Safari. The only way
// to get an HTML version is to fallback to letting the browser insert
// the content. Same for getting image data. *Sigh*.
//
// Firefox is even worse: it doesn't even let you know that there might be
// an RTF version on the clipboard, but it will also convert to HTML if you
// let the browser insert the content. I've filed
// https://bugzilla.mozilla.org/show_bug.cgi?id=1254028
types = clipboardData && clipboardData.types;
if ( !isEdge && types && (
indexOf.call( types, 'text/html' ) > -1 || (
!isGecko &&
indexOf.call( types, 'text/plain' ) > -1 &&
indexOf.call( types, 'text/rtf' ) < 0 )
)) {
event.preventDefault();
// Abiword on Linux copies a plain text and html version, but the HTML
// version is the empty string! So always try to get HTML, but if none,
// insert plain text instead.
if (( data = clipboardData.getData( 'text/html' ) )) {
this.insertHTML( data, true );
} else if (( data = clipboardData.getData( 'text/plain' ) )) {
this.insertPlainText( data, true );
}
return;
}
// No interface. Includes all versions of IE :(
// --------------------------------------------
this._awaitingPaste = true;
var body = this._body,
range = this.getSelection(),
startContainer = range.startContainer,
startOffset = range.startOffset,
endContainer = range.endContainer,
endOffset = range.endOffset,
startBlock = getStartBlockOfRange( range );
// We need to position the pasteArea in the visible portion of the screen
// to stop the browser auto-scrolling.
var pasteArea = this.createElement( 'DIV', {
style: 'position: absolute; overflow: hidden; top:' +
( body.scrollTop +
( startBlock ? startBlock.getBoundingClientRect().top : 0 ) ) +
'px; right: 150%; width: 1px; height: 1px;'
});
body.appendChild( pasteArea );
range.selectNodeContents( pasteArea );
this.setSelection( range );
// A setTimeout of 0 means this is added to the back of the
// single javascript thread, so it will be executed after the
// paste event.
setTimeout( function () {
try {
// IE sometimes fires the beforepaste event twice; make sure it is
// not run again before our after paste function is called.
self._awaitingPaste = false;
// Get the pasted content and clean
var html = '',
next = pasteArea,
first, range;
// #88: Chrome can apparently split the paste area if certain
// content is inserted; gather them all up.
while ( pasteArea = next ) {
next = pasteArea.nextSibling;
detach( pasteArea );
// Safari and IE like putting extra divs around things.
first = pasteArea.firstChild;
if ( first && first === pasteArea.lastChild &&
first.nodeName === 'DIV' ) {
pasteArea = first;
}
html += pasteArea.innerHTML;
}
range = self._createRange(
startContainer, startOffset, endContainer, endOffset );
self.setSelection( range );
if ( html ) {
self.insertHTML( html, true );
}
} catch ( error ) {
self.didError( error );
}
}, 0 );
};