0
Fork 0
mirror of https://github.com/fastmail/Squire.git synced 2024-12-22 07:13:08 -05:00

Update Squire & Implement Headings in Squire-UI

This commit is contained in:
Matthew Borden 2014-12-09 21:50:27 +01:00
parent c582d365a5
commit 1c9f303e75
7 changed files with 318 additions and 312 deletions

File diff suppressed because one or more lines are too long

View file

@ -124,6 +124,7 @@ $(document).ready(function() {
testQuote: editor.testPresenceinSelection(
'increaseQuoteLevel', action, 'blockquote', (
/>blockquote\b/)),
testHeading: editor.testPresenceinSelection('makeHeading', action, 'H1', (/>H1\b/)),
isNotValue: function (a) {return (a == action && this.value !== ''); }
};
@ -131,20 +132,20 @@ $(document).ready(function() {
editor.alignCenter = function () { editor.setTextAlignment('center'); };
editor.alignLeft = function () { editor.setTextAlignment('left'); };
editor.alignJustify = function () { editor.setTextAlignment('justify'); };
editor.makeHeading = function () { editor.setFontSize('2em'); editor.bold(); };
if (test.testBold | test.testItalic | test.testUnderline | test.testOrderedList | test.testLink | test.testQuote) {
if (test.testBold | test.testItalic | test.testUnderline | test.testOrderedList | test.testLink | test.testQuote | test.testHeading) {
if (test.testBold) editor.removeBold();
if (test.testItalic) editor.removeItalic();
if (test.testUnderline) editor.removeUnderline();
if (test.testLink) editor.removeLink();
if (test.testOrderedList) editor.removeList();
if (test.testQuote) editor.decreaseQuoteLevel();
if (test.testHeading) editor.removeHeading();
} else if (test.isNotValue('makeLink') | test.isNotValue('insertImage') | test.isNotValue('selectFont')) {
// do nothing these are dropdowns.
} else {
if (editor.getSelectedText() === '' && !(action == 'insertImage' || action == 'makeOrderedList' || action == 'increaseQuoteLevel' || action == 'redo' || action == 'undo')) return;
editor[action]();
if (editor.getSelectedText() === '' && !(action == 'insertImage' || action == 'makeOrderedList' || action == 'increaseQuoteLevel' || action == 'redo' || action == 'undo' || action == "makeHeading")) return;
editor[action]();
}
});
});

View file

