squire-rails 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/squire-rails/version.rb +1 -1
- data/vendor/assets/javascripts/squire/squire-raw.js +1972 -1610
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 604f8843d5e2ecfd3a4c5975af0c37e74d79b3a9
|
4
|
+
data.tar.gz: a1d403eccb978254d6df20ea2a6b08a6fd6893b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a72947f12e20854bc725199576c0d666814447a4ea656bf65774f4195b8d2211e2ef509eb7acb1f2725932a2449509203ea69d69004763c9fed702a99dd65757
|
7
|
+
data.tar.gz: bf05167d9a969b2a9de03d4faefbc82d7cdd189f093ba4a9cc9c34f5fb4d6efa58931fe3edf123dd0b173bf7f679b7ddc91d3a19906340298e2a32ee6ce5d51c
|
data/lib/squire-rails/version.rb
CHANGED
@@ -7,6 +7,7 @@
|
|
7
7
|
var DOCUMENT_POSITION_PRECEDING = 2; // Node.DOCUMENT_POSITION_PRECEDING
|
8
8
|
var ELEMENT_NODE = 1; // Node.ELEMENT_NODE;
|
9
9
|
var TEXT_NODE = 3; // Node.TEXT_NODE;
|
10
|
+
var DOCUMENT_FRAGMENT_NODE = 11; // Node.DOCUMENT_FRAGMENT_NODE;
|
10
11
|
var SHOW_ELEMENT = 1; // NodeFilter.SHOW_ELEMENT;
|
11
12
|
var SHOW_TEXT = 4; // NodeFilter.SHOW_TEXT;
|
12
13
|
|
@@ -140,7 +141,35 @@ TreeWalker.prototype.previousNode = function () {
|
|
140
141
|
}
|
141
142
|
};
|
142
143
|
|
143
|
-
|
144
|
+
// Previous node in post-order.
|
145
|
+
TreeWalker.prototype.previousPONode = function () {
|
146
|
+
var current = this.currentNode,
|
147
|
+
root = this.root,
|
148
|
+
nodeType = this.nodeType,
|
149
|
+
filter = this.filter,
|
150
|
+
node;
|
151
|
+
while ( true ) {
|
152
|
+
node = current.lastChild;
|
153
|
+
while ( !node && current ) {
|
154
|
+
if ( current === root ) {
|
155
|
+
break;
|
156
|
+
}
|
157
|
+
node = current.previousSibling;
|
158
|
+
if ( !node ) { current = current.parentNode; }
|
159
|
+
}
|
160
|
+
if ( !node ) {
|
161
|
+
return null;
|
162
|
+
}
|
163
|
+
if ( ( typeToBitArray[ node.nodeType ] & nodeType ) &&
|
164
|
+
filter( node ) ) {
|
165
|
+
this.currentNode = node;
|
166
|
+
return node;
|
167
|
+
}
|
168
|
+
current = node;
|
169
|
+
}
|
170
|
+
};
|
171
|
+
|
172
|
+
var inlineNodeNames = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|U|VAR|WBR)$/;
|
144
173
|
|
145
174
|
var leafNodeNames = {
|
146
175
|
BR: 1,
|
@@ -172,7 +201,7 @@ function hasTagAttributes ( node, tag, attributes ) {
|
|
172
201
|
return true;
|
173
202
|
}
|
174
203
|
function areAlike ( node, node2 ) {
|
175
|
-
return (
|
204
|
+
return !isLeaf( node ) && (
|
176
205
|
node.nodeType === node2.nodeType &&
|
177
206
|
node.nodeName === node2.nodeName &&
|
178
207
|
node.className === node2.className &&
|
@@ -189,11 +218,13 @@ function isInline ( node ) {
|
|
189
218
|
return inlineNodeNames.test( node.nodeName );
|
190
219
|
}
|
191
220
|
function isBlock ( node ) {
|
192
|
-
|
221
|
+
var type = node.nodeType;
|
222
|
+
return ( type === ELEMENT_NODE || type === DOCUMENT_FRAGMENT_NODE ) &&
|
193
223
|
!isInline( node ) && every( node.childNodes, isInline );
|
194
224
|
}
|
195
225
|
function isContainer ( node ) {
|
196
|
-
|
226
|
+
var type = node.nodeType;
|
227
|
+
return ( type === ELEMENT_NODE || type === DOCUMENT_FRAGMENT_NODE ) &&
|
197
228
|
!isInline( node ) && !isBlock( node );
|
198
229
|
}
|
199
230
|
|
@@ -667,7 +698,7 @@ var insertNodeInRange = function ( range, node ) {
|
|
667
698
|
|
668
699
|
childCount = children.length;
|
669
700
|
|
670
|
-
if ( startOffset === childCount) {
|
701
|
+
if ( startOffset === childCount ) {
|
671
702
|
startContainer.appendChild( node );
|
672
703
|
} else {
|
673
704
|
startContainer.insertBefore( node, children[ startOffset ] );
|
@@ -735,8 +766,16 @@ var extractContentsOfRange = function ( range, common ) {
|
|
735
766
|
|
736
767
|
var deleteContentsOfRange = function ( range ) {
|
737
768
|
// Move boundaries up as much as possible to reduce need to split.
|
769
|
+
// But we need to check whether we've moved the boundary outside of a
|
770
|
+
// block. If so, the entire block will be removed, so we shouldn't merge
|
771
|
+
// later.
|
738
772
|
moveRangeBoundariesUpTree( range );
|
739
773
|
|
774
|
+
var startBlock = range.startContainer,
|
775
|
+
endBlock = range.endContainer,
|
776
|
+
needsMerge = ( isInline( startBlock ) || isBlock( startBlock ) ) &&
|
777
|
+
( isInline( endBlock ) || isBlock( endBlock ) );
|
778
|
+
|
740
779
|
// Remove selected range
|
741
780
|
extractContentsOfRange( range );
|
742
781
|
|
@@ -746,10 +785,12 @@ var deleteContentsOfRange = function ( range ) {
|
|
746
785
|
moveRangeBoundariesDownTree( range );
|
747
786
|
|
748
787
|
// If we split into two different blocks, merge the blocks.
|
749
|
-
|
788
|
+
if ( needsMerge ) {
|
789
|
+
startBlock = getStartBlockOfRange( range );
|
750
790
|
endBlock = getEndBlockOfRange( range );
|
751
|
-
|
752
|
-
|
791
|
+
if ( startBlock && endBlock && startBlock !== endBlock ) {
|
792
|
+
mergeWithBlock( startBlock, endBlock, range );
|
793
|
+
}
|
753
794
|
}
|
754
795
|
|
755
796
|
// Ensure block has necessary children
|
@@ -763,6 +804,8 @@ var deleteContentsOfRange = function ( range ) {
|
|
763
804
|
if ( !child || child.nodeName === 'BR' ) {
|
764
805
|
fixCursor( body );
|
765
806
|
range.selectNodeContents( body.firstChild );
|
807
|
+
} else {
|
808
|
+
range.collapse( false );
|
766
809
|
}
|
767
810
|
};
|
768
811
|
|
@@ -788,15 +831,13 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
|
788
831
|
// Move range down into text nodes
|
789
832
|
moveRangeBoundariesDownTree( range );
|
790
833
|
|
791
|
-
// If inline, just insert at the current position.
|
792
834
|
if ( allInline ) {
|
835
|
+
// If inline, just insert at the current position.
|
793
836
|
insertNodeInRange( range, frag );
|
794
837
|
range.collapse( false );
|
795
|
-
}
|
796
|
-
|
797
|
-
|
798
|
-
// containers.
|
799
|
-
else {
|
838
|
+
} else {
|
839
|
+
// Otherwise...
|
840
|
+
// 1. Split up to blockquote (if a parent) or body
|
800
841
|
var splitPoint = range.startContainer,
|
801
842
|
nodeAfterSplit = split( splitPoint, range.startOffset,
|
802
843
|
getNearest( splitPoint.parentNode, 'BLOCKQUOTE' ) ||
|
@@ -807,11 +848,16 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
|
807
848
|
endContainer = nodeAfterSplit,
|
808
849
|
endOffset = 0,
|
809
850
|
parent = nodeAfterSplit.parentNode,
|
810
|
-
child, node;
|
851
|
+
child, node, prev, next, startAnchor;
|
811
852
|
|
853
|
+
// 2. Move down into edge either side of split and insert any inline
|
854
|
+
// nodes at the beginning/end of the fragment
|
812
855
|
while ( ( child = startContainer.lastChild ) &&
|
813
|
-
child.nodeType === ELEMENT_NODE
|
814
|
-
|
856
|
+
child.nodeType === ELEMENT_NODE ) {
|
857
|
+
if ( child.nodeName === 'BR' ) {
|
858
|
+
startOffset -= 1;
|
859
|
+
break;
|
860
|
+
}
|
815
861
|
startContainer = child;
|
816
862
|
startOffset = startContainer.childNodes.length;
|
817
863
|
}
|
@@ -820,40 +866,68 @@ var insertTreeFragmentIntoRange = function ( range, frag ) {
|
|
820
866
|
child.nodeName !== 'BR' ) {
|
821
867
|
endContainer = child;
|
822
868
|
}
|
869
|
+
startAnchor = startContainer.childNodes[ startOffset ] || null;
|
823
870
|
while ( ( child = frag.firstChild ) && isInline( child ) ) {
|
824
|
-
startContainer.
|
871
|
+
startContainer.insertBefore( child, startAnchor );
|
825
872
|
}
|
826
873
|
while ( ( child = frag.lastChild ) && isInline( child ) ) {
|
827
874
|
endContainer.insertBefore( child, endContainer.firstChild );
|
828
875
|
endOffset += 1;
|
829
876
|
}
|
830
877
|
|
831
|
-
// Fix cursor then insert block(s)
|
878
|
+
// 3. Fix cursor then insert block(s) in the fragment
|
832
879
|
node = frag;
|
833
880
|
while ( node = getNextBlock( node ) ) {
|
834
881
|
fixCursor( node );
|
835
882
|
}
|
836
883
|
parent.insertBefore( frag, nodeAfterSplit );
|
837
884
|
|
838
|
-
// Remove empty nodes created
|
839
|
-
//
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
885
|
+
// 4. Remove empty nodes created either side of split, then
|
886
|
+
// merge containers at the edges.
|
887
|
+
next = nodeBeforeSplit.nextSibling;
|
888
|
+
node = getPreviousBlock( next );
|
889
|
+
if ( !/\S/.test( node.textContent ) ) {
|
890
|
+
do {
|
891
|
+
parent = node.parentNode;
|
892
|
+
parent.removeChild( node );
|
893
|
+
node = parent;
|
894
|
+
} while ( parent && !parent.lastChild &&
|
895
|
+
parent.nodeName !== 'BODY' );
|
845
896
|
}
|
846
|
-
if ( !
|
847
|
-
|
848
|
-
|
897
|
+
if ( !nodeBeforeSplit.parentNode ) {
|
898
|
+
nodeBeforeSplit = next.previousSibling;
|
899
|
+
}
|
900
|
+
if ( !startContainer.parentNode ) {
|
901
|
+
startContainer = nodeBeforeSplit || next.parentNode;
|
902
|
+
startOffset = nodeBeforeSplit ?
|
903
|
+
nodeBeforeSplit.childNodes.length : 0;
|
904
|
+
}
|
905
|
+
// Merge inserted containers with edges of split
|
906
|
+
if ( isContainer( next ) ) {
|
907
|
+
mergeContainers( next );
|
849
908
|
}
|
850
909
|
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
910
|
+
prev = nodeAfterSplit.previousSibling;
|
911
|
+
node = isBlock( nodeAfterSplit ) ?
|
912
|
+
nodeAfterSplit : getNextBlock( nodeAfterSplit );
|
913
|
+
if ( !/\S/.test( node.textContent ) ) {
|
914
|
+
do {
|
915
|
+
parent = node.parentNode;
|
916
|
+
parent.removeChild( node );
|
917
|
+
node = parent;
|
918
|
+
} while ( parent && !parent.lastChild &&
|
919
|
+
parent.nodeName !== 'BODY' );
|
920
|
+
}
|
921
|
+
if ( !nodeAfterSplit.parentNode ) {
|
922
|
+
nodeAfterSplit = prev.nextSibling;
|
923
|
+
}
|
924
|
+
if ( !endOffset ) {
|
925
|
+
endContainer = prev;
|
926
|
+
endOffset = prev.childNodes.length;
|
927
|
+
}
|
928
|
+
// Merge inserted containers with edges of split
|
929
|
+
if ( nodeAfterSplit && isContainer( nodeAfterSplit ) ) {
|
930
|
+
mergeContainers( nodeAfterSplit );
|
857
931
|
}
|
858
932
|
|
859
933
|
range.setStart( startContainer, startOffset );
|
@@ -1022,6 +1096,7 @@ var rangeDoesStartAtBlockBoundary = function ( range ) {
|
|
1022
1096
|
startOffset = range.startOffset;
|
1023
1097
|
|
1024
1098
|
// If in the middle or end of a text node, we're not at the boundary.
|
1099
|
+
contentWalker.root = null;
|
1025
1100
|
if ( startContainer.nodeType === TEXT_NODE ) {
|
1026
1101
|
if ( startOffset ) {
|
1027
1102
|
return false;
|
@@ -1044,6 +1119,7 @@ var rangeDoesEndAtBlockBoundary = function ( range ) {
|
|
1044
1119
|
|
1045
1120
|
// If in a text node with content, and not at the end, we're not
|
1046
1121
|
// at the boundary
|
1122
|
+
contentWalker.root = null;
|
1047
1123
|
if ( endContainer.nodeType === TEXT_NODE ) {
|
1048
1124
|
length = endContainer.data.length;
|
1049
1125
|
if ( length && endOffset < length ) {
|
@@ -1073,6 +1149,77 @@ var expandRangeToBlockBoundaries = function ( range ) {
|
|
1073
1149
|
}
|
1074
1150
|
};
|
1075
1151
|
|
1152
|
+
var keys = {
|
1153
|
+
8: 'backspace',
|
1154
|
+
9: 'tab',
|
1155
|
+
13: 'enter',
|
1156
|
+
32: 'space',
|
1157
|
+
33: 'pageup',
|
1158
|
+
34: 'pagedown',
|
1159
|
+
37: 'left',
|
1160
|
+
39: 'right',
|
1161
|
+
46: 'delete',
|
1162
|
+
219: '[',
|
1163
|
+
221: ']'
|
1164
|
+
};
|
1165
|
+
|
1166
|
+
// Ref: http://unixpapa.com/js/key.html
|
1167
|
+
var onKey = function ( event ) {
|
1168
|
+
var code = event.keyCode,
|
1169
|
+
key = keys[ code ],
|
1170
|
+
modifiers = '',
|
1171
|
+
range = this.getSelection();
|
1172
|
+
|
1173
|
+
if ( event.defaultPrevented ) {
|
1174
|
+
return;
|
1175
|
+
}
|
1176
|
+
|
1177
|
+
if ( !key ) {
|
1178
|
+
key = String.fromCharCode( code ).toLowerCase();
|
1179
|
+
// Only reliable for letters and numbers
|
1180
|
+
if ( !/^[A-Za-z0-9]$/.test( key ) ) {
|
1181
|
+
key = '';
|
1182
|
+
}
|
1183
|
+
}
|
1184
|
+
|
1185
|
+
// On keypress, delete and '.' both have event.keyCode 46
|
1186
|
+
// Must check event.which to differentiate.
|
1187
|
+
if ( isPresto && event.which === 46 ) {
|
1188
|
+
key = '.';
|
1189
|
+
}
|
1190
|
+
|
1191
|
+
// Function keys
|
1192
|
+
if ( 111 < code && code < 124 ) {
|
1193
|
+
key = 'f' + ( code - 111 );
|
1194
|
+
}
|
1195
|
+
|
1196
|
+
// We need to apply the backspace/delete handlers regardless of
|
1197
|
+
// control key modifiers.
|
1198
|
+
if ( key !== 'backspace' && key !== 'delete' ) {
|
1199
|
+
if ( event.altKey ) { modifiers += 'alt-'; }
|
1200
|
+
if ( event.ctrlKey ) { modifiers += 'ctrl-'; }
|
1201
|
+
if ( event.metaKey ) { modifiers += 'meta-'; }
|
1202
|
+
}
|
1203
|
+
// However, on Windows, shift-delete is apparently "cut" (WTF right?), so
|
1204
|
+
// we want to let the browser handle shift-delete.
|
1205
|
+
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
1206
|
+
|
1207
|
+
key = modifiers + key;
|
1208
|
+
|
1209
|
+
if ( this._keyHandlers[ key ] ) {
|
1210
|
+
this._keyHandlers[ key ]( this, event, range );
|
1211
|
+
} else if ( key.length === 1 && !range.collapsed ) {
|
1212
|
+
// Record undo checkpoint.
|
1213
|
+
this._recordUndoState( range );
|
1214
|
+
this._getRangeAndRemoveBookmark( range );
|
1215
|
+
// Delete the selection
|
1216
|
+
deleteContentsOfRange( range );
|
1217
|
+
this._ensureBottomLine();
|
1218
|
+
this.setSelection( range );
|
1219
|
+
this._updatePath( range, true );
|
1220
|
+
}
|
1221
|
+
};
|
1222
|
+
|
1076
1223
|
var mapKeyTo = function ( method ) {
|
1077
1224
|
return function ( self, event ) {
|
1078
1225
|
event.preventDefault();
|
@@ -1129,6 +1276,14 @@ var afterDelete = function ( self, range ) {
|
|
1129
1276
|
// Move cursor into text node
|
1130
1277
|
moveRangeBoundariesDownTree( range );
|
1131
1278
|
}
|
1279
|
+
// If you delete the last character in the sole <div> in Chrome,
|
1280
|
+
// it removes the div and replaces it with just a <br> inside the
|
1281
|
+
// body. Detach the <br>; the _ensureBottomLine call will insert a new
|
1282
|
+
// block.
|
1283
|
+
if ( node.nodeName === 'BODY' &&
|
1284
|
+
( node = node.firstChild ) && node.nodeName === 'BR' ) {
|
1285
|
+
detach( node );
|
1286
|
+
}
|
1132
1287
|
self._ensureBottomLine();
|
1133
1288
|
self.setSelection( range );
|
1134
1289
|
self._updatePath( range, true );
|
@@ -1240,11 +1395,9 @@ var keyHandlers = {
|
|
1240
1395
|
if ( nodeAfterSplit.nodeType === TEXT_NODE ) {
|
1241
1396
|
nodeAfterSplit = nodeAfterSplit.parentNode;
|
1242
1397
|
}
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
( doc.documentElement.scrollTop || body.scrollTop ) +
|
1247
|
-
body.offsetHeight ) {
|
1398
|
+
// 16 ~ one standard line height in px.
|
1399
|
+
if ( nodeAfterSplit.getBoundingClientRect().top + 16 >
|
1400
|
+
self._doc.documentElement.clientHeight ) {
|
1248
1401
|
nodeAfterSplit.scrollIntoView( false );
|
1249
1402
|
}
|
1250
1403
|
},
|
@@ -1348,17 +1501,33 @@ var keyHandlers = {
|
|
1348
1501
|
// Otherwise, leave to browser but check afterwards whether it has
|
1349
1502
|
// left behind an empty inline tag.
|
1350
1503
|
else {
|
1351
|
-
|
1504
|
+
// But first check if the cursor is just before an IMG tag. If so,
|
1505
|
+
// delete it ourselves, because the browser won't if it is not
|
1506
|
+
// inline.
|
1507
|
+
var originalRange = range.cloneRange(),
|
1508
|
+
cursorContainer, cursorOffset, nodeAfterCursor;
|
1509
|
+
moveRangeBoundariesUpTree( range, self._body );
|
1510
|
+
cursorContainer = range.endContainer;
|
1511
|
+
cursorOffset = range.endOffset;
|
1512
|
+
if ( cursorContainer.nodeType === ELEMENT_NODE ) {
|
1513
|
+
nodeAfterCursor = cursorContainer.childNodes[ cursorOffset ];
|
1514
|
+
if ( nodeAfterCursor && nodeAfterCursor.nodeName === 'IMG' ) {
|
1515
|
+
event.preventDefault();
|
1516
|
+
detach( nodeAfterCursor );
|
1517
|
+
moveRangeBoundariesDownTree( range );
|
1518
|
+
afterDelete( self, range );
|
1519
|
+
return;
|
1520
|
+
}
|
1521
|
+
}
|
1522
|
+
self.setSelection( originalRange );
|
1352
1523
|
setTimeout( function () { afterDelete( self ); }, 0 );
|
1353
1524
|
}
|
1354
1525
|
},
|
1355
1526
|
tab: function ( self, event, range ) {
|
1356
1527
|
var node, parent;
|
1357
1528
|
self._removeZWS();
|
1358
|
-
// If no selection and
|
1359
|
-
if ( range.collapsed &&
|
1360
|
-
rangeDoesStartAtBlockBoundary( range ) &&
|
1361
|
-
rangeDoesEndAtBlockBoundary( range ) ) {
|
1529
|
+
// If no selection and at start of block
|
1530
|
+
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range ) ) {
|
1362
1531
|
node = getStartBlockOfRange( range );
|
1363
1532
|
// Iterate through the block's parents
|
1364
1533
|
while ( parent = node.parentNode ) {
|
@@ -1374,7 +1543,18 @@ var keyHandlers = {
|
|
1374
1543
|
}
|
1375
1544
|
node = parent;
|
1376
1545
|
}
|
1377
|
-
|
1546
|
+
}
|
1547
|
+
},
|
1548
|
+
'shift-tab': function ( self, event, range ) {
|
1549
|
+
self._removeZWS();
|
1550
|
+
// If no selection and at start of block
|
1551
|
+
if ( range.collapsed && rangeDoesStartAtBlockBoundary( range ) ) {
|
1552
|
+
// Break list
|
1553
|
+
var node = range.startContainer;
|
1554
|
+
if ( getNearest( node, 'UL' ) || getNearest( node, 'OL' ) ) {
|
1555
|
+
event.preventDefault();
|
1556
|
+
self.modifyBlocks( decreaseListLevel, range );
|
1557
|
+
}
|
1378
1558
|
}
|
1379
1559
|
},
|
1380
1560
|
space: function ( self, _, range ) {
|
@@ -1418,6 +1598,18 @@ if ( isMac && isGecko && win.getSelection().modify ) {
|
|
1418
1598
|
};
|
1419
1599
|
}
|
1420
1600
|
|
1601
|
+
// System standard for page up/down on Mac is to just scroll, not move the
|
1602
|
+
// cursor. On Linux/Windows, it should move the cursor, but some browsers don't
|
1603
|
+
// implement this natively. Override to support it.
|
1604
|
+
if ( !isMac ) {
|
1605
|
+
keyHandlers.pageup = function ( self ) {
|
1606
|
+
self.moveCursorToStart();
|
1607
|
+
};
|
1608
|
+
keyHandlers.pagedown = function ( self ) {
|
1609
|
+
self.moveCursorToEnd();
|
1610
|
+
};
|
1611
|
+
}
|
1612
|
+
|
1421
1613
|
keyHandlers[ ctrlKey + 'b' ] = mapKeyToFormat( 'B' );
|
1422
1614
|
keyHandlers[ ctrlKey + 'i' ] = mapKeyToFormat( 'I' );
|
1423
1615
|
keyHandlers[ ctrlKey + 'u' ] = mapKeyToFormat( 'U' );
|
@@ -1432,1815 +1624,1819 @@ keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
|
1432
1624
|
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
1433
1625
|
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
1434
1626
|
|
1435
|
-
var
|
1627
|
+
var fontSizes = {
|
1628
|
+
1: 10,
|
1629
|
+
2: 13,
|
1630
|
+
3: 16,
|
1631
|
+
4: 18,
|
1632
|
+
5: 24,
|
1633
|
+
6: 32,
|
1634
|
+
7: 48
|
1635
|
+
};
|
1436
1636
|
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1637
|
+
var spanToSemantic = {
|
1638
|
+
backgroundColor: {
|
1639
|
+
regexp: notWS,
|
1640
|
+
replace: function ( doc, colour ) {
|
1641
|
+
return createElement( doc, 'SPAN', {
|
1642
|
+
'class': 'highlight',
|
1643
|
+
style: 'background-color:' + colour
|
1644
|
+
});
|
1645
|
+
}
|
1646
|
+
},
|
1647
|
+
color: {
|
1648
|
+
regexp: notWS,
|
1649
|
+
replace: function ( doc, colour ) {
|
1650
|
+
return createElement( doc, 'SPAN', {
|
1651
|
+
'class': 'colour',
|
1652
|
+
style: 'color:' + colour
|
1653
|
+
});
|
1654
|
+
}
|
1655
|
+
},
|
1656
|
+
fontWeight: {
|
1657
|
+
regexp: /^bold/i,
|
1658
|
+
replace: function ( doc ) {
|
1659
|
+
return createElement( doc, 'B' );
|
1660
|
+
}
|
1661
|
+
},
|
1662
|
+
fontStyle: {
|
1663
|
+
regexp: /^italic/i,
|
1664
|
+
replace: function ( doc ) {
|
1665
|
+
return createElement( doc, 'I' );
|
1666
|
+
}
|
1667
|
+
},
|
1668
|
+
fontFamily: {
|
1669
|
+
regexp: notWS,
|
1670
|
+
replace: function ( doc, family ) {
|
1671
|
+
return createElement( doc, 'SPAN', {
|
1672
|
+
'class': 'font',
|
1673
|
+
style: 'font-family:' + family
|
1674
|
+
});
|
1675
|
+
}
|
1676
|
+
},
|
1677
|
+
fontSize: {
|
1678
|
+
regexp: notWS,
|
1679
|
+
replace: function ( doc, size ) {
|
1680
|
+
return createElement( doc, 'SPAN', {
|
1681
|
+
'class': 'size',
|
1682
|
+
style: 'font-size:' + size
|
1683
|
+
});
|
1444
1684
|
}
|
1445
1685
|
}
|
1446
|
-
|
1447
|
-
}
|
1448
|
-
|
1449
|
-
function mergeObjects ( base, extras ) {
|
1450
|
-
var prop, value;
|
1451
|
-
if ( !base ) {
|
1452
|
-
base = {};
|
1453
|
-
}
|
1454
|
-
for ( prop in extras ) {
|
1455
|
-
value = extras[ prop ];
|
1456
|
-
base[ prop ] = ( value && value.constructor === Object ) ?
|
1457
|
-
mergeObjects( base[ prop ], value ) :
|
1458
|
-
value;
|
1459
|
-
}
|
1460
|
-
return base;
|
1461
|
-
}
|
1686
|
+
};
|
1462
1687
|
|
1463
|
-
function
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1688
|
+
var replaceWithTag = function ( tag ) {
|
1689
|
+
return function ( node, parent ) {
|
1690
|
+
var el = createElement( node.ownerDocument, tag );
|
1691
|
+
parent.replaceChild( el, node );
|
1692
|
+
el.appendChild( empty( node ) );
|
1693
|
+
return el;
|
1694
|
+
};
|
1695
|
+
};
|
1467
1696
|
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1697
|
+
var stylesRewriters = {
|
1698
|
+
SPAN: function ( span, parent ) {
|
1699
|
+
var style = span.style,
|
1700
|
+
doc = span.ownerDocument,
|
1701
|
+
attr, converter, css, newTreeBottom, newTreeTop, el;
|
1471
1702
|
|
1472
|
-
|
1703
|
+
for ( attr in spanToSemantic ) {
|
1704
|
+
converter = spanToSemantic[ attr ];
|
1705
|
+
css = style[ attr ];
|
1706
|
+
if ( css && converter.regexp.test( css ) ) {
|
1707
|
+
el = converter.replace( doc, css );
|
1708
|
+
if ( newTreeBottom ) {
|
1709
|
+
newTreeBottom.appendChild( el );
|
1710
|
+
}
|
1711
|
+
newTreeBottom = el;
|
1712
|
+
if ( !newTreeTop ) {
|
1713
|
+
newTreeTop = el;
|
1714
|
+
}
|
1715
|
+
}
|
1716
|
+
}
|
1473
1717
|
|
1474
|
-
|
1475
|
-
|
1718
|
+
if ( newTreeTop ) {
|
1719
|
+
newTreeBottom.appendChild( empty( span ) );
|
1720
|
+
parent.replaceChild( newTreeTop, span );
|
1721
|
+
}
|
1476
1722
|
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1723
|
+
return newTreeBottom || span;
|
1724
|
+
},
|
1725
|
+
STRONG: replaceWithTag( 'B' ),
|
1726
|
+
EM: replaceWithTag( 'I' ),
|
1727
|
+
STRIKE: replaceWithTag( 'S' ),
|
1728
|
+
FONT: function ( node, parent ) {
|
1729
|
+
var face = node.face,
|
1730
|
+
size = node.size,
|
1731
|
+
colour = node.color,
|
1732
|
+
doc = node.ownerDocument,
|
1733
|
+
fontSpan, sizeSpan, colourSpan,
|
1734
|
+
newTreeBottom, newTreeTop;
|
1735
|
+
if ( face ) {
|
1736
|
+
fontSpan = createElement( doc, 'SPAN', {
|
1737
|
+
'class': 'font',
|
1738
|
+
style: 'font-family:' + face
|
1739
|
+
});
|
1740
|
+
newTreeTop = fontSpan;
|
1741
|
+
newTreeBottom = fontSpan;
|
1742
|
+
}
|
1743
|
+
if ( size ) {
|
1744
|
+
sizeSpan = createElement( doc, 'SPAN', {
|
1745
|
+
'class': 'size',
|
1746
|
+
style: 'font-size:' + fontSizes[ size ] + 'px'
|
1747
|
+
});
|
1748
|
+
if ( !newTreeTop ) {
|
1749
|
+
newTreeTop = sizeSpan;
|
1750
|
+
}
|
1751
|
+
if ( newTreeBottom ) {
|
1752
|
+
newTreeBottom.appendChild( sizeSpan );
|
1753
|
+
}
|
1754
|
+
newTreeBottom = sizeSpan;
|
1755
|
+
}
|
1756
|
+
if ( colour && /^#?([\dA-F]{3}){1,2}$/i.test( colour ) ) {
|
1757
|
+
if ( colour.charAt( 0 ) !== '#' ) {
|
1758
|
+
colour = '#' + colour;
|
1759
|
+
}
|
1760
|
+
colourSpan = createElement( doc, 'SPAN', {
|
1761
|
+
'class': 'colour',
|
1762
|
+
style: 'color:' + colour
|
1763
|
+
});
|
1764
|
+
if ( !newTreeTop ) {
|
1765
|
+
newTreeTop = colourSpan;
|
1766
|
+
}
|
1767
|
+
if ( newTreeBottom ) {
|
1768
|
+
newTreeBottom.appendChild( colourSpan );
|
1769
|
+
}
|
1770
|
+
newTreeBottom = colourSpan;
|
1771
|
+
}
|
1772
|
+
if ( !newTreeTop ) {
|
1773
|
+
newTreeTop = newTreeBottom = createElement( doc, 'SPAN' );
|
1774
|
+
}
|
1775
|
+
parent.replaceChild( newTreeTop, node );
|
1776
|
+
newTreeBottom.appendChild( empty( node ) );
|
1777
|
+
return newTreeBottom;
|
1778
|
+
},
|
1779
|
+
TT: function ( node, parent ) {
|
1780
|
+
var el = createElement( node.ownerDocument, 'SPAN', {
|
1781
|
+
'class': 'font',
|
1782
|
+
style: 'font-family:menlo,consolas,"courier new",monospace'
|
1783
|
+
});
|
1784
|
+
parent.replaceChild( el, node );
|
1785
|
+
el.appendChild( empty( node ) );
|
1786
|
+
return el;
|
1481
1787
|
}
|
1788
|
+
};
|
1482
1789
|
|
1483
|
-
|
1790
|
+
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|UL)$/;
|
1484
1791
|
|
1485
|
-
|
1486
|
-
this._lastFocusNode = null;
|
1487
|
-
this._path = '';
|
1792
|
+
var blacklist = /^(?:HEAD|META|STYLE)/;
|
1488
1793
|
|
1489
|
-
|
1490
|
-
|
1794
|
+
var walker = new TreeWalker( null, SHOW_TEXT|SHOW_ELEMENT, function () {
|
1795
|
+
return true;
|
1796
|
+
});
|
1491
1797
|
|
1492
|
-
|
1493
|
-
|
1798
|
+
/*
|
1799
|
+
Two purposes:
|
1494
1800
|
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1801
|
+
1. Remove nodes we don't want, such as weird <o:p> tags, comment nodes
|
1802
|
+
and whitespace nodes.
|
1803
|
+
2. Convert inline tags into our preferred format.
|
1804
|
+
*/
|
1805
|
+
var cleanTree = function cleanTree ( node ) {
|
1806
|
+
var children = node.childNodes,
|
1807
|
+
nonInlineParent, i, l, child, nodeName, nodeType, rewriter, childLength,
|
1808
|
+
startsWithWS, endsWithWS, data, sibling;
|
1500
1809
|
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
childList: true,
|
1505
|
-
attributes: true,
|
1506
|
-
characterData: true,
|
1507
|
-
subtree: true
|
1508
|
-
});
|
1509
|
-
this._mutation = mutation;
|
1510
|
-
} else {
|
1511
|
-
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
1810
|
+
nonInlineParent = node;
|
1811
|
+
while ( isInline( nonInlineParent ) ) {
|
1812
|
+
nonInlineParent = nonInlineParent.parentNode;
|
1512
1813
|
}
|
1814
|
+
walker.root = nonInlineParent;
|
1513
1815
|
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
// from the document and replaced by another, rather than just having its
|
1534
|
-
// data shortened.
|
1535
|
-
// We used to feature test for this, but then found the feature test would
|
1536
|
-
// sometimes pass, but later on the buggy behaviour would still appear.
|
1537
|
-
// I think IE10 does not have the same bug, but it doesn't hurt to replace
|
1538
|
-
// its native fn too and then we don't need yet another UA category.
|
1539
|
-
if ( isIElt11 ) {
|
1540
|
-
win.Text.prototype.splitText = function ( offset ) {
|
1541
|
-
var afterSplit = this.ownerDocument.createTextNode(
|
1542
|
-
this.data.slice( offset ) ),
|
1543
|
-
next = this.nextSibling,
|
1544
|
-
parent = this.parentNode,
|
1545
|
-
toDelete = this.length - offset;
|
1546
|
-
if ( next ) {
|
1547
|
-
parent.insertBefore( afterSplit, next );
|
1548
|
-
} else {
|
1549
|
-
parent.appendChild( afterSplit );
|
1816
|
+
for ( i = 0, l = children.length; i < l; i += 1 ) {
|
1817
|
+
child = children[i];
|
1818
|
+
nodeName = child.nodeName;
|
1819
|
+
nodeType = child.nodeType;
|
1820
|
+
rewriter = stylesRewriters[ nodeName ];
|
1821
|
+
if ( nodeType === ELEMENT_NODE ) {
|
1822
|
+
childLength = child.childNodes.length;
|
1823
|
+
if ( rewriter ) {
|
1824
|
+
child = rewriter( child, node );
|
1825
|
+
} else if ( blacklist.test( nodeName ) ) {
|
1826
|
+
node.removeChild( child );
|
1827
|
+
i -= 1;
|
1828
|
+
l -= 1;
|
1829
|
+
continue;
|
1830
|
+
} else if ( !allowedBlock.test( nodeName ) && !isInline( child ) ) {
|
1831
|
+
i -= 1;
|
1832
|
+
l += childLength - 1;
|
1833
|
+
node.replaceChild( empty( child ), child );
|
1834
|
+
continue;
|
1550
1835
|
}
|
1551
|
-
if (
|
1552
|
-
|
1836
|
+
if ( childLength ) {
|
1837
|
+
cleanTree( child );
|
1553
1838
|
}
|
1554
|
-
|
1555
|
-
|
1839
|
+
} else {
|
1840
|
+
if ( nodeType === TEXT_NODE ) {
|
1841
|
+
data = child.data;
|
1842
|
+
startsWithWS = !notWS.test( data.charAt( 0 ) );
|
1843
|
+
endsWithWS = !notWS.test( data.charAt( data.length - 1 ) );
|
1844
|
+
if ( !startsWithWS && !endsWithWS ) {
|
1845
|
+
continue;
|
1846
|
+
}
|
1847
|
+
// Iterate through the nodes; if we hit some other content
|
1848
|
+
// before the start of a new block we don't trim
|
1849
|
+
if ( startsWithWS ) {
|
1850
|
+
walker.currentNode = child;
|
1851
|
+
while ( sibling = walker.previousPONode() ) {
|
1852
|
+
nodeName = sibling.nodeName;
|
1853
|
+
if ( nodeName === 'IMG' ||
|
1854
|
+
( nodeName === '#text' &&
|
1855
|
+
/\S/.test( sibling.data ) ) ) {
|
1856
|
+
break;
|
1857
|
+
}
|
1858
|
+
if ( !isInline( sibling ) ) {
|
1859
|
+
sibling = null;
|
1860
|
+
break;
|
1861
|
+
}
|
1862
|
+
}
|
1863
|
+
if ( !sibling ) {
|
1864
|
+
data = data.replace( /^\s+/g, '' );
|
1865
|
+
}
|
1866
|
+
}
|
1867
|
+
if ( endsWithWS ) {
|
1868
|
+
walker.currentNode = child;
|
1869
|
+
while ( sibling = walker.nextNode() ) {
|
1870
|
+
if ( nodeName === 'IMG' ||
|
1871
|
+
( nodeName === '#text' &&
|
1872
|
+
/\S/.test( sibling.data ) ) ) {
|
1873
|
+
break;
|
1874
|
+
}
|
1875
|
+
if ( !isInline( sibling ) ) {
|
1876
|
+
sibling = null;
|
1877
|
+
break;
|
1878
|
+
}
|
1879
|
+
}
|
1880
|
+
if ( !sibling ) {
|
1881
|
+
data = data.replace( /^\s+/g, '' );
|
1882
|
+
}
|
1883
|
+
}
|
1884
|
+
if ( data ) {
|
1885
|
+
child.data = data;
|
1886
|
+
continue;
|
1887
|
+
}
|
1888
|
+
}
|
1889
|
+
node.removeChild( child );
|
1890
|
+
i -= 1;
|
1891
|
+
l -= 1;
|
1892
|
+
}
|
1556
1893
|
}
|
1894
|
+
return node;
|
1895
|
+
};
|
1557
1896
|
|
1558
|
-
|
1559
|
-
|
1560
|
-
// Remove Firefox's built-in controls
|
1561
|
-
try {
|
1562
|
-
doc.execCommand( 'enableObjectResizing', false, 'false' );
|
1563
|
-
doc.execCommand( 'enableInlineTableEditing', false, 'false' );
|
1564
|
-
} catch ( error ) {}
|
1565
|
-
|
1566
|
-
instances.push( this );
|
1567
|
-
|
1568
|
-
// Need to register instance before calling setHTML, so that the fixCursor
|
1569
|
-
// function can lookup any default block tag options set.
|
1570
|
-
this.setHTML( '' );
|
1571
|
-
}
|
1572
|
-
|
1573
|
-
var proto = Squire.prototype;
|
1897
|
+
// ---
|
1574
1898
|
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1899
|
+
var removeEmptyInlines = function removeEmptyInlines ( root ) {
|
1900
|
+
var children = root.childNodes,
|
1901
|
+
l = children.length,
|
1902
|
+
child;
|
1903
|
+
while ( l-- ) {
|
1904
|
+
child = children[l];
|
1905
|
+
if ( child.nodeType === ELEMENT_NODE && !isLeaf( child ) ) {
|
1906
|
+
removeEmptyInlines( child );
|
1907
|
+
if ( isInline( child ) && !child.firstChild ) {
|
1908
|
+
root.removeChild( child );
|
1909
|
+
}
|
1910
|
+
} else if ( child.nodeType === TEXT_NODE && !child.data ) {
|
1911
|
+
root.removeChild( child );
|
1584
1912
|
}
|
1585
|
-
}
|
1586
|
-
|
1587
|
-
// Users may specify block tag in lower case
|
1588
|
-
config.blockTag = config.blockTag.toUpperCase();
|
1589
|
-
|
1590
|
-
this._config = config;
|
1591
|
-
|
1592
|
-
return this;
|
1593
|
-
};
|
1594
|
-
|
1595
|
-
proto.createElement = function ( tag, props, children ) {
|
1596
|
-
return createElement( this._doc, tag, props, children );
|
1913
|
+
}
|
1597
1914
|
};
|
1598
1915
|
|
1599
|
-
|
1600
|
-
var config = this._config;
|
1601
|
-
return fixCursor(
|
1602
|
-
this.createElement( config.blockTag, config.blockAttributes, children )
|
1603
|
-
);
|
1604
|
-
};
|
1916
|
+
// ---
|
1605
1917
|
|
1606
|
-
|
1607
|
-
|
1918
|
+
var notWSTextNode = function ( node ) {
|
1919
|
+
return node.nodeType === ELEMENT_NODE ?
|
1920
|
+
node.nodeName === 'BR' :
|
1921
|
+
notWS.test( node.data );
|
1608
1922
|
};
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1923
|
+
var isLineBreak = function ( br ) {
|
1924
|
+
var block = br.parentNode,
|
1925
|
+
walker;
|
1926
|
+
while ( isInline( block ) ) {
|
1927
|
+
block = block.parentNode;
|
1928
|
+
}
|
1929
|
+
walker = new TreeWalker(
|
1930
|
+
block, SHOW_ELEMENT|SHOW_TEXT, notWSTextNode );
|
1931
|
+
walker.currentNode = br;
|
1932
|
+
return !!walker.nextNode();
|
1612
1933
|
};
|
1613
1934
|
|
1614
|
-
//
|
1615
|
-
|
1616
|
-
//
|
1617
|
-
//
|
1618
|
-
//
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
var handlers = this._events[ type ],
|
1626
|
-
i, l, obj;
|
1627
|
-
if ( handlers ) {
|
1628
|
-
if ( !event ) {
|
1629
|
-
event = {};
|
1630
|
-
}
|
1631
|
-
if ( event.type !== type ) {
|
1632
|
-
event.type = type;
|
1633
|
-
}
|
1634
|
-
// Clone handlers array, so any handlers added/removed do not affect it.
|
1635
|
-
handlers = handlers.slice();
|
1636
|
-
for ( i = 0, l = handlers.length; i < l; i += 1 ) {
|
1637
|
-
obj = handlers[i];
|
1638
|
-
try {
|
1639
|
-
if ( obj.handleEvent ) {
|
1640
|
-
obj.handleEvent( event );
|
1641
|
-
} else {
|
1642
|
-
obj.call( this, event );
|
1643
|
-
}
|
1644
|
-
} catch ( error ) {
|
1645
|
-
error.details = 'Squire: fireEvent error. Event type: ' + type;
|
1646
|
-
this.didError( error );
|
1647
|
-
}
|
1648
|
-
}
|
1649
|
-
}
|
1650
|
-
return this;
|
1651
|
-
};
|
1935
|
+
// <br> elements are treated specially, and differently depending on the
|
1936
|
+
// browser, when in rich text editor mode. When adding HTML from external
|
1937
|
+
// sources, we must remove them, replacing the ones that actually affect
|
1938
|
+
// line breaks by wrapping the inline text in a <div>. Browsers that want <br>
|
1939
|
+
// elements at the end of each block will then have them added back in a later
|
1940
|
+
// fixCursor method call.
|
1941
|
+
var cleanupBRs = function ( root ) {
|
1942
|
+
var brs = root.querySelectorAll( 'BR' ),
|
1943
|
+
brBreaksLine = [],
|
1944
|
+
l = brs.length,
|
1945
|
+
i, br, parent;
|
1652
1946
|
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
for ( type in events ) {
|
1661
|
-
if ( !customEvents[ type ] ) {
|
1662
|
-
doc.removeEventListener( type, this, true );
|
1663
|
-
}
|
1664
|
-
}
|
1665
|
-
if ( this._mutation ) {
|
1666
|
-
this._mutation.disconnect();
|
1947
|
+
// Must calculate whether the <br> breaks a line first, because if we
|
1948
|
+
// have two <br>s next to each other, after the first one is converted
|
1949
|
+
// to a block split, the second will be at the end of a block and
|
1950
|
+
// therefore seem to not be a line break. But in its original context it
|
1951
|
+
// was, so we should also convert it to a block split.
|
1952
|
+
for ( i = 0; i < l; i += 1 ) {
|
1953
|
+
brBreaksLine[i] = isLineBreak( brs[i] );
|
1667
1954
|
}
|
1668
|
-
var l = instances.length;
|
1669
1955
|
while ( l-- ) {
|
1670
|
-
|
1671
|
-
|
1956
|
+
br = brs[l];
|
1957
|
+
// Cleanup may have removed it
|
1958
|
+
parent = br.parentNode;
|
1959
|
+
if ( !parent ) { continue; }
|
1960
|
+
// If it doesn't break a line, just remove it; it's not doing
|
1961
|
+
// anything useful. We'll add it back later if required by the
|
1962
|
+
// browser. If it breaks a line, wrap the content in div tags
|
1963
|
+
// and replace the brs.
|
1964
|
+
if ( !brBreaksLine[l] ) {
|
1965
|
+
detach( br );
|
1966
|
+
} else if ( !isInline( parent ) ) {
|
1967
|
+
fixContainer( parent );
|
1672
1968
|
}
|
1673
1969
|
}
|
1674
1970
|
};
|
1675
1971
|
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1689
|
-
if ( !handlers ) {
|
1690
|
-
handlers = this._events[ type ] = [];
|
1691
|
-
if ( !customEvents[ type ] ) {
|
1692
|
-
this._doc.addEventListener( type, this, true );
|
1972
|
+
var onCut = function () {
|
1973
|
+
// Save undo checkpoint
|
1974
|
+
var range = this.getSelection();
|
1975
|
+
var self = this;
|
1976
|
+
this._recordUndoState( range );
|
1977
|
+
this._getRangeAndRemoveBookmark( range );
|
1978
|
+
this.setSelection( range );
|
1979
|
+
setTimeout( function () {
|
1980
|
+
try {
|
1981
|
+
// If all content removed, ensure div at start of body.
|
1982
|
+
self._ensureBottomLine();
|
1983
|
+
} catch ( error ) {
|
1984
|
+
self.didError( error );
|
1693
1985
|
}
|
1694
|
-
}
|
1695
|
-
handlers.push( fn );
|
1696
|
-
return this;
|
1986
|
+
}, 0 );
|
1697
1987
|
};
|
1698
1988
|
|
1699
|
-
|
1700
|
-
var
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1989
|
+
var onPaste = function ( event ) {
|
1990
|
+
var clipboardData = event.clipboardData,
|
1991
|
+
items = clipboardData && clipboardData.items,
|
1992
|
+
fireDrop = false,
|
1993
|
+
hasImage = false,
|
1994
|
+
plainItem = null,
|
1995
|
+
self = this,
|
1996
|
+
l, item, type, data;
|
1997
|
+
|
1998
|
+
// Current HTML5 Clipboard interface
|
1999
|
+
// ---------------------------------
|
2000
|
+
// https://html.spec.whatwg.org/multipage/interaction.html
|
2001
|
+
|
2002
|
+
if ( items ) {
|
2003
|
+
event.preventDefault();
|
2004
|
+
l = items.length;
|
1704
2005
|
while ( l-- ) {
|
1705
|
-
|
1706
|
-
|
2006
|
+
item = items[l];
|
2007
|
+
type = item.type;
|
2008
|
+
if ( type === 'text/html' ) {
|
2009
|
+
/*jshint loopfunc: true */
|
2010
|
+
item.getAsString( function ( html ) {
|
2011
|
+
self.insertHTML( html, true );
|
2012
|
+
});
|
2013
|
+
/*jshint loopfunc: false */
|
2014
|
+
return;
|
2015
|
+
}
|
2016
|
+
if ( type === 'text/plain' ) {
|
2017
|
+
plainItem = item;
|
2018
|
+
}
|
2019
|
+
if ( /^image\/.*/.test( type ) ) {
|
2020
|
+
hasImage = true;
|
1707
2021
|
}
|
1708
2022
|
}
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
2023
|
+
// Treat image paste as a drop of an image file.
|
2024
|
+
if ( hasImage ) {
|
2025
|
+
this.fireEvent( 'dragover', {
|
2026
|
+
dataTransfer: clipboardData,
|
2027
|
+
/*jshint loopfunc: true */
|
2028
|
+
preventDefault: function () {
|
2029
|
+
fireDrop = true;
|
2030
|
+
}
|
2031
|
+
/*jshint loopfunc: false */
|
2032
|
+
});
|
2033
|
+
if ( fireDrop ) {
|
2034
|
+
this.fireEvent( 'drop', {
|
2035
|
+
dataTransfer: clipboardData
|
2036
|
+
});
|
1713
2037
|
}
|
2038
|
+
} else if ( plainItem ) {
|
2039
|
+
item.getAsString( function ( text ) {
|
2040
|
+
self.insertPlainText( text, true );
|
2041
|
+
});
|
1714
2042
|
}
|
2043
|
+
return;
|
1715
2044
|
}
|
1716
|
-
return this;
|
1717
|
-
};
|
1718
|
-
|
1719
|
-
// --- Selection and Path ---
|
1720
2045
|
|
1721
|
-
|
1722
|
-
|
1723
|
-
if ( range instanceof this._win.Range ) {
|
1724
|
-
return range.cloneRange();
|
1725
|
-
}
|
1726
|
-
var domRange = this._doc.createRange();
|
1727
|
-
domRange.setStart( range, startOffset );
|
1728
|
-
if ( endContainer ) {
|
1729
|
-
domRange.setEnd( endContainer, endOffset );
|
1730
|
-
} else {
|
1731
|
-
domRange.setEnd( range, startOffset );
|
1732
|
-
}
|
1733
|
-
return domRange;
|
1734
|
-
};
|
2046
|
+
// Old interface
|
2047
|
+
// -------------
|
1735
2048
|
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
2049
|
+
// Safari (and indeed many other OS X apps) copies stuff as text/rtf
|
2050
|
+
// rather than text/html; even from a webpage in Safari. The only way
|
2051
|
+
// to get an HTML version is to fallback to letting the browser insert
|
2052
|
+
// the content. Same for getting image data. *Sigh*.
|
2053
|
+
if ( clipboardData && (
|
2054
|
+
indexOf.call( clipboardData.types, 'text/html' ) > -1 || (
|
2055
|
+
indexOf.call( clipboardData.types, 'text/plain' ) > -1 &&
|
2056
|
+
indexOf.call( clipboardData.types, 'text/rtf' ) < 0 ) ) ) {
|
2057
|
+
event.preventDefault();
|
2058
|
+
// Abiword on Linux copies a plain text and html version, but the HTML
|
2059
|
+
// version is the empty string! So always try to get HTML, but if none,
|
2060
|
+
// insert plain text instead.
|
2061
|
+
if (( data = clipboardData.getData( 'text/html' ) )) {
|
2062
|
+
this.insertHTML( data, true );
|
2063
|
+
} else if (( data = clipboardData.getData( 'text/plain' ) )) {
|
2064
|
+
this.insertPlainText( data, true );
|
1744
2065
|
}
|
1745
|
-
|
1746
|
-
sel.removeAllRanges();
|
1747
|
-
sel.addRange( range );
|
2066
|
+
return;
|
1748
2067
|
}
|
1749
|
-
return this;
|
1750
|
-
};
|
1751
2068
|
|
1752
|
-
|
1753
|
-
|
1754
|
-
selection, startContainer, endContainer;
|
1755
|
-
if ( sel.rangeCount ) {
|
1756
|
-
selection = sel.getRangeAt( 0 ).cloneRange();
|
1757
|
-
startContainer = selection.startContainer;
|
1758
|
-
endContainer = selection.endContainer;
|
1759
|
-
// FF can return the selection as being inside an <img>. WTF?
|
1760
|
-
if ( startContainer && isLeaf( startContainer ) ) {
|
1761
|
-
selection.setStartBefore( startContainer );
|
1762
|
-
}
|
1763
|
-
if ( endContainer && isLeaf( endContainer ) ) {
|
1764
|
-
selection.setEndBefore( endContainer );
|
1765
|
-
}
|
1766
|
-
this._lastSelection = selection;
|
1767
|
-
} else {
|
1768
|
-
selection = this._lastSelection;
|
1769
|
-
}
|
1770
|
-
if ( !selection ) {
|
1771
|
-
selection = this._createRange( this._body.firstChild, 0 );
|
1772
|
-
}
|
1773
|
-
return selection;
|
1774
|
-
};
|
2069
|
+
// No interface :(
|
2070
|
+
// ---------------
|
1775
2071
|
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
SHOW_TEXT|SHOW_ELEMENT,
|
1781
|
-
function ( node ) {
|
1782
|
-
return isNodeContainedInRange( range, node, true );
|
1783
|
-
}
|
1784
|
-
),
|
2072
|
+
this._awaitingPaste = true;
|
2073
|
+
|
2074
|
+
var body = this._body,
|
2075
|
+
range = this.getSelection(),
|
1785
2076
|
startContainer = range.startContainer,
|
2077
|
+
startOffset = range.startOffset,
|
1786
2078
|
endContainer = range.endContainer,
|
1787
|
-
|
1788
|
-
|
1789
|
-
addedTextInBlock = false,
|
1790
|
-
value;
|
2079
|
+
endOffset = range.endOffset,
|
2080
|
+
startBlock = getStartBlockOfRange( range );
|
1791
2081
|
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
2082
|
+
// We need to position the pasteArea in the visible portion of the screen
|
2083
|
+
// to stop the browser auto-scrolling.
|
2084
|
+
var pasteArea = this.createElement( 'DIV', {
|
2085
|
+
style: 'position: absolute; overflow: hidden; top:' +
|
2086
|
+
( body.scrollTop +
|
2087
|
+
( startBlock ? startBlock.getBoundingClientRect().top : 0 ) ) +
|
2088
|
+
'px; right: 150%; width: 1px; height: 1px;'
|
2089
|
+
});
|
2090
|
+
body.appendChild( pasteArea );
|
2091
|
+
range.selectNodeContents( pasteArea );
|
2092
|
+
this.setSelection( range );
|
1795
2093
|
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
2094
|
+
// A setTimeout of 0 means this is added to the back of the
|
2095
|
+
// single javascript thread, so it will be executed after the
|
2096
|
+
// paste event.
|
2097
|
+
setTimeout( function () {
|
2098
|
+
try {
|
2099
|
+
// IE sometimes fires the beforepaste event twice; make sure it is
|
2100
|
+
// not run again before our after paste function is called.
|
2101
|
+
self._awaitingPaste = false;
|
2102
|
+
|
2103
|
+
// Get the pasted content and clean
|
2104
|
+
var html = '',
|
2105
|
+
next = pasteArea,
|
2106
|
+
first, range;
|
2107
|
+
|
2108
|
+
// #88: Chrome can apparently split the paste area if certain
|
2109
|
+
// content is inserted; gather them all up.
|
2110
|
+
while ( pasteArea = next ) {
|
2111
|
+
next = pasteArea.nextSibling;
|
2112
|
+
detach( pasteArea );
|
2113
|
+
// Safari and IE like putting extra divs around things.
|
2114
|
+
first = pasteArea.firstChild;
|
2115
|
+
if ( first && first === pasteArea.lastChild &&
|
2116
|
+
first.nodeName === 'DIV' ) {
|
2117
|
+
pasteArea = first;
|
1805
2118
|
}
|
1806
|
-
|
1807
|
-
addedTextInBlock = true;
|
2119
|
+
html += pasteArea.innerHTML;
|
1808
2120
|
}
|
1809
|
-
} else if ( node.nodeName === 'BR' ||
|
1810
|
-
addedTextInBlock && !isInline( node ) ) {
|
1811
|
-
textContent += '\n';
|
1812
|
-
addedTextInBlock = false;
|
1813
|
-
}
|
1814
|
-
node = walker.nextNode();
|
1815
|
-
}
|
1816
2121
|
|
1817
|
-
|
1818
|
-
|
2122
|
+
range = self._createRange(
|
2123
|
+
startContainer, startOffset, endContainer, endOffset );
|
2124
|
+
self.setSelection( range );
|
1819
2125
|
|
1820
|
-
|
1821
|
-
|
2126
|
+
if ( html ) {
|
2127
|
+
self.insertHTML( html, true );
|
2128
|
+
}
|
2129
|
+
} catch ( error ) {
|
2130
|
+
self.didError( error );
|
2131
|
+
}
|
2132
|
+
}, 0 );
|
1822
2133
|
};
|
1823
2134
|
|
1824
|
-
|
1825
|
-
|
1826
|
-
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
2135
|
+
var instances = [];
|
1827
2136
|
|
1828
|
-
|
1829
|
-
var
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
if ( node.length === 1 ) {
|
1836
|
-
do {
|
1837
|
-
parent = node.parentNode;
|
1838
|
-
parent.removeChild( node );
|
1839
|
-
node = parent;
|
1840
|
-
} while ( isInline( node ) && !getLength( node ) );
|
1841
|
-
break;
|
1842
|
-
} else {
|
1843
|
-
node.deleteData( index, 1 );
|
1844
|
-
}
|
2137
|
+
function getSquireInstance ( doc ) {
|
2138
|
+
var l = instances.length,
|
2139
|
+
instance;
|
2140
|
+
while ( l-- ) {
|
2141
|
+
instance = instances[l];
|
2142
|
+
if ( instance._doc === doc ) {
|
2143
|
+
return instance;
|
1845
2144
|
}
|
1846
2145
|
}
|
1847
|
-
|
2146
|
+
return null;
|
2147
|
+
}
|
1848
2148
|
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1853
|
-
if ( !this._hasZWS ) {
|
1854
|
-
return;
|
2149
|
+
function mergeObjects ( base, extras ) {
|
2150
|
+
var prop, value;
|
2151
|
+
if ( !base ) {
|
2152
|
+
base = {};
|
1855
2153
|
}
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
2154
|
+
for ( prop in extras ) {
|
2155
|
+
value = extras[ prop ];
|
2156
|
+
base[ prop ] = ( value && value.constructor === Object ) ?
|
2157
|
+
mergeObjects( base[ prop ], value ) :
|
2158
|
+
value;
|
2159
|
+
}
|
2160
|
+
return base;
|
2161
|
+
}
|
1859
2162
|
|
1860
|
-
|
2163
|
+
function Squire ( doc, config ) {
|
2164
|
+
var win = doc.defaultView;
|
2165
|
+
var body = doc.body;
|
2166
|
+
var mutation;
|
1861
2167
|
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1865
|
-
newPath;
|
1866
|
-
if ( force || anchor !== this._lastAnchorNode ||
|
1867
|
-
focus !== this._lastFocusNode ) {
|
1868
|
-
this._lastAnchorNode = anchor;
|
1869
|
-
this._lastFocusNode = focus;
|
1870
|
-
newPath = ( anchor && focus ) ? ( anchor === focus ) ?
|
1871
|
-
getPath( focus ) : '(selection)' : '';
|
1872
|
-
if ( this._path !== newPath ) {
|
1873
|
-
this._path = newPath;
|
1874
|
-
this.fireEvent( 'pathChange', { path: newPath } );
|
1875
|
-
}
|
1876
|
-
}
|
1877
|
-
if ( !range.collapsed ) {
|
1878
|
-
this.fireEvent( 'select' );
|
1879
|
-
}
|
1880
|
-
};
|
2168
|
+
this._win = win;
|
2169
|
+
this._doc = doc;
|
2170
|
+
this._body = body;
|
1881
2171
|
|
1882
|
-
|
1883
|
-
this._updatePath( this.getSelection() );
|
1884
|
-
};
|
2172
|
+
this._events = {};
|
1885
2173
|
|
1886
|
-
|
2174
|
+
this._lastSelection = null;
|
1887
2175
|
|
1888
|
-
|
1889
|
-
//
|
1890
|
-
|
1891
|
-
|
1892
|
-
// Opera (Presto-variant) however will lose the selection if you call this!
|
1893
|
-
if ( !isPresto ) {
|
1894
|
-
this._body.focus();
|
2176
|
+
// IE loses selection state of iframe on blur, so make sure we
|
2177
|
+
// cache it just before it loses focus.
|
2178
|
+
if ( losesSelectionOnBlur ) {
|
2179
|
+
this.addEventListener( 'beforedeactivate', this.getSelection );
|
1895
2180
|
}
|
1896
|
-
this._win.focus();
|
1897
|
-
return this;
|
1898
|
-
};
|
1899
2181
|
|
1900
|
-
|
1901
|
-
// IE will remove the whole browser window from focus if you call
|
1902
|
-
// win.blur() or body.blur(), so instead we call top.focus() to focus
|
1903
|
-
// the top frame, thus blurring this frame. This works in everything
|
1904
|
-
// except FF, so we need to call body.blur() in that as well.
|
1905
|
-
if ( isGecko ) {
|
1906
|
-
this._body.blur();
|
1907
|
-
}
|
1908
|
-
top.focus();
|
1909
|
-
return this;
|
1910
|
-
};
|
2182
|
+
this._hasZWS = false;
|
1911
2183
|
|
1912
|
-
|
2184
|
+
this._lastAnchorNode = null;
|
2185
|
+
this._lastFocusNode = null;
|
2186
|
+
this._path = '';
|
1913
2187
|
|
1914
|
-
|
1915
|
-
|
2188
|
+
this.addEventListener( 'keyup', this._updatePathOnEvent );
|
2189
|
+
this.addEventListener( 'mouseup', this._updatePathOnEvent );
|
1916
2190
|
|
1917
|
-
|
1918
|
-
|
1919
|
-
id: startSelectionId,
|
1920
|
-
type: 'hidden'
|
1921
|
-
}),
|
1922
|
-
endNode = this.createElement( 'INPUT', {
|
1923
|
-
id: endSelectionId,
|
1924
|
-
type: 'hidden'
|
1925
|
-
}),
|
1926
|
-
temp;
|
2191
|
+
win.addEventListener( 'focus', this, false );
|
2192
|
+
win.addEventListener( 'blur', this, false );
|
1927
2193
|
|
1928
|
-
|
1929
|
-
|
1930
|
-
|
2194
|
+
this._undoIndex = -1;
|
2195
|
+
this._undoStack = [];
|
2196
|
+
this._undoStackLength = 0;
|
2197
|
+
this._isInUndoState = false;
|
2198
|
+
this._ignoreChange = false;
|
1931
2199
|
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
1938
|
-
|
1939
|
-
|
2200
|
+
if ( canObserveMutations ) {
|
2201
|
+
mutation = new MutationObserver( this._docWasChanged.bind( this ) );
|
2202
|
+
mutation.observe( body, {
|
2203
|
+
childList: true,
|
2204
|
+
attributes: true,
|
2205
|
+
characterData: true,
|
2206
|
+
subtree: true
|
2207
|
+
});
|
2208
|
+
this._mutation = mutation;
|
2209
|
+
} else {
|
2210
|
+
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
1940
2211
|
}
|
1941
2212
|
|
1942
|
-
|
1943
|
-
|
1944
|
-
|
2213
|
+
// IE sometimes fires the beforepaste event twice; make sure it is not run
|
2214
|
+
// again before our after paste function is called.
|
2215
|
+
this._awaitingPaste = false;
|
2216
|
+
this.addEventListener( isIElt11 ? 'beforecut' : 'cut', onCut );
|
2217
|
+
this.addEventListener( isIElt11 ? 'beforepaste' : 'paste', onPaste );
|
1945
2218
|
|
1946
|
-
|
1947
|
-
|
1948
|
-
start = doc.getElementById( startSelectionId ),
|
1949
|
-
end = doc.getElementById( endSelectionId );
|
2219
|
+
// Opera does not fire keydown repeatedly.
|
2220
|
+
this.addEventListener( isPresto ? 'keypress' : 'keydown', onKey );
|
1950
2221
|
|
1951
|
-
|
1952
|
-
|
1953
|
-
endContainer = end.parentNode,
|
1954
|
-
collapsed;
|
2222
|
+
// Add key handlers
|
2223
|
+
this._keyHandlers = Object.create( keyHandlers );
|
1955
2224
|
|
1956
|
-
|
1957
|
-
|
1958
|
-
endContainer: endContainer,
|
1959
|
-
startOffset: indexOf.call( startContainer.childNodes, start ),
|
1960
|
-
endOffset: indexOf.call( endContainer.childNodes, end )
|
1961
|
-
};
|
2225
|
+
// Override default properties
|
2226
|
+
this.setConfig( config );
|
1962
2227
|
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
2228
|
+
// Fix IE<10's buggy implementation of Text#splitText.
|
2229
|
+
// If the split is at the end of the node, it doesn't insert the newly split
|
2230
|
+
// node into the document, and sets its value to undefined rather than ''.
|
2231
|
+
// And even if the split is not at the end, the original node is removed
|
2232
|
+
// from the document and replaced by another, rather than just having its
|
2233
|
+
// data shortened.
|
2234
|
+
// We used to feature test for this, but then found the feature test would
|
2235
|
+
// sometimes pass, but later on the buggy behaviour would still appear.
|
2236
|
+
// I think IE10 does not have the same bug, but it doesn't hurt to replace
|
2237
|
+
// its native fn too and then we don't need yet another UA category.
|
2238
|
+
if ( isIElt11 ) {
|
2239
|
+
win.Text.prototype.splitText = function ( offset ) {
|
2240
|
+
var afterSplit = this.ownerDocument.createTextNode(
|
2241
|
+
this.data.slice( offset ) ),
|
2242
|
+
next = this.nextSibling,
|
2243
|
+
parent = this.parentNode,
|
2244
|
+
toDelete = this.length - offset;
|
2245
|
+
if ( next ) {
|
2246
|
+
parent.insertBefore( afterSplit, next );
|
2247
|
+
} else {
|
2248
|
+
parent.appendChild( afterSplit );
|
2249
|
+
}
|
2250
|
+
if ( toDelete ) {
|
2251
|
+
this.deleteData( offset, toDelete );
|
2252
|
+
}
|
2253
|
+
return afterSplit;
|
2254
|
+
};
|
2255
|
+
}
|
1966
2256
|
|
1967
|
-
|
1968
|
-
detach( end );
|
2257
|
+
body.setAttribute( 'contenteditable', 'true' );
|
1969
2258
|
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
2259
|
+
// Remove Firefox's built-in controls
|
2260
|
+
try {
|
2261
|
+
doc.execCommand( 'enableObjectResizing', false, 'false' );
|
2262
|
+
doc.execCommand( 'enableInlineTableEditing', false, 'false' );
|
2263
|
+
} catch ( error ) {}
|
1975
2264
|
|
1976
|
-
|
1977
|
-
range = doc.createRange();
|
1978
|
-
}
|
1979
|
-
range.setStart( _range.startContainer, _range.startOffset );
|
1980
|
-
range.setEnd( _range.endContainer, _range.endOffset );
|
1981
|
-
collapsed = range.collapsed;
|
2265
|
+
instances.push( this );
|
1982
2266
|
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
}
|
1988
|
-
return range || null;
|
1989
|
-
};
|
2267
|
+
// Need to register instance before calling setHTML, so that the fixCursor
|
2268
|
+
// function can lookup any default block tag options set.
|
2269
|
+
this.setHTML( '' );
|
2270
|
+
}
|
1990
2271
|
|
1991
|
-
|
2272
|
+
var proto = Squire.prototype;
|
1992
2273
|
|
1993
|
-
proto.
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
}
|
2274
|
+
proto.setConfig = function ( config ) {
|
2275
|
+
config = mergeObjects({
|
2276
|
+
blockTag: 'DIV',
|
2277
|
+
blockAttributes: null,
|
2278
|
+
tagAttributes: {
|
2279
|
+
blockquote: null,
|
2280
|
+
ul: null,
|
2281
|
+
ol: null,
|
2282
|
+
li: null
|
2283
|
+
}
|
2284
|
+
}, config );
|
2285
|
+
|
2286
|
+
// Users may specify block tag in lower case
|
2287
|
+
config.blockTag = config.blockTag.toUpperCase();
|
2288
|
+
|
2289
|
+
this._config = config;
|
2290
|
+
|
2291
|
+
return this;
|
2004
2292
|
};
|
2005
2293
|
|
2006
|
-
proto.
|
2007
|
-
|
2008
|
-
this._ignoreChange = false;
|
2009
|
-
return;
|
2010
|
-
}
|
2011
|
-
if ( this._isInUndoState ) {
|
2012
|
-
this._isInUndoState = false;
|
2013
|
-
this.fireEvent( 'undoStateChange', {
|
2014
|
-
canUndo: true,
|
2015
|
-
canRedo: false
|
2016
|
-
});
|
2017
|
-
}
|
2018
|
-
this.fireEvent( 'input' );
|
2294
|
+
proto.createElement = function ( tag, props, children ) {
|
2295
|
+
return createElement( this._doc, tag, props, children );
|
2019
2296
|
};
|
2020
2297
|
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
undoStack = this._undoStack;
|
2298
|
+
proto.createDefaultBlock = function ( children ) {
|
2299
|
+
var config = this._config;
|
2300
|
+
return fixCursor(
|
2301
|
+
this.createElement( config.blockTag, config.blockAttributes, children )
|
2302
|
+
);
|
2303
|
+
};
|
2028
2304
|
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2305
|
+
proto.didError = function ( error ) {
|
2306
|
+
console.log( error );
|
2307
|
+
};
|
2308
|
+
|
2309
|
+
proto.getDocument = function () {
|
2310
|
+
return this._doc;
|
2311
|
+
};
|
2312
|
+
|
2313
|
+
// --- Events ---
|
2314
|
+
|
2315
|
+
// Subscribing to these events won't automatically add a listener to the
|
2316
|
+
// document node, since these events are fired in a custom manner by the
|
2317
|
+
// editor code.
|
2318
|
+
var customEvents = {
|
2319
|
+
focus: 1, blur: 1,
|
2320
|
+
pathChange: 1, select: 1, input: 1, undoStateChange: 1
|
2321
|
+
};
|
2322
|
+
|
2323
|
+
proto.fireEvent = function ( type, event ) {
|
2324
|
+
var handlers = this._events[ type ],
|
2325
|
+
l, obj;
|
2326
|
+
if ( handlers ) {
|
2327
|
+
if ( !event ) {
|
2328
|
+
event = {};
|
2329
|
+
}
|
2330
|
+
if ( event.type !== type ) {
|
2331
|
+
event.type = type;
|
2332
|
+
}
|
2333
|
+
// Clone handlers array, so any handlers added/removed do not affect it.
|
2334
|
+
handlers = handlers.slice();
|
2335
|
+
l = handlers.length;
|
2336
|
+
while ( l-- ) {
|
2337
|
+
obj = handlers[l];
|
2338
|
+
try {
|
2339
|
+
if ( obj.handleEvent ) {
|
2340
|
+
obj.handleEvent( event );
|
2341
|
+
} else {
|
2342
|
+
obj.call( this, event );
|
2343
|
+
}
|
2344
|
+
} catch ( error ) {
|
2345
|
+
error.details = 'Squire: fireEvent error. Event type: ' + type;
|
2346
|
+
this.didError( error );
|
2347
|
+
}
|
2032
2348
|
}
|
2349
|
+
}
|
2350
|
+
return this;
|
2351
|
+
};
|
2033
2352
|
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2353
|
+
proto.destroy = function () {
|
2354
|
+
var win = this._win,
|
2355
|
+
doc = this._doc,
|
2356
|
+
events = this._events,
|
2357
|
+
type;
|
2358
|
+
win.removeEventListener( 'focus', this, false );
|
2359
|
+
win.removeEventListener( 'blur', this, false );
|
2360
|
+
for ( type in events ) {
|
2361
|
+
if ( !customEvents[ type ] ) {
|
2362
|
+
doc.removeEventListener( type, this, true );
|
2363
|
+
}
|
2364
|
+
}
|
2365
|
+
if ( this._mutation ) {
|
2366
|
+
this._mutation.disconnect();
|
2367
|
+
}
|
2368
|
+
var l = instances.length;
|
2369
|
+
while ( l-- ) {
|
2370
|
+
if ( instances[l] === this ) {
|
2371
|
+
instances.splice( l, 1 );
|
2037
2372
|
}
|
2038
|
-
undoStack[ undoIndex ] = this._getHTML();
|
2039
|
-
this._undoStackLength += 1;
|
2040
|
-
this._isInUndoState = true;
|
2041
2373
|
}
|
2042
2374
|
};
|
2043
2375
|
|
2044
|
-
proto.
|
2045
|
-
|
2046
|
-
|
2047
|
-
// Make sure any changes since last checkpoint are saved.
|
2048
|
-
this._recordUndoState( this.getSelection() );
|
2376
|
+
proto.handleEvent = function ( event ) {
|
2377
|
+
this.fireEvent( event.type, event );
|
2378
|
+
};
|
2049
2379
|
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
this._isInUndoState = true;
|
2057
|
-
this.fireEvent( 'undoStateChange', {
|
2058
|
-
canUndo: this._undoIndex !== 0,
|
2059
|
-
canRedo: true
|
2380
|
+
proto.addEventListener = function ( type, fn ) {
|
2381
|
+
var handlers = this._events[ type ];
|
2382
|
+
if ( !fn ) {
|
2383
|
+
this.didError({
|
2384
|
+
name: 'Squire: addEventListener with null or undefined fn',
|
2385
|
+
message: 'Event type: ' + type
|
2060
2386
|
});
|
2061
|
-
this
|
2387
|
+
return this;
|
2062
2388
|
}
|
2389
|
+
if ( !handlers ) {
|
2390
|
+
handlers = this._events[ type ] = [];
|
2391
|
+
if ( !customEvents[ type ] ) {
|
2392
|
+
this._doc.addEventListener( type, this, true );
|
2393
|
+
}
|
2394
|
+
}
|
2395
|
+
handlers.push( fn );
|
2063
2396
|
return this;
|
2064
2397
|
};
|
2065
2398
|
|
2066
|
-
proto.
|
2067
|
-
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2399
|
+
proto.removeEventListener = function ( type, fn ) {
|
2400
|
+
var handlers = this._events[ type ],
|
2401
|
+
l;
|
2402
|
+
if ( handlers ) {
|
2403
|
+
l = handlers.length;
|
2404
|
+
while ( l-- ) {
|
2405
|
+
if ( handlers[l] === fn ) {
|
2406
|
+
handlers.splice( l, 1 );
|
2407
|
+
}
|
2408
|
+
}
|
2409
|
+
if ( !handlers.length ) {
|
2410
|
+
delete this._events[ type ];
|
2411
|
+
if ( !customEvents[ type ] ) {
|
2412
|
+
this._doc.removeEventListener( type, this, false );
|
2413
|
+
}
|
2077
2414
|
}
|
2078
|
-
this.fireEvent( 'undoStateChange', {
|
2079
|
-
canUndo: true,
|
2080
|
-
canRedo: undoIndex + 2 < undoStackLength
|
2081
|
-
});
|
2082
|
-
this.fireEvent( 'input' );
|
2083
2415
|
}
|
2084
2416
|
return this;
|
2085
2417
|
};
|
2086
2418
|
|
2087
|
-
// ---
|
2088
|
-
|
2089
|
-
// Looks for matching tag and attributes, so won't work
|
2090
|
-
// if <strong> instead of <b> etc.
|
2091
|
-
proto.hasFormat = function ( tag, attributes, range ) {
|
2092
|
-
// 1. Normalise the arguments and get selection
|
2093
|
-
tag = tag.toUpperCase();
|
2094
|
-
if ( !attributes ) { attributes = {}; }
|
2095
|
-
if ( !range && !( range = this.getSelection() ) ) {
|
2096
|
-
return false;
|
2097
|
-
}
|
2419
|
+
// --- Selection and Path ---
|
2098
2420
|
|
2099
|
-
|
2100
|
-
|
2101
|
-
|
2102
|
-
|
2103
|
-
if ( getNearest( root, tag, attributes ) ) {
|
2104
|
-
return true;
|
2421
|
+
proto._createRange =
|
2422
|
+
function ( range, startOffset, endContainer, endOffset ) {
|
2423
|
+
if ( range instanceof this._win.Range ) {
|
2424
|
+
return range.cloneRange();
|
2105
2425
|
}
|
2106
|
-
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2426
|
+
var domRange = this._doc.createRange();
|
2427
|
+
domRange.setStart( range, startOffset );
|
2428
|
+
if ( endContainer ) {
|
2429
|
+
domRange.setEnd( endContainer, endOffset );
|
2430
|
+
} else {
|
2431
|
+
domRange.setEnd( range, startOffset );
|
2111
2432
|
}
|
2433
|
+
return domRange;
|
2434
|
+
};
|
2112
2435
|
|
2113
|
-
|
2114
|
-
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2436
|
+
proto._moveCursorTo = function ( toStart ) {
|
2437
|
+
var body = this._body,
|
2438
|
+
range = this._createRange( body, toStart ? 0 : body.childNodes.length );
|
2439
|
+
moveRangeBoundariesDownTree( range );
|
2440
|
+
this.setSelection( range );
|
2441
|
+
return this;
|
2442
|
+
};
|
2443
|
+
proto.moveCursorToStart = function () {
|
2444
|
+
return this._moveCursorTo( true );
|
2445
|
+
};
|
2446
|
+
proto.moveCursorToEnd = function () {
|
2447
|
+
return this._moveCursorTo( false );
|
2448
|
+
};
|
2118
2449
|
|
2119
|
-
|
2120
|
-
|
2121
|
-
if
|
2122
|
-
|
2450
|
+
proto.setSelection = function ( range ) {
|
2451
|
+
if ( range ) {
|
2452
|
+
// iOS bug: if you don't focus the iframe before setting the
|
2453
|
+
// selection, you can end up in a state where you type but the input
|
2454
|
+
// doesn't get directed into the contenteditable area but is instead
|
2455
|
+
// lost in a black hole. Very strange.
|
2456
|
+
if ( isIOS ) {
|
2457
|
+
this._win.focus();
|
2458
|
+
}
|
2459
|
+
var sel = this._getWindowSelection();
|
2460
|
+
if ( sel ) {
|
2461
|
+
sel.removeAllRanges();
|
2462
|
+
sel.addRange( range );
|
2123
2463
|
}
|
2124
|
-
seenNode = true;
|
2125
2464
|
}
|
2465
|
+
return this;
|
2466
|
+
};
|
2126
2467
|
|
2127
|
-
|
2468
|
+
proto._getWindowSelection = function () {
|
2469
|
+
return this._win.getSelection() || null;
|
2128
2470
|
};
|
2129
2471
|
|
2130
|
-
proto.
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2472
|
+
proto.getSelection = function () {
|
2473
|
+
var sel = this._getWindowSelection(),
|
2474
|
+
selection, startContainer, endContainer;
|
2475
|
+
if ( sel && sel.rangeCount ) {
|
2476
|
+
selection = sel.getRangeAt( 0 ).cloneRange();
|
2477
|
+
startContainer = selection.startContainer;
|
2478
|
+
endContainer = selection.endContainer;
|
2479
|
+
// FF can return the selection as being inside an <img>. WTF?
|
2480
|
+
if ( startContainer && isLeaf( startContainer ) ) {
|
2481
|
+
selection.setStartBefore( startContainer );
|
2482
|
+
}
|
2483
|
+
if ( endContainer && isLeaf( endContainer ) ) {
|
2484
|
+
selection.setEndBefore( endContainer );
|
2485
|
+
}
|
2486
|
+
this._lastSelection = selection;
|
2487
|
+
} else {
|
2488
|
+
selection = this._lastSelection;
|
2141
2489
|
}
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
// In Blink/WebKit, empty blocks may have no text nodes, just a <br>.
|
2151
|
-
// Therefore we wrap this in the tag as well, as this will then cause it
|
2152
|
-
// to apply when the user types something in the block, which is
|
2153
|
-
// presumably what was intended.
|
2490
|
+
if ( !selection ) {
|
2491
|
+
selection = this._createRange( this._body.firstChild, 0 );
|
2492
|
+
}
|
2493
|
+
return selection;
|
2494
|
+
};
|
2495
|
+
|
2496
|
+
proto.getSelectedText = function () {
|
2497
|
+
var range = this.getSelection(),
|
2154
2498
|
walker = new TreeWalker(
|
2155
2499
|
range.commonAncestorContainer,
|
2156
2500
|
SHOW_TEXT|SHOW_ELEMENT,
|
2157
2501
|
function ( node ) {
|
2158
|
-
return ( node
|
2159
|
-
|
2160
|
-
|
2161
|
-
|
2162
|
-
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
startContainer = range.startContainer;
|
2168
|
-
startOffset = range.startOffset;
|
2169
|
-
endContainer = range.endContainer;
|
2170
|
-
endOffset = range.endOffset;
|
2171
|
-
|
2172
|
-
// Make sure we start with a valid node.
|
2173
|
-
walker.currentNode = startContainer;
|
2174
|
-
if ( !walker.filter( startContainer ) ) {
|
2175
|
-
startContainer = walker.nextNode();
|
2176
|
-
startOffset = 0;
|
2177
|
-
}
|
2502
|
+
return isNodeContainedInRange( range, node, true );
|
2503
|
+
}
|
2504
|
+
),
|
2505
|
+
startContainer = range.startContainer,
|
2506
|
+
endContainer = range.endContainer,
|
2507
|
+
node = walker.currentNode = startContainer,
|
2508
|
+
textContent = '',
|
2509
|
+
addedTextInBlock = false,
|
2510
|
+
value;
|
2178
2511
|
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
2182
|
-
}
|
2512
|
+
if ( !walker.filter( node ) ) {
|
2513
|
+
node = walker.nextNode();
|
2514
|
+
}
|
2183
2515
|
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
if (
|
2188
|
-
|
2189
|
-
|
2190
|
-
if ( node === endContainer && node.length > endOffset ) {
|
2191
|
-
node.splitText( endOffset );
|
2516
|
+
while ( node ) {
|
2517
|
+
if ( node.nodeType === TEXT_NODE ) {
|
2518
|
+
value = node.data;
|
2519
|
+
if ( value && ( /\S/.test( value ) ) ) {
|
2520
|
+
if ( node === endContainer ) {
|
2521
|
+
value = value.slice( 0, range.endOffset );
|
2192
2522
|
}
|
2193
|
-
if ( node === startContainer
|
2194
|
-
|
2195
|
-
if ( endContainer === startContainer ) {
|
2196
|
-
endContainer = node;
|
2197
|
-
endOffset -= startOffset;
|
2198
|
-
}
|
2199
|
-
startContainer = node;
|
2200
|
-
startOffset = 0;
|
2523
|
+
if ( node === startContainer ) {
|
2524
|
+
value = value.slice( range.startOffset );
|
2201
2525
|
}
|
2202
|
-
|
2203
|
-
|
2204
|
-
el.appendChild( node );
|
2526
|
+
textContent += value;
|
2527
|
+
addedTextInBlock = true;
|
2205
2528
|
}
|
2206
|
-
}
|
2529
|
+
} else if ( node.nodeName === 'BR' ||
|
2530
|
+
addedTextInBlock && !isInline( node ) ) {
|
2531
|
+
textContent += '\n';
|
2532
|
+
addedTextInBlock = false;
|
2533
|
+
}
|
2534
|
+
node = walker.nextNode();
|
2535
|
+
}
|
2207
2536
|
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2537
|
+
return textContent;
|
2538
|
+
};
|
2539
|
+
|
2540
|
+
proto.getPath = function () {
|
2541
|
+
return this._path;
|
2542
|
+
};
|
2543
|
+
|
2544
|
+
// --- Workaround for browsers that can't focus empty text nodes ---
|
2545
|
+
|
2546
|
+
// WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=15256
|
2547
|
+
|
2548
|
+
var removeZWS = function ( root ) {
|
2549
|
+
var walker = new TreeWalker( root, SHOW_TEXT, function () {
|
2550
|
+
return true;
|
2551
|
+
}, false ),
|
2552
|
+
parent, node, index;
|
2553
|
+
while ( node = walker.nextNode() ) {
|
2554
|
+
while ( ( index = node.data.indexOf( ZWS ) ) > -1 ) {
|
2555
|
+
if ( node.length === 1 ) {
|
2556
|
+
do {
|
2557
|
+
parent = node.parentNode;
|
2558
|
+
parent.removeChild( node );
|
2559
|
+
node = parent;
|
2560
|
+
walker.currentNode = parent;
|
2561
|
+
} while ( isInline( node ) && !getLength( node ) );
|
2562
|
+
break;
|
2213
2563
|
} else {
|
2214
|
-
|
2215
|
-
// one child
|
2216
|
-
endContainer = node.parentNode;
|
2217
|
-
endOffset = 1;
|
2564
|
+
node.deleteData( index, 1 );
|
2218
2565
|
}
|
2219
2566
|
}
|
2567
|
+
}
|
2568
|
+
};
|
2220
2569
|
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2570
|
+
proto._didAddZWS = function () {
|
2571
|
+
this._hasZWS = true;
|
2572
|
+
};
|
2573
|
+
proto._removeZWS = function () {
|
2574
|
+
if ( !this._hasZWS ) {
|
2575
|
+
return;
|
2224
2576
|
}
|
2225
|
-
|
2577
|
+
removeZWS( this._body );
|
2578
|
+
this._hasZWS = false;
|
2226
2579
|
};
|
2227
2580
|
|
2228
|
-
|
2229
|
-
// Add bookmark
|
2230
|
-
this._saveRangeToBookmark( range );
|
2581
|
+
// --- Path change events ---
|
2231
2582
|
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
2235
|
-
|
2236
|
-
if (
|
2237
|
-
|
2238
|
-
|
2239
|
-
|
2240
|
-
|
2241
|
-
|
2583
|
+
proto._updatePath = function ( range, force ) {
|
2584
|
+
var anchor = range.startContainer,
|
2585
|
+
focus = range.endContainer,
|
2586
|
+
newPath;
|
2587
|
+
if ( force || anchor !== this._lastAnchorNode ||
|
2588
|
+
focus !== this._lastFocusNode ) {
|
2589
|
+
this._lastAnchorNode = anchor;
|
2590
|
+
this._lastFocusNode = focus;
|
2591
|
+
newPath = ( anchor && focus ) ? ( anchor === focus ) ?
|
2592
|
+
getPath( focus ) : '(selection)' : '';
|
2593
|
+
if ( this._path !== newPath ) {
|
2594
|
+
this._path = newPath;
|
2595
|
+
this.fireEvent( 'pathChange', { path: newPath } );
|
2242
2596
|
}
|
2243
|
-
insertNodeInRange( range, fixer );
|
2244
2597
|
}
|
2598
|
+
if ( !range.collapsed ) {
|
2599
|
+
this.fireEvent( 'select' );
|
2600
|
+
}
|
2601
|
+
};
|
2245
2602
|
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2603
|
+
proto._updatePathOnEvent = function () {
|
2604
|
+
this._updatePath( this.getSelection() );
|
2605
|
+
};
|
2606
|
+
|
2607
|
+
// --- Focus ---
|
2608
|
+
|
2609
|
+
proto.focus = function () {
|
2610
|
+
// FF seems to need the body to be focussed (at least on first load).
|
2611
|
+
// Chrome also now needs body to be focussed in order to show the cursor
|
2612
|
+
// (otherwise it is focussed, but the cursor doesn't appear).
|
2613
|
+
// Opera (Presto-variant) however will lose the selection if you call this!
|
2614
|
+
if ( !isPresto ) {
|
2615
|
+
this._body.focus();
|
2250
2616
|
}
|
2617
|
+
this._win.focus();
|
2618
|
+
return this;
|
2619
|
+
};
|
2251
2620
|
|
2252
|
-
|
2253
|
-
//
|
2254
|
-
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2259
|
-
|
2260
|
-
|
2261
|
-
|
2262
|
-
|
2263
|
-
return;
|
2264
|
-
}
|
2621
|
+
proto.blur = function () {
|
2622
|
+
// IE will remove the whole browser window from focus if you call
|
2623
|
+
// win.blur() or body.blur(), so instead we call top.focus() to focus
|
2624
|
+
// the top frame, thus blurring this frame. This works in everything
|
2625
|
+
// except FF, so we need to call body.blur() in that as well.
|
2626
|
+
if ( isGecko ) {
|
2627
|
+
this._body.blur();
|
2628
|
+
}
|
2629
|
+
top.focus();
|
2630
|
+
return this;
|
2631
|
+
};
|
2265
2632
|
|
2266
|
-
|
2267
|
-
child, next;
|
2633
|
+
// --- Bookmarking ---
|
2268
2634
|
|
2269
|
-
|
2270
|
-
|
2271
|
-
if ( !isNodeContainedInRange( range, node, true ) ) {
|
2272
|
-
// Ignore bookmarks and empty text nodes
|
2273
|
-
if ( node.nodeName !== 'INPUT' &&
|
2274
|
-
( !isText || node.data ) ) {
|
2275
|
-
toWrap.push([ exemplar, node ]);
|
2276
|
-
}
|
2277
|
-
return;
|
2278
|
-
}
|
2635
|
+
var startSelectionId = 'squire-selection-start';
|
2636
|
+
var endSelectionId = 'squire-selection-end';
|
2279
2637
|
|
2280
|
-
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2290
|
-
// If not a text node, recurse onto all children.
|
2291
|
-
// Beware, the tree may be rewritten with each call
|
2292
|
-
// to examineNode, hence find the next sibling first.
|
2293
|
-
else {
|
2294
|
-
for ( child = node.firstChild; child; child = next ) {
|
2295
|
-
next = child.nextSibling;
|
2296
|
-
examineNode( child, exemplar );
|
2297
|
-
}
|
2298
|
-
}
|
2299
|
-
},
|
2300
|
-
formatTags = Array.prototype.filter.call(
|
2301
|
-
root.getElementsByTagName( tag ), function ( el ) {
|
2302
|
-
return isNodeContainedInRange( range, el, true ) &&
|
2303
|
-
hasTagAttributes( el, tag, attributes );
|
2304
|
-
}
|
2305
|
-
);
|
2306
|
-
|
2307
|
-
if ( !partial ) {
|
2308
|
-
formatTags.forEach( function ( node ) {
|
2309
|
-
examineNode( node, node );
|
2310
|
-
});
|
2311
|
-
}
|
2638
|
+
proto._saveRangeToBookmark = function ( range ) {
|
2639
|
+
var startNode = this.createElement( 'INPUT', {
|
2640
|
+
id: startSelectionId,
|
2641
|
+
type: 'hidden'
|
2642
|
+
}),
|
2643
|
+
endNode = this.createElement( 'INPUT', {
|
2644
|
+
id: endSelectionId,
|
2645
|
+
type: 'hidden'
|
2646
|
+
}),
|
2647
|
+
temp;
|
2312
2648
|
|
2313
|
-
|
2314
|
-
|
2315
|
-
|
2316
|
-
var el = item[0].cloneNode( false ),
|
2317
|
-
node = item[1];
|
2318
|
-
replaceWith( node, el );
|
2319
|
-
el.appendChild( node );
|
2320
|
-
});
|
2321
|
-
// and remove old formatting tags.
|
2322
|
-
formatTags.forEach( function ( el ) {
|
2323
|
-
replaceWith( el, empty( el ) );
|
2324
|
-
});
|
2649
|
+
insertNodeInRange( range, startNode );
|
2650
|
+
range.collapse( false );
|
2651
|
+
insertNodeInRange( range, endNode );
|
2325
2652
|
|
2326
|
-
//
|
2327
|
-
|
2328
|
-
|
2329
|
-
|
2653
|
+
// In a collapsed range, the start is sometimes inserted after the end!
|
2654
|
+
if ( startNode.compareDocumentPosition( endNode ) &
|
2655
|
+
DOCUMENT_POSITION_PRECEDING ) {
|
2656
|
+
startNode.id = endSelectionId;
|
2657
|
+
endNode.id = startSelectionId;
|
2658
|
+
temp = startNode;
|
2659
|
+
startNode = endNode;
|
2660
|
+
endNode = temp;
|
2330
2661
|
}
|
2331
|
-
var _range = {
|
2332
|
-
startContainer: range.startContainer,
|
2333
|
-
startOffset: range.startOffset,
|
2334
|
-
endContainer: range.endContainer,
|
2335
|
-
endOffset: range.endOffset
|
2336
|
-
};
|
2337
|
-
mergeInlines( root, _range );
|
2338
|
-
range.setStart( _range.startContainer, _range.startOffset );
|
2339
|
-
range.setEnd( _range.endContainer, _range.endOffset );
|
2340
2662
|
|
2341
|
-
|
2663
|
+
range.setStartAfter( startNode );
|
2664
|
+
range.setEndBefore( endNode );
|
2342
2665
|
};
|
2343
2666
|
|
2344
|
-
proto.
|
2345
|
-
|
2346
|
-
|
2347
|
-
|
2348
|
-
}
|
2349
|
-
|
2350
|
-
// Save undo checkpoint
|
2351
|
-
this._recordUndoState( range );
|
2352
|
-
this._getRangeAndRemoveBookmark( range );
|
2353
|
-
|
2354
|
-
if ( remove ) {
|
2355
|
-
range = this._removeFormat( remove.tag.toUpperCase(),
|
2356
|
-
remove.attributes || {}, range, partial );
|
2357
|
-
}
|
2358
|
-
if ( add ) {
|
2359
|
-
range = this._addFormat( add.tag.toUpperCase(),
|
2360
|
-
add.attributes || {}, range );
|
2361
|
-
}
|
2362
|
-
|
2363
|
-
this.setSelection( range );
|
2364
|
-
this._updatePath( range, true );
|
2667
|
+
proto._getRangeAndRemoveBookmark = function ( range ) {
|
2668
|
+
var doc = this._doc,
|
2669
|
+
start = doc.getElementById( startSelectionId ),
|
2670
|
+
end = doc.getElementById( endSelectionId );
|
2365
2671
|
|
2366
|
-
|
2367
|
-
|
2368
|
-
|
2369
|
-
|
2672
|
+
if ( start && end ) {
|
2673
|
+
var startContainer = start.parentNode,
|
2674
|
+
endContainer = end.parentNode,
|
2675
|
+
collapsed;
|
2370
2676
|
|
2371
|
-
|
2372
|
-
|
2677
|
+
var _range = {
|
2678
|
+
startContainer: startContainer,
|
2679
|
+
endContainer: endContainer,
|
2680
|
+
startOffset: indexOf.call( startContainer.childNodes, start ),
|
2681
|
+
endOffset: indexOf.call( endContainer.childNodes, end )
|
2682
|
+
};
|
2373
2683
|
|
2374
|
-
|
2684
|
+
if ( startContainer === endContainer ) {
|
2685
|
+
_range.endOffset -= 1;
|
2686
|
+
}
|
2375
2687
|
|
2376
|
-
|
2377
|
-
|
2378
|
-
DD: 'DT',
|
2379
|
-
LI: 'LI'
|
2380
|
-
};
|
2688
|
+
detach( start );
|
2689
|
+
detach( end );
|
2381
2690
|
|
2382
|
-
|
2383
|
-
|
2384
|
-
|
2385
|
-
|
2386
|
-
|
2691
|
+
// Merge any text nodes we split
|
2692
|
+
mergeInlines( startContainer, _range );
|
2693
|
+
if ( startContainer !== endContainer ) {
|
2694
|
+
mergeInlines( endContainer, _range );
|
2695
|
+
}
|
2387
2696
|
|
2388
|
-
|
2389
|
-
|
2390
|
-
|
2391
|
-
|
2697
|
+
if ( !range ) {
|
2698
|
+
range = doc.createRange();
|
2699
|
+
}
|
2700
|
+
range.setStart( _range.startContainer, _range.startOffset );
|
2701
|
+
range.setEnd( _range.endContainer, _range.endOffset );
|
2702
|
+
collapsed = range.collapsed;
|
2392
2703
|
|
2393
|
-
|
2394
|
-
|
2395
|
-
|
2396
|
-
splitTag, splitProperties );
|
2397
|
-
if ( nodeAfterSplit.dir ) {
|
2398
|
-
block.dir = nodeAfterSplit.dir;
|
2704
|
+
moveRangeBoundariesDownTree( range );
|
2705
|
+
if ( collapsed ) {
|
2706
|
+
range.collapse( true );
|
2399
2707
|
}
|
2400
|
-
replaceWith( nodeAfterSplit, block );
|
2401
|
-
block.appendChild( empty( nodeAfterSplit ) );
|
2402
|
-
nodeAfterSplit = block;
|
2403
2708
|
}
|
2404
|
-
return
|
2709
|
+
return range || null;
|
2405
2710
|
};
|
2406
2711
|
|
2407
|
-
|
2408
|
-
if ( !range && !( range = this.getSelection() ) ) {
|
2409
|
-
return this;
|
2410
|
-
}
|
2712
|
+
// --- Undo ---
|
2411
2713
|
|
2412
|
-
|
2413
|
-
|
2414
|
-
|
2415
|
-
|
2714
|
+
proto._keyUpDetectChange = function ( event ) {
|
2715
|
+
var code = event.keyCode;
|
2716
|
+
// Presume document was changed if:
|
2717
|
+
// 1. A modifier key (other than shift) wasn't held down
|
2718
|
+
// 2. The key pressed is not in range 16<=x<=20 (control keys)
|
2719
|
+
// 3. The key pressed is not in range 33<=x<=45 (navigation keys)
|
2720
|
+
if ( !event.ctrlKey && !event.metaKey && !event.altKey &&
|
2721
|
+
( code < 16 || code > 20 ) &&
|
2722
|
+
( code < 33 || code > 45 ) ) {
|
2723
|
+
this._docWasChanged();
|
2416
2724
|
}
|
2725
|
+
};
|
2417
2726
|
|
2418
|
-
|
2419
|
-
|
2420
|
-
|
2421
|
-
|
2422
|
-
|
2423
|
-
|
2727
|
+
proto._docWasChanged = function () {
|
2728
|
+
if ( canObserveMutations && this._ignoreChange ) {
|
2729
|
+
this._ignoreChange = false;
|
2730
|
+
return;
|
2731
|
+
}
|
2732
|
+
if ( this._isInUndoState ) {
|
2733
|
+
this._isInUndoState = false;
|
2734
|
+
this.fireEvent( 'undoStateChange', {
|
2735
|
+
canUndo: true,
|
2736
|
+
canRedo: false
|
2737
|
+
});
|
2424
2738
|
}
|
2739
|
+
this.fireEvent( 'input' );
|
2740
|
+
};
|
2425
2741
|
|
2426
|
-
|
2427
|
-
|
2742
|
+
// Leaves bookmark
|
2743
|
+
proto._recordUndoState = function ( range ) {
|
2744
|
+
// Don't record if we're already in an undo state
|
2745
|
+
if ( !this._isInUndoState ) {
|
2746
|
+
// Advance pointer to new position
|
2747
|
+
var undoIndex = this._undoIndex += 1,
|
2748
|
+
undoStack = this._undoStack;
|
2428
2749
|
|
2429
|
-
//
|
2430
|
-
|
2750
|
+
// Truncate stack if longer (i.e. if has been previously undone)
|
2751
|
+
if ( undoIndex < this._undoStackLength ) {
|
2752
|
+
undoStack.length = this._undoStackLength = undoIndex;
|
2753
|
+
}
|
2431
2754
|
|
2432
|
-
//
|
2433
|
-
if (
|
2434
|
-
this.
|
2755
|
+
// Write out data
|
2756
|
+
if ( range ) {
|
2757
|
+
this._saveRangeToBookmark( range );
|
2435
2758
|
}
|
2759
|
+
undoStack[ undoIndex ] = this._getHTML();
|
2760
|
+
this._undoStackLength += 1;
|
2761
|
+
this._isInUndoState = true;
|
2436
2762
|
}
|
2437
|
-
return this;
|
2438
2763
|
};
|
2439
2764
|
|
2440
|
-
proto.
|
2441
|
-
|
2442
|
-
|
2443
|
-
|
2765
|
+
proto.undo = function () {
|
2766
|
+
// Sanity check: must not be at beginning of the history stack
|
2767
|
+
if ( this._undoIndex !== 0 || !this._isInUndoState ) {
|
2768
|
+
// Make sure any changes since last checkpoint are saved.
|
2769
|
+
this._recordUndoState( this.getSelection() );
|
2444
2770
|
|
2445
|
-
|
2446
|
-
|
2447
|
-
this.
|
2448
|
-
|
2449
|
-
|
2771
|
+
this._undoIndex -= 1;
|
2772
|
+
this._setHTML( this._undoStack[ this._undoIndex ] );
|
2773
|
+
var range = this._getRangeAndRemoveBookmark();
|
2774
|
+
if ( range ) {
|
2775
|
+
this.setSelection( range );
|
2776
|
+
}
|
2777
|
+
this._isInUndoState = true;
|
2778
|
+
this.fireEvent( 'undoStateChange', {
|
2779
|
+
canUndo: this._undoIndex !== 0,
|
2780
|
+
canRedo: true
|
2781
|
+
});
|
2782
|
+
this.fireEvent( 'input' );
|
2450
2783
|
}
|
2784
|
+
return this;
|
2785
|
+
};
|
2451
2786
|
|
2452
|
-
|
2453
|
-
|
2787
|
+
proto.redo = function () {
|
2788
|
+
// Sanity check: must not be at end of stack and must be in an undo
|
2789
|
+
// state.
|
2790
|
+
var undoIndex = this._undoIndex,
|
2791
|
+
undoStackLength = this._undoStackLength;
|
2792
|
+
if ( undoIndex + 1 < undoStackLength && this._isInUndoState ) {
|
2793
|
+
this._undoIndex += 1;
|
2794
|
+
this._setHTML( this._undoStack[ this._undoIndex ] );
|
2795
|
+
var range = this._getRangeAndRemoveBookmark();
|
2796
|
+
if ( range ) {
|
2797
|
+
this.setSelection( range );
|
2798
|
+
}
|
2799
|
+
this.fireEvent( 'undoStateChange', {
|
2800
|
+
canUndo: true,
|
2801
|
+
canRedo: undoIndex + 2 < undoStackLength
|
2802
|
+
});
|
2803
|
+
this.fireEvent( 'input' );
|
2804
|
+
}
|
2805
|
+
return this;
|
2806
|
+
};
|
2454
2807
|
|
2455
|
-
|
2456
|
-
var body = this._body,
|
2457
|
-
frag;
|
2458
|
-
moveRangeBoundariesUpTree( range, body );
|
2459
|
-
frag = extractContentsOfRange( range, body );
|
2808
|
+
// --- Inline formatting ---
|
2460
2809
|
|
2461
|
-
|
2462
|
-
|
2810
|
+
// Looks for matching tag and attributes, so won't work
|
2811
|
+
// if <strong> instead of <b> etc.
|
2812
|
+
proto.hasFormat = function ( tag, attributes, range ) {
|
2813
|
+
// 1. Normalise the arguments and get selection
|
2814
|
+
tag = tag.toUpperCase();
|
2815
|
+
if ( !attributes ) { attributes = {}; }
|
2816
|
+
if ( !range && !( range = this.getSelection() ) ) {
|
2817
|
+
return false;
|
2818
|
+
}
|
2463
2819
|
|
2464
|
-
//
|
2465
|
-
if ( range.
|
2466
|
-
|
2820
|
+
// Sanitize range to prevent weird IE artifacts
|
2821
|
+
if ( !range.collapsed &&
|
2822
|
+
range.startContainer.nodeType === TEXT_NODE &&
|
2823
|
+
range.startOffset === range.startContainer.length &&
|
2824
|
+
range.startContainer.nextSibling ) {
|
2825
|
+
range.setStartBefore( range.startContainer.nextSibling );
|
2826
|
+
}
|
2827
|
+
if ( !range.collapsed &&
|
2828
|
+
range.endContainer.nodeType === TEXT_NODE &&
|
2829
|
+
range.endOffset === 0 &&
|
2830
|
+
range.endContainer.previousSibling ) {
|
2831
|
+
range.setEndAfter( range.endContainer.previousSibling );
|
2467
2832
|
}
|
2468
|
-
mergeContainers( range.startContainer.childNodes[ range.startOffset ] );
|
2469
2833
|
|
2470
|
-
//
|
2471
|
-
|
2472
|
-
|
2473
|
-
|
2834
|
+
// If the common ancestor is inside the tag we require, we definitely
|
2835
|
+
// have the format.
|
2836
|
+
var root = range.commonAncestorContainer,
|
2837
|
+
walker, node;
|
2838
|
+
if ( getNearest( root, tag, attributes ) ) {
|
2839
|
+
return true;
|
2840
|
+
}
|
2474
2841
|
|
2475
|
-
//
|
2476
|
-
|
2477
|
-
|
2842
|
+
// If common ancestor is a text node and doesn't have the format, we
|
2843
|
+
// definitely don't have it.
|
2844
|
+
if ( root.nodeType === TEXT_NODE ) {
|
2845
|
+
return false;
|
2478
2846
|
}
|
2479
2847
|
|
2480
|
-
|
2481
|
-
|
2848
|
+
// Otherwise, check each text node at least partially contained within
|
2849
|
+
// the selection and make sure all of them have the format we want.
|
2850
|
+
walker = new TreeWalker( root, SHOW_TEXT, function ( node ) {
|
2851
|
+
return isNodeContainedInRange( range, node, true );
|
2852
|
+
}, false );
|
2482
2853
|
|
2483
|
-
var
|
2484
|
-
|
2485
|
-
|
2486
|
-
|
2487
|
-
|
2488
|
-
|
2854
|
+
var seenNode = false;
|
2855
|
+
while ( node = walker.nextNode() ) {
|
2856
|
+
if ( !getNearest( node, tag, attributes ) ) {
|
2857
|
+
return false;
|
2858
|
+
}
|
2859
|
+
seenNode = true;
|
2860
|
+
}
|
2489
2861
|
|
2490
|
-
|
2491
|
-
var blockquotes = frag.querySelectorAll( 'blockquote' );
|
2492
|
-
Array.prototype.filter.call( blockquotes, function ( el ) {
|
2493
|
-
return !getNearest( el.parentNode, 'BLOCKQUOTE' );
|
2494
|
-
}).forEach( function ( el ) {
|
2495
|
-
replaceWith( el, empty( el ) );
|
2496
|
-
});
|
2497
|
-
return frag;
|
2862
|
+
return seenNode;
|
2498
2863
|
};
|
2499
2864
|
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2503
|
-
|
2504
|
-
|
2505
|
-
|
2506
|
-
|
2507
|
-
|
2508
|
-
|
2509
|
-
|
2510
|
-
|
2511
|
-
};
|
2865
|
+
// Extracts the font-family and font-size (if any) of the element
|
2866
|
+
// holding the cursor. If there's a selection, returns an empty object.
|
2867
|
+
proto.getFontInfo = function ( range ) {
|
2868
|
+
var fontInfo = {
|
2869
|
+
color: undefined,
|
2870
|
+
backgroundColor: undefined,
|
2871
|
+
family: undefined,
|
2872
|
+
size: undefined
|
2873
|
+
};
|
2874
|
+
var seenAttributes = 0;
|
2875
|
+
var element, style;
|
2512
2876
|
|
2513
|
-
|
2514
|
-
|
2515
|
-
|
2516
|
-
tagAttributes = self._config.tagAttributes,
|
2517
|
-
listAttrs = tagAttributes[ type.toLowerCase() ],
|
2518
|
-
listItemAttrs = tagAttributes.li;
|
2877
|
+
if ( !range && !( range = this.getSelection() ) ) {
|
2878
|
+
return fontInfo;
|
2879
|
+
}
|
2519
2880
|
|
2520
|
-
|
2521
|
-
|
2522
|
-
if (
|
2523
|
-
|
2524
|
-
|
2525
|
-
|
2881
|
+
element = range.commonAncestorContainer;
|
2882
|
+
if ( range.collapsed || element.nodeType === TEXT_NODE ) {
|
2883
|
+
if ( element.nodeType === TEXT_NODE ) {
|
2884
|
+
element = element.parentNode;
|
2885
|
+
}
|
2886
|
+
while ( seenAttributes < 4 && element && ( style = element.style ) ) {
|
2887
|
+
if ( !fontInfo.color ) {
|
2888
|
+
fontInfo.color = style.color;
|
2889
|
+
seenAttributes += 1;
|
2526
2890
|
}
|
2527
|
-
|
2528
|
-
|
2529
|
-
|
2530
|
-
prev.nodeName === type ) {
|
2531
|
-
prev.appendChild( newLi );
|
2891
|
+
if ( !fontInfo.backgroundColor ) {
|
2892
|
+
fontInfo.backgroundColor = style.backgroundColor;
|
2893
|
+
seenAttributes += 1;
|
2532
2894
|
}
|
2533
|
-
|
2534
|
-
|
2535
|
-
|
2536
|
-
node,
|
2537
|
-
self.createElement( type, listAttrs, [
|
2538
|
-
newLi
|
2539
|
-
])
|
2540
|
-
);
|
2895
|
+
if ( !fontInfo.family ) {
|
2896
|
+
fontInfo.family = style.fontFamily;
|
2897
|
+
seenAttributes += 1;
|
2541
2898
|
}
|
2542
|
-
|
2543
|
-
|
2544
|
-
|
2545
|
-
tag = node.nodeName;
|
2546
|
-
if ( tag !== type && ( /^[OU]L$/.test( tag ) ) ) {
|
2547
|
-
replaceWith( node,
|
2548
|
-
self.createElement( type, listAttrs, [ empty( node ) ] )
|
2549
|
-
);
|
2899
|
+
if ( !fontInfo.size ) {
|
2900
|
+
fontInfo.size = style.fontSize;
|
2901
|
+
seenAttributes += 1;
|
2550
2902
|
}
|
2903
|
+
element = element.parentNode;
|
2551
2904
|
}
|
2552
2905
|
}
|
2553
|
-
|
2554
|
-
|
2555
|
-
var makeUnorderedList = function ( frag ) {
|
2556
|
-
makeList( this, frag, 'UL' );
|
2557
|
-
return frag;
|
2558
|
-
};
|
2906
|
+
return fontInfo;
|
2907
|
+
};
|
2559
2908
|
|
2560
|
-
|
2561
|
-
|
2562
|
-
|
2563
|
-
|
2909
|
+
proto._addFormat = function ( tag, attributes, range ) {
|
2910
|
+
// If the range is collapsed we simply insert the node by wrapping
|
2911
|
+
// it round the range and focus it.
|
2912
|
+
var el, walker, startContainer, endContainer, startOffset, endOffset,
|
2913
|
+
node, needsFormat;
|
2564
2914
|
|
2565
|
-
|
2566
|
-
|
2567
|
-
|
2568
|
-
|
2569
|
-
|
2570
|
-
listFrag = empty( list );
|
2571
|
-
children = listFrag.childNodes;
|
2572
|
-
ll = children.length;
|
2573
|
-
while ( ll-- ) {
|
2574
|
-
child = children[ll];
|
2575
|
-
replaceWith( child, empty( child ) );
|
2576
|
-
}
|
2577
|
-
fixContainer( listFrag );
|
2578
|
-
replaceWith( list, listFrag );
|
2915
|
+
if ( range.collapsed ) {
|
2916
|
+
el = fixCursor( this.createElement( tag, attributes ) );
|
2917
|
+
insertNodeInRange( range, el );
|
2918
|
+
range.setStart( el.firstChild, el.firstChild.length );
|
2919
|
+
range.collapse( true );
|
2579
2920
|
}
|
2580
|
-
|
2581
|
-
|
2921
|
+
// Otherwise we find all the textnodes in the range (splitting
|
2922
|
+
// partially selected nodes) and if they're not already formatted
|
2923
|
+
// correctly we wrap them in the appropriate tag.
|
2924
|
+
else {
|
2925
|
+
// Create an iterator to walk over all the text nodes under this
|
2926
|
+
// ancestor which are in the range and not already formatted
|
2927
|
+
// correctly.
|
2928
|
+
//
|
2929
|
+
// In Blink/WebKit, empty blocks may have no text nodes, just a <br>.
|
2930
|
+
// Therefore we wrap this in the tag as well, as this will then cause it
|
2931
|
+
// to apply when the user types something in the block, which is
|
2932
|
+
// presumably what was intended.
|
2933
|
+
//
|
2934
|
+
// IMG tags are included because we may want to create a link around them,
|
2935
|
+
// and adding other styles is harmless.
|
2936
|
+
walker = new TreeWalker(
|
2937
|
+
range.commonAncestorContainer,
|
2938
|
+
SHOW_TEXT|SHOW_ELEMENT,
|
2939
|
+
function ( node ) {
|
2940
|
+
return ( node.nodeType === TEXT_NODE ||
|
2941
|
+
node.nodeName === 'BR' ||
|
2942
|
+
node.nodeName === 'IMG'
|
2943
|
+
) && isNodeContainedInRange( range, node, true );
|
2944
|
+
},
|
2945
|
+
false
|
2946
|
+
);
|
2582
2947
|
|
2583
|
-
|
2584
|
-
|
2585
|
-
|
2586
|
-
|
2587
|
-
|
2588
|
-
|
2589
|
-
|
2590
|
-
|
2591
|
-
|
2592
|
-
if ( !
|
2593
|
-
|
2594
|
-
|
2595
|
-
newParent = item.previousSibling;
|
2596
|
-
if ( !newParent || !( newParent = newParent.lastChild ) ||
|
2597
|
-
newParent.nodeName !== type ) {
|
2598
|
-
listAttrs = tagAttributes[ type.toLowerCase() ];
|
2599
|
-
replaceWith(
|
2600
|
-
item,
|
2601
|
-
this.createElement( 'LI', listItemAttrs, [
|
2602
|
-
newParent = this.createElement( type, listAttrs )
|
2603
|
-
])
|
2604
|
-
);
|
2605
|
-
}
|
2606
|
-
newParent.appendChild( item );
|
2948
|
+
// Start at the beginning node of the range and iterate through
|
2949
|
+
// all the nodes in the range that need formatting.
|
2950
|
+
startContainer = range.startContainer;
|
2951
|
+
startOffset = range.startOffset;
|
2952
|
+
endContainer = range.endContainer;
|
2953
|
+
endOffset = range.endOffset;
|
2954
|
+
|
2955
|
+
// Make sure we start with a valid node.
|
2956
|
+
walker.currentNode = startContainer;
|
2957
|
+
if ( !walker.filter( startContainer ) ) {
|
2958
|
+
startContainer = walker.nextNode();
|
2959
|
+
startOffset = 0;
|
2607
2960
|
}
|
2608
|
-
}
|
2609
|
-
return frag;
|
2610
|
-
};
|
2611
2961
|
|
2612
|
-
|
2613
|
-
|
2614
|
-
|
2615
|
-
return !isContainer( el.firstChild );
|
2616
|
-
}).forEach( function ( item ) {
|
2617
|
-
var parent = item.parentNode,
|
2618
|
-
newParent = parent.parentNode,
|
2619
|
-
first = item.firstChild,
|
2620
|
-
node = first,
|
2621
|
-
next;
|
2622
|
-
if ( item.previousSibling ) {
|
2623
|
-
parent = split( parent, item, newParent );
|
2962
|
+
// If there are no interesting nodes in the selection, abort
|
2963
|
+
if ( !startContainer ) {
|
2964
|
+
return range;
|
2624
2965
|
}
|
2625
|
-
while ( node ) {
|
2626
|
-
next = node.nextSibling;
|
2627
|
-
if ( isContainer( node ) ) {
|
2628
|
-
break;
|
2629
|
-
}
|
2630
|
-
newParent.insertBefore( node, parent );
|
2631
|
-
node = next;
|
2632
|
-
}
|
2633
|
-
if ( newParent.nodeName === 'LI' && first.previousSibling ) {
|
2634
|
-
split( newParent, first, newParent.parentNode );
|
2635
|
-
}
|
2636
|
-
while ( item !== frag && !item.childNodes.length ) {
|
2637
|
-
parent = item.parentNode;
|
2638
|
-
parent.removeChild( item );
|
2639
|
-
item = parent;
|
2640
|
-
}
|
2641
|
-
}, this );
|
2642
|
-
fixContainer( frag );
|
2643
|
-
return frag;
|
2644
|
-
};
|
2645
2966
|
|
2646
|
-
|
2647
|
-
|
2648
|
-
|
2967
|
+
do {
|
2968
|
+
node = walker.currentNode;
|
2969
|
+
needsFormat = !getNearest( node, tag, attributes );
|
2970
|
+
if ( needsFormat ) {
|
2971
|
+
// <br> can never be a container node, so must have a text node
|
2972
|
+
// if node == (end|start)Container
|
2973
|
+
if ( node === endContainer && node.length > endOffset ) {
|
2974
|
+
node.splitText( endOffset );
|
2975
|
+
}
|
2976
|
+
if ( node === startContainer && startOffset ) {
|
2977
|
+
node = node.splitText( startOffset );
|
2978
|
+
if ( endContainer === startContainer ) {
|
2979
|
+
endContainer = node;
|
2980
|
+
endOffset -= startOffset;
|
2981
|
+
}
|
2982
|
+
startContainer = node;
|
2983
|
+
startOffset = 0;
|
2984
|
+
}
|
2985
|
+
el = this.createElement( tag, attributes );
|
2986
|
+
replaceWith( node, el );
|
2987
|
+
el.appendChild( node );
|
2988
|
+
}
|
2989
|
+
} while ( walker.nextNode() );
|
2649
2990
|
|
2650
|
-
|
2651
|
-
|
2652
|
-
|
2653
|
-
|
2654
|
-
|
2655
|
-
|
2656
|
-
|
2657
|
-
|
2658
|
-
|
2659
|
-
|
2660
|
-
while ( match = linkRegExp.exec( data ) ) {
|
2661
|
-
index = match.index;
|
2662
|
-
endIndex = index + match[0].length;
|
2663
|
-
if ( index ) {
|
2664
|
-
child = doc.createTextNode( data.slice( 0, index ) );
|
2665
|
-
parent.insertBefore( child, node );
|
2991
|
+
// If we don't finish inside a text node, offset may have changed.
|
2992
|
+
if ( endContainer.nodeType !== TEXT_NODE ) {
|
2993
|
+
if ( node.nodeType === TEXT_NODE ) {
|
2994
|
+
endContainer = node;
|
2995
|
+
endOffset = node.length;
|
2996
|
+
} else {
|
2997
|
+
// If <br>, we must have just wrapped it, so it must have only
|
2998
|
+
// one child
|
2999
|
+
endContainer = node.parentNode;
|
3000
|
+
endOffset = 1;
|
2666
3001
|
}
|
2667
|
-
child = doc.createElement( 'A' );
|
2668
|
-
child.textContent = data.slice( index, endIndex );
|
2669
|
-
child.href = match[1] ?
|
2670
|
-
/^(?:ht|f)tps?:/.test( match[1] ) ?
|
2671
|
-
match[1] :
|
2672
|
-
'http://' + match[1] :
|
2673
|
-
'mailto:' + match[2];
|
2674
|
-
parent.insertBefore( child, node );
|
2675
|
-
node.data = data = data.slice( endIndex );
|
2676
3002
|
}
|
3003
|
+
|
3004
|
+
// Now set the selection to as it was before
|
3005
|
+
range = this._createRange(
|
3006
|
+
startContainer, startOffset, endContainer, endOffset );
|
2677
3007
|
}
|
3008
|
+
return range;
|
2678
3009
|
};
|
2679
3010
|
|
2680
|
-
|
2681
|
-
|
2682
|
-
|
2683
|
-
1: 10,
|
2684
|
-
2: 13,
|
2685
|
-
3: 16,
|
2686
|
-
4: 18,
|
2687
|
-
5: 24,
|
2688
|
-
6: 32,
|
2689
|
-
7: 48
|
2690
|
-
};
|
3011
|
+
proto._removeFormat = function ( tag, attributes, range, partial ) {
|
3012
|
+
// Add bookmark
|
3013
|
+
this._saveRangeToBookmark( range );
|
2691
3014
|
|
2692
|
-
|
2693
|
-
|
2694
|
-
|
2695
|
-
|
2696
|
-
|
2697
|
-
|
2698
|
-
|
2699
|
-
|
2700
|
-
}
|
2701
|
-
|
2702
|
-
color: {
|
2703
|
-
regexp: notWS,
|
2704
|
-
replace: function ( doc, colour ) {
|
2705
|
-
return createElement( doc, 'SPAN', {
|
2706
|
-
'class': 'colour',
|
2707
|
-
style: 'color:' + colour
|
2708
|
-
});
|
2709
|
-
}
|
2710
|
-
},
|
2711
|
-
fontWeight: {
|
2712
|
-
regexp: /^bold/i,
|
2713
|
-
replace: function ( doc ) {
|
2714
|
-
return createElement( doc, 'B' );
|
2715
|
-
}
|
2716
|
-
},
|
2717
|
-
fontStyle: {
|
2718
|
-
regexp: /^italic/i,
|
2719
|
-
replace: function ( doc ) {
|
2720
|
-
return createElement( doc, 'I' );
|
2721
|
-
}
|
2722
|
-
},
|
2723
|
-
fontFamily: {
|
2724
|
-
regexp: notWS,
|
2725
|
-
replace: function ( doc, family ) {
|
2726
|
-
return createElement( doc, 'SPAN', {
|
2727
|
-
'class': 'font',
|
2728
|
-
style: 'font-family:' + family
|
2729
|
-
});
|
2730
|
-
}
|
2731
|
-
},
|
2732
|
-
fontSize: {
|
2733
|
-
regexp: notWS,
|
2734
|
-
replace: function ( doc, size ) {
|
2735
|
-
return createElement( doc, 'SPAN', {
|
2736
|
-
'class': 'size',
|
2737
|
-
style: 'font-size:' + size
|
2738
|
-
});
|
3015
|
+
// We need a node in the selection to break the surrounding
|
3016
|
+
// formatted text.
|
3017
|
+
var doc = this._doc,
|
3018
|
+
fixer;
|
3019
|
+
if ( range.collapsed ) {
|
3020
|
+
if ( cantFocusEmptyTextNodes ) {
|
3021
|
+
fixer = doc.createTextNode( ZWS );
|
3022
|
+
this._didAddZWS();
|
3023
|
+
} else {
|
3024
|
+
fixer = doc.createTextNode( '' );
|
2739
3025
|
}
|
3026
|
+
insertNodeInRange( range, fixer );
|
2740
3027
|
}
|
2741
|
-
};
|
2742
3028
|
|
2743
|
-
|
2744
|
-
|
2745
|
-
|
2746
|
-
|
2747
|
-
|
2748
|
-
return el;
|
2749
|
-
};
|
2750
|
-
};
|
2751
|
-
|
2752
|
-
var stylesRewriters = {
|
2753
|
-
SPAN: function ( span, parent ) {
|
2754
|
-
var style = span.style,
|
2755
|
-
doc = span.ownerDocument,
|
2756
|
-
attr, converter, css, newTreeBottom, newTreeTop, el;
|
3029
|
+
// Find block-level ancestor of selection
|
3030
|
+
var root = range.commonAncestorContainer;
|
3031
|
+
while ( isInline( root ) ) {
|
3032
|
+
root = root.parentNode;
|
3033
|
+
}
|
2757
3034
|
|
2758
|
-
|
2759
|
-
|
2760
|
-
|
2761
|
-
|
2762
|
-
|
2763
|
-
|
2764
|
-
|
2765
|
-
|
2766
|
-
|
2767
|
-
|
2768
|
-
|
2769
|
-
|
3035
|
+
// Find text nodes inside formatTags that are not in selection and
|
3036
|
+
// add an extra tag with the same formatting.
|
3037
|
+
var startContainer = range.startContainer,
|
3038
|
+
startOffset = range.startOffset,
|
3039
|
+
endContainer = range.endContainer,
|
3040
|
+
endOffset = range.endOffset,
|
3041
|
+
toWrap = [],
|
3042
|
+
examineNode = function ( node, exemplar ) {
|
3043
|
+
// If the node is completely contained by the range then
|
3044
|
+
// we're going to remove all formatting so ignore it.
|
3045
|
+
if ( isNodeContainedInRange( range, node, false ) ) {
|
3046
|
+
return;
|
2770
3047
|
}
|
2771
|
-
}
|
2772
3048
|
|
2773
|
-
|
2774
|
-
|
2775
|
-
parent.replaceChild( newTreeTop, span );
|
2776
|
-
}
|
3049
|
+
var isText = ( node.nodeType === TEXT_NODE ),
|
3050
|
+
child, next;
|
2777
3051
|
|
2778
|
-
|
2779
|
-
|
2780
|
-
|
2781
|
-
|
2782
|
-
|
2783
|
-
|
2784
|
-
|
2785
|
-
|
2786
|
-
|
2787
|
-
doc = node.ownerDocument,
|
2788
|
-
fontSpan, sizeSpan, colourSpan,
|
2789
|
-
newTreeBottom, newTreeTop;
|
2790
|
-
if ( face ) {
|
2791
|
-
fontSpan = createElement( doc, 'SPAN', {
|
2792
|
-
'class': 'font',
|
2793
|
-
style: 'font-family:' + face
|
2794
|
-
});
|
2795
|
-
newTreeTop = fontSpan;
|
2796
|
-
newTreeBottom = fontSpan;
|
2797
|
-
}
|
2798
|
-
if ( size ) {
|
2799
|
-
sizeSpan = createElement( doc, 'SPAN', {
|
2800
|
-
'class': 'size',
|
2801
|
-
style: 'font-size:' + fontSizes[ size ] + 'px'
|
2802
|
-
});
|
2803
|
-
if ( !newTreeTop ) {
|
2804
|
-
newTreeTop = sizeSpan;
|
2805
|
-
}
|
2806
|
-
if ( newTreeBottom ) {
|
2807
|
-
newTreeBottom.appendChild( sizeSpan );
|
3052
|
+
// If not at least partially contained, wrap entire contents
|
3053
|
+
// in a clone of the tag we're removing and we're done.
|
3054
|
+
if ( !isNodeContainedInRange( range, node, true ) ) {
|
3055
|
+
// Ignore bookmarks and empty text nodes
|
3056
|
+
if ( node.nodeName !== 'INPUT' &&
|
3057
|
+
( !isText || node.data ) ) {
|
3058
|
+
toWrap.push([ exemplar, node ]);
|
3059
|
+
}
|
3060
|
+
return;
|
2808
3061
|
}
|
2809
|
-
|
2810
|
-
|
2811
|
-
|
2812
|
-
|
2813
|
-
|
3062
|
+
|
3063
|
+
// Split any partially selected text nodes.
|
3064
|
+
if ( isText ) {
|
3065
|
+
if ( node === endContainer && endOffset !== node.length ) {
|
3066
|
+
toWrap.push([ exemplar, node.splitText( endOffset ) ]);
|
3067
|
+
}
|
3068
|
+
if ( node === startContainer && startOffset ) {
|
3069
|
+
node.splitText( startOffset );
|
3070
|
+
toWrap.push([ exemplar, node ]);
|
3071
|
+
}
|
2814
3072
|
}
|
2815
|
-
|
2816
|
-
|
2817
|
-
|
2818
|
-
|
2819
|
-
|
2820
|
-
|
3073
|
+
// If not a text node, recurse onto all children.
|
3074
|
+
// Beware, the tree may be rewritten with each call
|
3075
|
+
// to examineNode, hence find the next sibling first.
|
3076
|
+
else {
|
3077
|
+
for ( child = node.firstChild; child; child = next ) {
|
3078
|
+
next = child.nextSibling;
|
3079
|
+
examineNode( child, exemplar );
|
3080
|
+
}
|
2821
3081
|
}
|
2822
|
-
|
2823
|
-
|
3082
|
+
},
|
3083
|
+
formatTags = Array.prototype.filter.call(
|
3084
|
+
root.getElementsByTagName( tag ), function ( el ) {
|
3085
|
+
return isNodeContainedInRange( range, el, true ) &&
|
3086
|
+
hasTagAttributes( el, tag, attributes );
|
2824
3087
|
}
|
2825
|
-
|
2826
|
-
|
2827
|
-
|
2828
|
-
|
2829
|
-
|
2830
|
-
parent.replaceChild( newTreeTop, node );
|
2831
|
-
newTreeBottom.appendChild( empty( node ) );
|
2832
|
-
return newTreeBottom;
|
2833
|
-
},
|
2834
|
-
TT: function ( node, parent ) {
|
2835
|
-
var el = createElement( node.ownerDocument, 'SPAN', {
|
2836
|
-
'class': 'font',
|
2837
|
-
style: 'font-family:menlo,consolas,"courier new",monospace'
|
3088
|
+
);
|
3089
|
+
|
3090
|
+
if ( !partial ) {
|
3091
|
+
formatTags.forEach( function ( node ) {
|
3092
|
+
examineNode( node, node );
|
2838
3093
|
});
|
2839
|
-
parent.replaceChild( el, node );
|
2840
|
-
el.appendChild( empty( node ) );
|
2841
|
-
return el;
|
2842
3094
|
}
|
2843
|
-
};
|
2844
3095
|
|
2845
|
-
|
2846
|
-
|
2847
|
-
|
2848
|
-
|
2849
|
-
|
2850
|
-
|
2851
|
-
|
2852
|
-
|
2853
|
-
|
2854
|
-
|
2855
|
-
|
2856
|
-
|
2857
|
-
|
2858
|
-
|
3096
|
+
// Now wrap unselected nodes in the tag
|
3097
|
+
toWrap.forEach( function ( item ) {
|
3098
|
+
// [ exemplar, node ] tuple
|
3099
|
+
var el = item[0].cloneNode( false ),
|
3100
|
+
node = item[1];
|
3101
|
+
replaceWith( node, el );
|
3102
|
+
el.appendChild( node );
|
3103
|
+
});
|
3104
|
+
// and remove old formatting tags.
|
3105
|
+
formatTags.forEach( function ( el ) {
|
3106
|
+
replaceWith( el, empty( el ) );
|
3107
|
+
});
|
3108
|
+
|
3109
|
+
// Merge adjacent inlines:
|
3110
|
+
this._getRangeAndRemoveBookmark( range );
|
3111
|
+
if ( fixer ) {
|
3112
|
+
range.collapse( false );
|
2859
3113
|
}
|
3114
|
+
var _range = {
|
3115
|
+
startContainer: range.startContainer,
|
3116
|
+
startOffset: range.startOffset,
|
3117
|
+
endContainer: range.endContainer,
|
3118
|
+
endOffset: range.endOffset
|
3119
|
+
};
|
3120
|
+
mergeInlines( root, _range );
|
3121
|
+
range.setStart( _range.startContainer, _range.startOffset );
|
3122
|
+
range.setEnd( _range.endContainer, _range.endOffset );
|
3123
|
+
|
3124
|
+
return range;
|
2860
3125
|
};
|
2861
3126
|
|
2862
|
-
|
2863
|
-
|
3127
|
+
proto.changeFormat = function ( add, remove, range, partial ) {
|
3128
|
+
// Normalise the arguments and get selection
|
3129
|
+
if ( !range && !( range = this.getSelection() ) ) {
|
3130
|
+
return;
|
3131
|
+
}
|
2864
3132
|
|
2865
|
-
|
2866
|
-
|
2867
|
-
|
2868
|
-
|
2869
|
-
|
2870
|
-
|
2871
|
-
|
2872
|
-
|
2873
|
-
|
2874
|
-
|
2875
|
-
|
2876
|
-
|
2877
|
-
|
2878
|
-
|
2879
|
-
|
2880
|
-
|
2881
|
-
|
2882
|
-
|
2883
|
-
|
2884
|
-
i -= 1;
|
2885
|
-
l += childLength - 1;
|
2886
|
-
node.replaceChild( empty( child ), child );
|
2887
|
-
continue;
|
2888
|
-
} else if ( !allowStyles && child.style.cssText ) {
|
2889
|
-
child.removeAttribute( 'style' );
|
2890
|
-
}
|
2891
|
-
if ( childLength ) {
|
2892
|
-
cleanTree( child, allowStyles );
|
2893
|
-
}
|
2894
|
-
} else {
|
2895
|
-
if ( nodeType === TEXT_NODE ) {
|
2896
|
-
data = child.data;
|
2897
|
-
// Use \S instead of notWS, because we want to remove nodes
|
2898
|
-
// which are just nbsp, in order to cleanup <div>nbsp<br></div>
|
2899
|
-
// construct.
|
2900
|
-
if ( /\S/.test( data ) ) {
|
2901
|
-
// If the parent node is inline, don't trim this node as
|
2902
|
-
// it probably isn't at the end of the block.
|
2903
|
-
if ( isInline( node ) ) {
|
2904
|
-
continue;
|
2905
|
-
}
|
2906
|
-
j = 0;
|
2907
|
-
ll = data.length;
|
2908
|
-
if ( !i || !isInline( children[ i - 1 ] ) ) {
|
2909
|
-
while ( j < ll && !notWS.test( data.charAt( j ) ) ) {
|
2910
|
-
j += 1;
|
2911
|
-
}
|
2912
|
-
if ( j ) {
|
2913
|
-
child.data = data = data.slice( j );
|
2914
|
-
ll -= j;
|
2915
|
-
}
|
2916
|
-
}
|
2917
|
-
if ( i + 1 === l || !isInline( children[ i + 1 ] ) ) {
|
2918
|
-
j = ll;
|
2919
|
-
while ( j > 0 && !notWS.test( data.charAt( j - 1 ) ) ) {
|
2920
|
-
j -= 1;
|
2921
|
-
}
|
2922
|
-
if ( j < ll ) {
|
2923
|
-
child.data = data.slice( 0, j );
|
2924
|
-
}
|
2925
|
-
}
|
2926
|
-
continue;
|
2927
|
-
}
|
2928
|
-
// If we have just white space, it may still be important if it
|
2929
|
-
// separates two inline nodes, e.g. "<a>link</a> <a>link</a>".
|
2930
|
-
else if ( i && i + 1 < l &&
|
2931
|
-
isInline( children[ i - 1 ] ) &&
|
2932
|
-
isInline( children[ i + 1 ] ) ) {
|
2933
|
-
child.data = ' ';
|
2934
|
-
continue;
|
2935
|
-
}
|
2936
|
-
}
|
2937
|
-
node.removeChild( child );
|
2938
|
-
i -= 1;
|
2939
|
-
l -= 1;
|
2940
|
-
}
|
3133
|
+
// Save undo checkpoint
|
3134
|
+
this._recordUndoState( range );
|
3135
|
+
this._getRangeAndRemoveBookmark( range );
|
3136
|
+
|
3137
|
+
if ( remove ) {
|
3138
|
+
range = this._removeFormat( remove.tag.toUpperCase(),
|
3139
|
+
remove.attributes || {}, range, partial );
|
3140
|
+
}
|
3141
|
+
if ( add ) {
|
3142
|
+
range = this._addFormat( add.tag.toUpperCase(),
|
3143
|
+
add.attributes || {}, range );
|
3144
|
+
}
|
3145
|
+
|
3146
|
+
this.setSelection( range );
|
3147
|
+
this._updatePath( range, true );
|
3148
|
+
|
3149
|
+
// We're not still in an undo state
|
3150
|
+
if ( !canObserveMutations ) {
|
3151
|
+
this._docWasChanged();
|
2941
3152
|
}
|
2942
|
-
return node;
|
2943
|
-
};
|
2944
3153
|
|
2945
|
-
|
2946
|
-
return node.nodeType === ELEMENT_NODE ?
|
2947
|
-
node.nodeName === 'BR' :
|
2948
|
-
notWS.test( node.data );
|
3154
|
+
return this;
|
2949
3155
|
};
|
2950
|
-
|
2951
|
-
|
2952
|
-
|
2953
|
-
|
2954
|
-
|
2955
|
-
|
2956
|
-
|
2957
|
-
block, SHOW_ELEMENT|SHOW_TEXT, notWSTextNode );
|
2958
|
-
walker.currentNode = br;
|
2959
|
-
return !!walker.nextNode();
|
3156
|
+
|
3157
|
+
// --- Block formatting ---
|
3158
|
+
|
3159
|
+
var tagAfterSplit = {
|
3160
|
+
DT: 'DD',
|
3161
|
+
DD: 'DT',
|
3162
|
+
LI: 'LI'
|
2960
3163
|
};
|
2961
3164
|
|
2962
|
-
|
2963
|
-
|
2964
|
-
|
2965
|
-
|
2966
|
-
|
2967
|
-
// each block will then have them added back in a later fixCursor method
|
2968
|
-
// call.
|
2969
|
-
var cleanupBRs = function ( root ) {
|
2970
|
-
var brs = root.querySelectorAll( 'BR' ),
|
2971
|
-
brBreaksLine = [],
|
2972
|
-
l = brs.length,
|
2973
|
-
i, br, block;
|
3165
|
+
var splitBlock = function ( self, block, node, offset ) {
|
3166
|
+
var splitTag = tagAfterSplit[ block.nodeName ],
|
3167
|
+
splitProperties = null,
|
3168
|
+
nodeAfterSplit = split( node, offset, block.parentNode ),
|
3169
|
+
config = self._config;
|
2974
3170
|
|
2975
|
-
|
2976
|
-
|
2977
|
-
|
2978
|
-
// therefore seem to not be a line break. But in its original context it
|
2979
|
-
// was, so we should also convert it to a block split.
|
2980
|
-
for ( i = 0; i < l; i += 1 ) {
|
2981
|
-
brBreaksLine[i] = isLineBreak( brs[i] );
|
3171
|
+
if ( !splitTag ) {
|
3172
|
+
splitTag = config.blockTag;
|
3173
|
+
splitProperties = config.blockAttributes;
|
2982
3174
|
}
|
2983
|
-
|
2984
|
-
|
2985
|
-
|
2986
|
-
block =
|
2987
|
-
|
2988
|
-
|
2989
|
-
block =
|
2990
|
-
}
|
2991
|
-
// If this is not inside a block, replace it by wrapping
|
2992
|
-
// inlines in a <div>.
|
2993
|
-
if ( !isBlock( block ) ) {
|
2994
|
-
fixContainer( block );
|
2995
|
-
}
|
2996
|
-
else {
|
2997
|
-
// If it doesn't break a line, just remove it; it's not doing
|
2998
|
-
// anything useful. We'll add it back later if required by the
|
2999
|
-
// browser. If it breaks a line, split the block or leave it as
|
3000
|
-
// appropriate.
|
3001
|
-
if ( brBreaksLine[l] ) {
|
3002
|
-
// If in a <div>, split, but anywhere else we might change
|
3003
|
-
// the formatting too much (e.g. <li> -> to two list items!)
|
3004
|
-
// so just play it safe and leave it.
|
3005
|
-
if ( block.nodeName !== 'DIV' ) {
|
3006
|
-
continue;
|
3007
|
-
}
|
3008
|
-
split( br.parentNode, br, block.parentNode );
|
3009
|
-
}
|
3010
|
-
detach( br );
|
3175
|
+
|
3176
|
+
// Make sure the new node is the correct type.
|
3177
|
+
if ( !hasTagAttributes( nodeAfterSplit, splitTag, splitProperties ) ) {
|
3178
|
+
block = createElement( nodeAfterSplit.ownerDocument,
|
3179
|
+
splitTag, splitProperties );
|
3180
|
+
if ( nodeAfterSplit.dir ) {
|
3181
|
+
block.dir = nodeAfterSplit.dir;
|
3011
3182
|
}
|
3183
|
+
replaceWith( nodeAfterSplit, block );
|
3184
|
+
block.appendChild( empty( nodeAfterSplit ) );
|
3185
|
+
nodeAfterSplit = block;
|
3012
3186
|
}
|
3187
|
+
return nodeAfterSplit;
|
3013
3188
|
};
|
3014
3189
|
|
3015
|
-
proto.
|
3016
|
-
|
3017
|
-
|
3018
|
-
if ( !last ||
|
3019
|
-
last.nodeName !== this._config.blockTag || !isBlock( last ) ) {
|
3020
|
-
body.appendChild( this.createDefaultBlock() );
|
3190
|
+
proto.forEachBlock = function ( fn, mutates, range ) {
|
3191
|
+
if ( !range && !( range = this.getSelection() ) ) {
|
3192
|
+
return this;
|
3021
3193
|
}
|
3022
|
-
};
|
3023
3194
|
|
3024
|
-
// --- Cut and Paste ---
|
3025
|
-
|
3026
|
-
proto._onCut = function () {
|
3027
3195
|
// Save undo checkpoint
|
3028
|
-
|
3029
|
-
|
3030
|
-
|
3031
|
-
|
3032
|
-
|
3033
|
-
|
3034
|
-
|
3035
|
-
|
3036
|
-
|
3037
|
-
|
3038
|
-
|
3196
|
+
if ( mutates ) {
|
3197
|
+
this._recordUndoState( range );
|
3198
|
+
this._getRangeAndRemoveBookmark( range );
|
3199
|
+
}
|
3200
|
+
|
3201
|
+
var start = getStartBlockOfRange( range ),
|
3202
|
+
end = getEndBlockOfRange( range );
|
3203
|
+
if ( start && end ) {
|
3204
|
+
do {
|
3205
|
+
if ( fn( start ) || start === end ) { break; }
|
3206
|
+
} while ( start = getNextBlock( start ) );
|
3207
|
+
}
|
3208
|
+
|
3209
|
+
if ( mutates ) {
|
3210
|
+
this.setSelection( range );
|
3211
|
+
|
3212
|
+
// Path may have changed
|
3213
|
+
this._updatePath( range, true );
|
3214
|
+
|
3215
|
+
// We're not still in an undo state
|
3216
|
+
if ( !canObserveMutations ) {
|
3217
|
+
this._docWasChanged();
|
3039
3218
|
}
|
3040
|
-
}
|
3219
|
+
}
|
3220
|
+
return this;
|
3041
3221
|
};
|
3042
3222
|
|
3043
|
-
proto.
|
3044
|
-
if ( this.
|
3223
|
+
proto.modifyBlocks = function ( modify, range ) {
|
3224
|
+
if ( !range && !( range = this.getSelection() ) ) {
|
3225
|
+
return this;
|
3226
|
+
}
|
3045
3227
|
|
3046
|
-
//
|
3047
|
-
|
3048
|
-
|
3049
|
-
|
3050
|
-
|
3051
|
-
l, type;
|
3052
|
-
if ( items ) {
|
3053
|
-
l = items.length;
|
3054
|
-
while ( l-- ) {
|
3055
|
-
type = items[l].type;
|
3056
|
-
if ( type === 'text/html' ) {
|
3057
|
-
hasImage = false;
|
3058
|
-
break;
|
3059
|
-
}
|
3060
|
-
if ( /^image\/.*/.test( type ) ) {
|
3061
|
-
hasImage = true;
|
3062
|
-
}
|
3063
|
-
}
|
3064
|
-
if ( hasImage ) {
|
3065
|
-
event.preventDefault();
|
3066
|
-
this.fireEvent( 'dragover', {
|
3067
|
-
dataTransfer: clipboardData,
|
3068
|
-
/*jshint loopfunc: true */
|
3069
|
-
preventDefault: function () {
|
3070
|
-
fireDrop = true;
|
3071
|
-
}
|
3072
|
-
/*jshint loopfunc: false */
|
3073
|
-
});
|
3074
|
-
if ( fireDrop ) {
|
3075
|
-
this.fireEvent( 'drop', {
|
3076
|
-
dataTransfer: clipboardData
|
3077
|
-
});
|
3078
|
-
}
|
3079
|
-
return;
|
3080
|
-
}
|
3228
|
+
// 1. Save undo checkpoint and bookmark selection
|
3229
|
+
if ( this._isInUndoState ) {
|
3230
|
+
this._saveRangeToBookmark( range );
|
3231
|
+
} else {
|
3232
|
+
this._recordUndoState( range );
|
3081
3233
|
}
|
3082
3234
|
|
3083
|
-
|
3235
|
+
// 2. Expand range to block boundaries
|
3236
|
+
expandRangeToBlockBoundaries( range );
|
3084
3237
|
|
3085
|
-
|
3086
|
-
|
3087
|
-
|
3088
|
-
|
3238
|
+
// 3. Remove range.
|
3239
|
+
var body = this._body,
|
3240
|
+
frag;
|
3241
|
+
moveRangeBoundariesUpTree( range, body );
|
3242
|
+
frag = extractContentsOfRange( range, body );
|
3089
3243
|
|
3090
|
-
//
|
3091
|
-
|
3092
|
-
self._getRangeAndRemoveBookmark( range );
|
3244
|
+
// 4. Modify tree of fragment and reinsert.
|
3245
|
+
insertNodeInRange( range, modify.call( this, frag ) );
|
3093
3246
|
|
3094
|
-
//
|
3095
|
-
|
3096
|
-
|
3097
|
-
|
3098
|
-
|
3099
|
-
endOffset = range.endOffset;
|
3100
|
-
startBlock = getStartBlockOfRange( range );
|
3247
|
+
// 5. Merge containers at edges
|
3248
|
+
if ( range.endOffset < range.endContainer.childNodes.length ) {
|
3249
|
+
mergeContainers( range.endContainer.childNodes[ range.endOffset ] );
|
3250
|
+
}
|
3251
|
+
mergeContainers( range.startContainer.childNodes[ range.startOffset ] );
|
3101
3252
|
|
3102
|
-
//
|
3103
|
-
|
3104
|
-
var pasteArea = this.createElement( 'DIV', {
|
3105
|
-
style: 'position: absolute; overflow: hidden; top:' +
|
3106
|
-
( body.scrollTop +
|
3107
|
-
( startBlock ? startBlock.getBoundingClientRect().top : 0 ) ) +
|
3108
|
-
'px; left: 0; width: 1px; height: 1px;'
|
3109
|
-
});
|
3110
|
-
body.appendChild( pasteArea );
|
3111
|
-
range.selectNodeContents( pasteArea );
|
3253
|
+
// 6. Restore selection
|
3254
|
+
this._getRangeAndRemoveBookmark( range );
|
3112
3255
|
this.setSelection( range );
|
3256
|
+
this._updatePath( range, true );
|
3113
3257
|
|
3114
|
-
//
|
3115
|
-
|
3116
|
-
|
3117
|
-
|
3118
|
-
try {
|
3119
|
-
// Get the pasted content and clean
|
3120
|
-
var frag = empty( detach( pasteArea ) ),
|
3121
|
-
first = frag.firstChild,
|
3122
|
-
range = self._createRange(
|
3123
|
-
startContainer, startOffset, endContainer, endOffset );
|
3258
|
+
// 7. We're not still in an undo state
|
3259
|
+
if ( !canObserveMutations ) {
|
3260
|
+
this._docWasChanged();
|
3261
|
+
}
|
3124
3262
|
|
3125
|
-
|
3126
|
-
|
3127
|
-
// Safari and IE like putting extra divs around things.
|
3128
|
-
if ( first === frag.lastChild &&
|
3129
|
-
first.nodeName === 'DIV' ) {
|
3130
|
-
frag.replaceChild( empty( first ), first );
|
3131
|
-
}
|
3263
|
+
return this;
|
3264
|
+
};
|
3132
3265
|
|
3133
|
-
|
3134
|
-
|
3135
|
-
|
3136
|
-
|
3137
|
-
|
3138
|
-
|
3139
|
-
var node = frag,
|
3140
|
-
doPaste = true,
|
3141
|
-
event = {
|
3142
|
-
fragment: frag,
|
3143
|
-
preventDefault: function () {
|
3144
|
-
doPaste = false;
|
3145
|
-
},
|
3146
|
-
isDefaultPrevented: function () {
|
3147
|
-
return !doPaste;
|
3148
|
-
}
|
3149
|
-
};
|
3150
|
-
while ( node = getNextBlock( node ) ) {
|
3151
|
-
fixCursor( node );
|
3152
|
-
}
|
3266
|
+
var increaseBlockQuoteLevel = function ( frag ) {
|
3267
|
+
return this.createElement( 'BLOCKQUOTE',
|
3268
|
+
this._config.tagAttributes.blockquote, [
|
3269
|
+
frag
|
3270
|
+
]);
|
3271
|
+
};
|
3153
3272
|
|
3154
|
-
|
3273
|
+
var decreaseBlockQuoteLevel = function ( frag ) {
|
3274
|
+
var blockquotes = frag.querySelectorAll( 'blockquote' );
|
3275
|
+
Array.prototype.filter.call( blockquotes, function ( el ) {
|
3276
|
+
return !getNearest( el.parentNode, 'BLOCKQUOTE' );
|
3277
|
+
}).forEach( function ( el ) {
|
3278
|
+
replaceWith( el, empty( el ) );
|
3279
|
+
});
|
3280
|
+
return frag;
|
3281
|
+
};
|
3155
3282
|
|
3156
|
-
|
3157
|
-
|
3158
|
-
|
3159
|
-
|
3160
|
-
|
3161
|
-
|
3162
|
-
|
3163
|
-
|
3164
|
-
|
3283
|
+
var removeBlockQuote = function (/* frag */) {
|
3284
|
+
return this.createDefaultBlock([
|
3285
|
+
this.createElement( 'INPUT', {
|
3286
|
+
id: startSelectionId,
|
3287
|
+
type: 'hidden'
|
3288
|
+
}),
|
3289
|
+
this.createElement( 'INPUT', {
|
3290
|
+
id: endSelectionId,
|
3291
|
+
type: 'hidden'
|
3292
|
+
})
|
3293
|
+
]);
|
3294
|
+
};
|
3295
|
+
|
3296
|
+
var makeList = function ( self, frag, type ) {
|
3297
|
+
var walker = getBlockWalker( frag ),
|
3298
|
+
node, tag, prev, newLi,
|
3299
|
+
tagAttributes = self._config.tagAttributes,
|
3300
|
+
listAttrs = tagAttributes[ type.toLowerCase() ],
|
3301
|
+
listItemAttrs = tagAttributes.li;
|
3302
|
+
|
3303
|
+
while ( node = walker.nextNode() ) {
|
3304
|
+
tag = node.parentNode.nodeName;
|
3305
|
+
if ( tag !== 'LI' ) {
|
3306
|
+
newLi = self.createElement( 'LI', listItemAttrs );
|
3307
|
+
if ( node.dir ) {
|
3308
|
+
newLi.dir = node.dir;
|
3309
|
+
}
|
3310
|
+
|
3311
|
+
// Have we replaced the previous block with a new <ul>/<ol>?
|
3312
|
+
if ( ( prev = node.previousSibling ) &&
|
3313
|
+
prev.nodeName === type ) {
|
3314
|
+
prev.appendChild( newLi );
|
3315
|
+
}
|
3316
|
+
// Otherwise, replace this block with the <ul>/<ol>
|
3317
|
+
else {
|
3318
|
+
replaceWith(
|
3319
|
+
node,
|
3320
|
+
self.createElement( type, listAttrs, [
|
3321
|
+
newLi
|
3322
|
+
])
|
3323
|
+
);
|
3324
|
+
}
|
3325
|
+
newLi.appendChild( node );
|
3326
|
+
} else {
|
3327
|
+
node = node.parentNode.parentNode;
|
3328
|
+
tag = node.nodeName;
|
3329
|
+
if ( tag !== type && ( /^[OU]L$/.test( tag ) ) ) {
|
3330
|
+
replaceWith( node,
|
3331
|
+
self.createElement( type, listAttrs, [ empty( node ) ] )
|
3332
|
+
);
|
3165
3333
|
}
|
3166
|
-
|
3167
|
-
self.setSelection( range );
|
3168
|
-
self._updatePath( range, true );
|
3169
|
-
|
3170
|
-
self._awaitingPaste = false;
|
3171
|
-
} catch ( error ) {
|
3172
|
-
self.didError( error );
|
3173
3334
|
}
|
3174
|
-
}
|
3335
|
+
}
|
3175
3336
|
};
|
3176
3337
|
|
3177
|
-
|
3178
|
-
|
3179
|
-
|
3180
|
-
8: 'backspace',
|
3181
|
-
9: 'tab',
|
3182
|
-
13: 'enter',
|
3183
|
-
32: 'space',
|
3184
|
-
37: 'left',
|
3185
|
-
39: 'right',
|
3186
|
-
46: 'delete',
|
3187
|
-
219: '[',
|
3188
|
-
221: ']'
|
3338
|
+
var makeUnorderedList = function ( frag ) {
|
3339
|
+
makeList( this, frag, 'UL' );
|
3340
|
+
return frag;
|
3189
3341
|
};
|
3190
3342
|
|
3191
|
-
|
3192
|
-
|
3193
|
-
|
3194
|
-
|
3195
|
-
modifiers = '',
|
3196
|
-
range = this.getSelection();
|
3343
|
+
var makeOrderedList = function ( frag ) {
|
3344
|
+
makeList( this, frag, 'OL' );
|
3345
|
+
return frag;
|
3346
|
+
};
|
3197
3347
|
|
3198
|
-
|
3199
|
-
|
3200
|
-
|
3201
|
-
|
3202
|
-
|
3348
|
+
var removeList = function ( frag ) {
|
3349
|
+
var lists = frag.querySelectorAll( 'UL, OL' ),
|
3350
|
+
i, l, ll, list, listFrag, children, child;
|
3351
|
+
for ( i = 0, l = lists.length; i < l; i += 1 ) {
|
3352
|
+
list = lists[i];
|
3353
|
+
listFrag = empty( list );
|
3354
|
+
children = listFrag.childNodes;
|
3355
|
+
ll = children.length;
|
3356
|
+
while ( ll-- ) {
|
3357
|
+
child = children[ll];
|
3358
|
+
replaceWith( child, empty( child ) );
|
3203
3359
|
}
|
3360
|
+
fixContainer( listFrag );
|
3361
|
+
replaceWith( list, listFrag );
|
3204
3362
|
}
|
3363
|
+
return frag;
|
3364
|
+
};
|
3205
3365
|
|
3206
|
-
|
3207
|
-
|
3208
|
-
|
3209
|
-
|
3210
|
-
|
3211
|
-
|
3212
|
-
|
3213
|
-
|
3214
|
-
|
3215
|
-
|
3216
|
-
|
3217
|
-
|
3218
|
-
|
3219
|
-
|
3220
|
-
|
3221
|
-
|
3222
|
-
|
3366
|
+
var increaseListLevel = function ( frag ) {
|
3367
|
+
var items = frag.querySelectorAll( 'LI' ),
|
3368
|
+
i, l, item,
|
3369
|
+
type, newParent,
|
3370
|
+
tagAttributes = this._config.tagAttributes,
|
3371
|
+
listItemAttrs = tagAttributes.li,
|
3372
|
+
listAttrs;
|
3373
|
+
for ( i = 0, l = items.length; i < l; i += 1 ) {
|
3374
|
+
item = items[i];
|
3375
|
+
if ( !isContainer( item.firstChild ) ) {
|
3376
|
+
// type => 'UL' or 'OL'
|
3377
|
+
type = item.parentNode.nodeName;
|
3378
|
+
newParent = item.previousSibling;
|
3379
|
+
if ( !newParent || !( newParent = newParent.lastChild ) ||
|
3380
|
+
newParent.nodeName !== type ) {
|
3381
|
+
listAttrs = tagAttributes[ type.toLowerCase() ];
|
3382
|
+
replaceWith(
|
3383
|
+
item,
|
3384
|
+
this.createElement( 'LI', listItemAttrs, [
|
3385
|
+
newParent = this.createElement( type, listAttrs )
|
3386
|
+
])
|
3387
|
+
);
|
3388
|
+
}
|
3389
|
+
newParent.appendChild( item );
|
3390
|
+
}
|
3223
3391
|
}
|
3224
|
-
|
3225
|
-
|
3226
|
-
if ( event.shiftKey ) { modifiers += 'shift-'; }
|
3392
|
+
return frag;
|
3393
|
+
};
|
3227
3394
|
|
3228
|
-
|
3395
|
+
var decreaseListLevel = function ( frag ) {
|
3396
|
+
var items = frag.querySelectorAll( 'LI' );
|
3397
|
+
Array.prototype.filter.call( items, function ( el ) {
|
3398
|
+
return !isContainer( el.firstChild );
|
3399
|
+
}).forEach( function ( item ) {
|
3400
|
+
var parent = item.parentNode,
|
3401
|
+
newParent = parent.parentNode,
|
3402
|
+
first = item.firstChild,
|
3403
|
+
node = first,
|
3404
|
+
next;
|
3405
|
+
if ( item.previousSibling ) {
|
3406
|
+
parent = split( parent, item, newParent );
|
3407
|
+
}
|
3408
|
+
while ( node ) {
|
3409
|
+
next = node.nextSibling;
|
3410
|
+
if ( isContainer( node ) ) {
|
3411
|
+
break;
|
3412
|
+
}
|
3413
|
+
newParent.insertBefore( node, parent );
|
3414
|
+
node = next;
|
3415
|
+
}
|
3416
|
+
if ( newParent.nodeName === 'LI' && first.previousSibling ) {
|
3417
|
+
split( newParent, first, newParent.parentNode );
|
3418
|
+
}
|
3419
|
+
while ( item !== frag && !item.childNodes.length ) {
|
3420
|
+
parent = item.parentNode;
|
3421
|
+
parent.removeChild( item );
|
3422
|
+
item = parent;
|
3423
|
+
}
|
3424
|
+
}, this );
|
3425
|
+
fixContainer( frag );
|
3426
|
+
return frag;
|
3427
|
+
};
|
3229
3428
|
|
3230
|
-
|
3231
|
-
|
3232
|
-
|
3233
|
-
|
3234
|
-
|
3235
|
-
this.
|
3236
|
-
// Delete the selection
|
3237
|
-
deleteContentsOfRange( range );
|
3238
|
-
this._ensureBottomLine();
|
3239
|
-
this.setSelection( range );
|
3240
|
-
this._updatePath( range, true );
|
3429
|
+
proto._ensureBottomLine = function () {
|
3430
|
+
var body = this._body,
|
3431
|
+
last = body.lastElementChild;
|
3432
|
+
if ( !last ||
|
3433
|
+
last.nodeName !== this._config.blockTag || !isBlock( last ) ) {
|
3434
|
+
body.appendChild( this.createDefaultBlock() );
|
3241
3435
|
}
|
3242
3436
|
};
|
3243
3437
|
|
3438
|
+
// --- Keyboard interaction ---
|
3439
|
+
|
3244
3440
|
proto.setKeyHandler = function ( key, fn ) {
|
3245
3441
|
this._keyHandlers[ key ] = fn;
|
3246
3442
|
return this;
|
@@ -3299,7 +3495,7 @@ proto.setHTML = function ( html ) {
|
|
3299
3495
|
div.innerHTML = html;
|
3300
3496
|
frag.appendChild( empty( div ) );
|
3301
3497
|
|
3302
|
-
cleanTree( frag
|
3498
|
+
cleanTree( frag );
|
3303
3499
|
cleanupBRs( frag );
|
3304
3500
|
|
3305
3501
|
fixContainer( frag );
|
@@ -3394,10 +3590,42 @@ proto.insertImage = function ( src, attributes ) {
|
|
3394
3590
|
return img;
|
3395
3591
|
};
|
3396
3592
|
|
3593
|
+
var linkRegExp = /\b((?:(?:ht|f)tps?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,}\/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))|([\w\-.%+]+@(?:[\w\-]+\.)+[A-Z]{2,}\b)/i;
|
3594
|
+
|
3595
|
+
var addLinks = function ( frag ) {
|
3596
|
+
var doc = frag.ownerDocument,
|
3597
|
+
walker = new TreeWalker( frag, SHOW_TEXT,
|
3598
|
+
function ( node ) {
|
3599
|
+
return !getNearest( node, 'A' );
|
3600
|
+
}, false ),
|
3601
|
+
node, data, parent, match, index, endIndex, child;
|
3602
|
+
while ( node = walker.nextNode() ) {
|
3603
|
+
data = node.data;
|
3604
|
+
parent = node.parentNode;
|
3605
|
+
while ( match = linkRegExp.exec( data ) ) {
|
3606
|
+
index = match.index;
|
3607
|
+
endIndex = index + match[0].length;
|
3608
|
+
if ( index ) {
|
3609
|
+
child = doc.createTextNode( data.slice( 0, index ) );
|
3610
|
+
parent.insertBefore( child, node );
|
3611
|
+
}
|
3612
|
+
child = doc.createElement( 'A' );
|
3613
|
+
child.textContent = data.slice( index, endIndex );
|
3614
|
+
child.href = match[1] ?
|
3615
|
+
/^(?:ht|f)tps?:/.test( match[1] ) ?
|
3616
|
+
match[1] :
|
3617
|
+
'http://' + match[1] :
|
3618
|
+
'mailto:' + match[2];
|
3619
|
+
parent.insertBefore( child, node );
|
3620
|
+
node.data = data = data.slice( endIndex );
|
3621
|
+
}
|
3622
|
+
}
|
3623
|
+
};
|
3624
|
+
|
3397
3625
|
// Insert HTML at the cursor location. If the selection is not collapsed
|
3398
3626
|
// insertTreeFragmentIntoRange will delete the selection so that it is replaced
|
3399
3627
|
// by the html being inserted.
|
3400
|
-
proto.insertHTML = function ( html ) {
|
3628
|
+
proto.insertHTML = function ( html, isPaste ) {
|
3401
3629
|
var range = this.getSelection(),
|
3402
3630
|
frag = this._doc.createDocumentFragment(),
|
3403
3631
|
div = this.createElement( 'DIV' );
|
@@ -3411,24 +3639,37 @@ proto.insertHTML = function ( html ) {
|
|
3411
3639
|
this._getRangeAndRemoveBookmark( range );
|
3412
3640
|
|
3413
3641
|
try {
|
3414
|
-
frag
|
3642
|
+
var node = frag;
|
3643
|
+
var event = {
|
3644
|
+
fragment: frag,
|
3645
|
+
preventDefault: function () {
|
3646
|
+
this.defaultPrevented = true;
|
3647
|
+
},
|
3648
|
+
defaultPrevented: false
|
3649
|
+
};
|
3650
|
+
|
3415
3651
|
addLinks( frag );
|
3416
|
-
cleanTree( frag
|
3652
|
+
cleanTree( frag );
|
3417
3653
|
cleanupBRs( frag );
|
3418
3654
|
removeEmptyInlines( frag );
|
3419
|
-
|
3655
|
+
frag.normalize();
|
3420
3656
|
|
3421
|
-
var node = frag;
|
3422
3657
|
while ( node = getNextBlock( node ) ) {
|
3423
3658
|
fixCursor( node );
|
3424
3659
|
}
|
3425
3660
|
|
3426
|
-
|
3427
|
-
|
3428
|
-
|
3661
|
+
if ( isPaste ) {
|
3662
|
+
this.fireEvent( 'willPaste', event );
|
3663
|
+
}
|
3664
|
+
|
3665
|
+
if ( !event.defaultPrevented ) {
|
3666
|
+
insertTreeFragmentIntoRange( range, event.fragment );
|
3667
|
+
if ( !canObserveMutations ) {
|
3668
|
+
this._docWasChanged();
|
3669
|
+
}
|
3670
|
+
range.collapse( false );
|
3671
|
+
this._ensureBottomLine();
|
3429
3672
|
}
|
3430
|
-
range.collapse( false );
|
3431
|
-
this._ensureBottomLine();
|
3432
3673
|
|
3433
3674
|
this.setSelection( range );
|
3434
3675
|
this._updatePath( range, true );
|
@@ -3438,6 +3679,24 @@ proto.insertHTML = function ( html ) {
|
|
3438
3679
|
return this;
|
3439
3680
|
};
|
3440
3681
|
|
3682
|
+
proto.insertPlainText = function ( plainText, isPaste ) {
|
3683
|
+
var lines = plainText.split( '\n' ),
|
3684
|
+
i, l, line;
|
3685
|
+
for ( i = 0, l = lines.length; i < l; i += 1 ) {
|
3686
|
+
line = lines[i];
|
3687
|
+
line = line.split( '&' ).join( '&' )
|
3688
|
+
.split( '<' ).join( '<' )
|
3689
|
+
.split( '>' ).join( '>' )
|
3690
|
+
.replace( / (?= )/g, ' ' );
|
3691
|
+
// Wrap all but first/last lines in <div></div>
|
3692
|
+
if ( i && i + 1 < l ) {
|
3693
|
+
line = '<DIV>' + ( line || '<BR>' ) + '</DIV>';
|
3694
|
+
}
|
3695
|
+
lines[i] = line;
|
3696
|
+
}
|
3697
|
+
return this.insertHTML( lines.join( '' ), isPaste );
|
3698
|
+
};
|
3699
|
+
|
3441
3700
|
// --- Formatting ---
|
3442
3701
|
|
3443
3702
|
var command = function ( method, arg, arg2 ) {
|
@@ -3453,16 +3712,8 @@ proto.addStyles = function ( styles ) {
|
|
3453
3712
|
style = this.createElement( 'STYLE', {
|
3454
3713
|
type: 'text/css'
|
3455
3714
|
});
|
3456
|
-
|
3457
|
-
|
3458
|
-
// or you get the IE7 CSS parser!
|
3459
|
-
head.appendChild( style );
|
3460
|
-
style.styleSheet.cssText = styles;
|
3461
|
-
} else {
|
3462
|
-
// Everyone else
|
3463
|
-
style.appendChild( this._doc.createTextNode( styles ) );
|
3464
|
-
head.appendChild( style );
|
3465
|
-
}
|
3715
|
+
style.appendChild( this._doc.createTextNode( styles ) );
|
3716
|
+
head.appendChild( style );
|
3466
3717
|
}
|
3467
3718
|
return this;
|
3468
3719
|
};
|
@@ -3547,7 +3798,7 @@ proto.setTextColour = function ( colour ) {
|
|
3547
3798
|
tag: 'SPAN',
|
3548
3799
|
attributes: {
|
3549
3800
|
'class': 'colour',
|
3550
|
-
style: 'color:
|
3801
|
+
style: 'color:' + colour
|
3551
3802
|
}
|
3552
3803
|
}, {
|
3553
3804
|
tag: 'SPAN',
|
@@ -3561,7 +3812,7 @@ proto.setHighlightColour = function ( colour ) {
|
|
3561
3812
|
tag: 'SPAN',
|
3562
3813
|
attributes: {
|
3563
3814
|
'class': 'highlight',
|
3564
|
-
style: 'background-color:
|
3815
|
+
style: 'background-color:' + colour
|
3565
3816
|
}
|
3566
3817
|
}, {
|
3567
3818
|
tag: 'SPAN',
|
@@ -3591,6 +3842,112 @@ proto.setTextDirection = function ( direction ) {
|
|
3591
3842
|
return this.focus();
|
3592
3843
|
};
|
3593
3844
|
|
3845
|
+
function removeFormatting ( self, root, clean ) {
|
3846
|
+
var node, next;
|
3847
|
+
for ( node = root.firstChild; node; node = next ) {
|
3848
|
+
next = node.nextSibling;
|
3849
|
+
if ( isInline( node ) ) {
|
3850
|
+
if ( node.nodeType === TEXT_NODE || node.nodeName === 'BR' || node.nodeName === 'IMG' ) {
|
3851
|
+
clean.appendChild( node );
|
3852
|
+
continue;
|
3853
|
+
}
|
3854
|
+
} else if ( isBlock( node ) ) {
|
3855
|
+
clean.appendChild( self.createDefaultBlock([
|
3856
|
+
removeFormatting(
|
3857
|
+
self, node, self._doc.createDocumentFragment() )
|
3858
|
+
]));
|
3859
|
+
continue;
|
3860
|
+
}
|
3861
|
+
removeFormatting( self, node, clean );
|
3862
|
+
}
|
3863
|
+
return clean;
|
3864
|
+
}
|
3865
|
+
|
3866
|
+
proto.removeAllFormatting = function ( range ) {
|
3867
|
+
if ( !range && !( range = this.getSelection() ) || range.collapsed ) {
|
3868
|
+
return this;
|
3869
|
+
}
|
3870
|
+
|
3871
|
+
var stopNode = range.commonAncestorContainer;
|
3872
|
+
while ( stopNode && !isBlock( stopNode ) ) {
|
3873
|
+
stopNode = stopNode.parentNode;
|
3874
|
+
}
|
3875
|
+
if ( !stopNode ) {
|
3876
|
+
expandRangeToBlockBoundaries( range );
|
3877
|
+
stopNode = this._body;
|
3878
|
+
}
|
3879
|
+
if ( stopNode.nodeType === TEXT_NODE ) {
|
3880
|
+
return this;
|
3881
|
+
}
|
3882
|
+
|
3883
|
+
// Record undo point
|
3884
|
+
this._recordUndoState( range );
|
3885
|
+
this._getRangeAndRemoveBookmark( range );
|
3886
|
+
|
3887
|
+
|
3888
|
+
// Avoid splitting where we're already at edges.
|
3889
|
+
moveRangeBoundariesUpTree( range, stopNode );
|
3890
|
+
|
3891
|
+
// Split the selection up to the block, or if whole selection in same
|
3892
|
+
// block, expand range boundaries to ends of block and split up to body.
|
3893
|
+
var doc = stopNode.ownerDocument;
|
3894
|
+
var startContainer = range.startContainer;
|
3895
|
+
var startOffset = range.startOffset;
|
3896
|
+
var endContainer = range.endContainer;
|
3897
|
+
var endOffset = range.endOffset;
|
3898
|
+
|
3899
|
+
// Split end point first to avoid problems when end and start
|
3900
|
+
// in same container.
|
3901
|
+
var formattedNodes = doc.createDocumentFragment();
|
3902
|
+
var cleanNodes = doc.createDocumentFragment();
|
3903
|
+
var nodeAfterSplit = split( endContainer, endOffset, stopNode );
|
3904
|
+
var nodeInSplit = split( startContainer, startOffset, stopNode );
|
3905
|
+
var nextNode, _range, childNodes;
|
3906
|
+
|
3907
|
+
// Then replace contents in split with a cleaned version of the same:
|
3908
|
+
// blocks become default blocks, text and leaf nodes survive, everything
|
3909
|
+
// else is obliterated.
|
3910
|
+
while ( nodeInSplit !== nodeAfterSplit ) {
|
3911
|
+
nextNode = nodeInSplit.nextSibling;
|
3912
|
+
formattedNodes.appendChild( nodeInSplit );
|
3913
|
+
nodeInSplit = nextNode;
|
3914
|
+
}
|
3915
|
+
removeFormatting( this, formattedNodes, cleanNodes );
|
3916
|
+
cleanNodes.normalize();
|
3917
|
+
nodeInSplit = cleanNodes.firstChild;
|
3918
|
+
nextNode = cleanNodes.lastChild;
|
3919
|
+
|
3920
|
+
// Restore selection
|
3921
|
+
childNodes = stopNode.childNodes;
|
3922
|
+
if ( nodeInSplit ) {
|
3923
|
+
stopNode.insertBefore( cleanNodes, nodeAfterSplit );
|
3924
|
+
startOffset = indexOf.call( childNodes, nodeInSplit );
|
3925
|
+
endOffset = indexOf.call( childNodes, nextNode ) + 1;
|
3926
|
+
} else {
|
3927
|
+
startOffset = indexOf.call( childNodes, nodeAfterSplit );
|
3928
|
+
endOffset = startOffset;
|
3929
|
+
}
|
3930
|
+
|
3931
|
+
// Merge text nodes at edges, if possible
|
3932
|
+
_range = {
|
3933
|
+
startContainer: stopNode,
|
3934
|
+
startOffset: startOffset,
|
3935
|
+
endContainer: stopNode,
|
3936
|
+
endOffset: endOffset
|
3937
|
+
};
|
3938
|
+
mergeInlines( stopNode, _range );
|
3939
|
+
range.setStart( _range.startContainer, _range.startOffset );
|
3940
|
+
range.setEnd( _range.endContainer, _range.endOffset );
|
3941
|
+
|
3942
|
+
// And move back down the tree
|
3943
|
+
moveRangeBoundariesDownTree( range );
|
3944
|
+
|
3945
|
+
this.setSelection( range );
|
3946
|
+
this._updatePath( range, true );
|
3947
|
+
|
3948
|
+
return this.focus();
|
3949
|
+
};
|
3950
|
+
|
3594
3951
|
proto.increaseQuoteLevel = command( 'modifyBlocks', increaseBlockQuoteLevel );
|
3595
3952
|
proto.decreaseQuoteLevel = command( 'modifyBlocks', decreaseBlockQuoteLevel );
|
3596
3953
|
|
@@ -3601,18 +3958,23 @@ proto.removeList = command( 'modifyBlocks', removeList );
|
|
3601
3958
|
proto.increaseListLevel = command( 'modifyBlocks', increaseListLevel );
|
3602
3959
|
proto.decreaseListLevel = command( 'modifyBlocks', decreaseListLevel );
|
3603
3960
|
|
3604
|
-
if (
|
3605
|
-
|
3606
|
-
|
3607
|
-
|
3608
|
-
|
3609
|
-
}
|
3961
|
+
if ( typeof exports === 'object' ) {
|
3962
|
+
module.exports = Squire;
|
3963
|
+
} else if ( typeof define === 'function' && define.amd ) {
|
3964
|
+
define( function () {
|
3965
|
+
return Squire;
|
3966
|
+
});
|
3610
3967
|
} else {
|
3611
|
-
|
3612
|
-
|
3613
|
-
|
3614
|
-
|
3968
|
+
win.Squire = Squire;
|
3969
|
+
|
3970
|
+
if ( top !== win &&
|
3971
|
+
doc.documentElement.getAttribute( 'data-squireinit' ) === 'true' ) {
|
3972
|
+
win.editor = new Squire( doc );
|
3973
|
+
if ( win.onEditorLoad ) {
|
3974
|
+
win.onEditorLoad( win.editor );
|
3975
|
+
win.onEditorLoad = null;
|
3976
|
+
}
|
3615
3977
|
}
|
3616
3978
|
}
|
3617
3979
|
|
3618
|
-
}( document ) );
|
3980
|
+
}( document ) );
|