Revert my Squire node.remove() back to detach(node) and

Cleanup some code
This commit is contained in:
djmaze 2020-10-17 14:08:27 +02:00
parent 807c3696d4
commit d747de7649
6 changed files with 131 additions and 183 deletions

View file

@ -35,6 +35,7 @@ And don't forget to read the [RainLoop documentation](https://www.rainloop.net/d
http://www.gnu.org/licenses/agpl-3.0.html http://www.gnu.org/licenses/agpl-3.0.html
Copyright (c) 2020 SnappyMail Copyright (c) 2020 SnappyMail
Copyright (c) 2019 RainLoop
## Modifications ## Modifications
@ -106,22 +107,22 @@ RainLoop 1.14 vs SnappyMail
|js/* |RainLoop |Snappy | |js/* |RainLoop |Snappy |
|----------- |--------: |--------: | |----------- |--------: |--------: |
|admin.js |2.130.942 | 669.377 | |admin.js |2.130.942 | 666.175 |
|app.js |4.184.455 |2.418.170 | |app.js |4.184.455 |2.412.164 |
|boot.js | 671.522 | 5.285 | |boot.js | 671.522 | 5.285 |
|libs.js | 647.614 | 240.181 | |libs.js | 647.614 | 238.674 |
|polyfills.js | 325.834 | 0 | |polyfills.js | 325.834 | 0 |
|TOTAL |7.960.367 |3.333.013 | |TOTAL |7.960.367 |3.322.298 |
|js/min/* |RainLoop |Snappy |Rain gzip |gzip |brotli | |js/min/* |RainLoop |Snappy |Rain gzip |gzip |brotli |
|--------------- |--------: |--------: |--------: |--------: |--------: | |--------------- |--------: |--------: |--------: |--------: |--------: |
|admin.min.js | 252.147 | 92.040 | 73.657 | 23.656 | 20.584 | |admin.min.js | 252.147 | 91.598 | 73.657 | 23.547 | 20.518 |
|app.min.js | 511.202 | 325.791 |140.462 | 84.060 | 68.147 | |app.min.js | 511.202 | 325.187 |140.462 | 83.936 | 68.135 |
|boot.min.js | 66.007 | 2.918 | 22.567 | 1.500 | 1.275 | |boot.min.js | 66.007 | 2.918 | 22.567 | 1.500 | 1.275 |
|libs.min.js | 572.545 | 135.084 |176.720 | 48.346 | 42.934 | |libs.min.js | 572.545 | 134.223 |176.720 | 48.183 | 42.816 |
|polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 | |polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 |
|TOTAL |1.434.353 | 555.833 |424.718 |157.562 |132.940 | |TOTAL |1.434.353 | 553.926 |424.718 |157.166 |132.744 |
|TOTAL (no admin) |1.182.206 | 463.793 |351.061 |133.906 |112.356 | |TOTAL (no admin) |1.182.206 | 462.328 |351.061 |133.619 |112.226 |
For a user its around 61% smaller and faster than traditional RainLoop. For a user its around 61% smaller and faster than traditional RainLoop.

View file

@ -487,7 +487,7 @@ class SquireUI
} }
testPresenceinSelection(format, validation) { testPresenceinSelection(format, validation) {
return validation.test(this.squire.getPath()) | this.squire.hasFormat(format); return validation.test(this.squire.getPath()) || this.squire.hasFormat(format);
} }
setMode(mode) { setMode(mode) {

View file

@ -358,40 +358,27 @@ class EmailModel {
* @returns {string} * @returns {string}
*/ */
toLine(friendlyView, wrapWithLink, useEncodeHtml) { toLine(friendlyView, wrapWithLink, useEncodeHtml) {
let result = ''; let result = '',
toLink = (to, txt) => '<a href="mailto:' + to + '" target="_blank" tabindex="-1">' + encodeHtml(txt) + '</a>';
if (this.email) { if (this.email) {
if (friendlyView && this.name) { if (friendlyView && this.name) {
result = wrapWithLink result = wrapWithLink
? '<a href="mailto:' + ? toLink(
encodeHtml(this.email) + encodeHtml(this.email) + '?to=' + encodeURIComponent('"' + this.name + '" <' + this.email + '>'),
'?to=' + this.name
encodeURIComponent('"' + this.name + '" <' + this.email + '>') + )
'" target="_blank" tabindex="-1">' +
encodeHtml(this.name) +
'</a>'
: (useEncodeHtml ? encodeHtml(this.name) : this.name); : (useEncodeHtml ? encodeHtml(this.name) : this.name);
// result = wrapWithLink ? '<a href="mailto:' + encodeHtml('"' + this.name + '" <' + this.email + '>') +
// '" target="_blank" tabindex="-1">' + encodeHtml(this.name) + '</a>' : (useEncodeHtml ? encodeHtml(this.name) : this.name);
} else { } else {
result = this.email; result = this.email;
if (this.name) { if (this.name) {
if (wrapWithLink) { if (wrapWithLink) {
result = result =
encodeHtml('"' + this.name + '" <') + encodeHtml('"' + this.name + '" <')
'<a href="mailto:' + + toLink(
encodeHtml(this.email) + encodeHtml(this.email) + '?to=' + encodeURIComponent('"' + this.name + '" <' + this.email + '>'),
'?to=' + result
encodeURIComponent('"' + this.name + '" <' + this.email + '>') + )
'" target="_blank" tabindex="-1">' + + encodeHtml('>');
encodeHtml(result) +
'</a>' +
encodeHtml('>');
// result = encodeHtml('"' + this.name + '" <') + '<a href="mailto:' +
// encodeHtml('"' + this.name + '" <' + this.email + '>') +
// '" target="_blank" tabindex="-1">' +
// encodeHtml(result) +
// '</a>' +
// encodeHtml('>');
} else { } else {
result = '"' + this.name + '" <' + result + '>'; result = '"' + this.name + '" <' + result + '>';
if (useEncodeHtml) { if (useEncodeHtml) {
@ -399,12 +386,7 @@ class EmailModel {
} }
} }
} else if (wrapWithLink) { } else if (wrapWithLink) {
result = result = toLink(encodeHtml(this.email), this.email);
'<a href="mailto:' +
encodeHtml(this.email) +
'" target="_blank" tabindex="-1">' +
encodeHtml(this.email) +
'</a>';
} }
} }
} }

37
dev/prototype.js vendored
View file

@ -8,7 +8,6 @@
Date.defineRelativeTimeFormat = config => relativeTime = config; Date.defineRelativeTimeFormat = config => relativeTime = config;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
let formats = { let formats = {
LT : {hour: 'numeric', minute: 'numeric'}, LT : {hour: 'numeric', minute: 'numeric'},
L : {}, L : {},
@ -23,6 +22,7 @@
D: {weekday: 'short'}, D: {weekday: 'short'},
j: {day: 'numeric'}, j: {day: 'numeric'},
l: {weekday: 'long'}, l: {weekday: 'long'},
z: {},
// Month // Month
F: {month: 'long'}, F: {month: 'long'},
m: {month: '2-digit'}, m: {month: '2-digit'},
@ -31,11 +31,8 @@
Y: {year: 'numeric'}, Y: {year: 'numeric'},
y: {year: '2-digit'}, y: {year: '2-digit'},
// Time // Time
a: {hour12: true},
A: {hour12: true}, A: {hour12: true},
g: {hour: 'numeric', hourCycle: 'h12'},
G: {hour: 'numeric'}, G: {hour: 'numeric'},
h: {hour: '2-digit', hourCycle: 'h12'},
H: {hour: '2-digit'}, H: {hour: '2-digit'},
i: {minute: '2-digit'}, i: {minute: '2-digit'},
s: {second: '2-digit'}, s: {second: '2-digit'},
@ -49,24 +46,24 @@
// Format momentjs/PHP date formats to Intl.DateTimeFormat // Format momentjs/PHP date formats to Intl.DateTimeFormat
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
Date.prototype.format = function (str, UTC) { Date.prototype.format = function (options, UTC) {
if ('Y-m-d\\TH:i:s' == str) { if (typeof options == 'string') {
return this.getFullYear() + '-' + pad2(1 + this.getMonth()) + '-' + pad2(this.getDate()) if ('Y-m-d\\TH:i:s' == options) {
+ 'T' + pad2(this.getHours()) + ':' + pad2(this.getMinutes()) + ':' + pad2(this.getSeconds()); return this.getFullYear() + '-' + pad2(1 + this.getMonth()) + '-' + pad2(this.getDate())
} + 'T' + pad2(this.getHours()) + ':' + pad2(this.getMinutes()) + ':' + pad2(this.getSeconds());
let options = {};
if (formats[str]) {
options = formats[str];
} else {
console.log('Date.format('+str+', '+(UTC?'true':'false')+')');
if (UTC) {
str += 'Z';
} }
let i = str.length; if (formats[options]) {
while (i--) { options = formats[options];
phpFormats[str[i]] && Object.entries(phpFormats[str[i]]).forEach(([k,v])=>options[k]=v); } else {
let o, s = options + (UTC?'Z':''), i = s.length;
console.log('Date.format('+s+')');
options = {};
while (i--) {
o = phpFormats[s[i]] || phpFormats[s[i].toUpperCase()];
o && Object.entries(o).forEach(([k,v])=>options[k]=v);
}
formats[s] = options;
} }
formats[str] = options;
} }
return new Intl.DateTimeFormat(doc.documentElement.lang, options).format(this); return new Intl.DateTimeFormat(doc.documentElement.lang, options).format(this);
}; };

View file

@ -18,6 +18,6 @@ exports.zip = (srcDir, destDir, fileName) =>
.pipe(require('gulp-zip')(fileName)) .pipe(require('gulp-zip')(fileName))
.pipe(gulp.dest(destDir)); .pipe(gulp.dest(destDir));
exports.getHead = () => (!config.community ? config.head.rainloop : config.head.agpl); exports.getHead = () => config.head.agpl;
exports.cleanStatic = () => exports.del(config.paths.static); exports.cleanStatic = () => exports.del(config.paths.static);

View file

@ -14,7 +14,6 @@ const
DOCUMENT_POSITION_PRECEDING = 2, // Node.DOCUMENT_POSITION_PRECEDING DOCUMENT_POSITION_PRECEDING = 2, // Node.DOCUMENT_POSITION_PRECEDING
ELEMENT_NODE = 1, // Node.ELEMENT_NODE, ELEMENT_NODE = 1, // Node.ELEMENT_NODE,
TEXT_NODE = 3, // Node.TEXT_NODE, TEXT_NODE = 3, // Node.TEXT_NODE,
DOCUMENT_NODE = 9, // Node.DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE = 11, // Node.DOCUMENT_FRAGMENT_NODE, DOCUMENT_FRAGMENT_NODE = 11, // Node.DOCUMENT_FRAGMENT_NODE,
SHOW_ELEMENT = 1, // NodeFilter.SHOW_ELEMENT, SHOW_ELEMENT = 1, // NodeFilter.SHOW_ELEMENT,
SHOW_TEXT = 4, // NodeFilter.SHOW_TEXT, SHOW_TEXT = 4, // NodeFilter.SHOW_TEXT,
@ -207,6 +206,13 @@ const
node.childNodes.length : node.length || 0; node.childNodes.length : node.length || 0;
}, },
detach = node => {
// node.remove();
let parent = node.parentNode;
parent && parent.removeChild( node );
return node;
},
empty = node => { empty = node => {
let frag = doc.createDocumentFragment(), let frag = doc.createDocumentFragment(),
childNodes = node.childNodes; childNodes = node.childNodes;
@ -214,7 +220,7 @@ const
return frag; return frag;
}, },
createElement = ( doc, tag, props, children ) => { createElement = ( tag, props, children ) => {
let el = doc.createElement( tag ), let el = doc.createElement( tag ),
attr, value; attr, value;
if ( props instanceof Array ) { if ( props instanceof Array ) {
@ -277,7 +283,7 @@ const
} }
// } else if ( !node.querySelector( 'BR' ) ) { // } else if ( !node.querySelector( 'BR' ) ) {
} else if ( node.matches( ':empty' ) ) { } else if ( node.matches( ':empty' ) ) {
fixer = createElement( doc, 'BR' ); fixer = createElement( 'BR' );
while ( ( child = node.lastElementChild ) && !isInline( child ) ) { while ( ( child = node.lastElementChild ) && !isInline( child ) ) {
node = child; node = child;
} }
@ -310,14 +316,14 @@ const
// && (root.__squire__._config.blockTag !== 'DIV' || (child.matches && !child.matches(phrasingElements))) // && (root.__squire__._config.blockTag !== 'DIV' || (child.matches && !child.matches(phrasingElements)))
) { ) {
if ( !wrapper ) { if ( !wrapper ) {
wrapper = createElement( doc, 'div' ); wrapper = createElement( 'div' );
} }
wrapper.append( child ); wrapper.append( child );
--i; --i;
--l; --l;
} else if ( isBR || wrapper ) { } else if ( isBR || wrapper ) {
if ( !wrapper ) { if ( !wrapper ) {
wrapper = createElement( doc, 'div' ); wrapper = createElement( 'div' );
} }
fixCursor( wrapper, root ); fixCursor( wrapper, root );
if ( isBR ) { if ( isBR ) {
@ -341,12 +347,12 @@ const
// && (root.__squire__._config.blockTag !== 'DIV' || (child.matches && !child.matches(phrasingElements))) // && (root.__squire__._config.blockTag !== 'DIV' || (child.matches && !child.matches(phrasingElements)))
) { ) {
if ( !wrapper ) { if ( !wrapper ) {
wrapper = createElement( doc, 'div' ); wrapper = createElement( 'div' );
} }
wrapper.append( child ); wrapper.append( child );
} else if ( isBR || wrapper ) { } else if ( isBR || wrapper ) {
if ( !wrapper ) { if ( !wrapper ) {
wrapper = createElement( doc, 'div' ); wrapper = createElement( 'div' );
} }
fixCursor( wrapper, root ); fixCursor( wrapper, root );
if ( isBR ) { if ( isBR ) {
@ -452,7 +458,7 @@ const
fakeRange.endOffset = getLength( prev ); fakeRange.endOffset = getLength( prev );
} }
} }
child.remove(); detach( child );
if ( child.nodeType === TEXT_NODE ) { if ( child.nodeType === TEXT_NODE ) {
prev.appendData( child.data ); prev.appendData( child.data );
} }
@ -494,8 +500,7 @@ const
parent.childNodes.length === 1 ) { parent.childNodes.length === 1 ) {
container = parent; container = parent;
} }
// container.remove(); // not a function? detach( container );
container.parentNode && container.parentNode.removeChild( container );
offset = block.childNodes.length; offset = block.childNodes.length;
@ -527,14 +532,14 @@ const
if ( prev && areAlike( prev, node ) ) { if ( prev && areAlike( prev, node ) ) {
if ( !isContainer( prev ) ) { if ( !isContainer( prev ) ) {
if ( isListItem ) { if ( isListItem ) {
block = createElement( doc, 'DIV' ); block = createElement( 'DIV' );
block.append( empty( prev ) ); block.append( empty( prev ) );
prev.append( block ); prev.append( block );
} else { } else {
return; return;
} }
} }
node.remove(); detach( node );
needsFix = !isContainer( node ); needsFix = !isContainer( node );
prev.append( empty( node ) ); prev.append( empty( node ) );
if ( needsFix ) { if ( needsFix ) {
@ -544,7 +549,7 @@ const
mergeContainers( first, root ); mergeContainers( first, root );
} }
} else if ( isListItem ) { } else if ( isListItem ) {
prev = createElement( doc, 'DIV' ); prev = createElement( 'DIV' );
node.insertBefore( prev, first ); node.insertBefore( prev, first );
fixCursor( prev, root ); fixCursor( prev, root );
} }
@ -679,7 +684,7 @@ const
afterText = NBSP + afterText.slice( 1 ); // nbsp afterText = NBSP + afterText.slice( 1 ); // nbsp
} }
before.appendData( afterText ); before.appendData( afterText );
after.remove(); detach( after );
} }
range.setStart( startContainer, startOffset ); range.setStart( startContainer, startOffset );
@ -810,7 +815,7 @@ const
if ( replaceBlock ) { if ( replaceBlock ) {
range.setEndBefore( block ); range.setEndBefore( block );
range.collapse( false ); range.collapse( false );
block.remove(); detach( block );
} }
moveRangeBoundariesUpTree( range, stopPoint, stopPoint, root ); moveRangeBoundariesUpTree( range, stopPoint, stopPoint, root );
// Now split after block up to blockquote (if a parent) or root // Now split after block up to blockquote (if a parent) or root
@ -952,10 +957,7 @@ const
startContainer = parent; startContainer = parent;
} }
while ( true ) { while ( endContainer !== endMax && endContainer !== root ) {
if ( endContainer === endMax || endContainer === root ) {
break;
}
if ( maySkipBR && if ( maySkipBR &&
endContainer.nodeType !== TEXT_NODE && endContainer.nodeType !== TEXT_NODE &&
endContainer.childNodes[ endOffset ] && endContainer.childNodes[ endOffset ] &&
@ -1119,21 +1121,19 @@ TreeWalker.prototype.previousPONode = function () {
nodeType = this.nodeType, nodeType = this.nodeType,
filter = this.filter, filter = this.filter,
node; node;
while ( true ) { while ( current ) {
node = current.lastChild; node = current.lastChild;
while ( !node && current && current !== root) { while ( !node && current && current !== root) {
node = current.previousSibling; node = current.previousSibling;
if ( !node ) { current = current.parentNode; } if ( !node ) { current = current.parentNode; }
} }
if ( !node ) { if ( node && ( typeToBitArray[ node.nodeType ] & nodeType ) && filter( node ) ) {
return null;
}
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) && filter( node ) ) {
this.currentNode = node; this.currentNode = node;
return node; return node;
} }
current = node; current = node;
} }
return null;
}; };
let onKey = function ( event ) { let onKey = function ( event ) {
@ -1236,7 +1236,7 @@ let afterDelete = ( self, range ) => {
// block. // block.
if ( node === self._root && if ( node === self._root &&
( node = node.firstChild ) && node.nodeName === 'BR' ) { ( node = node.firstChild ) && node.nodeName === 'BR' ) {
node.remove(); detach( node );
} }
self._ensureBottomLine(); self._ensureBottomLine();
self.setSelection( range ); self.setSelection( range );
@ -1254,7 +1254,7 @@ let detachUneditableNode = ( node, root ) => {
} }
node = parent; node = parent;
} }
node.remove(); detach( node );
}; };
let handleEnter = ( self, shiftKey, range ) => { let handleEnter = ( self, shiftKey, range ) => {
@ -1299,12 +1299,12 @@ let handleEnter = ( self, shiftKey, range ) => {
split( node, offset && offset - 1, root, root ); split( node, offset && offset - 1, root, root );
node = nodeAfterSplit.previousSibling; node = nodeAfterSplit.previousSibling;
if ( !node.textContent ) { if ( !node.textContent ) {
node.remove(); detach( node );
} }
node = self.createDefaultBlock(); node = self.createDefaultBlock();
nodeAfterSplit.before( node ); nodeAfterSplit.before( node );
if ( !nodeAfterSplit.textContent ) { if ( !nodeAfterSplit.textContent ) {
nodeAfterSplit.remove(); detach( nodeAfterSplit );
} }
range.setStart( node, 0 ); range.setStart( node, 0 );
} else { } else {
@ -1332,7 +1332,7 @@ let handleEnter = ( self, shiftKey, range ) => {
if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) { if ( !block || shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
// If inside an <a>, move focus out // If inside an <a>, move focus out
moveRangeBoundaryOutOf( range, 'A', root ); moveRangeBoundaryOutOf( range, 'A', root );
insertNodeInRange( range, self.createElement( 'BR' ) ); insertNodeInRange( range, createElement( 'BR' ) );
range.collapse( false ); range.collapse( false );
self.setSelection( range ); self.setSelection( range );
self._updatePath( range, true ); self._updatePath( range, true );
@ -1382,7 +1382,7 @@ let handleEnter = ( self, shiftKey, range ) => {
if ( !next || next.nodeName === 'BR' ) { if ( !next || next.nodeName === 'BR' ) {
break; break;
} }
child.remove(); detach( child );
child = next; child = next;
} }
@ -1554,7 +1554,7 @@ let keyHandlers = {
nodeAfterCursor = cursorContainer.childNodes[ cursorOffset ]; nodeAfterCursor = cursorContainer.childNodes[ cursorOffset ];
if ( nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG' ) { if ( nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG' ) {
event.preventDefault(); event.preventDefault();
nodeAfterCursor.remove(); detach( nodeAfterCursor );
moveRangeBoundariesDownTree( range ); moveRangeBoundariesDownTree( range );
afterDelete( self, range ); afterDelete( self, range );
return; return;
@ -1694,40 +1694,40 @@ let fontSizes = {
let styleToSemantic = { let styleToSemantic = {
fontWeight: { fontWeight: {
regexp: /^bold|^700/i, regexp: /^bold|^700/i,
replace: doc => createElement( doc, 'B' ) replace: () => createElement( 'B' )
}, },
fontStyle: { fontStyle: {
regexp: /^italic/i, regexp: /^italic/i,
replace: doc => createElement( doc, 'I' ) replace: () => createElement( 'I' )
}, },
fontFamily: { fontFamily: {
regexp: notWS, regexp: notWS,
replace: ( doc, classNames, family ) => createElement( doc, 'SPAN', { replace: ( doc, classNames, family ) => createElement( 'SPAN', {
'class': classNames.fontFamily, 'class': classNames.fontFamily,
style: 'font-family:' + family style: 'font-family:' + family
}) })
}, },
fontSize: { fontSize: {
regexp: notWS, regexp: notWS,
replace: ( doc, classNames, size ) => createElement( doc, 'SPAN', { replace: ( doc, classNames, size ) => createElement( 'SPAN', {
'class': classNames.fontSize, 'class': classNames.fontSize,
style: 'font-size:' + size style: 'font-size:' + size
}) })
}, },
textDecoration: { textDecoration: {
regexp: /^underline/i, regexp: /^underline/i,
replace: doc => createElement( doc, 'U' ) replace: () => createElement( 'U' )
} }
/* /*
textDecoration: { textDecoration: {
regexp: /^line-through/i, regexp: /^line-through/i,
replace: doc => createElement( doc, 'S' ) replace: doc => createElement( 'S' )
} }
*/ */
}; };
let replaceWithTag = tag => node => { let replaceWithTag = tag => node => {
let el = createElement( doc, tag ); let el = createElement( tag );
Array.prototype.forEach.call( node.attributes, attr => el.setAttribute( attr.name, attr.value ) ); Array.prototype.forEach.call( node.attributes, attr => el.setAttribute( attr.name, attr.value ) );
node.replaceWith( el ); node.replaceWith( el );
el.append( empty( node ) ); el.append( empty( node ) );
@ -1780,7 +1780,7 @@ let stylesRewriters = {
let fontSpan, sizeSpan, colourSpan; let fontSpan, sizeSpan, colourSpan;
let newTreeBottom, newTreeTop; let newTreeBottom, newTreeTop;
if ( face ) { if ( face ) {
fontSpan = createElement( doc, 'SPAN', { fontSpan = createElement( 'SPAN', {
'class': classNames.fontFamily, 'class': classNames.fontFamily,
style: 'font-family:' + face style: 'font-family:' + face
}); });
@ -1788,7 +1788,7 @@ let stylesRewriters = {
newTreeBottom = fontSpan; newTreeBottom = fontSpan;
} }
if ( size ) { if ( size ) {
sizeSpan = createElement( doc, 'SPAN', { sizeSpan = createElement( 'SPAN', {
'class': classNames.fontSize, 'class': classNames.fontSize,
style: 'font-size:' + fontSizes[ size ] + 'px' style: 'font-size:' + fontSizes[ size ] + 'px'
}); });
@ -1804,7 +1804,7 @@ let stylesRewriters = {
if ( colour.charAt( 0 ) !== '#' ) { if ( colour.charAt( 0 ) !== '#' ) {
colour = '#' + colour; colour = '#' + colour;
} }
colourSpan = createElement( doc, 'SPAN', { colourSpan = createElement( 'SPAN', {
'class': classNames.colour, 'class': classNames.colour,
style: 'color:' + colour style: 'color:' + colour
}); });
@ -1817,7 +1817,7 @@ let stylesRewriters = {
newTreeBottom = colourSpan; newTreeBottom = colourSpan;
} }
if ( !newTreeTop ) { if ( !newTreeTop ) {
newTreeTop = newTreeBottom = createElement( doc, 'SPAN' ); newTreeTop = newTreeBottom = createElement( 'SPAN' );
} }
node.replaceWith( newTreeTop ); node.replaceWith( newTreeTop );
newTreeBottom.append( empty( node ) ); newTreeBottom.append( empty( node ) );
@ -1828,7 +1828,7 @@ let stylesRewriters = {
// CODE: // CODE:
// SAMP: // SAMP:
TT: ( node, parent, config ) => { TT: ( node, parent, config ) => {
let el = createElement( doc, 'SPAN', { let el = createElement( 'SPAN', {
'class': config.classNames.fontFamily, 'class': config.classNames.fontFamily,
style: 'font-family:menlo,consolas,"courier new",monospace' style: 'font-family:menlo,consolas,"courier new",monospace'
}); });
@ -2002,7 +2002,7 @@ let cleanupBRs = ( node, root, keepForBlankLine ) => {
// browser. If it breaks a line, wrap the content in div tags // browser. If it breaks a line, wrap the content in div tags
// and replace the brs. // and replace the brs.
if ( !brBreaksLine[l] ) { if ( !brBreaksLine[l] ) {
br.remove(); detach( br );
} else if ( !isInline( parent ) ) { } else if ( !isInline( parent ) ) {
fixContainer( parent, root ); fixContainer( parent, root );
} }
@ -2016,7 +2016,7 @@ let setClipboardData =
( event, contents, root, willCutCopy, toPlainText, plainTextOnly ) => { ( event, contents, root, willCutCopy, toPlainText, plainTextOnly ) => {
let clipboardData = event.clipboardData; let clipboardData = event.clipboardData;
let body = doc.body; let body = doc.body;
let node = createElement( doc, 'div' ); let node = createElement( 'div' );
let html, text; let html, text;
node.append( contents ); node.append( contents );
@ -2291,11 +2291,6 @@ function mergeObjects ( base, extras, mayOverride ) {
} }
function Squire ( root, config ) { function Squire ( root, config ) {
if ( root.nodeType === DOCUMENT_NODE ) {
root = root.body;
}
let mutation;
this._root = root; this._root = root;
this._events = {}; this._events = {};
@ -2308,16 +2303,14 @@ function Squire ( root, config ) {
this._lastAnchorNode = null; this._lastAnchorNode = null;
this._lastFocusNode = null; this._lastFocusNode = null;
this._path = ''; this._path = '';
this._willUpdatePath = false;
let _willUpdatePath;
const selectionchange = () => { const selectionchange = () => {
if (root.contains(doc.activeElement)) { if (root.contains(doc.activeElement)) {
let self = this; if ( this._isFocused && !_willUpdatePath ) {
if ( self._isFocused && !self._willUpdatePath ) { _willUpdatePath = setTimeout( () => {
self._willUpdatePath = true; _willUpdatePath = 0;
setTimeout( function () { this._updatePath( this.getSelection() );
self._willUpdatePath = false;
self._updatePath( self.getSelection() );
}, 0 ); }, 0 );
} }
} else { } else {
@ -2333,14 +2326,13 @@ function Squire ( root, config ) {
this._ignoreChange = false; this._ignoreChange = false;
this._ignoreAllChanges = false; this._ignoreAllChanges = false;
mutation = new MutationObserver( ()=>this._docWasChanged() ); this._mutation = new MutationObserver( ()=>this._docWasChanged() );
mutation.observe( root, { this._mutation.observe( root, {
childList: true, childList: true,
attributes: true, attributes: true,
characterData: true, characterData: true,
subtree: true subtree: true
}); });
this._mutation = mutation;
// On blur, restore focus except if the user taps or clicks to focus a // On blur, restore focus except if the user taps or clicks to focus a
// specific point. Can't actually use click event because focus happens // specific point. Can't actually use click event because focus happens
@ -2430,14 +2422,10 @@ proto.setConfig = function ( config ) {
return this; return this;
}; };
proto.createElement = function ( tag, props, children ) {
return createElement( doc, tag, props, children );
};
proto.createDefaultBlock = function ( children ) { proto.createDefaultBlock = function ( children ) {
let config = this._config; let config = this._config;
return fixCursor( return fixCursor(
this.createElement( config.blockTag, config.blockAttributes, children ), createElement( config.blockTag, config.blockAttributes, children ),
this._root this._root
); );
}; };
@ -2606,8 +2594,7 @@ proto.removeEventListener = function ( type, fn ) {
// --- Selection and Path --- // --- Selection and Path ---
proto.createRange = proto.createRange = ( range, startOffset, endContainer, endOffset ) => {
function ( range, startOffset, endContainer, endOffset ) {
if ( range instanceof win.Range ) { if ( range instanceof win.Range ) {
return range.cloneRange(); return range.cloneRange();
} }
@ -2631,7 +2618,7 @@ proto.getCursorPosition = function ( range ) {
let node, parent; let node, parent;
if ( rect && !rect.top ) { if ( rect && !rect.top ) {
this._ignoreChange = true; this._ignoreChange = true;
node = doc.createElement( 'SPAN' ); node = createElement( 'SPAN' );
node.textContent = ZWS; node.textContent = ZWS;
insertNodeInRange( range, node ); insertNodeInRange( range, node );
rect = node.getBoundingClientRect(); rect = node.getBoundingClientRect();
@ -2854,12 +2841,12 @@ proto.blur = function () {
let startSelectionId = 'squire-selection-start'; let startSelectionId = 'squire-selection-start';
let endSelectionId = 'squire-selection-end'; let endSelectionId = 'squire-selection-end';
const createBookmarkNodes = self => [ const createBookmarkNodes = () => [
self.createElement( 'INPUT', { createElement( 'INPUT', {
id: startSelectionId, id: startSelectionId,
type: 'hidden' type: 'hidden'
}), }),
self.createElement( 'INPUT', { createElement( 'INPUT', {
id: endSelectionId, id: endSelectionId,
type: 'hidden' type: 'hidden'
}) })
@ -2902,8 +2889,8 @@ proto._getRangeAndRemoveBookmark = function ( range ) {
--endOffset; --endOffset;
} }
start.remove(); detach( start );
end.remove(); detach( end );
if ( !range ) { if ( !range ) {
range = doc.createRange(); range = doc.createRange();
@ -3182,7 +3169,7 @@ proto._addFormat = function ( tag, attributes, range ) {
node, needsFormat, block; node, needsFormat, block;
if ( range.collapsed ) { if ( range.collapsed ) {
el = fixCursor( this.createElement( tag, attributes ), root ); el = fixCursor( createElement( tag, attributes ), root );
insertNodeInRange( range, el ); insertNodeInRange( range, el );
range.setStart( el.firstChild, el.firstChild.length ); range.setStart( el.firstChild, el.firstChild.length );
range.collapse( true ); range.collapse( true );
@ -3256,7 +3243,7 @@ proto._addFormat = function ( tag, attributes, range ) {
startContainer = node; startContainer = node;
startOffset = 0; startOffset = 0;
} }
el = this.createElement( tag, attributes ); el = createElement( tag, attributes );
node.replaceWith( el ); node.replaceWith( el );
el.append( node ); el.append( node );
} }
@ -3312,7 +3299,7 @@ proto._removeFormat = function ( tag, attributes, range, partial ) {
endContainer = range.endContainer, endContainer = range.endContainer,
endOffset = range.endOffset, endOffset = range.endOffset,
toWrap = [], toWrap = [],
examineNode = function ( node, exemplar ) { examineNode = ( node, exemplar ) => {
// If the node is completely contained by the range then // If the node is completely contained by the range then
// we're going to remove all formatting so ignore it. // we're going to remove all formatting so ignore it.
if ( isNodeContainedInRange( range, node, false ) ) { if ( isNodeContainedInRange( range, node, false ) ) {
@ -3354,20 +3341,16 @@ proto._removeFormat = function ( tag, attributes, range, partial ) {
} }
}, },
formatTags = Array.prototype.filter.call( formatTags = Array.prototype.filter.call(
root.getElementsByTagName( tag ), function ( el ) { root.getElementsByTagName( tag ),
return isNodeContainedInRange( range, el ) && el => isNodeContainedInRange( range, el ) && hasTagAttributes( el, tag, attributes )
hasTagAttributes( el, tag, attributes );
}
); );
if ( !partial ) { if ( !partial ) {
formatTags.forEach( function ( node ) { formatTags.forEach( node => examineNode( node, node ) );
examineNode( node, node );
});
} }
// Now wrap unselected nodes in the tag // Now wrap unselected nodes in the tag
toWrap.forEach( function ( item ) { toWrap.forEach( item => {
// [ exemplar, node ] tuple // [ exemplar, node ] tuple
let el = item[0].cloneNode( false ), let el = item[0].cloneNode( false ),
node = item[1]; node = item[1];
@ -3375,9 +3358,7 @@ proto._removeFormat = function ( tag, attributes, range, partial ) {
el.append( node ); el.append( node );
}); });
// and remove old formatting tags. // and remove old formatting tags.
formatTags.forEach( function ( el ) { formatTags.forEach( el => el.replaceWith( empty( el ) ) );
el.replaceWith( empty( el ) );
});
// Merge adjacent inlines: // Merge adjacent inlines:
this._getRangeAndRemoveBookmark( range ); this._getRangeAndRemoveBookmark( range );
@ -3435,8 +3416,7 @@ let splitBlock = ( self, block, node, offset ) => {
// Make sure the new node is the correct type. // Make sure the new node is the correct type.
if ( !hasTagAttributes( nodeAfterSplit, splitTag, splitProperties ) ) { if ( !hasTagAttributes( nodeAfterSplit, splitTag, splitProperties ) ) {
block = createElement( doc, block = createElement( splitTag, splitProperties );
splitTag, splitProperties );
if ( nodeAfterSplit.dir ) { if ( nodeAfterSplit.dir ) {
block.dir = nodeAfterSplit.dir; block.dir = nodeAfterSplit.dir;
} }
@ -3511,13 +3491,13 @@ proto.modifyBlocks = function ( modify, range ) {
}; };
let increaseBlockQuoteLevel = function ( frag ) { let increaseBlockQuoteLevel = function ( frag ) {
return this.createElement( 'BLOCKQUOTE', return createElement( 'BLOCKQUOTE',
this._config.tagAttributes.blockquote, [ this._config.tagAttributes.blockquote, [
frag frag
]); ]);
}; };
let decreaseBlockQuoteLevel = function ( frag ) { let decreaseBlockQuoteLevel = frag => {
var blockquotes = frag.querySelectorAll( 'blockquote' ); var blockquotes = frag.querySelectorAll( 'blockquote' );
Array.prototype.filter.call( blockquotes, el => Array.prototype.filter.call( blockquotes, el =>
!getClosest( el.parentNode, frag, 'BLOCKQUOTE' ) !getClosest( el.parentNode, frag, 'BLOCKQUOTE' )
@ -3538,7 +3518,7 @@ let makeList = ( self, frag, type ) => {
walker.currentNode = node.lastChild; walker.currentNode = node.lastChild;
} }
if ( node.nodeName !== 'LI' ) { if ( node.nodeName !== 'LI' ) {
newLi = self.createElement( 'LI', listItemAttrs ); newLi = createElement( 'LI', listItemAttrs );
if ( node.dir ) { if ( node.dir ) {
newLi.dir = node.dir; newLi.dir = node.dir;
} }
@ -3546,12 +3526,12 @@ let makeList = ( self, frag, type ) => {
// Have we replaced the previous block with a new <ul>/<ol>? // Have we replaced the previous block with a new <ul>/<ol>?
if ( ( prev = node.previousSibling ) && prev.nodeName === type ) { if ( ( prev = node.previousSibling ) && prev.nodeName === type ) {
prev.append( newLi ); prev.append( newLi );
node.remove(); detach( node );
} }
// Otherwise, replace this block with the <ul>/<ol> // Otherwise, replace this block with the <ul>/<ol>
else { else {
node.replaceWith( node.replaceWith(
self.createElement( type, listAttrs, [ createElement( type, listAttrs, [
newLi newLi
]) ])
); );
@ -3563,7 +3543,7 @@ let makeList = ( self, frag, type ) => {
tag = node.nodeName; tag = node.nodeName;
if ( tag !== type && ( /^[OU]L$/.test( tag ) ) ) { if ( tag !== type && ( /^[OU]L$/.test( tag ) ) ) {
node.replaceWith( node.replaceWith(
self.createElement( type, listAttrs, [ empty( node ) ] ) createElement( type, listAttrs, [ empty( node ) ] )
); );
} }
} }
@ -3659,7 +3639,7 @@ proto.increaseListLevel = function ( range ) {
let listAttrs, next; let listAttrs, next;
if ( newParent.nodeName !== type ) { if ( newParent.nodeName !== type ) {
listAttrs = this._config.tagAttributes[ type.toLowerCase() ]; listAttrs = this._config.tagAttributes[ type.toLowerCase() ];
newParent = this.createElement( type, listAttrs ); newParent = createElement( type, listAttrs );
startLi.before( newParent ); startLi.before( newParent );
} }
do { do {
@ -3735,7 +3715,7 @@ proto.decreaseListLevel = function ( range ) {
} }
if ( !list.firstChild ) { if ( !list.firstChild ) {
list.remove(); detach( list );
} }
if ( insertBefore ) { if ( insertBefore ) {
@ -3805,7 +3785,7 @@ proto.setHTML = function ( html ) {
if ( typeof sanitizeToDOMFragment === 'function' ) { if ( typeof sanitizeToDOMFragment === 'function' ) {
frag = sanitizeToDOMFragment( html, false, this ); frag = sanitizeToDOMFragment( html, false, this );
} else { } else {
div = this.createElement( 'DIV' ); div = createElement( 'DIV' );
div.innerHTML = html; div.innerHTML = html;
frag = doc.createDocumentFragment(); frag = doc.createDocumentFragment();
frag.append( empty( div ) ); frag.append( empty( div ) );
@ -3897,7 +3877,7 @@ proto.insertElement = function ( el, range ) {
}; };
proto.insertImage = function ( src, attributes ) { proto.insertImage = function ( src, attributes ) {
let img = this.createElement( 'IMG', mergeObjects({ let img = createElement( 'IMG', mergeObjects({
src: src src: src
}, attributes, true )); }, attributes, true ));
this.insertElement( img ); this.insertElement( img );
@ -3924,7 +3904,7 @@ let addLinks = ( frag, root, self ) => {
child = doc.createTextNode( data.slice( 0, index ) ); child = doc.createTextNode( data.slice( 0, index ) );
parent.insertBefore( child, node ); parent.insertBefore( child, node );
} }
child = self.createElement( 'A', mergeObjects({ child = createElement( 'A', mergeObjects({
href: match[1] ? href: match[1] ?
/^(?:ht|f)tps?:/i.test( match[1] ) ? /^(?:ht|f)tps?:/i.test( match[1] ) ?
match[1] : match[1] :
@ -3971,7 +3951,7 @@ proto.insertHTML = function ( html, isPaste ) {
html = '<TABLE>' + html + '</TABLE>'; html = '<TABLE>' + html + '</TABLE>';
} }
// Parse HTML into DOM tree // Parse HTML into DOM tree
div = this.createElement( 'DIV' ); div = createElement( 'DIV' );
div.innerHTML = html; div.innerHTML = html;
frag = doc.createDocumentFragment(); frag = doc.createDocumentFragment();
frag.append( empty( div ) ); frag.append( empty( div ) );
@ -4106,7 +4086,7 @@ let command = ( method, arg, arg2 ) => function () {
proto.addStyles = function ( styles ) { proto.addStyles = function ( styles ) {
if ( styles ) { if ( styles ) {
let head = doc.documentElement.firstChild, let head = doc.documentElement.firstChild,
style = this.createElement( 'STYLE', { style = createElement( 'STYLE', {
type: 'text/css' type: 'text/css'
}); });
style.append( doc.createTextNode( styles ) ); style.append( doc.createTextNode( styles ) );
@ -4225,26 +4205,14 @@ proto.setHighlightColour = function ( colour ) {
}; };
proto.setTextAlignment = function ( alignment ) { proto.setTextAlignment = function ( alignment ) {
this.forEachBlock( function ( block ) { this.forEachBlock( block => {
let className = block.className block.style.textAlign = alignment || '';
.split( /\s+/ )
.filter( function ( klass ) {
return !!klass && !/^align/.test( klass );
})
.join( ' ' );
if ( alignment ) {
block.className = className + ' align-' + alignment;
block.style.textAlign = alignment;
} else {
block.className = className;
block.style.textAlign = '';
}
}, true ); }, true );
return this.focus(); return this.focus();
}; };
proto.setTextDirection = function ( direction ) { proto.setTextDirection = function ( direction ) {
this.forEachBlock( function ( block ) { this.forEachBlock( block => {
if ( direction ) { if ( direction ) {
block.dir = direction; block.dir = direction;
} else { } else {
@ -4280,7 +4248,7 @@ let addPre = function ( frag ) {
while ( l-- ) { while ( l-- ) {
br = nodes[l]; br = nodes[l];
if ( !brBreaksLine[l] ) { if ( !brBreaksLine[l] ) {
br.remove(); detach( br );
} else { } else {
br.replaceWith( doc.createTextNode( '\n' ) ); br.replaceWith( doc.createTextNode( '\n' ) );
} }
@ -4289,7 +4257,7 @@ let addPre = function ( frag ) {
nodes = node.querySelectorAll( 'CODE' ); nodes = node.querySelectorAll( 'CODE' );
l = nodes.length; l = nodes.length;
while ( l-- ) { while ( l-- ) {
nodes[l].remove(); detach( nodes[l] );
} }
if ( output.childNodes.length ) { if ( output.childNodes.length ) {
output.append( doc.createTextNode( '\n' ) ); output.append( doc.createTextNode( '\n' ) );
@ -4302,7 +4270,7 @@ let addPre = function ( frag ) {
node.data = node.data.replace( NBSP, ' ' ); // nbsp -> sp node.data = node.data.replace( NBSP, ' ' ); // nbsp -> sp
} }
output.normalize(); output.normalize();
return fixCursor( this.createElement( 'PRE', return fixCursor( createElement( 'PRE',
this._config.tagAttributes.pre, [ this._config.tagAttributes.pre, [
output output
]), root ); ]), root );
@ -4324,7 +4292,7 @@ let removePre = function ( frag ) {
contents.append( contents.append(
doc.createTextNode( value.slice( 0, index ) ) doc.createTextNode( value.slice( 0, index ) )
); );
contents.append( doc.createElement( 'BR' ) ); contents.append( createElement( 'BR' ) );
value = value.slice( index + 1 ); value = value.slice( index + 1 );
} }
node.before( contents ); node.before( contents );