@ -49,9 +49,6 @@
</style>
</head>
<body>
<!--[if IE 8]>
<script type="text/javascript" src="ie8.js"></script>
<![endif]-->
<script type="text/javascript" src="squire.js"></script>
</body>
</html>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3,16 +3,13 @@
( function ( doc, undefined ) {
"use strict";
/*global doc, navigator */
/*jshint strict:false */
/*jshint strict:false, undef:false, unused:false */
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
var TEXT_NODE = 3; // Node.TEXT_NODE;
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
var FILTER_ACCEPT = 1; // NodeFilter.FILTER_ACCEPT;
var FILTER_SKIP = 3; // NodeFilter.FILTER_SKIP;
var START_TO_START = 0; // Range.START_TO_START
var START_TO_END = 1; // Range.START_TO_END
@ -29,27 +26,26 @@ var isMac = /Mac OS X/.test( ua );
var isGecko = /Gecko\//.test( ua );
var isIE8or9or10 = /Trident\/[456]\./.test( ua );
var isIE8 = ( win.ie === 8 );
var isOpera = !!win.opera;
var isPresto = !!win.opera;
var isWebKit = /WebKit\//.test( ua );
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
var useTextFixer = isIE8or9or10 || isOpera;
var useTextFixer = isIE8or9or10 || isPresto;
var cantFocusEmptyTextNodes = isIE8or9or10 || isWebKit;
var losesSelectionOnBlur = isIE8or9or10;
var hasBuggySplit = ( function () {
var hasBuggySplit = function ( doc ) {
var div = doc.createElement( 'DIV' ),
text = doc.createTextNode( '12' );
div.appendChild( text );
text.splitText( 2 );
return div.childNodes.length !== 2;
}() );
};
// Use [^ \t\r\n] instead of \S so that nbsp does not count as white-space
var notWS = /[^ \t\r\n]/;
var indexOf = Array.prototype.indexOf;
/*global FILTER_ACCEPT */
/*jshint strict:false */
/*
@ -103,7 +99,7 @@ TreeWalker.prototype.nextNode = function () {
return null;
}
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
filter( node ) === FILTER_ACCEPT ) {
filter( node ) ) {
this.currentNode = node;
return node;
}
@ -133,31 +129,16 @@ TreeWalker.prototype.previousNode = function () {
return null;
}
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
filter( node ) === FILTER_ACCEPT ) {
filter( node ) ) {
this.currentNode = node;
return node;
}
current = node;
}
};
/*global
ELEMENT_NODE,
TEXT_NODE,
SHOW_ELEMENT,
FILTER_ACCEPT,
FILTER_SKIP,
win,
isOpera,
useTextFixer,
cantFocusEmptyTextNodes,
/*jshint strict:false, undef:false, unused:false */
TreeWalker,
Text
*/
/*jshint strict:false */
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|FN|EL)|EM|FONT|HR|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TR(?:IKE|ONG)|MALL|AMP)?|U|VAR|WBR)$/;
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|FN|EL)|EM|FONT|HR|H1|I(?:NPUT|MG|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:U[BP]|PAN|TR(?:IKE|ONG)|MALL|AMP)?|U|VAR|WBR)$/;
var leafNodeNames = {
BR: 1,
@ -214,13 +195,10 @@ function isContainer ( node ) {
!isInline( node ) && !isBlock( node );
}
function acceptIfBlock ( el ) {
return isBlock( el ) ? FILTER_ACCEPT : FILTER_SKIP;
}
function getBlockWalker ( node ) {
var doc = node.ownerDocument,
walker = new TreeWalker(
doc.body, SHOW_ELEMENT, acceptIfBlock, false );
doc.body, SHOW_ELEMENT, isBlock, false );
walker.currentNode = node;
return walker;
}
@ -290,6 +268,29 @@ function empty ( node ) {
return frag;
}
function createElement ( doc, tag, props, children ) {
var el = doc.createElement( tag ),
attr, value, i, l;
if ( props instanceof Array ) {
children = props;
props = null;
}
if ( props ) {
for ( attr in props ) {
value = props[ attr ];
if ( value !== undefined ) {
el.setAttribute( attr, props[ attr ] );
}
}
}
if ( children ) {
for ( i = 0, l = children.length; i < l; i += 1 ) {
el.appendChild( children[i] );
}
}
return el;
}
function fixCursor ( node ) {
// In Webkit and Gecko, block level elements are collapsed and
// unfocussable if they have no content. To remedy this, a <BR> must be
@ -297,7 +298,8 @@ function fixCursor ( node ) {
// cursor to appear.
var doc = node.ownerDocument,
root = node,
fixer, child;
fixer, child,
l, instance;
if ( node.nodeName === 'BODY' ) {
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
@ -314,11 +316,22 @@ function fixCursor ( node ) {
}
if ( isInline( node ) ) {
if ( !node.firstChild ) {
child = node.firstChild;
while ( cantFocusEmptyTextNodes && child &&
child.nodeType === TEXT_NODE && !child.data ) {
node.removeChild( child );
child = node.firstChild;
}
if ( !child ) {
if ( cantFocusEmptyTextNodes ) {
fixer = doc.createTextNode( '\u200B' );
if ( win.editor ) {
win.editor._didAddZWS();
// Find the relevant Squire instance and notify
l = instances.length;
while ( l-- ) {
instance = instances[l];
if ( instance._doc === doc ) {
instance._didAddZWS();
}
}
} else {
fixer = doc.createTextNode( '' );
@ -358,6 +371,42 @@ function fixCursor ( node ) {
return root;
}
// Recursively examine container nodes and wrap any inline children.
function fixContainer ( container ) {
var children = container.childNodes,
doc = container.ownerDocument,
wrapper = null,
i, l, child, isBR;
for ( i = 0, l = children.length; i < l; i += 1 ) {
child = children[i];
isBR = child.nodeName === 'BR';
if ( !isBR && isInline( child ) ) {
if ( !wrapper ) { wrapper = createElement( doc, 'DIV' ); }
wrapper.appendChild( child );
i -= 1;
l -= 1;
} else if ( isBR || wrapper ) {
if ( !wrapper ) { wrapper = createElement( doc, 'DIV' ); }
fixCursor( wrapper );
if ( isBR ) {
container.replaceChild( wrapper, child );
} else {
container.insertBefore( wrapper, child );
i += 1;
l += 1;
}
wrapper = null;
}
if ( isContainer( child ) ) {
fixContainer( child );
}
}
if ( wrapper ) {
container.appendChild( fixCursor( wrapper ) );
}
return container;
}
function split ( node, offset, stopNode ) {
var nodeType = node.nodeType,
parent, clone, next;
@ -499,7 +548,7 @@ function mergeWithBlock ( block, next, range ) {
// Steps to reproduce bug: Type "a-b-c" (where - is return)
// then backspace twice. The cursor goes to the top instead
// of after "b".
if ( isOpera && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
if ( isPresto && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
block.removeChild( last );
}
}
@ -541,90 +590,7 @@ function mergeContainers ( node ) {
fixCursor( prev );
}
}
// Recursively examine container nodes and wrap any inline children.
function fixContainer ( container ) {
var children = container.childNodes,
doc = container.ownerDocument,
wrapper = null,
i, l, child, isBR;
for ( i = 0, l = children.length; i < l; i += 1 ) {
child = children[i];
isBR = child.nodeName === 'BR';
if ( !isBR && isInline( child ) ) {
if ( !wrapper ) { wrapper = createElement( doc, 'DIV' ); }
wrapper.appendChild( child );
i -= 1;
l -= 1;
} else if ( isBR || wrapper ) {
if ( !wrapper ) { wrapper = createElement( doc, 'DIV' ); }
fixCursor( wrapper );
if ( isBR ) {
container.replaceChild( wrapper, child );
} else {
container.insertBefore( wrapper, child );
i += 1;
l += 1;
}
wrapper = null;
}
if ( isContainer( child ) ) {
fixContainer( child );
}
}
if ( wrapper ) {
container.appendChild( fixCursor( wrapper ) );
}
return container;
}
function createElement ( doc, tag, props, children ) {
var el = doc.createElement( tag ),
attr, value, i, l;
if ( props instanceof Array ) {
children = props;
props = null;
}
if ( props ) {
for ( attr in props ) {
value = props[ attr ];
if ( value !== undefined ) {
el.setAttribute( attr, props[ attr ] );
}
}
}
if ( children ) {
for ( i = 0, l = children.length; i < l; i += 1 ) {
el.appendChild( children[i] );
}
}
return el;
}
/*global
ELEMENT_NODE,
TEXT_NODE,
SHOW_TEXT,
FILTER_ACCEPT,
START_TO_START,
START_TO_END,
END_TO_END,
END_TO_START,
indexOf,
TreeWalker,
isLeaf,
isInline,
isBlock,
getPreviousBlock,
getNextBlock,
getLength,
fixCursor,
split,
mergeWithBlock,
mergeContainers
*/
/*jshint strict:false */
/*jshint strict:false, undef:false, unused:false */
var getNodeBefore = function ( node, offset ) {
var children = node.childNodes;
@ -661,8 +627,8 @@ var forEachTextNodeInRange = function ( range, fn ) {
endContainer = range.endContainer,
root = range.commonAncestorContainer,
walker = new TreeWalker(
root, SHOW_TEXT, function ( node ) {
return FILTER_ACCEPT;
root, SHOW_TEXT, function (/* node */) {
return true;
}, false ),
textnode = walker.currentNode = startContainer;
@ -1053,50 +1019,56 @@ var getEndBlockOfRange = function ( range ) {
return block && isNodeContainedInRange( range, block, true ) ? block : null;
};
var contentWalker = new TreeWalker( null,
SHOW_TEXT|SHOW_ELEMENT,
function ( node ) {
return node.nodeType === TEXT_NODE ?
notWS.test( node.data ) :
node.nodeName === 'IMG';
}
);
var rangeDoesStartAtBlockBoundary = function ( range ) {
var startContainer = range.startContainer,
startOffset = range.startOffset,
parent, child;
startOffset = range.startOffset;
while ( isInline( startContainer ) ) {
// If in the middle or end of a text node, we're not at the boundary.
if ( startContainer.nodeType === TEXT_NODE ) {
if ( startOffset ) {
return false;
}
parent = startContainer.parentNode;
startOffset = indexOf.call( parent.childNodes, startContainer );
startContainer = parent;
contentWalker.currentNode = startContainer;
} else {
contentWalker.currentNode = getNodeAfter( startContainer, startOffset );
}
// Skip empty text nodes and <br>s.
while ( startOffset &&
( child = startContainer.childNodes[ startOffset - 1 ] ) &&
( child.data === '' || child.nodeName === 'BR' ) ) {
startOffset -= 1;
}
return !startOffset;
// Otherwise, look for any previous content in the same block.
contentWalker.root = getStartBlockOfRange( range );
return !contentWalker.previousNode();
};
var rangeDoesEndAtBlockBoundary = function ( range ) {
var endContainer = range.endContainer,
endOffset = range.endOffset,
length = getLength( endContainer ),
parent, child;
length;
while ( isInline( endContainer ) ) {
if ( endOffset !== length ) {
// If in a text node with content, and not at the end, we're not
// at the boundary
if ( endContainer.nodeType === TEXT_NODE ) {
length = endContainer.data.length;
if ( length && endOffset < length ) {
return false;
}
parent = endContainer.parentNode;
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
endContainer = parent;
length = endContainer.childNodes.length;
contentWalker.currentNode = endContainer;
} else {
contentWalker.currentNode = getNodeBefore( endContainer, endOffset );
}
// Skip empty text nodes and <br>s.
while ( endOffset < length &&
( child = endContainer.childNodes[ endOffset ] ) &&
( child.data === '' || child.nodeName === 'BR' ) ) {
endOffset += 1;
}
return endOffset === length;
// Otherwise, look for any further content in the same block.
contentWalker.root = getEndBlockOfRange( range );
return !contentWalker.nextNode();
};
var expandRangeToBlockBoundaries = function ( range ) {
@ -1111,73 +1083,9 @@ var expandRangeToBlockBoundaries = function ( range ) {
range.setEnd( parent, indexOf.call( parent.childNodes, end ) + 1 );
}
};
/*global
DOCUMENT_POSITION_PRECEDING,
ELEMENT_NODE,
TEXT_NODE,
SHOW_ELEMENT,
SHOW_TEXT,
FILTER_ACCEPT,
FILTER_SKIP,
win,
isIOS,
isMac,
isGecko,
isIE8or9or10,
isIE8,
isOpera,
ctrlKey,
useTextFixer,
cantFocusEmptyTextNodes,
losesSelectionOnBlur,
hasBuggySplit,
notWS,
indexOf,
/*jshint strict:false, undef:false, unused:false */
TreeWalker,
hasTagAttributes,
isLeaf,
isInline,
isBlock,
isContainer,
getBlockWalker,
getPreviousBlock,
getNextBlock,
getNearest,
getPath,
getLength,
detach,
replaceWith,
empty,
fixCursor,
split,
mergeInlines,
mergeWithBlock,
mergeContainers,
fixContainer,
createElement,
forEachTextNodeInRange,
getTextContentInRange,
insertNodeInRange,
extractContentsOfRange,
deleteContentsOfRange,
insertTreeFragmentIntoRange,
isNodeContainedInRange,
moveRangeBoundariesDownTree,
moveRangeBoundariesUpTree,
getStartBlockOfRange,
getEndBlockOfRange,
rangeDoesStartAtBlockBoundary,
rangeDoesEndAtBlockBoundary,
expandRangeToBlockBoundaries,
top,
console,
setTimeout
*/
/*jshint strict:false */
var instances = [];
function Squire ( doc ) {
var win = doc.defaultView;
@ -1227,7 +1135,7 @@ function Squire ( doc ) {
this.addEventListener( 'keyup', this._ieSelAllClean );
}
// Opera does not fire keydown repeatedly.
this.addEventListener( isOpera ? 'keypress' : 'keydown', this._onKey );
this.addEventListener( isPresto ? 'keypress' : 'keydown', this._onKey );
// Fix IE8/9's buggy implementation of Text#splitText.
// If the split is at the end of the node, it doesn't insert the newly split
@ -1235,7 +1143,7 @@ function Squire ( doc ) {
// And even if the split is not at the end, the original node is removed
// from the document and replaced by another, rather than just having its
// data shortened.
if ( hasBuggySplit ) {
if ( hasBuggySplit( doc ) ) {
win.Text.prototype.splitText = function ( offset ) {
var afterSplit = this.ownerDocument.createTextNode(
this.data.slice( offset ) ),
@ -1262,6 +1170,8 @@ function Squire ( doc ) {
doc.execCommand( 'enableObjectResizing', false, 'false' );
doc.execCommand( 'enableInlineTableEditing', false, 'false' );
} catch ( error ) {}
instances.push( this );
}
var proto = Squire.prototype;
@ -1323,6 +1233,26 @@ proto.fireEvent = function ( type, event ) {
return this;
};
proto.destroy = function () {
var win = this._win,
doc = this._doc,
events = this._events,
type;
win.removeEventListener( 'focus', this, false );
win.removeEventListener( 'blur', this, false );
for ( type in events ) {
if ( !customEvents[ type ] ) {
doc.removeEventListener( type, this, true );
}
}
var l = instances.length;
while ( l-- ) {
if ( instances[l] === this ) {
instances.splice( l, 1 );
}
}
};
proto.handleEvent = function ( event ) {
this.fireEvent( event.type, event );
};
@ -1339,7 +1269,7 @@ proto.addEventListener = function ( type, fn ) {
if ( !handlers ) {
handlers = this._events[ type ] = [];
if ( !customEvents[ type ] ) {
this._doc.addEventListener( type, this, false );
this._doc.addEventListener( type, this, true );
}
}
handlers.push( fn );
@ -1453,7 +1383,7 @@ proto._removeZWS = function () {
return;
}
var walker = new TreeWalker( this._body, SHOW_TEXT, function () {
return FILTER_ACCEPT;
return true;
}, false ),
node, index;
while ( node = walker.nextNode() ) {
@ -1493,11 +1423,10 @@ proto._updatePathOnEvent = function () {
// --- Focus ---
proto.focus = function () {
// FF seems to need the body to be focussed
// (at least on first load).
if ( isGecko ) {
this._body.focus();
}
// FF seems to need the body to be focussed (at least on first load).
// Chrome also now needs body to be focussed in order to show the cursor
// (otherwise it is focussed, but the cursor doesn't appear).
this._body.focus();
this._win.focus();
return this;
};
@ -1710,8 +1639,7 @@ proto.hasFormat = function ( tag, attributes, range ) {
// Otherwise, check each text node at least partially contained within
// the selection and make sure all of them have the format we want.
walker = new TreeWalker( root, SHOW_TEXT, function ( node ) {
return isNodeContainedInRange( range, node, true ) ?
FILTER_ACCEPT : FILTER_SKIP;
return isNodeContainedInRange( range, node, true );
}, false );
var seenNode = false;
@ -1729,7 +1657,7 @@ proto._addFormat = function ( tag, attributes, range ) {
// If the range is collapsed we simply insert the node by wrapping
// it round the range and focus it.
var el, walker, startContainer, endContainer, startOffset, endOffset,
textnode, needsFormat;
textNode, needsFormat;
if ( range.collapsed ) {
el = fixCursor( this.createElement( tag, attributes ) );
@ -1750,47 +1678,54 @@ proto._addFormat = function ( tag, attributes, range ) {
range.commonAncestorContainer,
SHOW_TEXT,
function ( node ) {
return isNodeContainedInRange( range, node, true ) ?
FILTER_ACCEPT : FILTER_SKIP;
return isNodeContainedInRange( range, node, true );
},
false
);
// Start at the beginning node of the range and iterate through
// all the nodes in the range that need formatting.
startOffset = 0;
endOffset = 0;
textnode = walker.currentNode = range.startContainer;
startContainer = range.startContainer;
startOffset = range.startOffset;
endContainer = range.endContainer;
endOffset = range.endOffset;
if ( textnode.nodeType !== TEXT_NODE ) {
textnode = walker.nextNode();
// Make sure we start inside a text node.
walker.currentNode = startContainer;
if ( startContainer.nodeType !== TEXT_NODE ) {
startContainer = walker.nextNode();
startOffset = 0;
}
do {
needsFormat = !getNearest( textnode, tag, attributes );
if ( textnode === range.endContainer ) {
if ( needsFormat && textnode.length > range.endOffset ) {
textnode.splitText( range.endOffset );
} else {
endOffset = range.endOffset;
}
}
if ( textnode === range.startContainer ) {
if ( needsFormat && range.startOffset ) {
textnode = textnode.splitText( range.startOffset );
} else {
startOffset = range.startOffset;
}
}
textNode = walker.currentNode;
needsFormat = !getNearest( textNode, tag, attributes );
if ( needsFormat ) {
if ( textNode === endContainer &&
textNode.length > endOffset ) {
textNode.splitText( endOffset );
}
if ( textNode === startContainer && startOffset ) {
textNode = textNode.splitText( startOffset );
if ( endContainer === startContainer ) {
endContainer = textNode;
endOffset -= startOffset;
}
startContainer = textNode;
startOffset = 0;
}
el = this.createElement( tag, attributes );
replaceWith( textnode, el );
el.appendChild( textnode );
endOffset = textnode.length;
replaceWith( textNode, el );
el.appendChild( textNode );
}
endContainer = textnode;
if ( !startContainer ) { startContainer = endContainer; }
} while ( textnode = walker.nextNode() );
} while ( walker.nextNode() );
// Make sure we finish inside a text node. Otherwise offset may have
// changed.
if ( endContainer.nodeType !== TEXT_NODE ) {
endContainer = textNode;
endOffset = textNode.length;
}
// Now set the selection to as it was before
range = this._createRange(
@ -2125,6 +2060,7 @@ var makeOrderedList = function ( frag ) {
return frag;
};
var removeList = function ( frag ) {
var lists = frag.querySelectorAll( 'UL, OL' ),
i, l, ll, list, listFrag, children, child;
@ -2210,7 +2146,7 @@ var addLinks = function ( frag ) {
var doc = frag.ownerDocument,
walker = new TreeWalker( frag, SHOW_TEXT,
function ( node ) {
return getNearest( node, 'A' ) ? FILTER_SKIP : FILTER_ACCEPT;
return !getNearest( node, 'A' );
}, false ),
node, data, parent, match, index, endIndex, child;
while ( node = walker.nextNode() ) {
@ -2482,6 +2418,14 @@ var cleanTree = function ( node, allowStyles ) {
}
continue;
}
// If we have just white space, it may still be important if it
// separates two inline nodes, e.g. "<a>link</a> <a>link</a>".
else if ( i && i + 1 < l &&
isInline( children[ i - 1 ] ) &&
isInline( children[ i + 1 ] ) ) {
child.data = ' ';
continue;
}
}
node.removeChild( child );
i -= 1;
@ -2492,10 +2436,9 @@ var cleanTree = function ( node, allowStyles ) {
};
var notWSTextNode = function ( node ) {
return ( node.nodeType === ELEMENT_NODE ?
return node.nodeType === ELEMENT_NODE ?
node.nodeName === 'BR' :
notWS.test( node.data ) ) ?
FILTER_ACCEPT : FILTER_SKIP;
notWS.test( node.data );
};
var isLineBreak = function ( br ) {
var block = br.parentNode,
@ -2539,17 +2482,23 @@ var cleanupBRs = function ( root ) {
block = block.parentNode;
}
// If this is not inside a block, replace it by wrapping
// inlines in DIV.
if ( !isBlock( block ) || !tagAfterSplit[ block.nodeName ] ) {
// inlines in a <div>.
if ( !isBlock( block ) ) {
fixContainer( block );
}
// If in a block we can split, split it instead, but only if there
// is actual text content in the block. Otherwise, the <br> is a
// placeholder to stop the block from collapsing, so we must leave
// it.
else {
// If it doesn't break a line, just remove it; it's not doing
// anything useful. We'll add it back later if required by the
// browser. If it breaks a line, split the block or leave it as
// appropriate.
if ( brBreaksLine[l] ) {
splitBlock( block, br.parentNode, br );
// If in a <div>, split, but anywhere else we might change
// the formatting too much (e.g. <li> -> to two list items!)
// so just play it safe and leave it.
if ( block.nodeName !== 'DIV' ) {
continue;
}
split( br.parentNode, br, block.parentNode );
}
detach( br );
}
@ -2631,11 +2580,20 @@ proto._onPaste = function ( event ) {
startContainer = range.startContainer,
startOffset = range.startOffset,
endContainer = range.endContainer,
endOffset = range.endOffset;
endOffset = range.endOffset,
startBlock = getStartBlockOfRange( range );
// Record undo checkpoint
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( 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 + 30) + 'px; left: 0; width: 1px; height: 1px;'
( body.scrollTop +
( startBlock ? startBlock.getBoundingClientRect().top : 0 ) ) +
'px; left: 0; width: 1px; height: 1px;'
});
body.appendChild( pasteArea );
range.selectNodeContents( pasteArea );
@ -2794,8 +2752,11 @@ var keyHandlers = {
if ( !range ) { return; }
// Save undo checkpoint and add any links in the preceding section.
// Remove any zws so we don't think there's content in an empty
// block.
self._recordUndoState( range );
addLinks( range.startContainer );
self._removeZWS();
self._getRangeAndRemoveBookmark( range );
// Selected text is overwritten, therefore delete the contents
@ -2903,7 +2864,7 @@ var keyHandlers = {
// If you try to select the contents of a 'BR', FF will not let
// you type anything!
if ( !child || child.nodeName === 'BR' ||
( child.nodeType === TEXT_NODE && !isOpera ) ) {
( child.nodeType === TEXT_NODE && !isPresto ) ) {
break;
}
nodeAfterSplit = child;
@ -2928,11 +2889,13 @@ var keyHandlers = {
self._docWasChanged();
},
backspace: function ( self, event ) {
self._removeZWS();
// Record undo checkpoint.
var range = self.getSelection();
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
// If not collapsed, delete contents
if ( !range.collapsed ) {
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
event.preventDefault();
deleteContentsOfRange( range );
self._ensureBottomLine();
@ -2941,8 +2904,6 @@ var keyHandlers = {
}
// If at beginning of block, merge with previous
else if ( rangeDoesStartAtBlockBoundary( range ) ) {
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
event.preventDefault();
var current = getStartBlockOfRange( range ),
previous = current && getPreviousBlock( current );
@ -2985,21 +2946,18 @@ var keyHandlers = {
// Otherwise, leave to browser but check afterwards whether it has
// left behind an empty inline tag.
else {
var text = range.startContainer.data || '';
if ( !notWS.test( text.charAt( range.startOffset - 1 ) ) ) {
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
self.setSelection( range );
}
self.setSelection( range );
setTimeout( function () { afterDelete( self ); }, 0 );
}
},
'delete': function ( self, event ) {
self._removeZWS();
// Record undo checkpoint.
var range = self.getSelection();
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
// If not collapsed, delete contents
if ( !range.collapsed ) {
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
event.preventDefault();
deleteContentsOfRange( range );
self._ensureBottomLine();
@ -3008,8 +2966,6 @@ var keyHandlers = {
}
// If at end of block, merge next into this block
else if ( rangeDoesEndAtBlockBoundary( range ) ) {
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
event.preventDefault();
var current = getStartBlockOfRange( range ),
next = current && getNextBlock( current );
@ -3038,17 +2994,12 @@ var keyHandlers = {
// Otherwise, leave to browser but check afterwards whether it has
// left behind an empty inline tag.
else {
// Record undo point if deleting whitespace
var text = range.startContainer.data || '';
if ( !notWS.test( text.charAt( range.startOffset ) ) ) {
self._recordUndoState( range );
self._getRangeAndRemoveBookmark( range );
self.setSelection( range );
}
self.setSelection( range );
setTimeout( function () { afterDelete( self ); }, 0 );
}
},
tab: function ( self, event ) {
self._removeZWS();
var range = self.getSelection(),
node, parent;
// If no selection and in an empty block
@ -3087,6 +3038,7 @@ var keyHandlers = {
self._removeZWS();
}
};
// Firefox incorrectly handles Cmd-left/Cmd-right on Mac:
// it goes back/forward in history! Override to do the right
// thing.
@ -3132,7 +3084,7 @@ proto._onKey = function ( event ) {
// On keypress, delete and '.' both have event.keyCode 46
// Must check event.which to differentiate.
if ( isOpera && event.which === 46 ) {
if ( isPresto && event.which === 46 ) {
key = '.';
}
@ -3141,9 +3093,15 @@ proto._onKey = function ( event ) {
key = 'f' + ( code - 111 );
}
if ( event.altKey ) { modifiers += 'alt-'; }
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
if ( event.metaKey ) { modifiers += 'meta-'; }
// We need to apply the backspace/delete handlers regardless of
// control key modifiers.
if ( key !== 'backspace' && key !== 'delete' ) {
if ( event.altKey ) { modifiers += 'alt-'; }
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
if ( event.metaKey ) { modifiers += 'meta-'; }
}
// However, on Windows, shift-delete is apparently "cut" (WTF right?), so
// we want to let the browser handle shift-delete.
if ( event.shiftKey ) { modifiers += 'shift-'; }
key = modifiers + key;
@ -3454,6 +3412,23 @@ proto.setTextDirection = function ( direction ) {
return this.focus();
};
proto.makeHeading = function () {
var range = this.getSelection();
this.changeFormat({
tag: 'H1',
}, null, range );
return this.focus();
};
proto.removeHeading = function () {
var range = this.getSelection();
this.changeFormat( null, {
tag: 'H1',
attributes: {}
}, range, false);
return this.focus();
};
proto.increaseQuoteLevel = command( 'modifyBlocks', increaseBlockQuoteLevel );
proto.decreaseQuoteLevel = command( 'modifyBlocks', decreaseBlockQuoteLevel );
@ -3463,7 +3438,6 @@ proto.removeList = command( 'modifyBlocks', removeList );
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );
/*global top, win, doc, Squire */
if ( top !== win ) {
win.editor = new Squire( doc );

File diff suppressed because one or more lines are too long