mirror of
https://github.com/fastmail/Squire.git
synced 2025-01-03 13:16:31 -05:00
parent
b3bf7bf563
commit
87b69183a6
15 changed files with 4637 additions and 0 deletions
13
serve.js
Normal file
13
serve.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
var static = require('node-static');
|
||||||
|
var sys = require('sys');
|
||||||
|
var exec = require('child_process').exec;
|
||||||
|
|
||||||
|
var file = new static.Server('./');
|
||||||
|
|
||||||
|
function puts(error, stdout, stderr) { sys.puts(stdout) };
|
||||||
|
|
||||||
|
require('http').createServer(function (request, response) {
|
||||||
|
request.addListener('end', function () {
|
||||||
|
file.serve(request, response);
|
||||||
|
}).resume();
|
||||||
|
}).listen(8080);
|
46
source/Constants.js
Normal file
46
source/Constants.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*global doc, navigator */
|
||||||
|
/*jshint strict: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
|
||||||
|
var END_TO_END = 2; // Range.END_TO_END
|
||||||
|
var END_TO_START = 3; // Range.END_TO_START
|
||||||
|
|
||||||
|
var win = doc.defaultView;
|
||||||
|
|
||||||
|
var ua = navigator.userAgent;
|
||||||
|
|
||||||
|
var isIOS = /iP(?:ad|hone|od)/.test( ua );
|
||||||
|
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 isWebKit = /WebKit\//.test( ua );
|
||||||
|
|
||||||
|
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
||||||
|
|
||||||
|
var useTextFixer = isIE8or9or10 || isOpera;
|
||||||
|
var cantFocusEmptyTextNodes = isIE8or9or10 || isWebKit;
|
||||||
|
var losesSelectionOnBlur = isIE8or9or10;
|
||||||
|
var hasBuggySplit = ( function () {
|
||||||
|
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;
|
2352
source/Editor.js
Normal file
2352
source/Editor.js
Normal file
File diff suppressed because it is too large
Load diff
460
source/Node.js
Normal file
460
source/Node.js
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
/*global
|
||||||
|
ELEMENT_NODE,
|
||||||
|
TEXT_NODE,
|
||||||
|
SHOW_ELEMENT,
|
||||||
|
FILTER_ACCEPT,
|
||||||
|
FILTER_SKIP,
|
||||||
|
win,
|
||||||
|
isOpera,
|
||||||
|
useTextFixer,
|
||||||
|
cantFocusEmptyTextNodes,
|
||||||
|
|
||||||
|
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 leafNodeNames = {
|
||||||
|
BR: 1,
|
||||||
|
IMG: 1,
|
||||||
|
INPUT: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
function every ( nodeList, fn ) {
|
||||||
|
var l = nodeList.length;
|
||||||
|
while ( l-- ) {
|
||||||
|
if ( !fn( nodeList[l] ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
function hasTagAttributes ( node, tag, attributes ) {
|
||||||
|
if ( node.nodeName !== tag ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for ( var attr in attributes ) {
|
||||||
|
if ( node.getAttribute( attr ) !== attributes[ attr ] ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function areAlike ( node, node2 ) {
|
||||||
|
return (
|
||||||
|
node.nodeType === node2.nodeType &&
|
||||||
|
node.nodeName === node2.nodeName &&
|
||||||
|
node.className === node2.className &&
|
||||||
|
( ( !node.style && !node2.style ) ||
|
||||||
|
node.style.cssText === node2.style.cssText )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLeaf ( node ) {
|
||||||
|
return node.nodeType === ELEMENT_NODE &&
|
||||||
|
!!leafNodeNames[ node.nodeName ];
|
||||||
|
}
|
||||||
|
function isInline ( node ) {
|
||||||
|
return inlineNodeNames.test( node.nodeName );
|
||||||
|
}
|
||||||
|
function isBlock ( node ) {
|
||||||
|
return node.nodeType === ELEMENT_NODE &&
|
||||||
|
!isInline( node ) && every( node.childNodes, isInline );
|
||||||
|
}
|
||||||
|
function isContainer ( node ) {
|
||||||
|
return node.nodeType === ELEMENT_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 );
|
||||||
|
walker.currentNode = node;
|
||||||
|
return walker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviousBlock ( node ) {
|
||||||
|
return getBlockWalker( node ).previousNode();
|
||||||
|
}
|
||||||
|
function getNextBlock ( node ) {
|
||||||
|
return getBlockWalker( node ).nextNode();
|
||||||
|
}
|
||||||
|
function getNearest ( node, tag, attributes ) {
|
||||||
|
do {
|
||||||
|
if ( hasTagAttributes( node, tag, attributes ) ) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
} while ( node = node.parentNode );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPath ( node ) {
|
||||||
|
var parent = node.parentNode,
|
||||||
|
path, id, className, classNames;
|
||||||
|
if ( !parent || node.nodeType !== ELEMENT_NODE ) {
|
||||||
|
path = parent ? getPath( parent ) : '';
|
||||||
|
} else {
|
||||||
|
path = getPath( parent );
|
||||||
|
path += ( path ? '>' : '' ) + node.nodeName;
|
||||||
|
if ( id = node.id ) {
|
||||||
|
path += '#' + id;
|
||||||
|
}
|
||||||
|
if ( className = node.className.trim() ) {
|
||||||
|
classNames = className.split( /\s\s*/ );
|
||||||
|
classNames.sort();
|
||||||
|
path += '.';
|
||||||
|
path += classNames.join( '.' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLength ( node ) {
|
||||||
|
var nodeType = node.nodeType;
|
||||||
|
return nodeType === ELEMENT_NODE ?
|
||||||
|
node.childNodes.length : node.length || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detach ( node ) {
|
||||||
|
var parent = node.parentNode;
|
||||||
|
if ( parent ) {
|
||||||
|
parent.removeChild( node );
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
function replaceWith ( node, node2 ) {
|
||||||
|
var parent = node.parentNode;
|
||||||
|
if ( parent ) {
|
||||||
|
parent.replaceChild( node2, node );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function empty ( node ) {
|
||||||
|
var frag = node.ownerDocument.createDocumentFragment(),
|
||||||
|
childNodes = node.childNodes,
|
||||||
|
l = childNodes ? childNodes.length : 0;
|
||||||
|
while ( l-- ) {
|
||||||
|
frag.appendChild( node.firstChild );
|
||||||
|
}
|
||||||
|
return frag;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// inserted. In Opera and IE, we just need a textnode in order for the
|
||||||
|
// cursor to appear.
|
||||||
|
var doc = node.ownerDocument,
|
||||||
|
root = node,
|
||||||
|
fixer, child;
|
||||||
|
|
||||||
|
if ( node.nodeName === 'BODY' ) {
|
||||||
|
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
||||||
|
fixer = doc.createElement( 'DIV' );
|
||||||
|
if ( child ) {
|
||||||
|
node.replaceChild( fixer, child );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node.appendChild( fixer );
|
||||||
|
}
|
||||||
|
node = fixer;
|
||||||
|
fixer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isInline( node ) ) {
|
||||||
|
if ( !node.firstChild ) {
|
||||||
|
if ( cantFocusEmptyTextNodes ) {
|
||||||
|
fixer = doc.createTextNode( '\u200B' );
|
||||||
|
if ( win.editor ) {
|
||||||
|
win.editor._didAddZWS();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fixer = doc.createTextNode( '' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( useTextFixer ) {
|
||||||
|
while ( node.nodeType !== TEXT_NODE && !isLeaf( node ) ) {
|
||||||
|
child = node.firstChild;
|
||||||
|
if ( !child ) {
|
||||||
|
fixer = doc.createTextNode( '' );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = child;
|
||||||
|
}
|
||||||
|
if ( node.nodeType === TEXT_NODE ) {
|
||||||
|
// Opera will collapse the block element if it contains
|
||||||
|
// just spaces (but not if it contains no data at all).
|
||||||
|
if ( /^ +$/.test( node.data ) ) {
|
||||||
|
node.data = '';
|
||||||
|
}
|
||||||
|
} else if ( isLeaf( node ) ) {
|
||||||
|
node.parentNode.insertBefore( doc.createTextNode( '' ), node );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( !node.querySelector( 'BR' ) ) {
|
||||||
|
fixer = doc.createElement( 'BR' );
|
||||||
|
while ( ( child = node.lastElementChild ) && !isInline( child ) ) {
|
||||||
|
node = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( fixer ) {
|
||||||
|
node.appendChild( fixer );
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
function split ( node, offset, stopNode ) {
|
||||||
|
var nodeType = node.nodeType,
|
||||||
|
parent, clone, next;
|
||||||
|
if ( nodeType === TEXT_NODE && node !== stopNode ) {
|
||||||
|
return split( node.parentNode, node.splitText( offset ), stopNode );
|
||||||
|
}
|
||||||
|
if ( nodeType === ELEMENT_NODE ) {
|
||||||
|
if ( typeof( offset ) === 'number' ) {
|
||||||
|
offset = offset < node.childNodes.length ?
|
||||||
|
node.childNodes[ offset ] : null;
|
||||||
|
}
|
||||||
|
if ( node === stopNode ) {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone node without children
|
||||||
|
parent = node.parentNode;
|
||||||
|
clone = node.cloneNode( false );
|
||||||
|
|
||||||
|
// Add right-hand siblings to the clone
|
||||||
|
while ( offset ) {
|
||||||
|
next = offset.nextSibling;
|
||||||
|
clone.appendChild( offset );
|
||||||
|
offset = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO NOT NORMALISE. This may undo the fixCursor() call
|
||||||
|
// of a node lower down the tree!
|
||||||
|
|
||||||
|
// We need something in the element in order for the cursor to appear.
|
||||||
|
fixCursor( node );
|
||||||
|
fixCursor( clone );
|
||||||
|
|
||||||
|
// Inject clone after original node
|
||||||
|
if ( next = node.nextSibling ) {
|
||||||
|
parent.insertBefore( clone, next );
|
||||||
|
} else {
|
||||||
|
parent.appendChild( clone );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep on splitting up the tree
|
||||||
|
return split( parent, clone, stopNode );
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeInlines ( node, range ) {
|
||||||
|
if ( node.nodeType !== ELEMENT_NODE ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var children = node.childNodes,
|
||||||
|
l = children.length,
|
||||||
|
frags = [],
|
||||||
|
child, prev, len;
|
||||||
|
while ( l-- ) {
|
||||||
|
child = children[l];
|
||||||
|
prev = l && children[ l - 1 ];
|
||||||
|
if ( l && isInline( child ) && areAlike( child, prev ) &&
|
||||||
|
!leafNodeNames[ child.nodeName ] ) {
|
||||||
|
if ( range.startContainer === child ) {
|
||||||
|
range.startContainer = prev;
|
||||||
|
range.startOffset += getLength( prev );
|
||||||
|
}
|
||||||
|
if ( range.endContainer === child ) {
|
||||||
|
range.endContainer = prev;
|
||||||
|
range.endOffset += getLength( prev );
|
||||||
|
}
|
||||||
|
if ( range.startContainer === node ) {
|
||||||
|
if ( range.startOffset > l ) {
|
||||||
|
range.startOffset -= 1;
|
||||||
|
}
|
||||||
|
else if ( range.startOffset === l ) {
|
||||||
|
range.startContainer = prev;
|
||||||
|
range.startOffset = getLength( prev );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( range.endContainer === node ) {
|
||||||
|
if ( range.endOffset > l ) {
|
||||||
|
range.endOffset -= 1;
|
||||||
|
}
|
||||||
|
else if ( range.endOffset === l ) {
|
||||||
|
range.endContainer = prev;
|
||||||
|
range.endOffset = getLength( prev );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detach( child );
|
||||||
|
if ( child.nodeType === TEXT_NODE ) {
|
||||||
|
prev.appendData( child.data );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frags.push( empty( child ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( child.nodeType === ELEMENT_NODE ) {
|
||||||
|
len = frags.length;
|
||||||
|
while ( len-- ) {
|
||||||
|
child.appendChild( frags.pop() );
|
||||||
|
}
|
||||||
|
mergeInlines( child, range );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeWithBlock ( block, next, range ) {
|
||||||
|
var container = next,
|
||||||
|
last, offset, _range;
|
||||||
|
while ( container.parentNode.childNodes.length === 1 ) {
|
||||||
|
container = container.parentNode;
|
||||||
|
}
|
||||||
|
detach( container );
|
||||||
|
|
||||||
|
offset = block.childNodes.length;
|
||||||
|
|
||||||
|
// Remove extra <BR> fixer if present.
|
||||||
|
last = block.lastChild;
|
||||||
|
if ( last && last.nodeName === 'BR' ) {
|
||||||
|
block.removeChild( last );
|
||||||
|
offset -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_range = {
|
||||||
|
startContainer: block,
|
||||||
|
startOffset: offset,
|
||||||
|
endContainer: block,
|
||||||
|
endOffset: offset
|
||||||
|
};
|
||||||
|
|
||||||
|
block.appendChild( empty( next ) );
|
||||||
|
mergeInlines( block, _range );
|
||||||
|
|
||||||
|
range.setStart( _range.startContainer, _range.startOffset );
|
||||||
|
range.collapse( true );
|
||||||
|
|
||||||
|
// Opera inserts a BR if you delete the last piece of text
|
||||||
|
// in a block-level element. Unfortunately, it then gets
|
||||||
|
// confused when setting the selection subsequently and
|
||||||
|
// refuses to accept the range that finishes just before the
|
||||||
|
// BR. Removing the BR fixes the bug.
|
||||||
|
// 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' ) {
|
||||||
|
block.removeChild( last );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeContainers ( node ) {
|
||||||
|
var prev = node.previousSibling,
|
||||||
|
first = node.firstChild,
|
||||||
|
doc = node.ownerDocument,
|
||||||
|
isListItem = ( node.nodeName === 'LI' ),
|
||||||
|
needsFix, block;
|
||||||
|
|
||||||
|
// Do not merge LIs, unless it only contains a UL
|
||||||
|
if ( isListItem && ( !first || !/^[OU]L$/.test( first.nodeName ) ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( prev && areAlike( prev, node ) ) {
|
||||||
|
if ( !isContainer( prev ) ) {
|
||||||
|
if ( isListItem ) {
|
||||||
|
block = doc.createElement( 'DIV' );
|
||||||
|
block.appendChild( empty( prev ) );
|
||||||
|
prev.appendChild( block );
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detach( node );
|
||||||
|
needsFix = !isContainer( node );
|
||||||
|
prev.appendChild( empty( node ) );
|
||||||
|
if ( needsFix ) {
|
||||||
|
fixContainer( prev );
|
||||||
|
}
|
||||||
|
if ( first ) {
|
||||||
|
mergeContainers( first );
|
||||||
|
}
|
||||||
|
} else if ( isListItem ) {
|
||||||
|
prev = doc.createElement( 'DIV' );
|
||||||
|
node.insertBefore( prev, first );
|
||||||
|
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;
|
||||||
|
}
|
511
source/Range.js
Normal file
511
source/Range.js
Normal file
|
@ -0,0 +1,511 @@
|
||||||
|
/*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 */
|
||||||
|
|
||||||
|
var getNodeBefore = function ( node, offset ) {
|
||||||
|
var children = node.childNodes;
|
||||||
|
while ( offset && node.nodeType === ELEMENT_NODE ) {
|
||||||
|
node = children[ offset - 1 ];
|
||||||
|
children = node.childNodes;
|
||||||
|
offset = children.length;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getNodeAfter = function ( node, offset ) {
|
||||||
|
if ( node.nodeType === ELEMENT_NODE ) {
|
||||||
|
var children = node.childNodes;
|
||||||
|
if ( offset < children.length ) {
|
||||||
|
node = children[ offset ];
|
||||||
|
} else {
|
||||||
|
while ( node && !node.nextSibling ) {
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
if ( node ) { node = node.nextSibling; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
var forEachTextNodeInRange = function ( range, fn ) {
|
||||||
|
range = range.cloneRange();
|
||||||
|
moveRangeBoundariesDownTree( range );
|
||||||
|
|
||||||
|
var startContainer = range.startContainer,
|
||||||
|
endContainer = range.endContainer,
|
||||||
|
root = range.commonAncestorContainer,
|
||||||
|
walker = new TreeWalker(
|
||||||
|
root, SHOW_TEXT, function ( node ) {
|
||||||
|
return FILTER_ACCEPT;
|
||||||
|
}, false ),
|
||||||
|
textnode = walker.currentNode = startContainer;
|
||||||
|
|
||||||
|
while ( !fn( textnode, range ) &&
|
||||||
|
textnode !== endContainer &&
|
||||||
|
( textnode = walker.nextNode() ) ) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
var getTextContentInRange = function ( range ) {
|
||||||
|
var textContent = '';
|
||||||
|
forEachTextNodeInRange( range, function ( textnode, range ) {
|
||||||
|
var value = textnode.data;
|
||||||
|
if ( value && ( /\S/.test( value ) ) ) {
|
||||||
|
if ( textnode === range.endContainer ) {
|
||||||
|
value = value.slice( 0, range.endOffset );
|
||||||
|
}
|
||||||
|
if ( textnode === range.startContainer ) {
|
||||||
|
value = value.slice( range.startOffset );
|
||||||
|
}
|
||||||
|
textContent += value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return textContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
var insertNodeInRange = function ( range, node ) {
|
||||||
|
// Insert at start.
|
||||||
|
var startContainer = range.startContainer,
|
||||||
|
startOffset = range.startOffset,
|
||||||
|
endContainer = range.endContainer,
|
||||||
|
endOffset = range.endOffset,
|
||||||
|
parent, children, childCount, afterSplit;
|
||||||
|
|
||||||
|
// If part way through a text node, split it.
|
||||||
|
if ( startContainer.nodeType === TEXT_NODE ) {
|
||||||
|
parent = startContainer.parentNode;
|
||||||
|
children = parent.childNodes;
|
||||||
|
if ( startOffset === startContainer.length ) {
|
||||||
|
startOffset = indexOf.call( children, startContainer ) + 1;
|
||||||
|
if ( range.collapsed ) {
|
||||||
|
endContainer = parent;
|
||||||
|
endOffset = startOffset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ( startOffset ) {
|
||||||
|
afterSplit = startContainer.splitText( startOffset );
|
||||||
|
if ( endContainer === startContainer ) {
|
||||||
|
endOffset -= startOffset;
|
||||||
|
endContainer = afterSplit;
|
||||||
|
}
|
||||||
|
else if ( endContainer === parent ) {
|
||||||
|
endOffset += 1;
|
||||||
|
}
|
||||||
|
startContainer = afterSplit;
|
||||||
|
}
|
||||||
|
startOffset = indexOf.call( children, startContainer );
|
||||||
|
}
|
||||||
|
startContainer = parent;
|
||||||
|
} else {
|
||||||
|
children = startContainer.childNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
childCount = children.length;
|
||||||
|
|
||||||
|
if ( startOffset === childCount) {
|
||||||
|
startContainer.appendChild( node );
|
||||||
|
} else {
|
||||||
|
startContainer.insertBefore( node, children[ startOffset ] );
|
||||||
|
}
|
||||||
|
if ( startContainer === endContainer ) {
|
||||||
|
endOffset += children.length - childCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
range.setStart( startContainer, startOffset );
|
||||||
|
range.setEnd( endContainer, endOffset );
|
||||||
|
};
|
||||||
|
|
||||||
|
var extractContentsOfRange = function ( range, common ) {
|
||||||
|
var startContainer = range.startContainer,
|
||||||
|
startOffset = range.startOffset,
|
||||||
|
endContainer = range.endContainer,
|
||||||
|
endOffset = range.endOffset;
|
||||||
|
|
||||||
|
if ( !common ) {
|
||||||
|
common = range.commonAncestorContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( common.nodeType === TEXT_NODE ) {
|
||||||
|
common = common.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endNode = split( endContainer, endOffset, common ),
|
||||||
|
startNode = split( startContainer, startOffset, common ),
|
||||||
|
frag = common.ownerDocument.createDocumentFragment(),
|
||||||
|
next;
|
||||||
|
|
||||||
|
// End node will be null if at end of child nodes list.
|
||||||
|
while ( startNode !== endNode ) {
|
||||||
|
next = startNode.nextSibling;
|
||||||
|
frag.appendChild( startNode );
|
||||||
|
startNode = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
range.setStart( common, endNode ?
|
||||||
|
indexOf.call( common.childNodes, endNode ) :
|
||||||
|
common.childNodes.length );
|
||||||
|
range.collapse( true );
|
||||||
|
|
||||||
|
fixCursor( common );
|
||||||
|
|
||||||
|
return frag;
|
||||||
|
};
|
||||||
|
|
||||||
|
var deleteContentsOfRange = function ( range ) {
|
||||||
|
// Move boundaries up as much as possible to reduce need to split.
|
||||||
|
moveRangeBoundariesUpTree( range );
|
||||||
|
|
||||||
|
// Remove selected range
|
||||||
|
extractContentsOfRange( range );
|
||||||
|
|
||||||
|
// If we split into two different blocks, merge the blocks.
|
||||||
|
var startBlock = getStartBlockOfRange( range ),
|
||||||
|
endBlock = getEndBlockOfRange( range );
|
||||||
|
if ( startBlock && endBlock && startBlock !== endBlock ) {
|
||||||
|
mergeWithBlock( startBlock, endBlock, range );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure block has necessary children
|
||||||
|
if ( startBlock ) {
|
||||||
|
fixCursor( startBlock );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure body has a block-level element in it.
|
||||||
|
var body = range.endContainer.ownerDocument.body,
|
||||||
|
child = body.firstChild;
|
||||||
|
if ( !child || child.nodeName === 'BR' ) {
|
||||||
|
fixCursor( body );
|
||||||
|
range.selectNodeContents( body.firstChild );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure valid range (must have only block or inline containers)
|
||||||
|
var isCollapsed = range.collapsed;
|
||||||
|
moveRangeBoundariesDownTree( range );
|
||||||
|
if ( isCollapsed ) {
|
||||||
|
// Collapse
|
||||||
|
range.collapse( true );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
var insertTreeFragmentIntoRange = function ( range, frag ) {
|
||||||
|
// Check if it's all inline content
|
||||||
|
var allInline = true,
|
||||||
|
children = frag.childNodes,
|
||||||
|
l = children.length;
|
||||||
|
while ( l-- ) {
|
||||||
|
if ( !isInline( children[l] ) ) {
|
||||||
|
allInline = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete any selected content
|
||||||
|
if ( !range.collapsed ) {
|
||||||
|
deleteContentsOfRange( range );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move range down into text ndoes
|
||||||
|
moveRangeBoundariesDownTree( range );
|
||||||
|
|
||||||
|
// If inline, just insert at the current position.
|
||||||
|
if ( allInline ) {
|
||||||
|
insertNodeInRange( range, frag );
|
||||||
|
range.collapse( false );
|
||||||
|
}
|
||||||
|
// Otherwise, split up to body, insert inline before and after split
|
||||||
|
// and insert block in between split, then merge containers.
|
||||||
|
else {
|
||||||
|
var nodeAfterSplit = split( range.startContainer, range.startOffset,
|
||||||
|
range.startContainer.ownerDocument.body ),
|
||||||
|
nodeBeforeSplit = nodeAfterSplit.previousSibling,
|
||||||
|
startContainer = nodeBeforeSplit,
|
||||||
|
startOffset = startContainer.childNodes.length,
|
||||||
|
endContainer = nodeAfterSplit,
|
||||||
|
endOffset = 0,
|
||||||
|
parent = nodeAfterSplit.parentNode,
|
||||||
|
child, node;
|
||||||
|
|
||||||
|
while ( ( child = startContainer.lastChild ) &&
|
||||||
|
child.nodeType === ELEMENT_NODE &&
|
||||||
|
child.nodeName !== 'BR' ) {
|
||||||
|
startContainer = child;
|
||||||
|
startOffset = startContainer.childNodes.length;
|
||||||
|
}
|
||||||
|
while ( ( child = endContainer.firstChild ) &&
|
||||||
|
child.nodeType === ELEMENT_NODE &&
|
||||||
|
child.nodeName !== 'BR' ) {
|
||||||
|
endContainer = child;
|
||||||
|
}
|
||||||
|
while ( ( child = frag.firstChild ) && isInline( child ) ) {
|
||||||
|
startContainer.appendChild( child );
|
||||||
|
}
|
||||||
|
while ( ( child = frag.lastChild ) && isInline( child ) ) {
|
||||||
|
endContainer.insertBefore( child, endContainer.firstChild );
|
||||||
|
endOffset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix cursor then insert block(s)
|
||||||
|
node = frag;
|
||||||
|
while ( node = getNextBlock( node ) ) {
|
||||||
|
fixCursor( node );
|
||||||
|
}
|
||||||
|
parent.insertBefore( frag, nodeAfterSplit );
|
||||||
|
|
||||||
|
// Remove empty nodes created by split and merge inserted containers
|
||||||
|
// with edges of split
|
||||||
|
node = nodeAfterSplit.previousSibling;
|
||||||
|
if ( !nodeAfterSplit.textContent ) {
|
||||||
|
parent.removeChild( nodeAfterSplit );
|
||||||
|
} else {
|
||||||
|
mergeContainers( nodeAfterSplit );
|
||||||
|
}
|
||||||
|
if ( !nodeAfterSplit.parentNode ) {
|
||||||
|
endContainer = node;
|
||||||
|
endOffset = getLength( endContainer );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !nodeBeforeSplit.textContent) {
|
||||||
|
startContainer = nodeBeforeSplit.nextSibling;
|
||||||
|
startOffset = 0;
|
||||||
|
parent.removeChild( nodeBeforeSplit );
|
||||||
|
} else {
|
||||||
|
mergeContainers( nodeBeforeSplit );
|
||||||
|
}
|
||||||
|
|
||||||
|
range.setStart( startContainer, startOffset );
|
||||||
|
range.setEnd( endContainer, endOffset );
|
||||||
|
moveRangeBoundariesDownTree( range );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
var isNodeContainedInRange = function ( range, node, partial ) {
|
||||||
|
var nodeRange = node.ownerDocument.createRange();
|
||||||
|
|
||||||
|
nodeRange.selectNode( node );
|
||||||
|
|
||||||
|
if ( partial ) {
|
||||||
|
// Node must not finish before range starts or start after range
|
||||||
|
// finishes.
|
||||||
|
var nodeEndBeforeStart = ( range.compareBoundaryPoints(
|
||||||
|
END_TO_START, nodeRange ) > -1 ),
|
||||||
|
nodeStartAfterEnd = ( range.compareBoundaryPoints(
|
||||||
|
START_TO_END, nodeRange ) < 1 );
|
||||||
|
return ( !nodeEndBeforeStart && !nodeStartAfterEnd );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Node must start after range starts and finish before range
|
||||||
|
// finishes
|
||||||
|
var nodeStartAfterStart = ( range.compareBoundaryPoints(
|
||||||
|
START_TO_START, nodeRange ) < 1 ),
|
||||||
|
nodeEndBeforeEnd = ( range.compareBoundaryPoints(
|
||||||
|
END_TO_END, nodeRange ) > -1 );
|
||||||
|
return ( nodeStartAfterStart && nodeEndBeforeEnd );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var moveRangeBoundariesDownTree = function ( range ) {
|
||||||
|
var startContainer = range.startContainer,
|
||||||
|
startOffset = range.startOffset,
|
||||||
|
endContainer = range.endContainer,
|
||||||
|
endOffset = range.endOffset,
|
||||||
|
child;
|
||||||
|
|
||||||
|
while ( startContainer.nodeType !== TEXT_NODE ) {
|
||||||
|
child = startContainer.childNodes[ startOffset ];
|
||||||
|
if ( !child || isLeaf( child ) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startContainer = child;
|
||||||
|
startOffset = 0;
|
||||||
|
}
|
||||||
|
if ( endOffset ) {
|
||||||
|
while ( endContainer.nodeType !== TEXT_NODE ) {
|
||||||
|
child = endContainer.childNodes[ endOffset - 1 ];
|
||||||
|
if ( !child || isLeaf( child ) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
endContainer = child;
|
||||||
|
endOffset = getLength( endContainer );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while ( endContainer.nodeType !== TEXT_NODE ) {
|
||||||
|
child = endContainer.firstChild;
|
||||||
|
if ( !child || isLeaf( child ) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
endContainer = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If collapsed, this algorithm finds the nearest text node positions
|
||||||
|
// *outside* the range rather than inside, but also it flips which is
|
||||||
|
// assigned to which.
|
||||||
|
if ( range.collapsed ) {
|
||||||
|
range.setStart( endContainer, endOffset );
|
||||||
|
range.setEnd( startContainer, startOffset );
|
||||||
|
} else {
|
||||||
|
range.setStart( startContainer, startOffset );
|
||||||
|
range.setEnd( endContainer, endOffset );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var moveRangeBoundariesUpTree = function ( range, common ) {
|
||||||
|
var startContainer = range.startContainer,
|
||||||
|
startOffset = range.startOffset,
|
||||||
|
endContainer = range.endContainer,
|
||||||
|
endOffset = range.endOffset,
|
||||||
|
parent;
|
||||||
|
|
||||||
|
if ( !common ) {
|
||||||
|
common = range.commonAncestorContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ( startContainer !== common && !startOffset ) {
|
||||||
|
parent = startContainer.parentNode;
|
||||||
|
startOffset = indexOf.call( parent.childNodes, startContainer );
|
||||||
|
startContainer = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ( endContainer !== common &&
|
||||||
|
endOffset === getLength( endContainer ) ) {
|
||||||
|
parent = endContainer.parentNode;
|
||||||
|
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
|
||||||
|
endContainer = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
range.setStart( startContainer, startOffset );
|
||||||
|
range.setEnd( endContainer, endOffset );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the first block at least partially contained by the range,
|
||||||
|
// or null if no block is contained by the range.
|
||||||
|
var getStartBlockOfRange = function ( range ) {
|
||||||
|
var container = range.startContainer,
|
||||||
|
block;
|
||||||
|
|
||||||
|
// If inline, get the containing block.
|
||||||
|
if ( isInline( container ) ) {
|
||||||
|
block = getPreviousBlock( container );
|
||||||
|
} else if ( isBlock( container ) ) {
|
||||||
|
block = container;
|
||||||
|
} else {
|
||||||
|
block = getNodeBefore( container, range.startOffset );
|
||||||
|
block = getNextBlock( block );
|
||||||
|
}
|
||||||
|
// Check the block actually intersects the range
|
||||||
|
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the last block at least partially contained by the range,
|
||||||
|
// or null if no block is contained by the range.
|
||||||
|
var getEndBlockOfRange = function ( range ) {
|
||||||
|
var container = range.endContainer,
|
||||||
|
block, child;
|
||||||
|
|
||||||
|
// If inline, get the containing block.
|
||||||
|
if ( isInline( container ) ) {
|
||||||
|
block = getPreviousBlock( container );
|
||||||
|
} else if ( isBlock( container ) ) {
|
||||||
|
block = container;
|
||||||
|
} else {
|
||||||
|
block = getNodeAfter( container, range.endOffset );
|
||||||
|
if ( !block ) {
|
||||||
|
block = container.ownerDocument.body;
|
||||||
|
while ( child = block.lastChild ) {
|
||||||
|
block = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block = getPreviousBlock( block );
|
||||||
|
|
||||||
|
}
|
||||||
|
// Check the block actually intersects the range
|
||||||
|
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var rangeDoesStartAtBlockBoundary = function ( range ) {
|
||||||
|
var startContainer = range.startContainer,
|
||||||
|
startOffset = range.startOffset,
|
||||||
|
parent, child;
|
||||||
|
|
||||||
|
while ( isInline( startContainer ) ) {
|
||||||
|
if ( startOffset ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
parent = startContainer.parentNode;
|
||||||
|
startOffset = indexOf.call( parent.childNodes, startContainer );
|
||||||
|
startContainer = parent;
|
||||||
|
}
|
||||||
|
// Skip empty text nodes and <br>s.
|
||||||
|
while ( startOffset &&
|
||||||
|
( child = startContainer.childNodes[ startOffset - 1 ] ) &&
|
||||||
|
( child.data === '' || child.nodeName === 'BR' ) ) {
|
||||||
|
startOffset -= 1;
|
||||||
|
}
|
||||||
|
return !startOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
var rangeDoesEndAtBlockBoundary = function ( range ) {
|
||||||
|
var endContainer = range.endContainer,
|
||||||
|
endOffset = range.endOffset,
|
||||||
|
length = getLength( endContainer ),
|
||||||
|
parent, child;
|
||||||
|
|
||||||
|
while ( isInline( endContainer ) ) {
|
||||||
|
if ( endOffset !== length ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
parent = endContainer.parentNode;
|
||||||
|
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
|
||||||
|
endContainer = parent;
|
||||||
|
length = endContainer.childNodes.length;
|
||||||
|
}
|
||||||
|
// Skip empty text nodes and <br>s.
|
||||||
|
while ( endOffset < length &&
|
||||||
|
( child = endContainer.childNodes[ endOffset ] ) &&
|
||||||
|
( child.data === '' || child.nodeName === 'BR' ) ) {
|
||||||
|
endOffset += 1;
|
||||||
|
}
|
||||||
|
return endOffset === length;
|
||||||
|
};
|
||||||
|
|
||||||
|
var expandRangeToBlockBoundaries = function ( range ) {
|
||||||
|
var start = getStartBlockOfRange( range ),
|
||||||
|
end = getEndBlockOfRange( range ),
|
||||||
|
parent;
|
||||||
|
|
||||||
|
if ( start && end ) {
|
||||||
|
parent = start.parentNode;
|
||||||
|
range.setStart( parent, indexOf.call( parent.childNodes, start ) );
|
||||||
|
parent = end.parentNode;
|
||||||
|
range.setEnd( parent, indexOf.call( parent.childNodes, end ) + 1 );
|
||||||
|
}
|
||||||
|
};
|
148
source/Squire-UI.css
Normal file
148
source/Squire-UI.css
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Lato', sans-serif !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 50px 0 30px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background: #1d193d;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 8em;
|
||||||
|
line-height: 1em;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h2 {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
font-size: 3em;
|
||||||
|
font-weight: 300;
|
||||||
|
text-transform: lowercase;
|
||||||
|
color: #afaedf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-centered{
|
||||||
|
float: none;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alignCenter {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px #919191 solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
color: #333;
|
||||||
|
background-color: #fff;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right center;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu .item {
|
||||||
|
color:#000;
|
||||||
|
float:left;
|
||||||
|
background:#FFF;
|
||||||
|
padding:10px;
|
||||||
|
border-left:1px #EEE solid;
|
||||||
|
-webkit-font-smoothing:subpixel-antialiased
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu .group {
|
||||||
|
border-radius:3px;
|
||||||
|
display:inline-block;
|
||||||
|
border:1px #EEE solid;
|
||||||
|
margin:5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu .group .item .flip {
|
||||||
|
-ms-transform:rotateY(180deg);
|
||||||
|
-webkit-transform:rotateY(180deg);
|
||||||
|
-moz-transform:rotateY(180deg);
|
||||||
|
transform:rotateY(180deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: #516066;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
background-color: #fff;
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 100%;
|
||||||
|
border: 1px solid #a8afb2;
|
||||||
|
border-color: #a8afb2 #d4d7d9 #d4d7d9;
|
||||||
|
color: #516066;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-transition: border linear 150ms;
|
||||||
|
-moz-transition: border linear 150ms;
|
||||||
|
-o-transition: border linear 150ms;
|
||||||
|
transition: border linear 150ms;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu .group .item:hover, .menu .item:first-child:hover {
|
||||||
|
border-left: 3px #55ACEE solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu .item:first-child {
|
||||||
|
border-left:none;
|
||||||
|
}
|
||||||
|
.menu {
|
||||||
|
text-align: center;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.drop {
|
||||||
|
border: 1px solid #dbdbdb;
|
||||||
|
padding: 14px;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 1px 0 rgba(255,255,255,0),0 0 10px rgba(0,0,0,0.1);
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quit {
|
||||||
|
float: right;
|
||||||
|
top:0;
|
||||||
|
right:0;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
67
source/Squire-UI.html
Normal file
67
source/Squire-UI.html
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<div class="menu" contenteditable="false">
|
||||||
|
<div class="group">
|
||||||
|
<div data-action="bold" class="item"><i class="fa fa-bold"></i></div>
|
||||||
|
<div data-action="italic" class="item"><i class="fa fa-italic"></i></div>
|
||||||
|
<div data-action="underline" class="item"><i class="fa fa-underline"></i></div>
|
||||||
|
<div id="selectFont" data-action="selectFont" class="item">
|
||||||
|
<i class="fa fa-font"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="group">
|
||||||
|
<div id="makeLink" data-action="makeLink" class="item"><i class="fa fa-link"></i></div>
|
||||||
|
<div data-action="makeOrderedList" class="item"><i class="fa fa-list"></i></div>
|
||||||
|
<div id="insertImage" data-action="insertImage" class="item">
|
||||||
|
<i class="fa fa-picture-o"></i>
|
||||||
|
</div>
|
||||||
|
<div data-action="increaseQuoteLevel" class="item"><i class="fa fa-quote-right"></i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<div data-action="makeHeading" data-action="" class="item"><i class="fa fa-header"></i></div>
|
||||||
|
<div data-action="alignLeft" data-action="" class="item"><i class="fa fa-align-left"></i></div>
|
||||||
|
<div data-action="alignCenter" data-action="" class="item"><i class="fa fa-align-center"></i></div>
|
||||||
|
<div data-action="alignRight" data-action=""class="item"><i class="fa fa-align-right"></i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<div data-action="undo" class="item"><i class="fa fa-undo"></i></div>
|
||||||
|
<div data-action="redo" class="item"><i class="fa fa-undo flip"></i></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="templates hidden">
|
||||||
|
<div id="drop-font">
|
||||||
|
<strong>Change Font</strong>
|
||||||
|
<i class="fa fa-chevron-up quit"></i><br>
|
||||||
|
Text Size:
|
||||||
|
<select id="textSelector">
|
||||||
|
<option data-size="12">Small</option>
|
||||||
|
<option data-size="24">Medium</option>
|
||||||
|
<option data-size="30">Large</option>
|
||||||
|
</select>
|
||||||
|
<br> Font:
|
||||||
|
<select id="fontSelect">
|
||||||
|
<option data-fonts="georgia">Georgia</option>
|
||||||
|
<option data-fonts="arial">Arial</option>
|
||||||
|
<option data-fonts="helvetica, arial">Helvetica</option>
|
||||||
|
<option data-fonts="menlo, consolas, courier new, monospace">Monospace</option>
|
||||||
|
<option data-fonts="\"Times New Roman\", times">Times New Roman</option>
|
||||||
|
<option data-fonts="tahoma, sans-serif">Tahoma</option>
|
||||||
|
<option data-fonts="\"Trebuchet MS\"">Trebuchet MS</option>
|
||||||
|
<option data-fonts="verdana">Verdana</option>
|
||||||
|
</select><br>
|
||||||
|
|
||||||
|
<div class="btn submitFont">Apply</div>
|
||||||
|
</div>
|
||||||
|
<div id="drop-link">
|
||||||
|
<strong>Insert Link</strong>
|
||||||
|
<i class="fa fa-chevron-up quit"></i>
|
||||||
|
<input placeholder="Link URL" type="text" id="url" />
|
||||||
|
<div class="btn submitLink">Insert</div>
|
||||||
|
</div>
|
||||||
|
<div id="drop-image">
|
||||||
|
<strong>Insert Image</strong>
|
||||||
|
<i class="fa fa-chevron-up quit"></i>
|
||||||
|
<input placeholder="Image URL" type="text" id="imageUrl" />
|
||||||
|
<div class="btn sumbitImageURL">Insert</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
167
source/Squire-UI.js
Normal file
167
source/Squire-UI.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
Squire.prototype.testPresenceinSelection = function(name, action, format,
|
||||||
|
validation) {
|
||||||
|
var path = this.getPath(),
|
||||||
|
test = (validation.test(path) | this.hasFormat(format));
|
||||||
|
if (name == action && test) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SquireUI = function(options) {
|
||||||
|
if (typeof options.buildPath == "undefined") {
|
||||||
|
options.buildPath = 'build/';
|
||||||
|
}
|
||||||
|
// Create instance of iFrame
|
||||||
|
var container, editor;
|
||||||
|
if (options.replace) {
|
||||||
|
container = $(options.replace).parent();
|
||||||
|
$(options.replace).remove();
|
||||||
|
} else if (options.div) {
|
||||||
|
container = $(options.div);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"No element was defined for the editor to inject to.");
|
||||||
|
}
|
||||||
|
var iframe = document.createElement('iframe');
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.className = 'Squire-UI';
|
||||||
|
iframe.height = options.height;
|
||||||
|
|
||||||
|
$(div).load(options.buildPath + 'Squire-UI.html', function() {
|
||||||
|
this.linkDrop = new Drop({
|
||||||
|
target: $('#makeLink').first()[0],
|
||||||
|
content: $('#drop-link').html(),
|
||||||
|
position: 'bottom center',
|
||||||
|
openOn: 'click'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.linkDrop.on('open', function () {
|
||||||
|
$('.quit').click(function () {
|
||||||
|
$(this).parent().parent().removeClass('drop-open');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.submitLink').click(function () {
|
||||||
|
var editor = iframe.contentWindow.editor;
|
||||||
|
editor.makeLink($(this).parent().children('#url').first().val());
|
||||||
|
$(this).parent().parent().removeClass('drop-open');
|
||||||
|
$(this).parent().children('#url').attr('value', '');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.imageDrop = new Drop({
|
||||||
|
target: $('#insertImage').first()[0],
|
||||||
|
content: $('#drop-image').html(),
|
||||||
|
position: 'bottom center',
|
||||||
|
openOn: 'click'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.imageDrop.on('open', function () {
|
||||||
|
$('.quit').unbind().click(function () {
|
||||||
|
$(this).parent().parent().removeClass('drop-open');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.sumbitImageURL').unbind().click(function () {
|
||||||
|
console.log("Passed through .sumbitImageURL");
|
||||||
|
var editor = iframe.contentWindow.editor;
|
||||||
|
url = $(this).parent().children('#imageUrl').first()[0];
|
||||||
|
editor.insertImage(url.value);
|
||||||
|
$(this).parent().parent().removeClass('drop-open');
|
||||||
|
$(this).parent().children('#imageUrl').attr('value', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fontDrop = new Drop({
|
||||||
|
target: $('#selectFont').first()[0],
|
||||||
|
content: $('#drop-font').html(),
|
||||||
|
position: 'bottom center',
|
||||||
|
openOn: 'click'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fontDrop.on('open', function () {
|
||||||
|
$('.quit').click(function () {
|
||||||
|
$(this).parent().parent().removeClass('drop-open');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.submitFont').unbind().click(function () {
|
||||||
|
var editor = iframe.contentWindow.editor;
|
||||||
|
var selectedFonts = $('select#fontSelect option:selected').last().data('fonts');
|
||||||
|
var fontSize = $('select#textSelector option:selected').last().data('size') + 'px';
|
||||||
|
editor.setFontSize(fontSize);
|
||||||
|
|
||||||
|
try {
|
||||||
|
editor.setFontFace(selectedFonts);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Please make a selection of text.');
|
||||||
|
} finally {
|
||||||
|
$(this).parent().parent().removeClass('drop-open');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.item').click(function() {
|
||||||
|
var iframe = $(this).parents('.Squire-UI').next('iframe').first()[0];
|
||||||
|
var editor = iframe.contentWindow.editor;
|
||||||
|
var action = $(this).data('action');
|
||||||
|
|
||||||
|
test = {
|
||||||
|
value: $(this).data('action'),
|
||||||
|
testBold: editor.testPresenceinSelection('bold',
|
||||||
|
action, 'B', (/>B\b/)),
|
||||||
|
testItalic: editor.testPresenceinSelection('italic',
|
||||||
|
action, 'I', (/>I\b/)),
|
||||||
|
testUnderline: editor.testPresenceinSelection(
|
||||||
|
'underline', action, 'U', (/>U\b/)),
|
||||||
|
testOrderedList: editor.testPresenceinSelection(
|
||||||
|
'makeOrderedList', action, 'OL', (/>OL\b/)),
|
||||||
|
testLink: editor.testPresenceinSelection('makeLink',
|
||||||
|
action, 'A', (/>A\b/)),
|
||||||
|
testQuote: editor.testPresenceinSelection(
|
||||||
|
'increaseQuoteLevel', action, 'blockquote', (
|
||||||
|
/>blockquote\b/)),
|
||||||
|
isNotValue: function (a) {return (a == action && this.value !== ''); }
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.alignRight = function () { editor.setTextAlignment('right'); };
|
||||||
|
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) 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();
|
||||||
|
} 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]();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(container).append(div);
|
||||||
|
$(container).append(iframe);
|
||||||
|
|
||||||
|
var style = document.createElement('style');
|
||||||
|
style.innerHTML = 'blockquote { border-left: 3px green solid; padding-left: 5px; }';
|
||||||
|
|
||||||
|
|
||||||
|
iframe.contentWindow.editor = new Squire(iframe.contentWindow.document);
|
||||||
|
iframe.addEventListener('load', function() {
|
||||||
|
iframe.contentWindow.editor = new Squire(iframe.contentWindow.document);
|
||||||
|
});
|
||||||
|
|
||||||
|
iframe.contentWindow.document.head.appendChild(style);
|
||||||
|
return iframe.contentWindow.editor;
|
||||||
|
};
|
||||||
|
});
|
91
source/TreeWalker.js
Normal file
91
source/TreeWalker.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*global FILTER_ACCEPT */
|
||||||
|
/*jshint strict:false */
|
||||||
|
|
||||||
|
/*
|
||||||
|
Native TreeWalker is buggy in IE and Opera:
|
||||||
|
* IE9/10 sometimes throw errors when calling TreeWalker#nextNode or
|
||||||
|
TreeWalker#previousNode. No way to feature detect this.
|
||||||
|
* Some versions of Opera have a bug in TreeWalker#previousNode which makes
|
||||||
|
it skip to the wrong node.
|
||||||
|
|
||||||
|
Rather than risk further bugs, it's easiest just to implement our own
|
||||||
|
(subset) of the spec in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var typeToBitArray = {
|
||||||
|
// ELEMENT_NODE
|
||||||
|
1: 1,
|
||||||
|
// ATTRIBUTE_NODE
|
||||||
|
2: 2,
|
||||||
|
// TEXT_NODE
|
||||||
|
3: 4,
|
||||||
|
// COMMENT_NODE
|
||||||
|
8: 128,
|
||||||
|
// DOCUMENT_NODE
|
||||||
|
9: 256,
|
||||||
|
// DOCUMENT_FRAGMENT_NODE
|
||||||
|
11: 1024
|
||||||
|
};
|
||||||
|
|
||||||
|
function TreeWalker ( root, nodeType, filter ) {
|
||||||
|
this.root = this.currentNode = root;
|
||||||
|
this.nodeType = nodeType;
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeWalker.prototype.nextNode = function () {
|
||||||
|
var current = this.currentNode,
|
||||||
|
root = this.root,
|
||||||
|
nodeType = this.nodeType,
|
||||||
|
filter = this.filter,
|
||||||
|
node;
|
||||||
|
while ( true ) {
|
||||||
|
node = current.firstChild;
|
||||||
|
while ( !node && current ) {
|
||||||
|
if ( current === root ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = current.nextSibling;
|
||||||
|
if ( !node ) { current = current.parentNode; }
|
||||||
|
}
|
||||||
|
if ( !node ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
||||||
|
filter( node ) === FILTER_ACCEPT ) {
|
||||||
|
this.currentNode = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
current = node;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TreeWalker.prototype.previousNode = function () {
|
||||||
|
var current = this.currentNode,
|
||||||
|
root = this.root,
|
||||||
|
nodeType = this.nodeType,
|
||||||
|
filter = this.filter,
|
||||||
|
node;
|
||||||
|
while ( true ) {
|
||||||
|
if ( current === root ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
node = current.previousSibling;
|
||||||
|
if ( node ) {
|
||||||
|
while ( current = node.lastChild ) {
|
||||||
|
node = current;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node = current.parentNode;
|
||||||
|
}
|
||||||
|
if ( !node ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
||||||
|
filter( node ) === FILTER_ACCEPT ) {
|
||||||
|
this.currentNode = node;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
current = node;
|
||||||
|
}
|
||||||
|
};
|
57
source/document.html
Normal file
57
source/document.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<title></title>
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
padding: 1em;
|
||||||
|
background: transparent;
|
||||||
|
color: #2b2b2b;
|
||||||
|
font: 13px/1.35 Helvetica, arial, sans-serif;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 138.5%;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 123.1%;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 108%;
|
||||||
|
}
|
||||||
|
h1,h2,h3,p {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
h4,h5,h6 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
ul, ol {
|
||||||
|
margin: 0 1em;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
border-left: 2px solid blue;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
</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>
|
157
source/ie8dom.js
Normal file
157
source/ie8dom.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */
|
||||||
|
|
||||||
|
( function () {
|
||||||
|
|
||||||
|
/*global window, document, Element, HTMLDocument */
|
||||||
|
/*jshint strict: false */
|
||||||
|
|
||||||
|
var doc = document;
|
||||||
|
|
||||||
|
// Add JS hook
|
||||||
|
window.ie = 8;
|
||||||
|
|
||||||
|
// Add defaultView property to document
|
||||||
|
doc.defaultView = window;
|
||||||
|
|
||||||
|
// Fake W3C events support
|
||||||
|
var translate = {
|
||||||
|
focus: 'focusin',
|
||||||
|
blur: 'focusout'
|
||||||
|
};
|
||||||
|
|
||||||
|
var returnTrue = function () { return true; };
|
||||||
|
var returnFalse = function () { return false; };
|
||||||
|
|
||||||
|
var toCopy = 'altKey ctrlKey metaKey shiftKey clientX clientY charCode keyCode'.split( ' ' );
|
||||||
|
|
||||||
|
var DOMEvent = function ( event ) {
|
||||||
|
var type = event.type,
|
||||||
|
doc = document,
|
||||||
|
target = event.srcElement || doc,
|
||||||
|
html = ( target.ownerDocument || doc ).documentElement,
|
||||||
|
l = toCopy.length,
|
||||||
|
property;
|
||||||
|
|
||||||
|
while ( l-- ) {
|
||||||
|
property = toCopy[l];
|
||||||
|
this[ property ] = event[ property ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( type === 'propertychange' ) {
|
||||||
|
type = ( target.nodeName === 'INPUT' &&
|
||||||
|
target.type !== 'text' && target.type !== 'password' ) ?
|
||||||
|
'change' : 'input';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.type = Object.keyOf( translate, type ) || type;
|
||||||
|
this.target = target;
|
||||||
|
this.pageX = event.clientX + html.scrollLeft;
|
||||||
|
this.pageY = event.clientY + html.scrollTop;
|
||||||
|
|
||||||
|
if ( event.button ) {
|
||||||
|
this.button = ( event.button & 4 ? 1 :
|
||||||
|
( event.button & 2 ? 2 : 0 ) );
|
||||||
|
this.which = this.button + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.relatedTarget = event.fromElement === target ?
|
||||||
|
event.toElement : event.fromElement;
|
||||||
|
this._event = event;
|
||||||
|
};
|
||||||
|
|
||||||
|
DOMEvent.prototype = {
|
||||||
|
constructor: DOMEvent,
|
||||||
|
isEvent: true,
|
||||||
|
preventDefault: function () {
|
||||||
|
this.isDefaultPrevented = returnTrue;
|
||||||
|
this._event.returnValue = false;
|
||||||
|
},
|
||||||
|
stopPropagation: function () {
|
||||||
|
this.isPropagationStopped = returnTrue;
|
||||||
|
this._event.cancelBubble = true;
|
||||||
|
},
|
||||||
|
isDefaultPrevented: returnFalse,
|
||||||
|
isPropagationStopped: returnFalse
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add W3C event add/remove methods to elements and document.
|
||||||
|
[ doc, Element.prototype ].forEach(
|
||||||
|
function ( dom ) {
|
||||||
|
dom.addEventListener = function ( type, handler, capture ) {
|
||||||
|
var fn = handler._ie_handleEvent || ( handler._ie_handleEvent =
|
||||||
|
function () {
|
||||||
|
var event = new DOMEvent( window.event );
|
||||||
|
if ( typeof handler === 'object' ) {
|
||||||
|
handler.handleEvent( event );
|
||||||
|
} else {
|
||||||
|
handler.call( this, event );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
node = /paste|cut/.test( type ) ? this.body || this : this;
|
||||||
|
|
||||||
|
handler._ie_registeredCount = ( handler._ie_registeredCount || 0 ) + 1;
|
||||||
|
|
||||||
|
node.attachEvent( 'on' + ( translate[ type ] || type ), fn );
|
||||||
|
};
|
||||||
|
dom.addEventListener.isFake = true;
|
||||||
|
|
||||||
|
dom.removeEventListener = function ( type, handler, capture ) {
|
||||||
|
var fn = handler._ie_handleEvent,
|
||||||
|
node = /paste|cut/.test( type ) ? this.body || this : this;
|
||||||
|
if ( !( handler._ie_registeredCount -= 1 ) ) {
|
||||||
|
delete handler._ie_handleEvent;
|
||||||
|
}
|
||||||
|
if ( fn ) {
|
||||||
|
node.detachEvent( 'on' + ( translate[ type ] || type ), fn );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dom.removeEventListener.isFake = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// The events that we normally attach to the window object, IE8 wants on the
|
||||||
|
// body.
|
||||||
|
doc.defaultView.addEventListener = function ( type, handler, capture ) {
|
||||||
|
return doc.addEventListener( type, handler, capture );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add textContent property to elements.
|
||||||
|
Object.defineProperty( Element.prototype, 'textContent', {
|
||||||
|
get: function () {
|
||||||
|
return this.innerText;
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function ( text ) {
|
||||||
|
this.innerText = text;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add compareDocumentPosition method to elements.
|
||||||
|
Element.prototype.compareDocumentPosition = function ( b ) {
|
||||||
|
if ( b.nodeType !== 1 ) { b = b.parentNode; }
|
||||||
|
var a = this,
|
||||||
|
different = ( a !== b ),
|
||||||
|
aIndex = a.sourceIndex,
|
||||||
|
bIndex = b.sourceIndex;
|
||||||
|
|
||||||
|
return ( different && a.contains( b ) ? 16 : 0 ) +
|
||||||
|
( different && b.contains( a ) ? 8 : 0 ) +
|
||||||
|
( aIndex < bIndex ? 4 : 0 ) +
|
||||||
|
( bIndex < aIndex ? 2 : 0 );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add normalize method to document fragments
|
||||||
|
HTMLDocument.prototype.normalize = function () {
|
||||||
|
var children = this.childNodes,
|
||||||
|
l = children.length,
|
||||||
|
child;
|
||||||
|
|
||||||
|
while ( l-- ) {
|
||||||
|
child = children[l];
|
||||||
|
if ( child.nodeType === 1 ) {
|
||||||
|
child.normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
487
source/ie8range.js
Normal file
487
source/ie8range.js
Normal file
|
@ -0,0 +1,487 @@
|
||||||
|
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license.
|
||||||
|
|
||||||
|
IE TextRange <-> W3C Range code adapted from Rangy:
|
||||||
|
http://code.google.com/p/rangy/
|
||||||
|
Copyright 2012, Tim Down. Licensed under the MIT license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Range;
|
||||||
|
|
||||||
|
( function () {
|
||||||
|
|
||||||
|
/*global window, document */
|
||||||
|
/*jshint strict: false */
|
||||||
|
|
||||||
|
var indexOf = Array.prototype.indexOf;
|
||||||
|
|
||||||
|
var START_TO_START = 0;
|
||||||
|
var START_TO_END = 1;
|
||||||
|
var END_TO_START = 3;
|
||||||
|
|
||||||
|
var contains = function ( a, b ) {
|
||||||
|
while ( b = b.parentNode ) {
|
||||||
|
if ( a === b ) { return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getCommonAncestor = function ( a, b ) {
|
||||||
|
var commonAncestor,
|
||||||
|
aParents, bParents,
|
||||||
|
aL, bL;
|
||||||
|
|
||||||
|
if ( a === b || contains( a, b ) ) {
|
||||||
|
commonAncestor = a;
|
||||||
|
} else if ( contains( b, a ) ) {
|
||||||
|
commonAncestor = b;
|
||||||
|
} else {
|
||||||
|
aParents = [];
|
||||||
|
bParents = [];
|
||||||
|
while ( a = a.parentNode ) {
|
||||||
|
aParents.push( a );
|
||||||
|
}
|
||||||
|
while ( b = b.parentNode ) {
|
||||||
|
bParents.push( b );
|
||||||
|
}
|
||||||
|
aL = aParents.length;
|
||||||
|
bL = bParents.length;
|
||||||
|
while ( aL-- && bL-- ) {
|
||||||
|
if ( aParents[ aL ] !== bParents[ bL ] ) {
|
||||||
|
commonAncestor = aParents[ aL + 1 ];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !commonAncestor ) {
|
||||||
|
commonAncestor = ( aL === -1 ? aParents[0] : bParents[0] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commonAncestor;
|
||||||
|
};
|
||||||
|
|
||||||
|
Range = function ( startContainer, startOffset, endContainer, endOffset ) {
|
||||||
|
startContainer = startContainer || document;
|
||||||
|
startOffset = startOffset || 0;
|
||||||
|
|
||||||
|
this.startContainer = startContainer;
|
||||||
|
this.startOffset = startOffset;
|
||||||
|
this.endContainer = endContainer || startContainer;
|
||||||
|
this.endOffset = endOffset !== undefined ? endOffset : startOffset;
|
||||||
|
this._updateCollapsedAndAncestor();
|
||||||
|
};
|
||||||
|
|
||||||
|
Range.prototype = {
|
||||||
|
constructor: Range,
|
||||||
|
|
||||||
|
_updateCollapsedAndAncestor: function () {
|
||||||
|
this.collapsed = (
|
||||||
|
this.startContainer === this.endContainer &&
|
||||||
|
this.startOffset === this.endOffset
|
||||||
|
);
|
||||||
|
this.commonAncestorContainer =
|
||||||
|
getCommonAncestor( this.startContainer, this.endContainer );
|
||||||
|
},
|
||||||
|
setStart: function ( node, offset ) {
|
||||||
|
this.startContainer = node;
|
||||||
|
this.startOffset = offset;
|
||||||
|
this._updateCollapsedAndAncestor();
|
||||||
|
},
|
||||||
|
setEnd: function ( node, offset ) {
|
||||||
|
this.endContainer = node;
|
||||||
|
this.endOffset = offset;
|
||||||
|
this._updateCollapsedAndAncestor();
|
||||||
|
},
|
||||||
|
setStartAfter: function ( node ) {
|
||||||
|
var parent = node.parentNode;
|
||||||
|
this.setStart( parent, indexOf.call( parent.childNodes, node ) + 1 );
|
||||||
|
},
|
||||||
|
setEndBefore: function ( node ) {
|
||||||
|
var parent = node.parentNode;
|
||||||
|
this.setEnd( parent, indexOf.call( parent.childNodes, node ) );
|
||||||
|
},
|
||||||
|
selectNode: function ( node ) {
|
||||||
|
var parent = node.parentNode,
|
||||||
|
offset = indexOf.call( parent.childNodes, node );
|
||||||
|
this.setStart( parent, offset );
|
||||||
|
this.setEnd( parent, offset + 1 );
|
||||||
|
},
|
||||||
|
selectNodeContents: function ( node ) {
|
||||||
|
this.setStart( node, 0 );
|
||||||
|
this.setEnd( node, node.childNodes.length );
|
||||||
|
},
|
||||||
|
cloneRange: function () {
|
||||||
|
return new Range(
|
||||||
|
this.startContainer,
|
||||||
|
this.startOffset,
|
||||||
|
this.endContainer,
|
||||||
|
this.endOffset
|
||||||
|
);
|
||||||
|
},
|
||||||
|
collapse: function ( toStart ) {
|
||||||
|
if ( toStart ) {
|
||||||
|
this.setEnd( this.startContainer, this.startOffset );
|
||||||
|
} else {
|
||||||
|
this.setStart( this.endContainer, this.endOffset );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
compareBoundaryPoints: function ( how, sourceRange ) {
|
||||||
|
var aContainer, aOffset, bContainer, bOffset, node, parent;
|
||||||
|
if ( how === START_TO_START || how === END_TO_START ) {
|
||||||
|
aContainer = this.startContainer;
|
||||||
|
aOffset = this.startOffset;
|
||||||
|
} else {
|
||||||
|
aContainer = this.endContainer;
|
||||||
|
aOffset = this.endOffset;
|
||||||
|
}
|
||||||
|
if ( how === START_TO_START || how === START_TO_END ) {
|
||||||
|
bContainer = sourceRange.startContainer;
|
||||||
|
bOffset = sourceRange.startOffset;
|
||||||
|
} else {
|
||||||
|
bContainer = sourceRange.endContainer;
|
||||||
|
bOffset = sourceRange.endOffset;
|
||||||
|
}
|
||||||
|
if ( aContainer === bContainer ) {
|
||||||
|
return aOffset < bOffset ? -1 :
|
||||||
|
aOffset > bOffset ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = aContainer;
|
||||||
|
while ( parent = node.parentNode ) {
|
||||||
|
if ( parent === bContainer ) {
|
||||||
|
return indexOf.call( parent.childNodes, node ) < bOffset ?
|
||||||
|
-1 : 1;
|
||||||
|
}
|
||||||
|
node = parent;
|
||||||
|
}
|
||||||
|
node = bContainer;
|
||||||
|
while ( parent = node.parentNode ) {
|
||||||
|
if ( parent === aContainer ) {
|
||||||
|
return indexOf.call( parent.childNodes, node ) < aOffset ?
|
||||||
|
1 : -1;
|
||||||
|
}
|
||||||
|
node = parent;
|
||||||
|
}
|
||||||
|
if ( aContainer.nodeType !== 1 ) {
|
||||||
|
aContainer = aContainer.parentNode;
|
||||||
|
}
|
||||||
|
if ( bContainer.nodeType !== 1 ) {
|
||||||
|
bContainer = bContainer.parentNode;
|
||||||
|
}
|
||||||
|
return aContainer.sourceIndex < bContainer.sourceIndex ? -1 :
|
||||||
|
aContainer.sourceIndex > bContainer.sourceIndex ? 1 : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.createRange = function () {
|
||||||
|
return new Range();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
var isAncestorOf = function ( ancestor, descendant ) {
|
||||||
|
return ancestor === descendant || contains( ancestor, descendant );
|
||||||
|
};
|
||||||
|
|
||||||
|
var isCharacterDataNode = function ( node ) {
|
||||||
|
var nodeType = node.nodeType;
|
||||||
|
// Text, CDataSection or Comment
|
||||||
|
return nodeType === 3 || nodeType === 4 || nodeType === 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
var DomPosition = function ( node, offset ) {
|
||||||
|
this.node = node;
|
||||||
|
this.offset = offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getTextRangeContainerElement = function ( textRange ) {
|
||||||
|
var parentEl = textRange.parentElement(),
|
||||||
|
range, startEl, endEl, startEndContainer;
|
||||||
|
|
||||||
|
range = textRange.duplicate();
|
||||||
|
range.collapse( true );
|
||||||
|
startEl = range.parentElement();
|
||||||
|
range = textRange.duplicate();
|
||||||
|
range.collapse( false );
|
||||||
|
endEl = range.parentElement();
|
||||||
|
startEndContainer = ( startEl === endEl ) ?
|
||||||
|
startEl : getCommonAncestor( startEl, endEl );
|
||||||
|
|
||||||
|
return startEndContainer === parentEl ?
|
||||||
|
startEndContainer : getCommonAncestor( parentEl, startEndContainer );
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gets the boundary of a TextRange expressed as a node and an offset within
|
||||||
|
// that node. This function started out as an improved version of code found in
|
||||||
|
// Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has grown,
|
||||||
|
// fixing problems with line breaks in preformatted text, adding workaround for
|
||||||
|
// IE TextRange bugs, handling for inputs and images, plus optimizations.
|
||||||
|
var getTextRangeBoundaryPosition = function (
|
||||||
|
textRange, wholeRangeContainerElement, isStart, isCollapsed ) {
|
||||||
|
var workingRange = textRange.duplicate();
|
||||||
|
|
||||||
|
workingRange.collapse( isStart );
|
||||||
|
|
||||||
|
var containerElement = workingRange.parentElement();
|
||||||
|
|
||||||
|
// Sometimes collapsing a TextRange that's at the start of a text node can
|
||||||
|
// move it into the previous node, so check for that TODO: Find out when.
|
||||||
|
// Workaround for wholeRangeContainerElement may break this
|
||||||
|
if ( !isAncestorOf( wholeRangeContainerElement, containerElement ) ) {
|
||||||
|
containerElement = wholeRangeContainerElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal with nodes that cannot "contain rich HTML markup". In practice, this
|
||||||
|
// means form inputs, images and similar. See
|
||||||
|
// http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
|
||||||
|
if ( !containerElement.canHaveHTML ) {
|
||||||
|
return new DomPosition(
|
||||||
|
containerElement.parentNode,
|
||||||
|
indexOf.call(
|
||||||
|
containerElement.parentNode.childNodes, containerElement )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var workingNode = document.createElement( 'span' ),
|
||||||
|
workingComparisonType = isStart ? 'StartToStart' : 'StartToEnd',
|
||||||
|
comparison, previousNode, nextNode, boundaryPosition, boundaryNode;
|
||||||
|
|
||||||
|
// Move the working range through the container's children, starting at the
|
||||||
|
// end and working backwards, until the working range reaches or goes past
|
||||||
|
// the boundary we're interested in
|
||||||
|
do {
|
||||||
|
containerElement.insertBefore(
|
||||||
|
workingNode, workingNode.previousSibling );
|
||||||
|
workingRange.moveToElementText( workingNode );
|
||||||
|
comparison =
|
||||||
|
workingRange.compareEndPoints( workingComparisonType, textRange );
|
||||||
|
} while ( comparison > 0 && workingNode.previousSibling );
|
||||||
|
|
||||||
|
// We've now reached or gone past the boundary of the text range we're
|
||||||
|
// interested in so have identified the node we want
|
||||||
|
boundaryNode = workingNode.nextSibling;
|
||||||
|
|
||||||
|
if ( comparison === -1 && boundaryNode &&
|
||||||
|
isCharacterDataNode( boundaryNode ) ) {
|
||||||
|
// This is a character data node (text, comment, cdata). The working
|
||||||
|
// range is collapsed at the start of the node containing the text
|
||||||
|
// range's boundary, so we move the end of the working range to the
|
||||||
|
// boundary point and measure the length of its text to get the
|
||||||
|
// boundary's offset within the node.
|
||||||
|
workingRange.setEndPoint(
|
||||||
|
isStart ? 'EndToStart' : 'EndToEnd', textRange );
|
||||||
|
|
||||||
|
var offset;
|
||||||
|
|
||||||
|
if ( /[\r\n]/.test( boundaryNode.data ) ||
|
||||||
|
/[\r\n]/.test( workingRange.text ) ) {
|
||||||
|
/*
|
||||||
|
For the particular case of a boundary within a text node containing
|
||||||
|
line breaks (within a <pre> element, for example), we need a
|
||||||
|
slightly complicated approach to get the boundary's offset in IE.
|
||||||
|
The facts:
|
||||||
|
|
||||||
|
- Each line break is represented as \r in the text node's
|
||||||
|
data/nodeValue properties
|
||||||
|
- Each line break is represented as \r\n in the TextRange's 'text'
|
||||||
|
property
|
||||||
|
- The 'text' property of the TextRange does not contain trailing
|
||||||
|
line breaks
|
||||||
|
|
||||||
|
To get round the problem presented by the final fact above, we can
|
||||||
|
use the fact that TextRange's moveStart() and moveEnd() methods
|
||||||
|
return the actual number of characters moved, which is not
|
||||||
|
necessarily the same as the number of characters it was instructed
|
||||||
|
to move. The simplest approach is to use this to store the
|
||||||
|
characters moved when moving both the start and end of the range to
|
||||||
|
the start of the document body and subtracting the start offset from
|
||||||
|
the end offset (the "move-negative-gazillion" method). However, this
|
||||||
|
is extremely slow when the document is large and the range is near
|
||||||
|
the end of it. Clearly doing the mirror image (i.e. moving the range
|
||||||
|
boundaries to the end of the document) has the same problem.
|
||||||
|
|
||||||
|
Another approach that works is to use moveStart() to move the start
|
||||||
|
boundary of the range up to the end boundary one character at a time
|
||||||
|
and incrementing a counter with the value returned by the
|
||||||
|
moveStart() call. However, the check for whether the start boundary
|
||||||
|
has reached the end boundary is expensive, so this method is slow
|
||||||
|
(although unlike "move-negative-gazillion" is largely unaffected by
|
||||||
|
the location of the range within the document).
|
||||||
|
|
||||||
|
The method below is a hybrid of the two methods above. It uses the
|
||||||
|
fact that a string containing the TextRange's 'text' property with
|
||||||
|
each \r\n converted to a single \r character cannot be longer than
|
||||||
|
the text of the TextRange, so the start of the range is moved that
|
||||||
|
length initially and then a character at a time to make up for any
|
||||||
|
trailing line breaks not contained in the 'text' property. This has
|
||||||
|
good performance in most situations compared to the previous two
|
||||||
|
methods.
|
||||||
|
*/
|
||||||
|
var tempRange = workingRange.duplicate();
|
||||||
|
var rangeLength = tempRange.text.replace( /\r\n/g, '\r' ).length;
|
||||||
|
|
||||||
|
offset = tempRange.moveStart( 'character', rangeLength);
|
||||||
|
while ( ( comparison =
|
||||||
|
tempRange.compareEndPoints( 'StartToEnd', tempRange )
|
||||||
|
) === -1 ) {
|
||||||
|
offset += 1;
|
||||||
|
tempRange.moveStart( 'character', 1 );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offset = workingRange.text.length;
|
||||||
|
}
|
||||||
|
boundaryPosition = new DomPosition( boundaryNode, offset );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If the boundary immediately follows a character data node and this is
|
||||||
|
// the end boundary, we should favour a position within that, and
|
||||||
|
// likewise for a start boundary preceding a character data node
|
||||||
|
previousNode = ( isCollapsed || !isStart ) &&
|
||||||
|
workingNode.previousSibling;
|
||||||
|
nextNode = ( isCollapsed || isStart ) && workingNode.nextSibling;
|
||||||
|
|
||||||
|
if ( nextNode && isCharacterDataNode( nextNode ) ) {
|
||||||
|
boundaryPosition = new DomPosition( nextNode, 0 );
|
||||||
|
} else if ( previousNode && isCharacterDataNode( previousNode ) ) {
|
||||||
|
// Strange bug: if we don't read the data property, the length
|
||||||
|
// property is often returned incorrectly as 0. Don't ask me why.
|
||||||
|
// Therefore get the length from the data property rather than
|
||||||
|
// reading it directly from the node.
|
||||||
|
boundaryPosition = new DomPosition(
|
||||||
|
previousNode, previousNode.data.length );
|
||||||
|
} else {
|
||||||
|
boundaryPosition = new DomPosition(
|
||||||
|
containerElement,
|
||||||
|
indexOf.call( containerElement.childNodes, workingNode )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
workingNode.parentNode.removeChild( workingNode );
|
||||||
|
|
||||||
|
return boundaryPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a TextRange representing the boundary of a TextRange expressed as a
|
||||||
|
// node and an offset within that node. This function started out as an
|
||||||
|
// optimized version of code found in Tim Cameron Ryan's IERange
|
||||||
|
// (http://code.google.com/p/ierange/)
|
||||||
|
var createBoundaryTextRange = function ( boundaryPosition, isStart ) {
|
||||||
|
var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
|
||||||
|
var doc = document;
|
||||||
|
var workingNode, childNodes, workingRange = doc.body.createTextRange();
|
||||||
|
var nodeIsDataNode = isCharacterDataNode( boundaryPosition.node );
|
||||||
|
|
||||||
|
if ( nodeIsDataNode ) {
|
||||||
|
boundaryNode = boundaryPosition.node;
|
||||||
|
boundaryParent = boundaryNode.parentNode;
|
||||||
|
} else {
|
||||||
|
childNodes = boundaryPosition.node.childNodes;
|
||||||
|
boundaryNode = ( boundaryOffset < childNodes.length ) ?
|
||||||
|
childNodes[ boundaryOffset ] : null;
|
||||||
|
boundaryParent = boundaryPosition.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position the range immediately before the node containing the boundary
|
||||||
|
workingNode = doc.createElement( 'span' );
|
||||||
|
|
||||||
|
// Making the working element non-empty element persuades IE to consider the
|
||||||
|
// TextRange boundary to be within the element rather than immediately
|
||||||
|
// before or after it, which is what we want
|
||||||
|
workingNode.innerHTML = '';
|
||||||
|
|
||||||
|
// insertBefore is supposed to work like appendChild if the second parameter
|
||||||
|
// is null. However, a bug report for IERange suggests that it can crash the
|
||||||
|
// browser: http://code.google.com/p/ierange/issues/detail?id=12
|
||||||
|
if ( boundaryNode ) {
|
||||||
|
boundaryParent.insertBefore( workingNode, boundaryNode );
|
||||||
|
} else {
|
||||||
|
boundaryParent.appendChild( workingNode );
|
||||||
|
}
|
||||||
|
|
||||||
|
workingRange.moveToElementText( workingNode );
|
||||||
|
workingRange.collapse( !isStart );
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
boundaryParent.removeChild( workingNode );
|
||||||
|
|
||||||
|
// Move the working range to the text offset, if required
|
||||||
|
if ( nodeIsDataNode ) {
|
||||||
|
workingRange[ isStart ? 'moveStart' : 'moveEnd' ](
|
||||||
|
'character', boundaryOffset );
|
||||||
|
}
|
||||||
|
|
||||||
|
return workingRange;
|
||||||
|
};
|
||||||
|
|
||||||
|
var toDOMRange = function ( textRange ) {
|
||||||
|
var rangeContainerElement = getTextRangeContainerElement( textRange ),
|
||||||
|
start, end;
|
||||||
|
|
||||||
|
if ( textRange.compareEndPoints( 'StartToEnd', textRange ) === 0 ) {
|
||||||
|
start = end = getTextRangeBoundaryPosition(
|
||||||
|
textRange, rangeContainerElement, true, true );
|
||||||
|
} else {
|
||||||
|
start = getTextRangeBoundaryPosition(
|
||||||
|
textRange, rangeContainerElement, true, false );
|
||||||
|
end = getTextRangeBoundaryPosition(
|
||||||
|
textRange, rangeContainerElement, false, false );
|
||||||
|
}
|
||||||
|
return new Range(
|
||||||
|
start.node,
|
||||||
|
start.offset,
|
||||||
|
end.node,
|
||||||
|
end.offset
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
var toTextRange = function ( range ) {
|
||||||
|
var textRange, startRange, endRange;
|
||||||
|
if ( range.collapsed ) {
|
||||||
|
textRange = createBoundaryTextRange(
|
||||||
|
new DomPosition( range.startContainer, range.startOffset ), true);
|
||||||
|
} else {
|
||||||
|
startRange = createBoundaryTextRange(
|
||||||
|
new DomPosition( range.startContainer, range.startOffset ), true);
|
||||||
|
endRange = createBoundaryTextRange(
|
||||||
|
new DomPosition( range.endContainer, range.endOffset ), false );
|
||||||
|
textRange = document.body.createTextRange();
|
||||||
|
textRange.setEndPoint( 'StartToStart', startRange);
|
||||||
|
textRange.setEndPoint( 'EndToEnd', endRange);
|
||||||
|
}
|
||||||
|
return textRange;
|
||||||
|
};
|
||||||
|
|
||||||
|
var selection = {
|
||||||
|
rangeCount: 0,
|
||||||
|
getRangeAt: function ( index ) {
|
||||||
|
if ( index !== 0 ) { return undefined; }
|
||||||
|
var sel = document.selection.createRange();
|
||||||
|
// Check if we have a control range.
|
||||||
|
if ( sel.add ) {
|
||||||
|
var range = document.createRange();
|
||||||
|
range.moveToElementText( sel.item( 0 ) );
|
||||||
|
range.collapse( false );
|
||||||
|
range.select();
|
||||||
|
sel = range;
|
||||||
|
}
|
||||||
|
return toDOMRange( sel );
|
||||||
|
},
|
||||||
|
removeAllRanges: function () {},
|
||||||
|
addRange: function ( range ) {
|
||||||
|
toTextRange( range ).select();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.attachEvent( 'onbeforeactivate', function () {
|
||||||
|
selection.rangeCount = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.attachEvent( 'ondeactivate', function () {
|
||||||
|
selection.rangeCount = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.getSelection = function () {
|
||||||
|
return selection;
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
63
source/ie8types.js
Normal file
63
source/ie8types.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/* Copyright © 2011-2012 by Neil Jenkins. Licensed under the MIT license. */
|
||||||
|
|
||||||
|
( function () {
|
||||||
|
|
||||||
|
/*jshint strict: false */
|
||||||
|
|
||||||
|
// Note: Does not inclue the `if ( i in this ) {}` check these function should
|
||||||
|
// have, as IE8 will return false if this[i] is undefined (at least if the array
|
||||||
|
// was defined with a literal, e.g. `[ undefined, undefined ]`).
|
||||||
|
|
||||||
|
Array.prototype.indexOf = function ( item, from ) {
|
||||||
|
var l = this.length;
|
||||||
|
for ( var i = ( from < 0 ) ? Math.max( 0, l + from ) : from || 0;
|
||||||
|
i < l; i += 1 ) {
|
||||||
|
if ( this[i] === item ) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.forEach = function ( fn, bind ) {
|
||||||
|
var l = this.length >>> 0;
|
||||||
|
if ( typeof fn !== 'function' ) {
|
||||||
|
throw new TypeError();
|
||||||
|
}
|
||||||
|
for ( var i = 0; i < l; i += 1 ) {
|
||||||
|
fn.call( bind, this[i], i, this );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Array.prototype.filter = function ( fn, bind ) {
|
||||||
|
var results = [];
|
||||||
|
for ( var i = 0, l = this.length; i < l; i += 1 ) {
|
||||||
|
var value = this[i];
|
||||||
|
if ( fn.call( bind, value, i, this ) ) {
|
||||||
|
results.push( value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keyOf = function ( object, value ) {
|
||||||
|
for ( var key in object ) {
|
||||||
|
if ( object[ key ] === value ) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Date.now = function () {
|
||||||
|
return +( new Date() );
|
||||||
|
};
|
||||||
|
|
||||||
|
String.prototype.trim = function () {
|
||||||
|
var str = this.replace( /^\s\s*/, '' ),
|
||||||
|
ws = /\s/,
|
||||||
|
i = str.length;
|
||||||
|
while ( ws.test( str.charAt( i -= 1 ) ) ) {/* Empty! */}
|
||||||
|
return str.slice( 0, i + 1 );
|
||||||
|
};
|
||||||
|
|
||||||
|
}() );
|
5
source/intro.js
Normal file
5
source/intro.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* Copyright © 2011-2013 by Neil Jenkins. MIT Licensed. */
|
||||||
|
|
||||||
|
( function ( doc, undefined ) {
|
||||||
|
|
||||||
|
"use strict";
|
13
source/outro.js
Normal file
13
source/outro.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/*global top, win, doc, Squire */
|
||||||
|
|
||||||
|
if ( top !== win ) {
|
||||||
|
win.editor = new Squire( doc );
|
||||||
|
if ( win.onEditorLoad ) {
|
||||||
|
win.onEditorLoad( win.editor );
|
||||||
|
win.onEditorLoad = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
win.Squire = Squire;
|
||||||
|
}
|
||||||
|
|
||||||
|
}( document ) );
|
Loading…
Reference in a new issue