mirror of
https://github.com/fastmail/Squire.git
synced 2024-12-22 07:13:08 -05:00
Merge branch 'matthewborden-Squire-UI' into Squire-UI
This commit is contained in:
commit
0b42bf7fda
26 changed files with 663 additions and 1359 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
serve.js
|
node_modules
|
||||||
|
bower_components
|
|
@ -1,4 +1,6 @@
|
||||||
Copyright © 2011 by Neil Jenkins
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © 2011–2014 by Neil Jenkins
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to
|
of this software and associated documentation files (the "Software"), to
|
19
Makefile
19
Makefile
|
@ -1,21 +1,20 @@
|
||||||
.PHONY: all build clean
|
.PHONY: all build clean
|
||||||
|
|
||||||
all: build ui
|
all: install build build-ui
|
||||||
|
|
||||||
|
install:
|
||||||
|
npm install
|
||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf build
|
||||||
|
|
||||||
ui: build/Squire-UI.html build/Squire-UI.css build/Squire-UI.js fonts jQuery bootstrap
|
|
||||||
|
|
||||||
build: build/ie8.js build/squire.js build/document.html
|
build-ui: build/Squire-UI.html build/Squire-UI.css build/Squire-UI.js build/assets
|
||||||
|
|
||||||
fonts:
|
build: build/squire.js build/squire-raw.js build/document.html
|
||||||
|
|
||||||
|
build/assets:
|
||||||
cp -r source/assets/font-awesome build
|
cp -r source/assets/font-awesome build
|
||||||
|
|
||||||
jQuery:
|
|
||||||
cp -r source/assets/jQuery build
|
cp -r source/assets/jQuery build
|
||||||
|
|
||||||
bootstrap:
|
|
||||||
cp -r source/assets/bootstrap build
|
cp -r source/assets/bootstrap build
|
||||||
|
|
||||||
build/Squire-UI.html: source/Squire-UI.html
|
build/Squire-UI.html: source/Squire-UI.html
|
||||||
|
@ -30,10 +29,6 @@ build/Squire-UI.js: source/Squire-UI.js source/assets/drop/drop.min.js
|
||||||
mkdir -p $(@D)
|
mkdir -p $(@D)
|
||||||
cat $^ >$@
|
cat $^ >$@
|
||||||
|
|
||||||
build/ie8.js: source/ie8types.js source/ie8dom.js source/ie8range.js
|
|
||||||
mkdir -p $(@D)
|
|
||||||
uglifyjs $^ -c -m -o $@
|
|
||||||
|
|
||||||
build/squire-raw.js: source/intro.js source/Constants.js source/TreeWalker.js source/Node.js source/Range.js source/Editor.js source/outro.js
|
build/squire-raw.js: source/intro.js source/Constants.js source/TreeWalker.js source/Node.js source/Range.js source/Editor.js source/outro.js
|
||||||
mkdir -p $(@D)
|
mkdir -p $(@D)
|
||||||
cat $^ >$@
|
cat $^ >$@
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
Squire
|
Squire
|
||||||
======
|
======
|
||||||
|
|
||||||
Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation, whilst being supremely lightweight and flexible. It is built for the present and the future, and as such does not support truly ancient browsers. It should work fine back to around Opera 10, Firefox 3.5, Safari 4, Chrome 9 and IE8.
|
Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation, whilst being supremely lightweight and flexible. It is built for the present and the future, and as such does not support truly ancient browsers. It should work fine back to around Opera 12, Firefox 3.5, Safari 5, Chrome 9 and IE9.
|
||||||
|
|
||||||
Unlike other HTML5 rich text editors, Squire was written as a component for writing documents (emails, essays, etc.), not doing wysiwyg websites. If you are looking for support for inserting form controls or flash components or the like, you'll need to look elsewhere. However for many purposes, Squire may be just what you need, providing the power without the bloat. The key features are:
|
Unlike other HTML5 rich text editors, Squire was written as a component for writing documents (emails, essays, etc.), not doing wysiwyg websites. If you are looking for support for inserting form controls or flash components or the like, you'll need to look elsewhere. However for many purposes, Squire may be just what you need, providing the power without the bloat. The key features are:
|
||||||
|
|
||||||
### Lightweight ###
|
### Lightweight ###
|
||||||
|
|
||||||
* Only 10KB of JS after minification and gzip (33KB before gzip).
|
* Only 11.5KB of JS after minification and gzip (35KB before gzip).
|
||||||
* IE8 support does not add extra bloat to the core library; instead, a separate
|
|
||||||
3KB (7KB before gzip) file patches the browser to support the W3C APIs.
|
|
||||||
* Does not include its own XHR wrapper, widget library or lightbox overlays.
|
* Does not include its own XHR wrapper, widget library or lightbox overlays.
|
||||||
* No dependencies.
|
* No dependencies.
|
||||||
* No UI for a toolbar is supplied, allowing you to integrate seamlessly with the
|
* No UI for a toolbar is supplied, allowing you to integrate seamlessly with the
|
25
bower.json
Normal file
25
bower.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "Squire",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"homepage": "https://github.com/neilj/Squire",
|
||||||
|
"authors": [
|
||||||
|
"Neil Jenkins <neil@nmjenkins.com>"
|
||||||
|
],
|
||||||
|
"description": "Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation, whilst being supremely lightweight and flexible.",
|
||||||
|
"main": "build/squire.js",
|
||||||
|
"keywords": [
|
||||||
|
"wysiwyg",
|
||||||
|
"editor",
|
||||||
|
"text",
|
||||||
|
"html",
|
||||||
|
"squire"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"ignore": [
|
||||||
|
"**/.*",
|
||||||
|
"node_modules",
|
||||||
|
"bower_components",
|
||||||
|
"test",
|
||||||
|
"tests"
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -49,9 +49,6 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!--[if IE 8]>
|
|
||||||
<script type="text/javascript" src="ie8.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
<script type="text/javascript" src="squire.js"></script>
|
<script type="text/javascript" src="squire.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
20
build/font-awesome/font-awesome.min.css
vendored
20
build/font-awesome/font-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -3,16 +3,13 @@
|
||||||
( function ( doc, undefined ) {
|
( function ( doc, undefined ) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
/*global doc, navigator */
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
/*jshint strict:false */
|
|
||||||
|
|
||||||
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
||||||
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
||||||
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
||||||
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
||||||
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
|
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_START = 0; // Range.START_TO_START
|
||||||
var START_TO_END = 1; // Range.START_TO_END
|
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 isGecko = /Gecko\//.test( ua );
|
||||||
var isIE8or9or10 = /Trident\/[456]\./.test( ua );
|
var isIE8or9or10 = /Trident\/[456]\./.test( ua );
|
||||||
var isIE8 = ( win.ie === 8 );
|
var isIE8 = ( win.ie === 8 );
|
||||||
var isOpera = !!win.opera;
|
var isPresto = !!win.opera;
|
||||||
var isWebKit = /WebKit\//.test( ua );
|
var isWebKit = /WebKit\//.test( ua );
|
||||||
|
|
||||||
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
||||||
|
|
||||||
var useTextFixer = isIE8or9or10 || isOpera;
|
var useTextFixer = isIE8or9or10 || isPresto;
|
||||||
var cantFocusEmptyTextNodes = isIE8or9or10 || isWebKit;
|
var cantFocusEmptyTextNodes = isIE8or9or10 || isWebKit;
|
||||||
var losesSelectionOnBlur = isIE8or9or10;
|
var losesSelectionOnBlur = isIE8or9or10;
|
||||||
var hasBuggySplit = ( function () {
|
var hasBuggySplit = function ( doc ) {
|
||||||
var div = doc.createElement( 'DIV' ),
|
var div = doc.createElement( 'DIV' ),
|
||||||
text = doc.createTextNode( '12' );
|
text = doc.createTextNode( '12' );
|
||||||
div.appendChild( text );
|
div.appendChild( text );
|
||||||
text.splitText( 2 );
|
text.splitText( 2 );
|
||||||
return div.childNodes.length !== 2;
|
return div.childNodes.length !== 2;
|
||||||
}() );
|
};
|
||||||
|
|
||||||
// Use [^ \t\r\n] instead of \S so that nbsp does not count as white-space
|
// Use [^ \t\r\n] instead of \S so that nbsp does not count as white-space
|
||||||
var notWS = /[^ \t\r\n]/;
|
var notWS = /[^ \t\r\n]/;
|
||||||
|
|
||||||
var indexOf = Array.prototype.indexOf;
|
var indexOf = Array.prototype.indexOf;
|
||||||
/*global FILTER_ACCEPT */
|
|
||||||
/*jshint strict:false */
|
/*jshint strict:false */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -103,7 +99,7 @@ TreeWalker.prototype.nextNode = function () {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
||||||
filter( node ) === FILTER_ACCEPT ) {
|
filter( node ) ) {
|
||||||
this.currentNode = node;
|
this.currentNode = node;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -133,29 +129,14 @@ TreeWalker.prototype.previousNode = function () {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
||||||
filter( node ) === FILTER_ACCEPT ) {
|
filter( node ) ) {
|
||||||
this.currentNode = node;
|
this.currentNode = node;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
current = node;
|
current = node;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/*global
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
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 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)$/;
|
||||||
|
|
||||||
|
@ -214,13 +195,10 @@ function isContainer ( node ) {
|
||||||
!isInline( node ) && !isBlock( node );
|
!isInline( node ) && !isBlock( node );
|
||||||
}
|
}
|
||||||
|
|
||||||
function acceptIfBlock ( el ) {
|
|
||||||
return isBlock( el ) ? FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
}
|
|
||||||
function getBlockWalker ( node ) {
|
function getBlockWalker ( node ) {
|
||||||
var doc = node.ownerDocument,
|
var doc = node.ownerDocument,
|
||||||
walker = new TreeWalker(
|
walker = new TreeWalker(
|
||||||
doc.body, SHOW_ELEMENT, acceptIfBlock, false );
|
doc.body, SHOW_ELEMENT, isBlock, false );
|
||||||
walker.currentNode = node;
|
walker.currentNode = node;
|
||||||
return walker;
|
return walker;
|
||||||
}
|
}
|
||||||
|
@ -290,6 +268,29 @@ function empty ( node ) {
|
||||||
return frag;
|
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 ) {
|
function fixCursor ( node ) {
|
||||||
// In Webkit and Gecko, block level elements are collapsed and
|
// In Webkit and Gecko, block level elements are collapsed and
|
||||||
// unfocussable if they have no content. To remedy this, a <BR> must be
|
// unfocussable if they have no content. To remedy this, a <BR> must be
|
||||||
|
@ -297,7 +298,8 @@ function fixCursor ( node ) {
|
||||||
// cursor to appear.
|
// cursor to appear.
|
||||||
var doc = node.ownerDocument,
|
var doc = node.ownerDocument,
|
||||||
root = node,
|
root = node,
|
||||||
fixer, child;
|
fixer, child,
|
||||||
|
l, instance;
|
||||||
|
|
||||||
if ( node.nodeName === 'BODY' ) {
|
if ( node.nodeName === 'BODY' ) {
|
||||||
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
||||||
|
@ -314,11 +316,22 @@ function fixCursor ( node ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isInline( 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 ) {
|
if ( cantFocusEmptyTextNodes ) {
|
||||||
fixer = doc.createTextNode( '\u200B' );
|
fixer = doc.createTextNode( '\u200B' );
|
||||||
if ( win.editor ) {
|
// Find the relevant Squire instance and notify
|
||||||
win.editor._didAddZWS();
|
l = instances.length;
|
||||||
|
while ( l-- ) {
|
||||||
|
instance = instances[l];
|
||||||
|
if ( instance._doc === doc ) {
|
||||||
|
instance._didAddZWS();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fixer = doc.createTextNode( '' );
|
fixer = doc.createTextNode( '' );
|
||||||
|
@ -358,6 +371,42 @@ function fixCursor ( node ) {
|
||||||
return root;
|
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 ) {
|
function split ( node, offset, stopNode ) {
|
||||||
var nodeType = node.nodeType,
|
var nodeType = node.nodeType,
|
||||||
parent, clone, next;
|
parent, clone, next;
|
||||||
|
@ -499,7 +548,7 @@ function mergeWithBlock ( block, next, range ) {
|
||||||
// Steps to reproduce bug: Type "a-b-c" (where - is return)
|
// Steps to reproduce bug: Type "a-b-c" (where - is return)
|
||||||
// then backspace twice. The cursor goes to the top instead
|
// then backspace twice. The cursor goes to the top instead
|
||||||
// of after "b".
|
// of after "b".
|
||||||
if ( isOpera && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
|
if ( isPresto && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
|
||||||
block.removeChild( last );
|
block.removeChild( last );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -541,90 +590,7 @@ function mergeContainers ( node ) {
|
||||||
fixCursor( prev );
|
fixCursor( prev );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
// 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 */
|
|
||||||
|
|
||||||
var getNodeBefore = function ( node, offset ) {
|
var getNodeBefore = function ( node, offset ) {
|
||||||
var children = node.childNodes;
|
var children = node.childNodes;
|
||||||
|
@ -661,8 +627,8 @@ var forEachTextNodeInRange = function ( range, fn ) {
|
||||||
endContainer = range.endContainer,
|
endContainer = range.endContainer,
|
||||||
root = range.commonAncestorContainer,
|
root = range.commonAncestorContainer,
|
||||||
walker = new TreeWalker(
|
walker = new TreeWalker(
|
||||||
root, SHOW_TEXT, function ( node ) {
|
root, SHOW_TEXT, function (/* node */) {
|
||||||
return FILTER_ACCEPT;
|
return true;
|
||||||
}, false ),
|
}, false ),
|
||||||
textnode = walker.currentNode = startContainer;
|
textnode = walker.currentNode = startContainer;
|
||||||
|
|
||||||
|
@ -1053,50 +1019,56 @@ var getEndBlockOfRange = function ( range ) {
|
||||||
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
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 rangeDoesStartAtBlockBoundary = function ( range ) {
|
||||||
var startContainer = range.startContainer,
|
var startContainer = range.startContainer,
|
||||||
startOffset = range.startOffset,
|
startOffset = range.startOffset;
|
||||||
parent, child;
|
|
||||||
|
|
||||||
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 ) {
|
if ( startOffset ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
parent = startContainer.parentNode;
|
contentWalker.currentNode = startContainer;
|
||||||
startOffset = indexOf.call( parent.childNodes, startContainer );
|
} else {
|
||||||
startContainer = parent;
|
contentWalker.currentNode = getNodeAfter( startContainer, startOffset );
|
||||||
}
|
}
|
||||||
// Skip empty text nodes and <br>s.
|
|
||||||
while ( startOffset &&
|
// Otherwise, look for any previous content in the same block.
|
||||||
( child = startContainer.childNodes[ startOffset - 1 ] ) &&
|
contentWalker.root = getStartBlockOfRange( range );
|
||||||
( child.data === '' || child.nodeName === 'BR' ) ) {
|
|
||||||
startOffset -= 1;
|
return !contentWalker.previousNode();
|
||||||
}
|
|
||||||
return !startOffset;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var rangeDoesEndAtBlockBoundary = function ( range ) {
|
var rangeDoesEndAtBlockBoundary = function ( range ) {
|
||||||
var endContainer = range.endContainer,
|
var endContainer = range.endContainer,
|
||||||
endOffset = range.endOffset,
|
endOffset = range.endOffset,
|
||||||
length = getLength( endContainer ),
|
length;
|
||||||
parent, child;
|
|
||||||
|
|
||||||
while ( isInline( endContainer ) ) {
|
// If in a text node with content, and not at the end, we're not
|
||||||
if ( endOffset !== length ) {
|
// at the boundary
|
||||||
|
if ( endContainer.nodeType === TEXT_NODE ) {
|
||||||
|
length = endContainer.data.length;
|
||||||
|
if ( length && endOffset < length ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
parent = endContainer.parentNode;
|
contentWalker.currentNode = endContainer;
|
||||||
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
|
} else {
|
||||||
endContainer = parent;
|
contentWalker.currentNode = getNodeBefore( endContainer, endOffset );
|
||||||
length = endContainer.childNodes.length;
|
|
||||||
}
|
}
|
||||||
// Skip empty text nodes and <br>s.
|
|
||||||
while ( endOffset < length &&
|
// Otherwise, look for any further content in the same block.
|
||||||
( child = endContainer.childNodes[ endOffset ] ) &&
|
contentWalker.root = getEndBlockOfRange( range );
|
||||||
( child.data === '' || child.nodeName === 'BR' ) ) {
|
|
||||||
endOffset += 1;
|
return !contentWalker.nextNode();
|
||||||
}
|
|
||||||
return endOffset === length;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var expandRangeToBlockBoundaries = function ( range ) {
|
var expandRangeToBlockBoundaries = function ( range ) {
|
||||||
|
@ -1111,73 +1083,9 @@ var expandRangeToBlockBoundaries = function ( range ) {
|
||||||
range.setEnd( parent, indexOf.call( parent.childNodes, end ) + 1 );
|
range.setEnd( parent, indexOf.call( parent.childNodes, end ) + 1 );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/*global
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
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,
|
|
||||||
|
|
||||||
TreeWalker,
|
var instances = [];
|
||||||
|
|
||||||
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 */
|
|
||||||
|
|
||||||
function Squire ( doc ) {
|
function Squire ( doc ) {
|
||||||
var win = doc.defaultView;
|
var win = doc.defaultView;
|
||||||
|
@ -1227,7 +1135,7 @@ function Squire ( doc ) {
|
||||||
this.addEventListener( 'keyup', this._ieSelAllClean );
|
this.addEventListener( 'keyup', this._ieSelAllClean );
|
||||||
}
|
}
|
||||||
// Opera does not fire keydown repeatedly.
|
// 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.
|
// 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
|
// 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
|
// 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
|
// from the document and replaced by another, rather than just having its
|
||||||
// data shortened.
|
// data shortened.
|
||||||
if ( hasBuggySplit ) {
|
if ( hasBuggySplit( doc ) ) {
|
||||||
win.Text.prototype.splitText = function ( offset ) {
|
win.Text.prototype.splitText = function ( offset ) {
|
||||||
var afterSplit = this.ownerDocument.createTextNode(
|
var afterSplit = this.ownerDocument.createTextNode(
|
||||||
this.data.slice( offset ) ),
|
this.data.slice( offset ) ),
|
||||||
|
@ -1262,6 +1170,8 @@ function Squire ( doc ) {
|
||||||
doc.execCommand( 'enableObjectResizing', false, 'false' );
|
doc.execCommand( 'enableObjectResizing', false, 'false' );
|
||||||
doc.execCommand( 'enableInlineTableEditing', false, 'false' );
|
doc.execCommand( 'enableInlineTableEditing', false, 'false' );
|
||||||
} catch ( error ) {}
|
} catch ( error ) {}
|
||||||
|
|
||||||
|
instances.push( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
var proto = Squire.prototype;
|
var proto = Squire.prototype;
|
||||||
|
@ -1323,6 +1233,26 @@ proto.fireEvent = function ( type, event ) {
|
||||||
return this;
|
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 ) {
|
proto.handleEvent = function ( event ) {
|
||||||
this.fireEvent( event.type, event );
|
this.fireEvent( event.type, event );
|
||||||
};
|
};
|
||||||
|
@ -1339,7 +1269,7 @@ proto.addEventListener = function ( type, fn ) {
|
||||||
if ( !handlers ) {
|
if ( !handlers ) {
|
||||||
handlers = this._events[ type ] = [];
|
handlers = this._events[ type ] = [];
|
||||||
if ( !customEvents[ type ] ) {
|
if ( !customEvents[ type ] ) {
|
||||||
this._doc.addEventListener( type, this, false );
|
this._doc.addEventListener( type, this, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handlers.push( fn );
|
handlers.push( fn );
|
||||||
|
@ -1453,7 +1383,7 @@ proto._removeZWS = function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var walker = new TreeWalker( this._body, SHOW_TEXT, function () {
|
var walker = new TreeWalker( this._body, SHOW_TEXT, function () {
|
||||||
return FILTER_ACCEPT;
|
return true;
|
||||||
}, false ),
|
}, false ),
|
||||||
node, index;
|
node, index;
|
||||||
while ( node = walker.nextNode() ) {
|
while ( node = walker.nextNode() ) {
|
||||||
|
@ -1493,11 +1423,10 @@ proto._updatePathOnEvent = function () {
|
||||||
// --- Focus ---
|
// --- Focus ---
|
||||||
|
|
||||||
proto.focus = function () {
|
proto.focus = function () {
|
||||||
// FF seems to need the body to be focussed
|
// FF seems to need the body to be focussed (at least on first load).
|
||||||
// (at least on first load).
|
// Chrome also now needs body to be focussed in order to show the cursor
|
||||||
if ( isGecko ) {
|
// (otherwise it is focussed, but the cursor doesn't appear).
|
||||||
this._body.focus();
|
this._body.focus();
|
||||||
}
|
|
||||||
this._win.focus();
|
this._win.focus();
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
@ -1710,8 +1639,7 @@ proto.hasFormat = function ( tag, attributes, range ) {
|
||||||
// Otherwise, check each text node at least partially contained within
|
// Otherwise, check each text node at least partially contained within
|
||||||
// the selection and make sure all of them have the format we want.
|
// the selection and make sure all of them have the format we want.
|
||||||
walker = new TreeWalker( root, SHOW_TEXT, function ( node ) {
|
walker = new TreeWalker( root, SHOW_TEXT, function ( node ) {
|
||||||
return isNodeContainedInRange( range, node, true ) ?
|
return isNodeContainedInRange( range, node, true );
|
||||||
FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
}, false );
|
}, false );
|
||||||
|
|
||||||
var seenNode = 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
|
// If the range is collapsed we simply insert the node by wrapping
|
||||||
// it round the range and focus it.
|
// it round the range and focus it.
|
||||||
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
||||||
textnode, needsFormat;
|
textNode, needsFormat;
|
||||||
|
|
||||||
if ( range.collapsed ) {
|
if ( range.collapsed ) {
|
||||||
el = fixCursor( this.createElement( tag, attributes ) );
|
el = fixCursor( this.createElement( tag, attributes ) );
|
||||||
|
@ -1750,47 +1678,54 @@ proto._addFormat = function ( tag, attributes, range ) {
|
||||||
range.commonAncestorContainer,
|
range.commonAncestorContainer,
|
||||||
SHOW_TEXT,
|
SHOW_TEXT,
|
||||||
function ( node ) {
|
function ( node ) {
|
||||||
return isNodeContainedInRange( range, node, true ) ?
|
return isNodeContainedInRange( range, node, true );
|
||||||
FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start at the beginning node of the range and iterate through
|
// Start at the beginning node of the range and iterate through
|
||||||
// all the nodes in the range that need formatting.
|
// all the nodes in the range that need formatting.
|
||||||
startOffset = 0;
|
startContainer = range.startContainer;
|
||||||
endOffset = 0;
|
startOffset = range.startOffset;
|
||||||
textnode = walker.currentNode = range.startContainer;
|
endContainer = range.endContainer;
|
||||||
|
endOffset = range.endOffset;
|
||||||
|
|
||||||
if ( textnode.nodeType !== TEXT_NODE ) {
|
// Make sure we start inside a text node.
|
||||||
textnode = walker.nextNode();
|
walker.currentNode = startContainer;
|
||||||
|
if ( startContainer.nodeType !== TEXT_NODE ) {
|
||||||
|
startContainer = walker.nextNode();
|
||||||
|
startOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
needsFormat = !getNearest( textnode, tag, attributes );
|
textNode = walker.currentNode;
|
||||||
if ( textnode === range.endContainer ) {
|
needsFormat = !getNearest( textNode, tag, attributes );
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( needsFormat ) {
|
if ( needsFormat ) {
|
||||||
el = this.createElement( tag, attributes );
|
if ( textNode === endContainer &&
|
||||||
replaceWith( textnode, el );
|
textNode.length > endOffset ) {
|
||||||
el.appendChild( textnode );
|
textNode.splitText( endOffset );
|
||||||
endOffset = textnode.length;
|
}
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
endContainer = textnode;
|
|
||||||
if ( !startContainer ) { startContainer = endContainer; }
|
|
||||||
} while ( textnode = walker.nextNode() );
|
|
||||||
|
|
||||||
// Now set the selection to as it was before
|
// Now set the selection to as it was before
|
||||||
range = this._createRange(
|
range = this._createRange(
|
||||||
|
@ -2210,7 +2145,7 @@ var addLinks = function ( frag ) {
|
||||||
var doc = frag.ownerDocument,
|
var doc = frag.ownerDocument,
|
||||||
walker = new TreeWalker( frag, SHOW_TEXT,
|
walker = new TreeWalker( frag, SHOW_TEXT,
|
||||||
function ( node ) {
|
function ( node ) {
|
||||||
return getNearest( node, 'A' ) ? FILTER_SKIP : FILTER_ACCEPT;
|
return !getNearest( node, 'A' );
|
||||||
}, false ),
|
}, false ),
|
||||||
node, data, parent, match, index, endIndex, child;
|
node, data, parent, match, index, endIndex, child;
|
||||||
while ( node = walker.nextNode() ) {
|
while ( node = walker.nextNode() ) {
|
||||||
|
@ -2482,6 +2417,14 @@ var cleanTree = function ( node, allowStyles ) {
|
||||||
}
|
}
|
||||||
continue;
|
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 );
|
node.removeChild( child );
|
||||||
i -= 1;
|
i -= 1;
|
||||||
|
@ -2492,10 +2435,9 @@ var cleanTree = function ( node, allowStyles ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var notWSTextNode = function ( node ) {
|
var notWSTextNode = function ( node ) {
|
||||||
return ( node.nodeType === ELEMENT_NODE ?
|
return node.nodeType === ELEMENT_NODE ?
|
||||||
node.nodeName === 'BR' :
|
node.nodeName === 'BR' :
|
||||||
notWS.test( node.data ) ) ?
|
notWS.test( node.data );
|
||||||
FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
};
|
};
|
||||||
var isLineBreak = function ( br ) {
|
var isLineBreak = function ( br ) {
|
||||||
var block = br.parentNode,
|
var block = br.parentNode,
|
||||||
|
@ -2539,17 +2481,23 @@ var cleanupBRs = function ( root ) {
|
||||||
block = block.parentNode;
|
block = block.parentNode;
|
||||||
}
|
}
|
||||||
// If this is not inside a block, replace it by wrapping
|
// If this is not inside a block, replace it by wrapping
|
||||||
// inlines in DIV.
|
// inlines in a <div>.
|
||||||
if ( !isBlock( block ) || !tagAfterSplit[ block.nodeName ] ) {
|
if ( !isBlock( block ) ) {
|
||||||
fixContainer( 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 {
|
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] ) {
|
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 );
|
detach( br );
|
||||||
}
|
}
|
||||||
|
@ -2631,11 +2579,20 @@ proto._onPaste = function ( event ) {
|
||||||
startContainer = range.startContainer,
|
startContainer = range.startContainer,
|
||||||
startOffset = range.startOffset,
|
startOffset = range.startOffset,
|
||||||
endContainer = range.endContainer,
|
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', {
|
var pasteArea = this.createElement( 'DIV', {
|
||||||
style: 'position: absolute; overflow: hidden; top:' +
|
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 );
|
body.appendChild( pasteArea );
|
||||||
range.selectNodeContents( pasteArea );
|
range.selectNodeContents( pasteArea );
|
||||||
|
@ -2794,8 +2751,11 @@ var keyHandlers = {
|
||||||
if ( !range ) { return; }
|
if ( !range ) { return; }
|
||||||
|
|
||||||
// Save undo checkpoint and add any links in the preceding section.
|
// 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 );
|
self._recordUndoState( range );
|
||||||
addLinks( range.startContainer );
|
addLinks( range.startContainer );
|
||||||
|
self._removeZWS();
|
||||||
self._getRangeAndRemoveBookmark( range );
|
self._getRangeAndRemoveBookmark( range );
|
||||||
|
|
||||||
// Selected text is overwritten, therefore delete the contents
|
// Selected text is overwritten, therefore delete the contents
|
||||||
|
@ -2903,7 +2863,7 @@ var keyHandlers = {
|
||||||
// If you try to select the contents of a 'BR', FF will not let
|
// If you try to select the contents of a 'BR', FF will not let
|
||||||
// you type anything!
|
// you type anything!
|
||||||
if ( !child || child.nodeName === 'BR' ||
|
if ( !child || child.nodeName === 'BR' ||
|
||||||
( child.nodeType === TEXT_NODE && !isOpera ) ) {
|
( child.nodeType === TEXT_NODE && !isPresto ) ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
nodeAfterSplit = child;
|
nodeAfterSplit = child;
|
||||||
|
@ -2928,11 +2888,13 @@ var keyHandlers = {
|
||||||
self._docWasChanged();
|
self._docWasChanged();
|
||||||
},
|
},
|
||||||
backspace: function ( self, event ) {
|
backspace: function ( self, event ) {
|
||||||
|
self._removeZWS();
|
||||||
|
// Record undo checkpoint.
|
||||||
var range = self.getSelection();
|
var range = self.getSelection();
|
||||||
// If not collapsed, delete contents
|
|
||||||
if ( !range.collapsed ) {
|
|
||||||
self._recordUndoState( range );
|
self._recordUndoState( range );
|
||||||
self._getRangeAndRemoveBookmark( range );
|
self._getRangeAndRemoveBookmark( range );
|
||||||
|
// If not collapsed, delete contents
|
||||||
|
if ( !range.collapsed ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range );
|
||||||
self._ensureBottomLine();
|
self._ensureBottomLine();
|
||||||
|
@ -2941,8 +2903,6 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
// If at beginning of block, merge with previous
|
// If at beginning of block, merge with previous
|
||||||
else if ( rangeDoesStartAtBlockBoundary( range ) ) {
|
else if ( rangeDoesStartAtBlockBoundary( range ) ) {
|
||||||
self._recordUndoState( range );
|
|
||||||
self._getRangeAndRemoveBookmark( range );
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var current = getStartBlockOfRange( range ),
|
var current = getStartBlockOfRange( range ),
|
||||||
previous = current && getPreviousBlock( current );
|
previous = current && getPreviousBlock( current );
|
||||||
|
@ -2985,21 +2945,18 @@ var keyHandlers = {
|
||||||
// Otherwise, leave to browser but check afterwards whether it has
|
// Otherwise, leave to browser but check afterwards whether it has
|
||||||
// left behind an empty inline tag.
|
// left behind an empty inline tag.
|
||||||
else {
|
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 );
|
setTimeout( function () { afterDelete( self ); }, 0 );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'delete': function ( self, event ) {
|
'delete': function ( self, event ) {
|
||||||
|
self._removeZWS();
|
||||||
|
// Record undo checkpoint.
|
||||||
var range = self.getSelection();
|
var range = self.getSelection();
|
||||||
// If not collapsed, delete contents
|
|
||||||
if ( !range.collapsed ) {
|
|
||||||
self._recordUndoState( range );
|
self._recordUndoState( range );
|
||||||
self._getRangeAndRemoveBookmark( range );
|
self._getRangeAndRemoveBookmark( range );
|
||||||
|
// If not collapsed, delete contents
|
||||||
|
if ( !range.collapsed ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range );
|
||||||
self._ensureBottomLine();
|
self._ensureBottomLine();
|
||||||
|
@ -3008,8 +2965,6 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
// If at end of block, merge next into this block
|
// If at end of block, merge next into this block
|
||||||
else if ( rangeDoesEndAtBlockBoundary( range ) ) {
|
else if ( rangeDoesEndAtBlockBoundary( range ) ) {
|
||||||
self._recordUndoState( range );
|
|
||||||
self._getRangeAndRemoveBookmark( range );
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var current = getStartBlockOfRange( range ),
|
var current = getStartBlockOfRange( range ),
|
||||||
next = current && getNextBlock( current );
|
next = current && getNextBlock( current );
|
||||||
|
@ -3038,17 +2993,12 @@ var keyHandlers = {
|
||||||
// Otherwise, leave to browser but check afterwards whether it has
|
// Otherwise, leave to browser but check afterwards whether it has
|
||||||
// left behind an empty inline tag.
|
// left behind an empty inline tag.
|
||||||
else {
|
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 );
|
setTimeout( function () { afterDelete( self ); }, 0 );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tab: function ( self, event ) {
|
tab: function ( self, event ) {
|
||||||
|
self._removeZWS();
|
||||||
var range = self.getSelection(),
|
var range = self.getSelection(),
|
||||||
node, parent;
|
node, parent;
|
||||||
// If no selection and in an empty block
|
// If no selection and in an empty block
|
||||||
|
@ -3087,6 +3037,7 @@ var keyHandlers = {
|
||||||
self._removeZWS();
|
self._removeZWS();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Firefox incorrectly handles Cmd-left/Cmd-right on Mac:
|
// Firefox incorrectly handles Cmd-left/Cmd-right on Mac:
|
||||||
// it goes back/forward in history! Override to do the right
|
// it goes back/forward in history! Override to do the right
|
||||||
// thing.
|
// thing.
|
||||||
|
@ -3132,7 +3083,7 @@ proto._onKey = function ( event ) {
|
||||||
|
|
||||||
// On keypress, delete and '.' both have event.keyCode 46
|
// On keypress, delete and '.' both have event.keyCode 46
|
||||||
// Must check event.which to differentiate.
|
// Must check event.which to differentiate.
|
||||||
if ( isOpera && event.which === 46 ) {
|
if ( isPresto && event.which === 46 ) {
|
||||||
key = '.';
|
key = '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3141,9 +3092,15 @@ proto._onKey = function ( event ) {
|
||||||
key = 'f' + ( code - 111 );
|
key = 'f' + ( code - 111 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.altKey ) { modifiers += 'alt-'; }
|
||||||
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
|
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
|
||||||
if ( event.metaKey ) { modifiers += 'meta-'; }
|
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-'; }
|
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
||||||
|
|
||||||
key = modifiers + key;
|
key = modifiers + key;
|
||||||
|
@ -3463,7 +3420,6 @@ proto.removeList = command( 'modifyBlocks', removeList );
|
||||||
|
|
||||||
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
|
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
|
||||||
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );
|
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );
|
||||||
/*global top, win, doc, Squire */
|
|
||||||
|
|
||||||
if ( top !== win ) {
|
if ( top !== win ) {
|
||||||
win.editor = new Squire( doc );
|
win.editor = new Squire( doc );
|
||||||
|
|
File diff suppressed because one or more lines are too long
29
package.json
Normal file
29
package.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "Squire",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Squire is an HTML5 rich text editor, which provides powerful cross-browser normalisation, whilst being supremely lightweight and flexible.",
|
||||||
|
"main": "build/squire.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/neilj/Squire.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"wysiwyg",
|
||||||
|
"editor",
|
||||||
|
"text",
|
||||||
|
"html",
|
||||||
|
"squire"
|
||||||
|
],
|
||||||
|
"author": "Neil Jenkins",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/neilj/Squire/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/neilj/Squire",
|
||||||
|
"devDependencies": {
|
||||||
|
"uglify-js": "^2.4.15"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,10 @@
|
||||||
/*global doc, navigator */
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
/*jshint strict:false */
|
|
||||||
|
|
||||||
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
||||||
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
||||||
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
||||||
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
||||||
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
|
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_START = 0; // Range.START_TO_START
|
||||||
var START_TO_END = 1; // Range.START_TO_END
|
var START_TO_END = 1; // Range.START_TO_END
|
||||||
|
@ -24,21 +21,21 @@ var isMac = /Mac OS X/.test( ua );
|
||||||
var isGecko = /Gecko\//.test( ua );
|
var isGecko = /Gecko\//.test( ua );
|
||||||
var isIE8or9or10 = /Trident\/[456]\./.test( ua );
|
var isIE8or9or10 = /Trident\/[456]\./.test( ua );
|
||||||
var isIE8 = ( win.ie === 8 );
|
var isIE8 = ( win.ie === 8 );
|
||||||
var isOpera = !!win.opera;
|
var isPresto = !!win.opera;
|
||||||
var isWebKit = /WebKit\//.test( ua );
|
var isWebKit = /WebKit\//.test( ua );
|
||||||
|
|
||||||
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
var ctrlKey = isMac ? 'meta-' : 'ctrl-';
|
||||||
|
|
||||||
var useTextFixer = isIE8or9or10 || isOpera;
|
var useTextFixer = isIE8or9or10 || isPresto;
|
||||||
var cantFocusEmptyTextNodes = isIE8or9or10 || isWebKit;
|
var cantFocusEmptyTextNodes = isIE8or9or10 || isWebKit;
|
||||||
var losesSelectionOnBlur = isIE8or9or10;
|
var losesSelectionOnBlur = isIE8or9or10;
|
||||||
var hasBuggySplit = ( function () {
|
var hasBuggySplit = function ( doc ) {
|
||||||
var div = doc.createElement( 'DIV' ),
|
var div = doc.createElement( 'DIV' ),
|
||||||
text = doc.createTextNode( '12' );
|
text = doc.createTextNode( '12' );
|
||||||
div.appendChild( text );
|
div.appendChild( text );
|
||||||
text.splitText( 2 );
|
text.splitText( 2 );
|
||||||
return div.childNodes.length !== 2;
|
return div.childNodes.length !== 2;
|
||||||
}() );
|
};
|
||||||
|
|
||||||
// Use [^ \t\r\n] instead of \S so that nbsp does not count as white-space
|
// Use [^ \t\r\n] instead of \S so that nbsp does not count as white-space
|
||||||
var notWS = /[^ \t\r\n]/;
|
var notWS = /[^ \t\r\n]/;
|
||||||
|
|
265
source/Editor.js
265
source/Editor.js
|
@ -1,70 +1,6 @@
|
||||||
/*global
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
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,
|
|
||||||
|
|
||||||
TreeWalker,
|
var instances = [];
|
||||||
|
|
||||||
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 */
|
|
||||||
|
|
||||||
function Squire ( doc ) {
|
function Squire ( doc ) {
|
||||||
var win = doc.defaultView;
|
var win = doc.defaultView;
|
||||||
|
@ -114,7 +50,7 @@ function Squire ( doc ) {
|
||||||
this.addEventListener( 'keyup', this._ieSelAllClean );
|
this.addEventListener( 'keyup', this._ieSelAllClean );
|
||||||
}
|
}
|
||||||
// Opera does not fire keydown repeatedly.
|
// 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.
|
// 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
|
// If the split is at the end of the node, it doesn't insert the newly split
|
||||||
|
@ -122,7 +58,7 @@ function Squire ( doc ) {
|
||||||
// And even if the split is not at the end, the original node is removed
|
// 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
|
// from the document and replaced by another, rather than just having its
|
||||||
// data shortened.
|
// data shortened.
|
||||||
if ( hasBuggySplit ) {
|
if ( hasBuggySplit( doc ) ) {
|
||||||
win.Text.prototype.splitText = function ( offset ) {
|
win.Text.prototype.splitText = function ( offset ) {
|
||||||
var afterSplit = this.ownerDocument.createTextNode(
|
var afterSplit = this.ownerDocument.createTextNode(
|
||||||
this.data.slice( offset ) ),
|
this.data.slice( offset ) ),
|
||||||
|
@ -149,6 +85,8 @@ function Squire ( doc ) {
|
||||||
doc.execCommand( 'enableObjectResizing', false, 'false' );
|
doc.execCommand( 'enableObjectResizing', false, 'false' );
|
||||||
doc.execCommand( 'enableInlineTableEditing', false, 'false' );
|
doc.execCommand( 'enableInlineTableEditing', false, 'false' );
|
||||||
} catch ( error ) {}
|
} catch ( error ) {}
|
||||||
|
|
||||||
|
instances.push( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
var proto = Squire.prototype;
|
var proto = Squire.prototype;
|
||||||
|
@ -210,6 +148,26 @@ proto.fireEvent = function ( type, event ) {
|
||||||
return this;
|
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 ) {
|
proto.handleEvent = function ( event ) {
|
||||||
this.fireEvent( event.type, event );
|
this.fireEvent( event.type, event );
|
||||||
};
|
};
|
||||||
|
@ -226,7 +184,7 @@ proto.addEventListener = function ( type, fn ) {
|
||||||
if ( !handlers ) {
|
if ( !handlers ) {
|
||||||
handlers = this._events[ type ] = [];
|
handlers = this._events[ type ] = [];
|
||||||
if ( !customEvents[ type ] ) {
|
if ( !customEvents[ type ] ) {
|
||||||
this._doc.addEventListener( type, this, false );
|
this._doc.addEventListener( type, this, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handlers.push( fn );
|
handlers.push( fn );
|
||||||
|
@ -340,7 +298,7 @@ proto._removeZWS = function () {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var walker = new TreeWalker( this._body, SHOW_TEXT, function () {
|
var walker = new TreeWalker( this._body, SHOW_TEXT, function () {
|
||||||
return FILTER_ACCEPT;
|
return true;
|
||||||
}, false ),
|
}, false ),
|
||||||
node, index;
|
node, index;
|
||||||
while ( node = walker.nextNode() ) {
|
while ( node = walker.nextNode() ) {
|
||||||
|
@ -380,11 +338,10 @@ proto._updatePathOnEvent = function () {
|
||||||
// --- Focus ---
|
// --- Focus ---
|
||||||
|
|
||||||
proto.focus = function () {
|
proto.focus = function () {
|
||||||
// FF seems to need the body to be focussed
|
// FF seems to need the body to be focussed (at least on first load).
|
||||||
// (at least on first load).
|
// Chrome also now needs body to be focussed in order to show the cursor
|
||||||
if ( isGecko ) {
|
// (otherwise it is focussed, but the cursor doesn't appear).
|
||||||
this._body.focus();
|
this._body.focus();
|
||||||
}
|
|
||||||
this._win.focus();
|
this._win.focus();
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
@ -597,8 +554,7 @@ proto.hasFormat = function ( tag, attributes, range ) {
|
||||||
// Otherwise, check each text node at least partially contained within
|
// Otherwise, check each text node at least partially contained within
|
||||||
// the selection and make sure all of them have the format we want.
|
// the selection and make sure all of them have the format we want.
|
||||||
walker = new TreeWalker( root, SHOW_TEXT, function ( node ) {
|
walker = new TreeWalker( root, SHOW_TEXT, function ( node ) {
|
||||||
return isNodeContainedInRange( range, node, true ) ?
|
return isNodeContainedInRange( range, node, true );
|
||||||
FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
}, false );
|
}, false );
|
||||||
|
|
||||||
var seenNode = false;
|
var seenNode = false;
|
||||||
|
@ -616,7 +572,7 @@ proto._addFormat = function ( tag, attributes, range ) {
|
||||||
// If the range is collapsed we simply insert the node by wrapping
|
// If the range is collapsed we simply insert the node by wrapping
|
||||||
// it round the range and focus it.
|
// it round the range and focus it.
|
||||||
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
||||||
textnode, needsFormat;
|
textNode, needsFormat;
|
||||||
|
|
||||||
if ( range.collapsed ) {
|
if ( range.collapsed ) {
|
||||||
el = fixCursor( this.createElement( tag, attributes ) );
|
el = fixCursor( this.createElement( tag, attributes ) );
|
||||||
|
@ -637,47 +593,54 @@ proto._addFormat = function ( tag, attributes, range ) {
|
||||||
range.commonAncestorContainer,
|
range.commonAncestorContainer,
|
||||||
SHOW_TEXT,
|
SHOW_TEXT,
|
||||||
function ( node ) {
|
function ( node ) {
|
||||||
return isNodeContainedInRange( range, node, true ) ?
|
return isNodeContainedInRange( range, node, true );
|
||||||
FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start at the beginning node of the range and iterate through
|
// Start at the beginning node of the range and iterate through
|
||||||
// all the nodes in the range that need formatting.
|
// all the nodes in the range that need formatting.
|
||||||
startOffset = 0;
|
startContainer = range.startContainer;
|
||||||
endOffset = 0;
|
startOffset = range.startOffset;
|
||||||
textnode = walker.currentNode = range.startContainer;
|
endContainer = range.endContainer;
|
||||||
|
endOffset = range.endOffset;
|
||||||
|
|
||||||
if ( textnode.nodeType !== TEXT_NODE ) {
|
// Make sure we start inside a text node.
|
||||||
textnode = walker.nextNode();
|
walker.currentNode = startContainer;
|
||||||
|
if ( startContainer.nodeType !== TEXT_NODE ) {
|
||||||
|
startContainer = walker.nextNode();
|
||||||
|
startOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
needsFormat = !getNearest( textnode, tag, attributes );
|
textNode = walker.currentNode;
|
||||||
if ( textnode === range.endContainer ) {
|
needsFormat = !getNearest( textNode, tag, attributes );
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( needsFormat ) {
|
if ( needsFormat ) {
|
||||||
el = this.createElement( tag, attributes );
|
if ( textNode === endContainer &&
|
||||||
replaceWith( textnode, el );
|
textNode.length > endOffset ) {
|
||||||
el.appendChild( textnode );
|
textNode.splitText( endOffset );
|
||||||
endOffset = textnode.length;
|
}
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
}
|
}
|
||||||
endContainer = textnode;
|
|
||||||
if ( !startContainer ) { startContainer = endContainer; }
|
|
||||||
} while ( textnode = walker.nextNode() );
|
|
||||||
|
|
||||||
// Now set the selection to as it was before
|
// Now set the selection to as it was before
|
||||||
range = this._createRange(
|
range = this._createRange(
|
||||||
|
@ -1097,7 +1060,7 @@ var addLinks = function ( frag ) {
|
||||||
var doc = frag.ownerDocument,
|
var doc = frag.ownerDocument,
|
||||||
walker = new TreeWalker( frag, SHOW_TEXT,
|
walker = new TreeWalker( frag, SHOW_TEXT,
|
||||||
function ( node ) {
|
function ( node ) {
|
||||||
return getNearest( node, 'A' ) ? FILTER_SKIP : FILTER_ACCEPT;
|
return !getNearest( node, 'A' );
|
||||||
}, false ),
|
}, false ),
|
||||||
node, data, parent, match, index, endIndex, child;
|
node, data, parent, match, index, endIndex, child;
|
||||||
while ( node = walker.nextNode() ) {
|
while ( node = walker.nextNode() ) {
|
||||||
|
@ -1369,6 +1332,14 @@ var cleanTree = function ( node, allowStyles ) {
|
||||||
}
|
}
|
||||||
continue;
|
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 );
|
node.removeChild( child );
|
||||||
i -= 1;
|
i -= 1;
|
||||||
|
@ -1379,10 +1350,9 @@ var cleanTree = function ( node, allowStyles ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var notWSTextNode = function ( node ) {
|
var notWSTextNode = function ( node ) {
|
||||||
return ( node.nodeType === ELEMENT_NODE ?
|
return node.nodeType === ELEMENT_NODE ?
|
||||||
node.nodeName === 'BR' :
|
node.nodeName === 'BR' :
|
||||||
notWS.test( node.data ) ) ?
|
notWS.test( node.data );
|
||||||
FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
};
|
};
|
||||||
var isLineBreak = function ( br ) {
|
var isLineBreak = function ( br ) {
|
||||||
var block = br.parentNode,
|
var block = br.parentNode,
|
||||||
|
@ -1426,17 +1396,23 @@ var cleanupBRs = function ( root ) {
|
||||||
block = block.parentNode;
|
block = block.parentNode;
|
||||||
}
|
}
|
||||||
// If this is not inside a block, replace it by wrapping
|
// If this is not inside a block, replace it by wrapping
|
||||||
// inlines in DIV.
|
// inlines in a <div>.
|
||||||
if ( !isBlock( block ) || !tagAfterSplit[ block.nodeName ] ) {
|
if ( !isBlock( block ) ) {
|
||||||
fixContainer( 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 {
|
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] ) {
|
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 );
|
detach( br );
|
||||||
}
|
}
|
||||||
|
@ -1518,11 +1494,20 @@ proto._onPaste = function ( event ) {
|
||||||
startContainer = range.startContainer,
|
startContainer = range.startContainer,
|
||||||
startOffset = range.startOffset,
|
startOffset = range.startOffset,
|
||||||
endContainer = range.endContainer,
|
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', {
|
var pasteArea = this.createElement( 'DIV', {
|
||||||
style: 'position: absolute; overflow: hidden; top:' +
|
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 );
|
body.appendChild( pasteArea );
|
||||||
range.selectNodeContents( pasteArea );
|
range.selectNodeContents( pasteArea );
|
||||||
|
@ -1681,8 +1666,11 @@ var keyHandlers = {
|
||||||
if ( !range ) { return; }
|
if ( !range ) { return; }
|
||||||
|
|
||||||
// Save undo checkpoint and add any links in the preceding section.
|
// 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 );
|
self._recordUndoState( range );
|
||||||
addLinks( range.startContainer );
|
addLinks( range.startContainer );
|
||||||
|
self._removeZWS();
|
||||||
self._getRangeAndRemoveBookmark( range );
|
self._getRangeAndRemoveBookmark( range );
|
||||||
|
|
||||||
// Selected text is overwritten, therefore delete the contents
|
// Selected text is overwritten, therefore delete the contents
|
||||||
|
@ -1790,7 +1778,7 @@ var keyHandlers = {
|
||||||
// If you try to select the contents of a 'BR', FF will not let
|
// If you try to select the contents of a 'BR', FF will not let
|
||||||
// you type anything!
|
// you type anything!
|
||||||
if ( !child || child.nodeName === 'BR' ||
|
if ( !child || child.nodeName === 'BR' ||
|
||||||
( child.nodeType === TEXT_NODE && !isOpera ) ) {
|
( child.nodeType === TEXT_NODE && !isPresto ) ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
nodeAfterSplit = child;
|
nodeAfterSplit = child;
|
||||||
|
@ -1815,11 +1803,13 @@ var keyHandlers = {
|
||||||
self._docWasChanged();
|
self._docWasChanged();
|
||||||
},
|
},
|
||||||
backspace: function ( self, event ) {
|
backspace: function ( self, event ) {
|
||||||
|
self._removeZWS();
|
||||||
|
// Record undo checkpoint.
|
||||||
var range = self.getSelection();
|
var range = self.getSelection();
|
||||||
// If not collapsed, delete contents
|
|
||||||
if ( !range.collapsed ) {
|
|
||||||
self._recordUndoState( range );
|
self._recordUndoState( range );
|
||||||
self._getRangeAndRemoveBookmark( range );
|
self._getRangeAndRemoveBookmark( range );
|
||||||
|
// If not collapsed, delete contents
|
||||||
|
if ( !range.collapsed ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range );
|
||||||
self._ensureBottomLine();
|
self._ensureBottomLine();
|
||||||
|
@ -1828,8 +1818,6 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
// If at beginning of block, merge with previous
|
// If at beginning of block, merge with previous
|
||||||
else if ( rangeDoesStartAtBlockBoundary( range ) ) {
|
else if ( rangeDoesStartAtBlockBoundary( range ) ) {
|
||||||
self._recordUndoState( range );
|
|
||||||
self._getRangeAndRemoveBookmark( range );
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var current = getStartBlockOfRange( range ),
|
var current = getStartBlockOfRange( range ),
|
||||||
previous = current && getPreviousBlock( current );
|
previous = current && getPreviousBlock( current );
|
||||||
|
@ -1872,21 +1860,18 @@ var keyHandlers = {
|
||||||
// Otherwise, leave to browser but check afterwards whether it has
|
// Otherwise, leave to browser but check afterwards whether it has
|
||||||
// left behind an empty inline tag.
|
// left behind an empty inline tag.
|
||||||
else {
|
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 );
|
setTimeout( function () { afterDelete( self ); }, 0 );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'delete': function ( self, event ) {
|
'delete': function ( self, event ) {
|
||||||
|
self._removeZWS();
|
||||||
|
// Record undo checkpoint.
|
||||||
var range = self.getSelection();
|
var range = self.getSelection();
|
||||||
// If not collapsed, delete contents
|
|
||||||
if ( !range.collapsed ) {
|
|
||||||
self._recordUndoState( range );
|
self._recordUndoState( range );
|
||||||
self._getRangeAndRemoveBookmark( range );
|
self._getRangeAndRemoveBookmark( range );
|
||||||
|
// If not collapsed, delete contents
|
||||||
|
if ( !range.collapsed ) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
deleteContentsOfRange( range );
|
deleteContentsOfRange( range );
|
||||||
self._ensureBottomLine();
|
self._ensureBottomLine();
|
||||||
|
@ -1895,8 +1880,6 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
// If at end of block, merge next into this block
|
// If at end of block, merge next into this block
|
||||||
else if ( rangeDoesEndAtBlockBoundary( range ) ) {
|
else if ( rangeDoesEndAtBlockBoundary( range ) ) {
|
||||||
self._recordUndoState( range );
|
|
||||||
self._getRangeAndRemoveBookmark( range );
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var current = getStartBlockOfRange( range ),
|
var current = getStartBlockOfRange( range ),
|
||||||
next = current && getNextBlock( current );
|
next = current && getNextBlock( current );
|
||||||
|
@ -1925,17 +1908,12 @@ var keyHandlers = {
|
||||||
// Otherwise, leave to browser but check afterwards whether it has
|
// Otherwise, leave to browser but check afterwards whether it has
|
||||||
// left behind an empty inline tag.
|
// left behind an empty inline tag.
|
||||||
else {
|
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 );
|
setTimeout( function () { afterDelete( self ); }, 0 );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tab: function ( self, event ) {
|
tab: function ( self, event ) {
|
||||||
|
self._removeZWS();
|
||||||
var range = self.getSelection(),
|
var range = self.getSelection(),
|
||||||
node, parent;
|
node, parent;
|
||||||
// If no selection and in an empty block
|
// If no selection and in an empty block
|
||||||
|
@ -1974,6 +1952,7 @@ var keyHandlers = {
|
||||||
self._removeZWS();
|
self._removeZWS();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Firefox incorrectly handles Cmd-left/Cmd-right on Mac:
|
// Firefox incorrectly handles Cmd-left/Cmd-right on Mac:
|
||||||
// it goes back/forward in history! Override to do the right
|
// it goes back/forward in history! Override to do the right
|
||||||
// thing.
|
// thing.
|
||||||
|
@ -2019,7 +1998,7 @@ proto._onKey = function ( event ) {
|
||||||
|
|
||||||
// On keypress, delete and '.' both have event.keyCode 46
|
// On keypress, delete and '.' both have event.keyCode 46
|
||||||
// Must check event.which to differentiate.
|
// Must check event.which to differentiate.
|
||||||
if ( isOpera && event.which === 46 ) {
|
if ( isPresto && event.which === 46 ) {
|
||||||
key = '.';
|
key = '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2028,9 +2007,15 @@ proto._onKey = function ( event ) {
|
||||||
key = 'f' + ( code - 111 );
|
key = 'f' + ( code - 111 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.altKey ) { modifiers += 'alt-'; }
|
||||||
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
|
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
|
||||||
if ( event.metaKey ) { modifiers += 'meta-'; }
|
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-'; }
|
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
||||||
|
|
||||||
key = modifiers + key;
|
key = modifiers + key;
|
||||||
|
|
162
source/Node.js
162
source/Node.js
|
@ -1,19 +1,4 @@
|
||||||
/*global
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
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 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)$/;
|
||||||
|
|
||||||
|
@ -72,13 +57,10 @@ function isContainer ( node ) {
|
||||||
!isInline( node ) && !isBlock( node );
|
!isInline( node ) && !isBlock( node );
|
||||||
}
|
}
|
||||||
|
|
||||||
function acceptIfBlock ( el ) {
|
|
||||||
return isBlock( el ) ? FILTER_ACCEPT : FILTER_SKIP;
|
|
||||||
}
|
|
||||||
function getBlockWalker ( node ) {
|
function getBlockWalker ( node ) {
|
||||||
var doc = node.ownerDocument,
|
var doc = node.ownerDocument,
|
||||||
walker = new TreeWalker(
|
walker = new TreeWalker(
|
||||||
doc.body, SHOW_ELEMENT, acceptIfBlock, false );
|
doc.body, SHOW_ELEMENT, isBlock, false );
|
||||||
walker.currentNode = node;
|
walker.currentNode = node;
|
||||||
return walker;
|
return walker;
|
||||||
}
|
}
|
||||||
|
@ -148,6 +130,29 @@ function empty ( node ) {
|
||||||
return frag;
|
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 ) {
|
function fixCursor ( node ) {
|
||||||
// In Webkit and Gecko, block level elements are collapsed and
|
// In Webkit and Gecko, block level elements are collapsed and
|
||||||
// unfocussable if they have no content. To remedy this, a <BR> must be
|
// unfocussable if they have no content. To remedy this, a <BR> must be
|
||||||
|
@ -155,7 +160,8 @@ function fixCursor ( node ) {
|
||||||
// cursor to appear.
|
// cursor to appear.
|
||||||
var doc = node.ownerDocument,
|
var doc = node.ownerDocument,
|
||||||
root = node,
|
root = node,
|
||||||
fixer, child;
|
fixer, child,
|
||||||
|
l, instance;
|
||||||
|
|
||||||
if ( node.nodeName === 'BODY' ) {
|
if ( node.nodeName === 'BODY' ) {
|
||||||
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
if ( !( child = node.firstChild ) || child.nodeName === 'BR' ) {
|
||||||
|
@ -172,11 +178,22 @@ function fixCursor ( node ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isInline( 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 ) {
|
if ( cantFocusEmptyTextNodes ) {
|
||||||
fixer = doc.createTextNode( '\u200B' );
|
fixer = doc.createTextNode( '\u200B' );
|
||||||
if ( win.editor ) {
|
// Find the relevant Squire instance and notify
|
||||||
win.editor._didAddZWS();
|
l = instances.length;
|
||||||
|
while ( l-- ) {
|
||||||
|
instance = instances[l];
|
||||||
|
if ( instance._doc === doc ) {
|
||||||
|
instance._didAddZWS();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fixer = doc.createTextNode( '' );
|
fixer = doc.createTextNode( '' );
|
||||||
|
@ -216,6 +233,42 @@ function fixCursor ( node ) {
|
||||||
return root;
|
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 ) {
|
function split ( node, offset, stopNode ) {
|
||||||
var nodeType = node.nodeType,
|
var nodeType = node.nodeType,
|
||||||
parent, clone, next;
|
parent, clone, next;
|
||||||
|
@ -357,7 +410,7 @@ function mergeWithBlock ( block, next, range ) {
|
||||||
// Steps to reproduce bug: Type "a-b-c" (where - is return)
|
// Steps to reproduce bug: Type "a-b-c" (where - is return)
|
||||||
// then backspace twice. The cursor goes to the top instead
|
// then backspace twice. The cursor goes to the top instead
|
||||||
// of after "b".
|
// of after "b".
|
||||||
if ( isOpera && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
|
if ( isPresto && ( last = block.lastChild ) && last.nodeName === 'BR' ) {
|
||||||
block.removeChild( last );
|
block.removeChild( last );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,62 +452,3 @@ function mergeContainers ( node ) {
|
||||||
fixCursor( prev );
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,28 +1,4 @@
|
||||||
/*global
|
/*jshint strict:false, undef:false, unused:false */
|
||||||
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 getNodeBefore = function ( node, offset ) {
|
||||||
var children = node.childNodes;
|
var children = node.childNodes;
|
||||||
|
@ -59,8 +35,8 @@ var forEachTextNodeInRange = function ( range, fn ) {
|
||||||
endContainer = range.endContainer,
|
endContainer = range.endContainer,
|
||||||
root = range.commonAncestorContainer,
|
root = range.commonAncestorContainer,
|
||||||
walker = new TreeWalker(
|
walker = new TreeWalker(
|
||||||
root, SHOW_TEXT, function ( node ) {
|
root, SHOW_TEXT, function (/* node */) {
|
||||||
return FILTER_ACCEPT;
|
return true;
|
||||||
}, false ),
|
}, false ),
|
||||||
textnode = walker.currentNode = startContainer;
|
textnode = walker.currentNode = startContainer;
|
||||||
|
|
||||||
|
@ -451,50 +427,56 @@ var getEndBlockOfRange = function ( range ) {
|
||||||
return block && isNodeContainedInRange( range, block, true ) ? block : null;
|
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 rangeDoesStartAtBlockBoundary = function ( range ) {
|
||||||
var startContainer = range.startContainer,
|
var startContainer = range.startContainer,
|
||||||
startOffset = range.startOffset,
|
startOffset = range.startOffset;
|
||||||
parent, child;
|
|
||||||
|
|
||||||
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 ) {
|
if ( startOffset ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
parent = startContainer.parentNode;
|
contentWalker.currentNode = startContainer;
|
||||||
startOffset = indexOf.call( parent.childNodes, startContainer );
|
} else {
|
||||||
startContainer = parent;
|
contentWalker.currentNode = getNodeAfter( startContainer, startOffset );
|
||||||
}
|
}
|
||||||
// Skip empty text nodes and <br>s.
|
|
||||||
while ( startOffset &&
|
// Otherwise, look for any previous content in the same block.
|
||||||
( child = startContainer.childNodes[ startOffset - 1 ] ) &&
|
contentWalker.root = getStartBlockOfRange( range );
|
||||||
( child.data === '' || child.nodeName === 'BR' ) ) {
|
|
||||||
startOffset -= 1;
|
return !contentWalker.previousNode();
|
||||||
}
|
|
||||||
return !startOffset;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var rangeDoesEndAtBlockBoundary = function ( range ) {
|
var rangeDoesEndAtBlockBoundary = function ( range ) {
|
||||||
var endContainer = range.endContainer,
|
var endContainer = range.endContainer,
|
||||||
endOffset = range.endOffset,
|
endOffset = range.endOffset,
|
||||||
length = getLength( endContainer ),
|
length;
|
||||||
parent, child;
|
|
||||||
|
|
||||||
while ( isInline( endContainer ) ) {
|
// If in a text node with content, and not at the end, we're not
|
||||||
if ( endOffset !== length ) {
|
// at the boundary
|
||||||
|
if ( endContainer.nodeType === TEXT_NODE ) {
|
||||||
|
length = endContainer.data.length;
|
||||||
|
if ( length && endOffset < length ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
parent = endContainer.parentNode;
|
contentWalker.currentNode = endContainer;
|
||||||
endOffset = indexOf.call( parent.childNodes, endContainer ) + 1;
|
} else {
|
||||||
endContainer = parent;
|
contentWalker.currentNode = getNodeBefore( endContainer, endOffset );
|
||||||
length = endContainer.childNodes.length;
|
|
||||||
}
|
}
|
||||||
// Skip empty text nodes and <br>s.
|
|
||||||
while ( endOffset < length &&
|
// Otherwise, look for any further content in the same block.
|
||||||
( child = endContainer.childNodes[ endOffset ] ) &&
|
contentWalker.root = getEndBlockOfRange( range );
|
||||||
( child.data === '' || child.nodeName === 'BR' ) ) {
|
|
||||||
endOffset += 1;
|
return !contentWalker.nextNode();
|
||||||
}
|
|
||||||
return endOffset === length;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var expandRangeToBlockBoundaries = function ( range ) {
|
var expandRangeToBlockBoundaries = function ( range ) {
|
||||||
|
|
|
@ -57,6 +57,7 @@ iframe {
|
||||||
background:#FFF;
|
background:#FFF;
|
||||||
padding:10px;
|
padding:10px;
|
||||||
border-left:1px #EEE solid;
|
border-left:1px #EEE solid;
|
||||||
|
border-bottom: 3px transparent solid;
|
||||||
-webkit-font-smoothing:subpixel-antialiased
|
-webkit-font-smoothing:subpixel-antialiased
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ input[type=text] {
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu .group .item:hover, .menu .item:first-child:hover {
|
.menu .group .item:hover, .menu .item:first-child:hover {
|
||||||
border-left: 3px #55ACEE solid;
|
border-bottom: 3px #55ACEE solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu .item:first-child {
|
.menu .item:first-child {
|
||||||
|
|
|
@ -143,8 +143,8 @@ $(document).ready(function() {
|
||||||
} else if (test.isNotValue('makeLink') | test.isNotValue('insertImage') | test.isNotValue('selectFont')) {
|
} else if (test.isNotValue('makeLink') | test.isNotValue('insertImage') | test.isNotValue('selectFont')) {
|
||||||
// do nothing these are dropdowns.
|
// do nothing these are dropdowns.
|
||||||
} else {
|
} else {
|
||||||
if (editor.getSelectedText() === '' && !(action == 'insertImage' || action == 'makeOrderedList' || action == 'increaseQuoteLevel' || action == 'redo' || action == 'undo')) return;
|
|
||||||
editor[action]();
|
editor[action]();
|
||||||
|
editor.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/*global FILTER_ACCEPT */
|
|
||||||
/*jshint strict:false */
|
/*jshint strict:false */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -52,7 +51,7 @@ TreeWalker.prototype.nextNode = function () {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
||||||
filter( node ) === FILTER_ACCEPT ) {
|
filter( node ) ) {
|
||||||
this.currentNode = node;
|
this.currentNode = node;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +81,7 @@ TreeWalker.prototype.previousNode = function () {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
||||||
filter( node ) === FILTER_ACCEPT ) {
|
filter( node ) ) {
|
||||||
this.currentNode = node;
|
this.currentNode = node;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
20
source/assets/font-awesome/font-awesome.min.css
vendored
20
source/assets/font-awesome/font-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -49,9 +49,6 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!--[if IE 8]>
|
|
||||||
<script type="text/javascript" src="ie8.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
<script type="text/javascript" src="squire.js"></script>
|
<script type="text/javascript" src="squire.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
157
source/ie8dom.js
157
source/ie8dom.js
|
@ -1,157 +0,0 @@
|
||||||
/* 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}() );
|
|
|
@ -1,487 +0,0 @@
|
||||||
/* 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}() );
|
|
|
@ -1,63 +0,0 @@
|
||||||
/* 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 );
|
|
||||||
};
|
|
||||||
|
|
||||||
}() );
|
|
|
@ -1,4 +1,3 @@
|
||||||
/*global top, win, doc, Squire */
|
|
||||||
|
|
||||||
if ( top !== win ) {
|
if ( top !== win ) {
|
||||||
win.editor = new Squire( doc );
|
win.editor = new Squire( doc );
|
||||||
|
|
Loading…
Reference in a new issue