mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Squire
* drop isWin and isEdge (legacy) * simplify isWebKit * some code to ES 2015
This commit is contained in:
parent
d4f55a02a4
commit
5efbcf5ad3
1 changed files with 61 additions and 122 deletions
183
vendors/squire/build/squire-raw.js
vendored
183
vendors/squire/build/squire-raw.js
vendored
|
|
@ -32,12 +32,10 @@ const
|
||||||
ua = navigator.userAgent,
|
ua = navigator.userAgent,
|
||||||
|
|
||||||
isMac = /Mac OS X/.test( ua ),
|
isMac = /Mac OS X/.test( ua ),
|
||||||
isWin = /Windows NT/.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 ),
|
isGecko = /Gecko\//.test( ua ),
|
||||||
isEdge = /Edge\//.test( ua ),
|
isWebKit = /WebKit\//.test( ua ),
|
||||||
isWebKit = !isEdge && /WebKit\//.test( ua ),
|
|
||||||
|
|
||||||
ctrlKey = isMac ? 'meta-' : 'ctrl-',
|
ctrlKey = isMac ? 'meta-' : 'ctrl-',
|
||||||
osKey = isMac ? 'metaKey' : 'ctrlKey',
|
osKey = isMac ? 'metaKey' : 'ctrlKey',
|
||||||
|
|
@ -143,15 +141,7 @@ const
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
hasTagAttributes = ( node, tag, attributes ) => {
|
hasTagAttributes = ( node, tag, attributes ) => {
|
||||||
if ( node.nodeName !== tag ) {
|
return node.nodeName === tag && Object.entries(attributes).every(([k,v]) => node.getAttribute(k) === v);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for ( let attr in attributes ) {
|
|
||||||
if ( node.getAttribute( attr ) !== attributes[ attr ] ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
getClosest = ( node, root, selector ) => {
|
getClosest = ( node, root, selector ) => {
|
||||||
node = (!node || node.closest ? node : node.parentElement);
|
node = (!node || node.closest ? node : node.parentElement);
|
||||||
|
|
@ -213,31 +203,25 @@ const
|
||||||
|
|
||||||
setStyle = ( node, style ) => {
|
setStyle = ( node, style ) => {
|
||||||
if (typeof style === 'object') {
|
if (typeof style === 'object') {
|
||||||
for ( let p in style ) {
|
Object.entries(style).forEach(([k,v]) => node.style[k] = v);
|
||||||
node.style[p] = style[p];
|
|
||||||
}
|
|
||||||
} else if ( style !== undefined ) {
|
} else if ( style !== undefined ) {
|
||||||
node.setAttribute( 'style', style );
|
node.setAttribute( 'style', style );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
createElement = ( tag, props, children ) => {
|
createElement = ( tag, props, children ) => {
|
||||||
let el = doc.createElement( tag ),
|
let el = doc.createElement( tag );
|
||||||
attr, value;
|
|
||||||
if ( props instanceof Array ) {
|
if ( props instanceof Array ) {
|
||||||
children = props;
|
children = props;
|
||||||
props = null;
|
props = null;
|
||||||
}
|
}
|
||||||
if ( props ) {
|
props && Object.entries(props).forEach(([k,v]) => {
|
||||||
for ( attr in props ) {
|
if ('style' === k) {
|
||||||
value = props[ attr ];
|
setStyle( el, v );
|
||||||
if ('style' === attr) {
|
} else if ( v !== undefined ) {
|
||||||
setStyle( el, value );
|
el.setAttribute( k, v );
|
||||||
} else if ( value !== undefined ) {
|
|
||||||
el.setAttribute( attr, value );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
children && el.append( ...children );
|
children && el.append( ...children );
|
||||||
return el;
|
return el;
|
||||||
},
|
},
|
||||||
|
|
@ -530,13 +514,12 @@ const
|
||||||
|
|
||||||
if ( prev && areAlike( prev, node ) ) {
|
if ( prev && areAlike( prev, node ) ) {
|
||||||
if ( !isContainer( prev ) ) {
|
if ( !isContainer( prev ) ) {
|
||||||
if ( isListItem ) {
|
if ( !isListItem ) {
|
||||||
block = createElement( 'DIV' );
|
|
||||||
block.append( empty( prev ) );
|
|
||||||
prev.append( block );
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
block = createElement( 'DIV' );
|
||||||
|
block.append( empty( prev ) );
|
||||||
|
prev.append( block );
|
||||||
}
|
}
|
||||||
detach( node );
|
detach( node );
|
||||||
needsFix = !isContainer( node );
|
needsFix = !isContainer( node );
|
||||||
|
|
@ -727,11 +710,11 @@ const
|
||||||
|
|
||||||
// Ensure root has a block-level element in it.
|
// Ensure root has a block-level element in it.
|
||||||
child = root.firstChild;
|
child = root.firstChild;
|
||||||
if ( !child || child.nodeName === 'BR' ) {
|
if ( child && child.nodeName !== 'BR' ) {
|
||||||
|
range.collapse( true );
|
||||||
|
} else {
|
||||||
fixCursor( root, root );
|
fixCursor( root, root );
|
||||||
range.selectNodeContents( root.firstChild );
|
range.selectNodeContents( root.firstChild );
|
||||||
} else {
|
|
||||||
range.collapse( true );
|
|
||||||
}
|
}
|
||||||
return frag;
|
return frag;
|
||||||
},
|
},
|
||||||
|
|
@ -861,24 +844,13 @@ const
|
||||||
|
|
||||||
nodeRange.selectNode( node );
|
nodeRange.selectNode( node );
|
||||||
|
|
||||||
if ( partial ) {
|
return partial
|
||||||
// Node must not finish before range starts or start after range
|
// Node must not finish before range starts or start after range finishes.
|
||||||
// finishes.
|
? range.compareBoundaryPoints( END_TO_START, nodeRange ) < 0
|
||||||
let nodeEndBeforeStart = ( range.compareBoundaryPoints(
|
&& range.compareBoundaryPoints( START_TO_END, nodeRange ) > 0
|
||||||
END_TO_START, nodeRange ) > -1 ),
|
// Node must start after range starts and finish before range finishes
|
||||||
nodeStartAfterEnd = ( range.compareBoundaryPoints(
|
: range.compareBoundaryPoints( START_TO_START, nodeRange ) < 1
|
||||||
START_TO_END, nodeRange ) < 1 );
|
&& range.compareBoundaryPoints( END_TO_END, nodeRange ) > -1;
|
||||||
return ( !nodeEndBeforeStart && !nodeStartAfterEnd );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Node must start after range starts and finish before range
|
|
||||||
// finishes
|
|
||||||
let nodeStartAfterStart = ( range.compareBoundaryPoints(
|
|
||||||
START_TO_START, nodeRange ) < 1 ),
|
|
||||||
nodeEndBeforeEnd = ( range.compareBoundaryPoints(
|
|
||||||
END_TO_END, nodeRange ) > -1 );
|
|
||||||
return ( nodeStartAfterStart && nodeEndBeforeEnd );
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
moveRangeBoundariesDownTree = range => {
|
moveRangeBoundariesDownTree = range => {
|
||||||
|
|
@ -1002,8 +974,7 @@ const
|
||||||
} else if ( container !== root && isBlock( container ) ) {
|
} else if ( container !== root && isBlock( container ) ) {
|
||||||
block = container;
|
block = container;
|
||||||
} else {
|
} else {
|
||||||
block = getNodeBefore( container, range.startOffset );
|
block = getNextBlock( getNodeBefore( container, range.startOffset ), root );
|
||||||
block = getNextBlock( block, root );
|
|
||||||
}
|
}
|
||||||
// Check the block actually intersects the range
|
// Check the block actually intersects the range
|
||||||
return block && isNodeContainedInRange( range, block ) ? block : null;
|
return block && isNodeContainedInRange( range, block ) ? block : null;
|
||||||
|
|
@ -1414,16 +1385,15 @@ const
|
||||||
|
|
||||||
replaceStyles = node => {
|
replaceStyles = node => {
|
||||||
let style = node.style;
|
let style = node.style;
|
||||||
let attr, converter, css, newTreeBottom, newTreeTop, el;
|
let css, newTreeBottom, newTreeTop, el;
|
||||||
|
|
||||||
for ( attr in styleToSemantic ) {
|
Object.entries(styleToSemantic).forEach(([attr,converter])=>{
|
||||||
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 ( el.nodeName === node.nodeName &&
|
if ( el.nodeName === node.nodeName &&
|
||||||
el.className === node.className ) {
|
el.className === node.className ) {
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
if ( !newTreeTop ) {
|
if ( !newTreeTop ) {
|
||||||
newTreeTop = el;
|
newTreeTop = el;
|
||||||
|
|
@ -1434,7 +1404,7 @@ const
|
||||||
newTreeBottom = el;
|
newTreeBottom = el;
|
||||||
node.style[ attr ] = '';
|
node.style[ attr ] = '';
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if ( newTreeTop ) {
|
if ( newTreeTop ) {
|
||||||
newTreeBottom.append( empty( node ) );
|
newTreeBottom.append( empty( node ) );
|
||||||
|
|
@ -1681,13 +1651,6 @@ const
|
||||||
text = text.replace( NBSP, ' ' ); // Replace nbsp with regular space
|
text = text.replace( NBSP, ' ' ); // Replace nbsp with regular space
|
||||||
node.remove( );
|
node.remove( );
|
||||||
|
|
||||||
// Firefox (and others?) returns unix line endings (\n) even on Windows.
|
|
||||||
// If on Windows, normalise to \r\n, since Notepad and some other crappy
|
|
||||||
// apps do not understand just \n.
|
|
||||||
if ( isWin ) {
|
|
||||||
text = text.replace( /\r?\n/g, '\r\n' );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( text !== html ) {
|
if ( text !== html ) {
|
||||||
clipboardData.setData( 'text/html', html );
|
clipboardData.setData( 'text/html', html );
|
||||||
}
|
}
|
||||||
|
|
@ -1696,20 +1659,14 @@ const
|
||||||
},
|
},
|
||||||
|
|
||||||
mergeObjects = ( base, extras, mayOverride ) => {
|
mergeObjects = ( base, extras, mayOverride ) => {
|
||||||
let prop, value;
|
base = base || {};
|
||||||
if ( !base ) {
|
extras && Object.entries(extras).forEach(([prop,value])=>{
|
||||||
base = {};
|
if ( mayOverride || !( prop in base ) ) {
|
||||||
}
|
base[ prop ] = ( value && value.constructor === Object ) ?
|
||||||
if ( extras ) {
|
mergeObjects( base[ prop ], value, mayOverride ) :
|
||||||
for ( prop in extras ) {
|
value;
|
||||||
if ( mayOverride || !( prop in base ) ) {
|
|
||||||
value = extras[ prop ];
|
|
||||||
base[ prop ] = ( value && value.constructor === Object ) ?
|
|
||||||
mergeObjects( base[ prop ], value, mayOverride ) :
|
|
||||||
value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return base;
|
return base;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1777,13 +1734,8 @@ const
|
||||||
},
|
},
|
||||||
|
|
||||||
splitBlock = ( self, block, node, offset ) => {
|
splitBlock = ( self, block, node, offset ) => {
|
||||||
let splitTag = tagAfterSplit[ block.nodeName ],
|
let splitTag = tagAfterSplit[ block.nodeName ] || self._config.blockTag,
|
||||||
nodeAfterSplit = split( node, offset, block.parentNode, self._root ),
|
nodeAfterSplit = split( node, offset, block.parentNode, self._root );
|
||||||
config = self._config;
|
|
||||||
|
|
||||||
if ( !splitTag ) {
|
|
||||||
splitTag = config.blockTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the new node is the correct type.
|
// Make sure the new node is the correct type.
|
||||||
if ( !hasTagAttributes( nodeAfterSplit, splitTag, {} ) ) {
|
if ( !hasTagAttributes( nodeAfterSplit, splitTag, {} ) ) {
|
||||||
|
|
@ -1964,11 +1916,6 @@ function onKey ( event ) {
|
||||||
if ( event[osKey] ) { modifiers += ctrlKey; }
|
if ( event[osKey] ) { modifiers += ctrlKey; }
|
||||||
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
||||||
}
|
}
|
||||||
// However, on Windows, shift-delete is apparently "cut" (WTF right?), so
|
|
||||||
// we want to let the browser handle shift-delete in this situation.
|
|
||||||
if ( isWin && event.shiftKey && key === 'delete' ) {
|
|
||||||
modifiers += 'shift-';
|
|
||||||
}
|
|
||||||
|
|
||||||
key = modifiers + key;
|
key = modifiers + key;
|
||||||
|
|
||||||
|
|
@ -2004,7 +1951,7 @@ function onCut ( event ) {
|
||||||
this.saveUndoState( range );
|
this.saveUndoState( range );
|
||||||
|
|
||||||
// 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.
|
||||||
if ( !isEdge && event.clipboardData ) {
|
if ( event.clipboardData ) {
|
||||||
// Clipboard content should include all parents within block, or all
|
// Clipboard content should include all parents within block, or all
|
||||||
// parents up to root if selection across blocks
|
// parents up to root if selection across blocks
|
||||||
startBlock = getStartBlockOfRange( range, root );
|
startBlock = getStartBlockOfRange( range, root );
|
||||||
|
|
@ -2041,7 +1988,7 @@ function onCut ( event ) {
|
||||||
|
|
||||||
function onCopy ( event ) {
|
function onCopy ( event ) {
|
||||||
// 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.
|
||||||
if ( !isEdge && event.clipboardData ) {
|
if ( event.clipboardData ) {
|
||||||
let range = this.getSelection(), root = this._root,
|
let range = this.getSelection(), root = this._root,
|
||||||
// Clipboard content should include all parents within block, or all
|
// Clipboard content should include all parents within block, or all
|
||||||
// parents up to root if selection across blocks
|
// parents up to root if selection across blocks
|
||||||
|
|
@ -2141,15 +2088,13 @@ function onPaste ( event ) {
|
||||||
// indication there should be an HTML part. However, it does support
|
// indication there should be an HTML part. However, it does support
|
||||||
// access to image data, so we check for that first. Otherwise though,
|
// access to image data, so we check for that first. Otherwise though,
|
||||||
// fall through to fallback clipboard handling methods
|
// fall through to fallback clipboard handling methods
|
||||||
if ( !isEdge ) {
|
event.preventDefault();
|
||||||
event.preventDefault();
|
if ( htmlItem && ( !choosePlain || !plainItem ) ) {
|
||||||
if ( htmlItem && ( !choosePlain || !plainItem ) ) {
|
htmlItem.getAsString( html => self.insertHTML( html, true ) );
|
||||||
htmlItem.getAsString( html => self.insertHTML( html, true ) );
|
} else if ( plainItem ) {
|
||||||
} else if ( plainItem ) {
|
plainItem.getAsString( text => self.insertPlainText( text, true ) );
|
||||||
plainItem.getAsString( text => self.insertPlainText( text, true ) );
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safari (and indeed many other OS X apps) copies stuff as text/rtf
|
// Safari (and indeed many other OS X apps) copies stuff as text/rtf
|
||||||
|
|
@ -2157,11 +2102,11 @@ function onPaste ( event ) {
|
||||||
// to get an HTML version is to fallback to letting the browser insert
|
// to get an HTML version is to fallback to letting the browser insert
|
||||||
// the content. Same for getting image data. *Sigh*.
|
// the content. Same for getting image data. *Sigh*.
|
||||||
types = clipboardData && clipboardData.types;
|
types = clipboardData && clipboardData.types;
|
||||||
if ( !isEdge && types && (
|
if ( types && (
|
||||||
indexOf( types, 'text/html' ) > -1 || (
|
types.includes( 'text/html' ) || (
|
||||||
!isGecko &&
|
!isGecko &&
|
||||||
indexOf( types, 'text/plain' ) > -1 &&
|
types.includes( 'text/plain') &&
|
||||||
indexOf( types, 'text/rtf' ) < 0 )
|
!types.includes( 'text/rtf' ))
|
||||||
)) {
|
)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// Abiword on Linux copies a plain text and html version, but the HTML
|
// Abiword on Linux copies a plain text and html version, but the HTML
|
||||||
|
|
@ -2171,9 +2116,7 @@ function onPaste ( event ) {
|
||||||
// text/plain item onto the clipboard. Why? Who knows.
|
// text/plain item onto the clipboard. Why? Who knows.
|
||||||
if ( !choosePlain && ( 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' ) || clipboardData.getData( 'text/uri-list' ) ) {
|
||||||
( data = clipboardData.getData( 'text/plain' ) ) ||
|
|
||||||
( data = clipboardData.getData( 'text/uri-list' ) ) ) {
|
|
||||||
this.insertPlainText( data, true );
|
this.insertPlainText( data, true );
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -2186,21 +2129,20 @@ function onPaste ( event ) {
|
||||||
function onDrop ( event ) {
|
function onDrop ( event ) {
|
||||||
let types = event.dataTransfer.types;
|
let types = event.dataTransfer.types;
|
||||||
let l = types.length;
|
let l = types.length;
|
||||||
let hasPlain = false;
|
let hasData = false;
|
||||||
let hasHTML = false;
|
|
||||||
while ( l-- ) {
|
while ( l-- ) {
|
||||||
switch ( types[l] ) {
|
switch ( types[l] ) {
|
||||||
case 'text/plain':
|
case 'text/plain':
|
||||||
hasPlain = true;
|
|
||||||
break;
|
|
||||||
case 'text/html':
|
case 'text/html':
|
||||||
hasHTML = true;
|
hasData = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( hasHTML || hasPlain ) {
|
|
||||||
|
// if ( types.includes('text/plain') || types.includes('text/html') ) {
|
||||||
|
if ( hasData ) {
|
||||||
this.saveUndoState();
|
this.saveUndoState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2859,9 +2801,7 @@ class Squire
|
||||||
this._lastRange = range;
|
this._lastRange = range;
|
||||||
// If we're setting selection, that automatically, and synchronously, // triggers a focus event. So just store the selection and mark it as
|
// 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.
|
// needing restore on focus.
|
||||||
if ( !this._isFocused ) {
|
if ( this._isFocused ) {
|
||||||
this._restoreSelection = true;
|
|
||||||
} 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
|
||||||
|
|
@ -2882,6 +2822,8 @@ class Squire
|
||||||
sel.removeAllRanges();
|
sel.removeAllRanges();
|
||||||
sel.addRange( range );
|
sel.addRange( range );
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this._restoreSelection = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -2989,11 +2931,8 @@ class Squire
|
||||||
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
||||||
|
|
||||||
_addZWS () {
|
_addZWS () {
|
||||||
if ( isWebKit ) {
|
this._hasZWS = isWebKit;
|
||||||
this._hasZWS = true;
|
return doc.createTextNode( isWebKit ? ZWS : '' );
|
||||||
return doc.createTextNode( ZWS );
|
|
||||||
}
|
|
||||||
return doc.createTextNode( '' );
|
|
||||||
}
|
}
|
||||||
_removeZWS () {
|
_removeZWS () {
|
||||||
if ( this._hasZWS ) {
|
if ( this._hasZWS ) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue