mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-01-04 14:02:17 +08:00
Resolve #296 and cleanup Squire a bit
This commit is contained in:
parent
a9b9b7df03
commit
40a0391092
1 changed files with 41 additions and 133 deletions
174
vendors/squire/build/squire-raw.js
vendored
174
vendors/squire/build/squire-raw.js
vendored
|
@ -34,7 +34,6 @@ const
|
||||||
isMac = /Mac OS X/.test( ua ),
|
isMac = /Mac OS X/.test( ua ),
|
||||||
isIOS = /iP(?:ad|hone|od)/.test( ua ) || ( isMac && !!navigator.maxTouchPoints ),
|
isIOS = /iP(?:ad|hone|od)/.test( ua ) || ( isMac && !!navigator.maxTouchPoints ),
|
||||||
|
|
||||||
isGecko = /Gecko\//.test( ua ),
|
|
||||||
isWebKit = /WebKit\//.test( ua ),
|
isWebKit = /WebKit\//.test( ua ),
|
||||||
|
|
||||||
ctrlKey = isMac ? 'meta-' : 'ctrl-',
|
ctrlKey = isMac ? 'meta-' : 'ctrl-',
|
||||||
|
@ -1460,6 +1459,28 @@ const
|
||||||
|
|
||||||
blacklist = /^(?:HEAD|META|STYLE)/,
|
blacklist = /^(?:HEAD|META|STYLE)/,
|
||||||
|
|
||||||
|
// Previous node in post-order.
|
||||||
|
previousPONode = walker => {
|
||||||
|
let current = walker.currentNode,
|
||||||
|
root = walker.root,
|
||||||
|
nodeType = walker.nodeType, // whatToShow?
|
||||||
|
filter = walker.filter,
|
||||||
|
node;
|
||||||
|
while ( current ) {
|
||||||
|
node = current.lastChild;
|
||||||
|
while ( !node && current && current !== root) {
|
||||||
|
node = current.previousSibling;
|
||||||
|
if ( !node ) { current = current.parentNode; }
|
||||||
|
}
|
||||||
|
if ( node && ( typeToBitArray[ node.nodeType ] & nodeType ) && filter( node ) ) {
|
||||||
|
walker.currentNode = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
current = node;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Two purposes:
|
Two purposes:
|
||||||
|
|
||||||
|
@ -1513,7 +1534,7 @@ const
|
||||||
// before the start of a new block we don't trim
|
// before the start of a new block we don't trim
|
||||||
if ( startsWithWS ) {
|
if ( startsWithWS ) {
|
||||||
walker.currentNode = child;
|
walker.currentNode = child;
|
||||||
while ( sibling = walker.previousPONode() ) {
|
while ( sibling = previousPONode(walker) ) {
|
||||||
nodeName = sibling.nodeName;
|
nodeName = sibling.nodeName;
|
||||||
if ( nodeName === 'IMG' ||
|
if ( nodeName === 'IMG' ||
|
||||||
( nodeName === '#text' &&
|
( nodeName === '#text' &&
|
||||||
|
@ -1842,54 +1863,11 @@ const
|
||||||
escapeHTML = text => text.replace( '&', '&' )
|
escapeHTML = text => text.replace( '&', '&' )
|
||||||
.replace( '<', '<' )
|
.replace( '<', '<' )
|
||||||
.replace( '>', '>' )
|
.replace( '>', '>' )
|
||||||
.replace( '"', '"' ),
|
.replace( '"', '"' );
|
||||||
|
|
||||||
removeFormatting = ( self, root, clean ) => {
|
|
||||||
let node, next;
|
|
||||||
for ( node = root.firstChild; node; node = next ) {
|
|
||||||
next = node.nextSibling;
|
|
||||||
if ( isInline( node ) ) {
|
|
||||||
if ( node.nodeType === TEXT_NODE || node.nodeName === 'BR' || node.nodeName === 'IMG' ) {
|
|
||||||
clean.append( node );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if ( isBlock( node ) ) {
|
|
||||||
clean.append( self.createDefaultBlock([
|
|
||||||
removeFormatting(
|
|
||||||
self, node, doc.createDocumentFragment() )
|
|
||||||
]));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
removeFormatting( self, node, clean );
|
|
||||||
}
|
|
||||||
return clean;
|
|
||||||
};
|
|
||||||
|
|
||||||
let contentWalker,
|
let contentWalker,
|
||||||
nodeCategoryCache = new WeakMap();
|
nodeCategoryCache = new WeakMap();
|
||||||
|
|
||||||
// Previous node in post-order.
|
|
||||||
TreeWalker.prototype.previousPONode = function () {
|
|
||||||
let current = this.currentNode,
|
|
||||||
root = this.root,
|
|
||||||
nodeType = this.nodeType, // whatToShow?
|
|
||||||
filter = this.filter,
|
|
||||||
node;
|
|
||||||
while ( current ) {
|
|
||||||
node = current.lastChild;
|
|
||||||
while ( !node && current && current !== root) {
|
|
||||||
node = current.previousSibling;
|
|
||||||
if ( !node ) { current = current.parentNode; }
|
|
||||||
}
|
|
||||||
if ( node && ( typeToBitArray[ node.nodeType ] & nodeType ) && filter( node ) ) {
|
|
||||||
this.currentNode = node;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
current = node;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function onKey ( event ) {
|
function onKey ( event ) {
|
||||||
if ( event.defaultPrevented ) {
|
if ( event.defaultPrevented ) {
|
||||||
return;
|
return;
|
||||||
|
@ -2009,30 +1987,20 @@ function onCopy ( 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPaste ( event ) {
|
function onPaste ( event ) {
|
||||||
let clipboardData = event.clipboardData;
|
let clipboardData = event.clipboardData;
|
||||||
let items = clipboardData && clipboardData.items;
|
let items = clipboardData && clipboardData.items;
|
||||||
let choosePlain = this.isShiftDown;
|
let imageItem = null;
|
||||||
let hasRTF = false;
|
|
||||||
let hasImage = false;
|
|
||||||
let plainItem = null;
|
let plainItem = null;
|
||||||
let htmlItem = null;
|
let htmlItem = null;
|
||||||
let self = this;
|
let self = this;
|
||||||
let l, item, type, types, data;
|
let type;
|
||||||
|
|
||||||
// Current HTML5 Clipboard interface
|
// Current HTML5 Clipboard interface
|
||||||
// ---------------------------------
|
// ---------------------------------
|
||||||
// https://html.spec.whatwg.org/multipage/interaction.html
|
// https://html.spec.whatwg.org/multipage/interaction.html
|
||||||
if ( items ) {
|
if ( items ) {
|
||||||
l = items.length;
|
[...items].forEach(item => {
|
||||||
while ( l-- ) {
|
|
||||||
item = items[l];
|
|
||||||
type = item.type;
|
type = item.type;
|
||||||
if ( type === 'text/html' ) {
|
if ( type === 'text/html' ) {
|
||||||
htmlItem = item;
|
htmlItem = item;
|
||||||
|
@ -2041,70 +2009,23 @@ function onPaste ( event ) {
|
||||||
// plain text.
|
// plain text.
|
||||||
} else if ( type === 'text/plain' || type === 'text/uri-list' ) {
|
} else if ( type === 'text/plain' || type === 'text/uri-list' ) {
|
||||||
plainItem = item;
|
plainItem = item;
|
||||||
} else if ( type === 'text/rtf' ) {
|
} else if ( item.kind === 'file' && /^image\/(png|jpeg|webp)/.test( type ) ) {
|
||||||
hasRTF = true;
|
imageItem = item;
|
||||||
} else if ( /^image\//.test( type ) ) {
|
|
||||||
hasImage = true;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
if (htmlItem || plainItem || imageItem) {
|
||||||
// Treat image paste as a drop of an image file. When you copy
|
event.preventDefault();
|
||||||
// an image in Chrome/Firefox (at least), it copies the image data
|
if ( htmlItem && ( !self.isShiftDown || !plainItem ) ) {
|
||||||
// but also an HTML version (referencing the original URL of the image)
|
htmlItem.getAsString( html => self.insertHTML( html, true ) );
|
||||||
// and a plain text version.
|
} else if ( plainItem ) {
|
||||||
//
|
plainItem.getAsString( text => self.insertPlainText( text, true ) );
|
||||||
// However, when you copy in Excel, you get html, rtf, text, image;
|
} else if ( imageItem ) {
|
||||||
// in this instance you want the html version! So let's try using
|
|
||||||
// the presence of text/rtf as an indicator to choose the html version
|
|
||||||
// over the image.
|
|
||||||
if ( hasImage && !( hasRTF && htmlItem ) ) {
|
|
||||||
/*
|
|
||||||
if (item.kind === 'file') {
|
|
||||||
event.preventDefault();
|
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
reader.onload = event =>
|
reader.onload = event =>
|
||||||
self.insertHTML( '<img src="'+event.target.result+'">', true );
|
self.insertHTML( '<img src="'+event.target.result+'">', true );
|
||||||
reader.readAsDataURL(item.getAsFile());
|
reader.readAsDataURL(imageItem.getAsFile());
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge only provides access to plain text as of 2016-03-11 and gives no
|
|
||||||
// indication there should be an HTML part. However, it does support
|
|
||||||
// access to image data, so we check for that first. Otherwise though,
|
|
||||||
// fall through to fallback clipboard handling methods
|
|
||||||
event.preventDefault();
|
|
||||||
if ( htmlItem && ( !choosePlain || !plainItem ) ) {
|
|
||||||
htmlItem.getAsString( html => self.insertHTML( html, true ) );
|
|
||||||
} else if ( plainItem ) {
|
|
||||||
plainItem.getAsString( text => self.insertPlainText( text, true ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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*.
|
|
||||||
types = clipboardData && clipboardData.types;
|
|
||||||
if ( types && (
|
|
||||||
types.includes( 'text/html' ) || (
|
|
||||||
!isGecko &&
|
|
||||||
types.includes( 'text/plain') &&
|
|
||||||
!types.includes( 'text/rtf' ))
|
|
||||||
)) {
|
|
||||||
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. On iOS, Facebook (and possibly other
|
|
||||||
// apps?) copy links as type text/uri-list, but also insert a **blank**
|
|
||||||
// text/plain item onto the clipboard. Why? Who knows.
|
|
||||||
if ( !choosePlain && ( data = clipboardData.getData( 'text/html' ) ) ) {
|
|
||||||
this.insertHTML( data, true );
|
|
||||||
} else if ( data = clipboardData.getData( 'text/plain' ) || clipboardData.getData( 'text/uri-list' ) ) {
|
|
||||||
this.insertPlainText( data, true );
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2113,21 +2034,7 @@ function onPaste ( event ) {
|
||||||
// save an undo state and hope for the best.
|
// save an undo state and hope for the best.
|
||||||
function onDrop ( event ) {
|
function onDrop ( event ) {
|
||||||
let types = event.dataTransfer.types;
|
let types = event.dataTransfer.types;
|
||||||
let l = types.length;
|
if ( types.includes('text/plain') || types.includes('text/html') ) {
|
||||||
let hasData = false;
|
|
||||||
while ( l-- ) {
|
|
||||||
switch ( types[l] ) {
|
|
||||||
case 'text/plain':
|
|
||||||
case 'text/html':
|
|
||||||
hasData = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if ( types.includes('text/plain') || types.includes('text/html') ) {
|
|
||||||
if ( hasData ) {
|
|
||||||
this.saveUndoState();
|
this.saveUndoState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2568,7 +2475,8 @@ class Squire
|
||||||
.addEventListener( 'focus', () => this._restoreSelection && this.setSelection( this._lastRange ) )
|
.addEventListener( 'focus', () => this._restoreSelection && this.setSelection( this._lastRange ) )
|
||||||
.addEventListener( 'cut', onCut )
|
.addEventListener( 'cut', onCut )
|
||||||
.addEventListener( 'copy', onCopy )
|
.addEventListener( 'copy', onCopy )
|
||||||
.addEventListener( 'keydown keyup', monitorShiftKey )
|
// Need to monitor for shift key like this, as event.shiftKey is not available in paste event.
|
||||||
|
.addEventListener( 'keydown keyup', event => this.isShiftDown = event.shiftKey )
|
||||||
.addEventListener( 'paste', onPaste )
|
.addEventListener( 'paste', onPaste )
|
||||||
.addEventListener( 'drop', onDrop )
|
.addEventListener( 'drop', onDrop )
|
||||||
.addEventListener( 'keydown', onKey )
|
.addEventListener( 'keydown', onKey )
|
||||||
|
|
Loading…
Reference in a new issue