PluginProbe
Wp-Insert / 1.2
Wp-Insert v1.2
trunk 1.0 1.1 1.2 1.2.1 1.2.2 1.3.0 1.3.1 1.3.3 1.4 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.6.2 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 2.0 All 63 releases
wp-insert / fckeditor / editor / _source / classes / fckstyle.js

fckstyle.js in Wp-Insert 1.2, at fckeditor/editor/_source/classes/fckstyle.js

3,001 lines 47.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2
3 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
4
5 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
6
7 *
8
9 * == BEGIN LICENSE ==
10
11 *
12
13 * Licensed under the terms of any of the following licenses at your
14
15 * choice:
16
17 *
18
19 * - GNU General Public License Version 2 or later (the "GPL")
20
21 * http://www.gnu.org/licenses/gpl.html
22
23 *
24
25 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
26
27 * http://www.gnu.org/licenses/lgpl.html
28
29 *
30
31 * - Mozilla Public License Version 1.1 or later (the "MPL")
32
33 * http://www.mozilla.org/MPL/MPL-1.1.html
34
35 *
36
37 * == END LICENSE ==
38
39 *
40
41 * FCKStyle Class: contains a style definition, and all methods to work with
42
43 * the style in a document.
44
45 */
46
47
48
49 /**
50
51 * @param {Object} styleDesc A "style descriptor" object, containing the raw
52
53 * style definition in the following format:
54
55 * '<style name>' : {
56
57 * Element : '<element name>',
58
59 * Attributes : {
60
61 * '<att name>' : '<att value>',
62
63 * ...
64
65 * },
66
67 * Styles : {
68
69 * '<style name>' : '<style value>',
70
71 * ...
72
73 * },
74
75 * Overrides : '<element name>'|{
76
77 * Element : '<element name>',
78
79 * Attributes : {
80
81 * '<att name>' : '<att value>'|/<att regex>/
82
83 * },
84
85 * Styles : {
86
87 * '<style name>' : '<style value>'|/<style regex>/
88
89 * },
90
91 * }
92
93 * }
94
95 */
96
97 var FCKStyle = function( styleDesc )
98
99 {
100
101 this.Element = ( styleDesc.Element || 'span' ).toLowerCase() ;
102
103 this._StyleDesc = styleDesc ;
104
105 }
106
107
108
109 FCKStyle.prototype =
110
111 {
112
113 /**
114
115 * Get the style type, based on its element name:
116
117 * - FCK_STYLE_BLOCK (0): Block Style
118
119 * - FCK_STYLE_INLINE (1): Inline Style
120
121 * - FCK_STYLE_OBJECT (2): Object Style
122
123 */
124
125 GetType : function()
126
127 {
128
129 var type = this.GetType_$ ;
130
131
132
133 if ( type != undefined )
134
135 return type ;
136
137
138
139 var elementName = this.Element ;
140
141
142
143 if ( elementName == '#' || FCKListsLib.StyleBlockElements[ elementName ] )
144
145 type = FCK_STYLE_BLOCK ;
146
147 else if ( FCKListsLib.StyleObjectElements[ elementName ] )
148
149 type = FCK_STYLE_OBJECT ;
150
151 else
152
153 type = FCK_STYLE_INLINE ;
154
155
156
157 return ( this.GetType_$ = type ) ;
158
159 },
160
161
162
163 /**
164
165 * Apply the style to the current selection.
166
167 */
168
169 ApplyToSelection : function( targetWindow )
170
171 {
172
173 // Create a range for the current selection.
174
175 var range = new FCKDomRange( targetWindow ) ;
176
177 range.MoveToSelection() ;
178
179
180
181 this.ApplyToRange( range, true ) ;
182
183 },
184
185
186
187 /**
188
189 * Apply the style to a FCKDomRange.
190
191 */
192
193 ApplyToRange : function( range, selectIt, updateRange )
194
195 {
196
197 // ApplyToRange is not valid for FCK_STYLE_OBJECT types.
198
199 // Use ApplyToObject instead.
200
201
202
203 switch ( this.GetType() )
204
205 {
206
207 case FCK_STYLE_BLOCK :
208
209 this.ApplyToRange = this._ApplyBlockStyle ;
210
211 break ;
212
213 case FCK_STYLE_INLINE :
214
215 this.ApplyToRange = this._ApplyInlineStyle ;
216
217 break ;
218
219 default :
220
221 return ;
222
223 }
224
225
226
227 this.ApplyToRange( range, selectIt, updateRange ) ;
228
229 },
230
231
232
233 /**
234
235 * Apply the style to an object. Valid for FCK_STYLE_BLOCK types only.
236
237 */
238
239 ApplyToObject : function( objectElement )
240
241 {
242
243 if ( !objectElement )
244
245 return ;
246
247
248
249 this.BuildElement( null, objectElement ) ;
250
251 },
252
253
254
255 /**
256
257 * Remove the style from the current selection.
258
259 */
260
261 RemoveFromSelection : function( targetWindow )
262
263 {
264
265 // Create a range for the current selection.
266
267 var range = new FCKDomRange( targetWindow ) ;
268
269 range.MoveToSelection() ;
270
271
272
273 this.RemoveFromRange( range, true ) ;
274
275 },
276
277
278
279 /**
280
281 * Remove the style from a FCKDomRange. Block type styles will have no
282
283 * effect.
284
285 */
286
287 RemoveFromRange : function( range, selectIt, updateRange )
288
289 {
290
291 var bookmark ;
292
293
294
295 // Create the attribute list to be used later for element comparisons.
296
297 var styleAttribs = this._GetAttribsForComparison() ;
298
299 var styleOverrides = this._GetOverridesForComparison() ;
300
301
302
303 // If collapsed, we are removing all conflicting styles from the range
304
305 // parent tree.
306
307 if ( range.CheckIsCollapsed() )
308
309 {
310
311 // Bookmark the range so we can re-select it after processing.
312
313 var bookmark = range.CreateBookmark( true ) ;
314
315
316
317 // Let's start from the bookmark <span> parent.
318
319 var bookmarkStart = range.GetBookmarkNode( bookmark, true ) ;
320
321
322
323 var path = new FCKElementPath( bookmarkStart.parentNode ) ;
324
325
326
327 // While looping through the path, we'll be saving references to
328
329 // parent elements if the range is in one of their boundaries. In
330
331 // this way, we are able to create a copy of those elements when
332
333 // removing a style if the range is in a boundary limit (see #1270).
334
335 var boundaryElements = [] ;
336
337
338
339 // Check if the range is in the boundary limits of an element
340
341 // (related to #1270).
342
343 var isBoundaryRight = !FCKDomTools.GetNextSibling( bookmarkStart ) ;
344
345 var isBoundary = isBoundaryRight || !FCKDomTools.GetPreviousSibling( bookmarkStart ) ;
346
347
348
349 // This is the last element to be removed in the boundary situation
350
351 // described at #1270.
352
353 var lastBoundaryElement ;
354
355 var boundaryLimitIndex = -1 ;
356
357
358
359 for ( var i = 0 ; i < path.Elements.length ; i++ )
360
361 {
362
363 var pathElement = path.Elements[i] ;
364
365 if ( this.CheckElementRemovable( pathElement ) )
366
367 {
368
369 if ( isBoundary
370
371 && !FCKDomTools.CheckIsEmptyElement( pathElement,
372
373 function( el )
374
375 {
376
377 return ( el != bookmarkStart ) ;
378
379 } )
380
381 )
382
383 {
384
385 lastBoundaryElement = pathElement ;
386
387
388
389 // We'll be continuously including elements in the
390
391 // boundaryElements array, but only those added before
392
393 // setting lastBoundaryElement must be used later, so
394
395 // let's mark the current index here.
396
397 boundaryLimitIndex = boundaryElements.length - 1 ;
398
399 }
400
401 else
402
403 {
404
405 var pathElementName = pathElement.nodeName.toLowerCase() ;
406
407
408
409 if ( pathElementName == this.Element )
410
411 {
412
413 // Remove any attribute that conflict with this style, no
414
415 // matter their values.
416
417 for ( var att in styleAttribs )
418
419 {
420
421 if ( FCKDomTools.HasAttribute( pathElement, att ) )
422
423 {
424
425 switch ( att )
426
427 {
428
429 case 'style' :
430
431 this._RemoveStylesFromElement( pathElement ) ;
432
433 break ;
434
435
436
437 case 'class' :
438
439 // The 'class' element value must match (#1318).
440
441 if ( FCKDomTools.GetAttributeValue( pathElement, att ) != this.GetFinalAttributeValue( att ) )
442
443 continue ;
444
445
446
447 /*jsl:fallthru*/
448
449
450
451 default :
452
453 FCKDomTools.RemoveAttribute( pathElement, att ) ;
454
455 }
456
457 }
458
459 }
460
461 }
462
463
464
465 // Remove overrides defined to the same element name.
466
467 this._RemoveOverrides( pathElement, styleOverrides[ pathElementName ] ) ;
468
469
470
471 // Remove the element if no more attributes are available and it's an inline style element
472
473 if ( this.GetType() == FCK_STYLE_INLINE)
474
475 this._RemoveNoAttribElement( pathElement ) ;
476
477 }
478
479 }
480
481 else if ( isBoundary )
482
483 boundaryElements.push( pathElement ) ;
484
485
486
487 // Check if we are still in a boundary (at the same side).
488
489 isBoundary = isBoundary && ( ( isBoundaryRight && !FCKDomTools.GetNextSibling( pathElement ) ) || ( !isBoundaryRight && !FCKDomTools.GetPreviousSibling( pathElement ) ) ) ;
490
491
492
493 // If we are in an element that is not anymore a boundary, or
494
495 // we are at the last element, let's move things outside the
496
497 // boundary (if available).
498
499 if ( lastBoundaryElement && ( !isBoundary || ( i == path.Elements.length - 1 ) ) )
500
501 {
502
503 // Remove the bookmark node from the DOM.
504
505 var currentElement = FCKDomTools.RemoveNode( bookmarkStart ) ;
506
507
508
509 // Build the collapsed group of elements that are not
510
511 // removed by this style, but share the boundary.
512
513 // (see comment 1 and 2 at #1270)
514
515 for ( var j = 0 ; j <= boundaryLimitIndex ; j++ )
516
517 {
518
519 var newElement = FCKDomTools.CloneElement( boundaryElements[j] ) ;
520
521 newElement.appendChild( currentElement ) ;
522
523 currentElement = newElement ;
524
525 }
526
527
528
529 // Re-insert the bookmark node (and the collapsed elements)
530
531 // in the DOM, in the new position next to the styled element.
532
533 if ( isBoundaryRight )
534
535 FCKDomTools.InsertAfterNode( lastBoundaryElement, currentElement ) ;
536
537 else
538
539 lastBoundaryElement.parentNode.insertBefore( currentElement, lastBoundaryElement ) ;
540
541
542
543 isBoundary = false ;
544
545 lastBoundaryElement = null ;
546
547 }
548
549 }
550
551
552
553 // Re-select the original range.
554
555 if ( selectIt )
556
557 range.SelectBookmark( bookmark ) ;
558
559
560
561 if ( updateRange )
562
563 range.MoveToBookmark( bookmark ) ;
564
565
566
567 return ;
568
569 }
570
571
572
573 // Expand the range, if inside inline element boundaries.
574
575 range.Expand( 'inline_elements' ) ;
576
577
578
579 // Bookmark the range so we can re-select it after processing.
580
581 bookmark = range.CreateBookmark( true ) ;
582
583
584
585 // The style will be applied within the bookmark boundaries.
586
587 var startNode = range.GetBookmarkNode( bookmark, true ) ;
588
589 var endNode = range.GetBookmarkNode( bookmark, false ) ;
590
591
592
593 range.Release( true ) ;
594
595
596
597 // We need to check the selection boundaries (bookmark spans) to break
598
599 // the code in a way that we can properly remove partially selected nodes.
600
601 // For example, removing a <b> style from
602
603 // <b>This is [some text</b> to show <b>the] problem</b>
604
605 // ... where [ and ] represent the selection, must result:
606
607 // <b>This is </b>[some text to show the]<b> problem</b>
608
609 // The strategy is simple, we just break the partial nodes before the
610
611 // removal logic, having something that could be represented this way:
612
613 // <b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b>
614
615
616
617 // Let's start checking the start boundary.
618
619 var path = new FCKElementPath( startNode ) ;
620
621 var pathElements = path.Elements ;
622
623 var pathElement ;
624
625
626
627 for ( var i = 1 ; i < pathElements.length ; i++ )
628
629 {
630
631 pathElement = pathElements[i] ;
632
633
634
635 if ( pathElement == path.Block || pathElement == path.BlockLimit )
636
637 break ;
638
639
640
641 // If this element can be removed (even partially).
642
643 if ( this.CheckElementRemovable( pathElement ) )
644
645 FCKDomTools.BreakParent( startNode, pathElement, range ) ;
646
647 }
648
649
650
651 // Now the end boundary.
652
653 path = new FCKElementPath( endNode ) ;
654
655 pathElements = path.Elements ;
656
657
658
659 for ( var i = 1 ; i < pathElements.length ; i++ )
660
661 {
662
663 pathElement = pathElements[i] ;
664
665
666
667 if ( pathElement == path.Block || pathElement == path.BlockLimit )
668
669 break ;
670
671
672
673 elementName = pathElement.nodeName.toLowerCase() ;
674
675
676
677 // If this element can be removed (even partially).
678
679 if ( this.CheckElementRemovable( pathElement ) )
680
681 FCKDomTools.BreakParent( endNode, pathElement, range ) ;
682
683 }
684
685
686
687 // Navigate through all nodes between the bookmarks.
688
689 var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
690
691
692
693 while ( currentNode )
694
695 {
696
697 // Cache the next node to be processed. Do it now, because
698
699 // currentNode may be removed.
700
701 var nextNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
702
703
704
705 // Remove elements nodes that match with this style rules.
706
707 if ( currentNode.nodeType == 1 )
708
709 {
710
711 var elementName = currentNode.nodeName.toLowerCase() ;
712
713
714
715 var mayRemove = ( elementName == this.Element ) ;
716
717 if ( mayRemove )
718
719 {
720
721 // Remove any attribute that conflict with this style, no matter
722
723 // their values.
724
725 for ( var att in styleAttribs )
726
727 {
728
729 if ( FCKDomTools.HasAttribute( currentNode, att ) )
730
731 {
732
733 switch ( att )
734
735 {
736
737 case 'style' :
738
739 this._RemoveStylesFromElement( currentNode ) ;
740
741 break ;
742
743
744
745 case 'class' :
746
747 // The 'class' element value must match (#1318).
748
749 if ( FCKDomTools.GetAttributeValue( currentNode, att ) != this.GetFinalAttributeValue( att ) )
750
751 continue ;
752
753
754
755 /*jsl:fallthru*/
756
757
758
759 default :
760
761 FCKDomTools.RemoveAttribute( currentNode, att ) ;
762
763 }
764
765 }
766
767 }
768
769 }
770
771 else
772
773 mayRemove = !!styleOverrides[ elementName ] ;
774
775
776
777 if ( mayRemove )
778
779 {
780
781 // Remove overrides defined to the same element name.
782
783 this._RemoveOverrides( currentNode, styleOverrides[ elementName ] ) ;
784
785
786
787 // Remove the element if no more attributes are available.
788
789 this._RemoveNoAttribElement( currentNode ) ;
790
791 }
792
793 }
794
795
796
797 // If we have reached the end of the selection, stop looping.
798
799 if ( nextNode == endNode )
800
801 break ;
802
803
804
805 currentNode = nextNode ;
806
807 }
808
809
810
811 this._FixBookmarkStart( startNode ) ;
812
813
814
815 // Re-select the original range.
816
817 if ( selectIt )
818
819 range.SelectBookmark( bookmark ) ;
820
821
822
823 if ( updateRange )
824
825 range.MoveToBookmark( bookmark ) ;
826
827 },
828
829
830
831 /**
832
833 * Checks if an element, or any of its attributes, is removable by the
834
835 * current style definition.
836
837 */
838
839 CheckElementRemovable : function( element, fullMatch )
840
841 {
842
843 if ( !element )
844
845 return false ;
846
847
848
849 var elementName = element.nodeName.toLowerCase() ;
850
851
852
853 // If the element name is the same as the style name.
854
855 if ( elementName == this.Element )
856
857 {
858
859 // If no attributes are defined in the element.
860
861 if ( !fullMatch && !FCKDomTools.HasAttributes( element ) )
862
863 return true ;
864
865
866
867 // If any attribute conflicts with the style attributes.
868
869 var attribs = this._GetAttribsForComparison() ;
870
871 var allMatched = ( attribs._length == 0 ) ;
872
873 for ( var att in attribs )
874
875 {
876
877 if ( att == '_length' )
878
879 continue ;
880
881
882
883 if ( this._CompareAttributeValues( att, FCKDomTools.GetAttributeValue( element, att ), ( this.GetFinalAttributeValue( att ) || '' ) ) )
884
885 {
886
887 allMatched = true ;
888
889 if ( !fullMatch )
890
891 break ;
892
893 }
894
895 else
896
897 {
898
899 allMatched = false ;
900
901 if ( fullMatch )
902
903 return false ;
904
905 }
906
907 }
908
909 if ( allMatched )
910
911 return true ;
912
913 }
914
915
916
917 // Check if the element can be somehow overriden.
918
919 var override = this._GetOverridesForComparison()[ elementName ] ;
920
921 if ( override )
922
923 {
924
925 // If no attributes have been defined, remove the element.
926
927 if ( !( attribs = override.Attributes ) ) // Only one "="
928
929 return true ;
930
931
932
933 for ( var i = 0 ; i < attribs.length ; i++ )
934
935 {
936
937 var attName = attribs[i][0] ;
938
939 if ( FCKDomTools.HasAttribute( element, attName ) )
940
941 {
942
943 var attValue = attribs[i][1] ;
944
945
946
947 // Remove the attribute if:
948
949 // - The override definition value is null ;
950
951 // - The override definition valie is a string that
952
953 // matches the attribute value exactly.
954
955 // - The override definition value is a regex that
956
957 // has matches in the attribute value.
958
959 if ( attValue == null ||
960
961 ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) ||
962
963 attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) )
964
965 return true ;
966
967 }
968
969 }
970
971 }
972
973
974
975 return false ;
976
977 },
978
979
980
981 /**
982
983 * Get the style state for an element path. Returns "true" if the element
984
985 * is active in the path.
986
987 */
988
989 CheckActive : function( elementPath )
990
991 {
992
993 switch ( this.GetType() )
994
995 {
996
997 case FCK_STYLE_BLOCK :
998
999 return this.CheckElementRemovable( elementPath.Block || elementPath.BlockLimit, true ) ;
1000
1001
1002
1003 case FCK_STYLE_INLINE :
1004
1005
1006
1007 var elements = elementPath.Elements ;
1008
1009
1010
1011 for ( var i = 0 ; i < elements.length ; i++ )
1012
1013 {
1014
1015 var element = elements[i] ;
1016
1017
1018
1019 if ( element == elementPath.Block || element == elementPath.BlockLimit )
1020
1021 continue ;
1022
1023
1024
1025 if ( this.CheckElementRemovable( element, true ) )
1026
1027 return true ;
1028
1029 }
1030
1031 }
1032
1033 return false ;
1034
1035 },
1036
1037
1038
1039 /**
1040
1041 * Removes an inline style from inside an element tree. The element node
1042
1043 * itself is not checked or removed, only the child tree inside of it.
1044
1045 */
1046
1047 RemoveFromElement : function( element )
1048
1049 {
1050
1051 var attribs = this._GetAttribsForComparison() ;
1052
1053 var overrides = this._GetOverridesForComparison() ;
1054
1055
1056
1057 // Get all elements with the same name.
1058
1059 var innerElements = element.getElementsByTagName( this.Element ) ;
1060
1061
1062
1063 for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
1064
1065 {
1066
1067 var innerElement = innerElements[i] ;
1068
1069
1070
1071 // Remove any attribute that conflict with this style, no matter
1072
1073 // their values.
1074
1075 for ( var att in attribs )
1076
1077 {
1078
1079 if ( FCKDomTools.HasAttribute( innerElement, att ) )
1080
1081 {
1082
1083 switch ( att )
1084
1085 {
1086
1087 case 'style' :
1088
1089 this._RemoveStylesFromElement( innerElement ) ;
1090
1091 break ;
1092
1093
1094
1095 case 'class' :
1096
1097 // The 'class' element value must match (#1318).
1098
1099 if ( FCKDomTools.GetAttributeValue( innerElement, att ) != this.GetFinalAttributeValue( att ) )
1100
1101 continue ;
1102
1103
1104
1105 /*jsl:fallthru*/
1106
1107
1108
1109 default :
1110
1111 FCKDomTools.RemoveAttribute( innerElement, att ) ;
1112
1113 }
1114
1115 }
1116
1117 }
1118
1119
1120
1121 // Remove overrides defined to the same element name.
1122
1123 this._RemoveOverrides( innerElement, overrides[ this.Element ] ) ;
1124
1125
1126
1127 // Remove the element if no more attributes are available.
1128
1129 this._RemoveNoAttribElement( innerElement ) ;
1130
1131 }
1132
1133
1134
1135 // Now remove any other element with different name that is
1136
1137 // defined to be overriden.
1138
1139 for ( var overrideElement in overrides )
1140
1141 {
1142
1143 if ( overrideElement != this.Element )
1144
1145 {
1146
1147 // Get all elements.
1148
1149 innerElements = element.getElementsByTagName( overrideElement ) ;
1150
1151
1152
1153 for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
1154
1155 {
1156
1157 var innerElement = innerElements[i] ;
1158
1159 this._RemoveOverrides( innerElement, overrides[ overrideElement ] ) ;
1160
1161 this._RemoveNoAttribElement( innerElement ) ;
1162
1163 }
1164
1165 }
1166
1167 }
1168
1169 },
1170
1171
1172
1173 _RemoveStylesFromElement : function( element )
1174
1175 {
1176
1177 var elementStyle = element.style.cssText ;
1178
1179 var pattern = this.GetFinalStyleValue() ;
1180
1181
1182
1183 if ( elementStyle.length > 0 && pattern.length == 0 )
1184
1185 return ;
1186
1187
1188
1189 pattern = '(^|;)\\s*(' +
1190
1191 pattern.replace( /\s*([^ ]+):.*?(;|$)/g, '$1|' ).replace( /\|$/, '' ) +
1192
1193 '):[^;]+' ;
1194
1195
1196
1197 var regex = new RegExp( pattern, 'gi' ) ;
1198
1199
1200
1201 elementStyle = elementStyle.replace( regex, '' ).Trim() ;
1202
1203
1204
1205 if ( elementStyle.length == 0 || elementStyle == ';' )
1206
1207 FCKDomTools.RemoveAttribute( element, 'style' ) ;
1208
1209 else
1210
1211 element.style.cssText = elementStyle.replace( regex, '' ) ;
1212
1213 },
1214
1215
1216
1217 /**
1218
1219 * Remove all attributes that are defined to be overriden,
1220
1221 */
1222
1223 _RemoveOverrides : function( element, override )
1224
1225 {
1226
1227 var attributes = override && override.Attributes ;
1228
1229
1230
1231 if ( attributes )
1232
1233 {
1234
1235 for ( var i = 0 ; i < attributes.length ; i++ )
1236
1237 {
1238
1239 var attName = attributes[i][0] ;
1240
1241
1242
1243 if ( FCKDomTools.HasAttribute( element, attName ) )
1244
1245 {
1246
1247 var attValue = attributes[i][1] ;
1248
1249
1250
1251 // Remove the attribute if:
1252
1253 // - The override definition value is null ;
1254
1255 // - The override definition valie is a string that
1256
1257 // matches the attribute value exactly.
1258
1259 // - The override definition value is a regex that
1260
1261 // has matches in the attribute value.
1262
1263 if ( attValue == null ||
1264
1265 ( attValue.test && attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) ) ||
1266
1267 ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) )
1268
1269 FCKDomTools.RemoveAttribute( element, attName ) ;
1270
1271 }
1272
1273 }
1274
1275 }
1276
1277 },
1278
1279
1280
1281 /**
1282
1283 * If the element has no more attributes, remove it.
1284
1285 */
1286
1287 _RemoveNoAttribElement : function( element )
1288
1289 {
1290
1291 // If no more attributes remained in the element, remove it,
1292
1293 // leaving its children.
1294
1295 if ( !FCKDomTools.HasAttributes( element ) )
1296
1297 {
1298
1299 // Removing elements may open points where merging is possible,
1300
1301 // so let's cache the first and last nodes for later checking.
1302
1303 var firstChild = element.firstChild ;
1304
1305 var lastChild = element.lastChild ;
1306
1307
1308
1309 FCKDomTools.RemoveNode( element, true ) ;
1310
1311
1312
1313 // Check the cached nodes for merging.
1314
1315 this._MergeSiblings( firstChild ) ;
1316
1317
1318
1319 if ( firstChild != lastChild )
1320
1321 this._MergeSiblings( lastChild ) ;
1322
1323 }
1324
1325 },
1326
1327
1328
1329 /**
1330
1331 * Creates a DOM element for this style object.
1332
1333 */
1334
1335 BuildElement : function( targetDoc, element )
1336
1337 {
1338
1339 // Create the element.
1340
1341 var el = element || targetDoc.createElement( this.Element ) ;
1342
1343
1344
1345 // Assign all defined attributes.
1346
1347 var attribs = this._StyleDesc.Attributes ;
1348
1349 var attValue ;
1350
1351 if ( attribs )
1352
1353 {
1354
1355 for ( var att in attribs )
1356
1357 {
1358
1359 attValue = this.GetFinalAttributeValue( att ) ;
1360
1361
1362
1363 if ( att.toLowerCase() == 'class' )
1364
1365 el.className = attValue ;
1366
1367 else
1368
1369 el.setAttribute( att, attValue ) ;
1370
1371 }
1372
1373 }
1374
1375
1376
1377 // Assign the style attribute.
1378
1379 if ( this._GetStyleText().length > 0 )
1380
1381 el.style.cssText = this.GetFinalStyleValue() ;
1382
1383
1384
1385 return el ;
1386
1387 },
1388
1389
1390
1391 _CompareAttributeValues : function( attName, valueA, valueB )
1392
1393 {
1394
1395 if ( attName == 'style' && valueA && valueB )
1396
1397 {
1398
1399 valueA = valueA.replace( /;$/, '' ).toLowerCase() ;
1400
1401 valueB = valueB.replace( /;$/, '' ).toLowerCase() ;
1402
1403 }
1404
1405
1406
1407 // Return true if they match or if valueA is null and valueB is an empty string
1408
1409 return ( valueA == valueB || ( ( valueA === null || valueA === '' ) && ( valueB === null || valueB === '' ) ) )
1410
1411 },
1412
1413
1414
1415 GetFinalAttributeValue : function( attName )
1416
1417 {
1418
1419 var attValue = this._StyleDesc.Attributes ;
1420
1421 var attValue = attValue ? attValue[ attName ] : null ;
1422
1423
1424
1425 if ( !attValue && attName == 'style' )
1426
1427 return this.GetFinalStyleValue() ;
1428
1429
1430
1431 if ( attValue && this._Variables )
1432
1433 // Using custom Replace() to guarantee the correct scope.
1434
1435 attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
1436
1437
1438
1439 return attValue ;
1440
1441 },
1442
1443
1444
1445 GetFinalStyleValue : function()
1446
1447 {
1448
1449 var attValue = this._GetStyleText() ;
1450
1451
1452
1453 if ( attValue.length > 0 && this._Variables )
1454
1455 {
1456
1457 // Using custom Replace() to guarantee the correct scope.
1458
1459 attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
1460
1461 attValue = FCKTools.NormalizeCssText( attValue ) ;
1462
1463 }
1464
1465
1466
1467 return attValue ;
1468
1469 },
1470
1471
1472
1473 _GetVariableReplace : function()
1474
1475 {
1476
1477 // The second group in the regex is the variable name.
1478
1479 return this._Variables[ arguments[2] ] || arguments[0] ;
1480
1481 },
1482
1483
1484
1485 /**
1486
1487 * Set the value of a variable attribute or style, to be used when
1488
1489 * appliying the style.
1490
1491 */
1492
1493 SetVariable : function( name, value )
1494
1495 {
1496
1497 var variables = this._Variables ;
1498
1499
1500
1501 if ( !variables )
1502
1503 variables = this._Variables = {} ;
1504
1505
1506
1507 this._Variables[ name ] = value ;
1508
1509 },
1510
1511
1512
1513 /**
1514
1515 * Converting from a PRE block to a non-PRE block in formatting operations.
1516
1517 */
1518
1519 _FromPre : function( doc, block, newBlock )
1520
1521 {
1522
1523 var innerHTML = block.innerHTML ;
1524
1525
1526
1527 // Trim the first and last linebreaks immediately after and before <pre>, </pre>,
1528
1529 // if they exist.
1530
1531 // This is done because the linebreaks are not rendered.
1532
1533 innerHTML = innerHTML.replace( /(\r\n|\r)/g, '\n' ) ;
1534
1535 innerHTML = innerHTML.replace( /^[ \t]*\n/, '' ) ;
1536
1537 innerHTML = innerHTML.replace( /\n$/, '' ) ;
1538
1539
1540
1541 // 1. Convert spaces or tabs at the beginning or at the end to &nbsp;
1542
1543 innerHTML = innerHTML.replace( /^[ \t]+|[ \t]+$/g, function( match, offset, s )
1544
1545 {
1546
1547 if ( match.length == 1 ) // one space, preserve it
1548
1549 return '&nbsp;' ;
1550
1551 else if ( offset == 0 ) // beginning of block
1552
1553 return new Array( match.length ).join( '&nbsp;' ) + ' ' ;
1554
1555 else // end of block
1556
1557 return ' ' + new Array( match.length ).join( '&nbsp;' ) ;
1558
1559 } ) ;
1560
1561
1562
1563 // 2. Convert \n to <BR>.
1564
1565 // 3. Convert contiguous (i.e. non-singular) spaces or tabs to &nbsp;
1566
1567 var htmlIterator = new FCKHtmlIterator( innerHTML ) ;
1568
1569 var results = [] ;
1570
1571 htmlIterator.Each( function( isTag, value )
1572
1573 {
1574
1575 if ( !isTag )
1576
1577 {
1578
1579 value = value.replace( /\n/g, '<br>' ) ;
1580
1581 value = value.replace( /[ \t]{2,}/g,
1582
1583 function ( match )
1584
1585 {
1586
1587 return new Array( match.length ).join( '&nbsp;' ) + ' ' ;
1588
1589 } ) ;
1590
1591 }
1592
1593 results.push( value ) ;
1594
1595 } ) ;
1596
1597 newBlock.innerHTML = results.join( '' ) ;
1598
1599 return newBlock ;
1600
1601 },
1602
1603
1604
1605 /**
1606
1607 * Converting from a non-PRE block to a PRE block in formatting operations.
1608
1609 */
1610
1611 _ToPre : function( doc, block, newBlock )
1612
1613 {
1614
1615 // Handle converting from a regular block to a <pre> block.
1616
1617 var innerHTML = block.innerHTML.Trim() ;
1618
1619
1620
1621 // 1. Delete ANSI whitespaces immediately before and after <BR> because
1622
1623 // they are not visible.
1624
1625 // 2. Mark down any <BR /> nodes here so they can be turned into \n in
1626
1627 // the next step and avoid being compressed.
1628
1629 innerHTML = innerHTML.replace( /[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '<br />' ) ;
1630
1631
1632
1633 // 3. Compress other ANSI whitespaces since they're only visible as one
1634
1635 // single space previously.
1636
1637 // 4. Convert &nbsp; to spaces since &nbsp; is no longer needed in <PRE>.
1638
1639 // 5. Convert any <BR /> to \n. This must not be done earlier because
1640
1641 // the \n would then get compressed.
1642
1643 var htmlIterator = new FCKHtmlIterator( innerHTML ) ;
1644
1645 var results = [] ;
1646
1647 htmlIterator.Each( function( isTag, value )
1648
1649 {
1650
1651 if ( !isTag )
1652
1653 value = value.replace( /([ \t\n\r]+|&nbsp;)/g, ' ' ) ;
1654
1655 else if ( isTag && value == '<br />' )
1656
1657 value = '\n' ;
1658
1659 results.push( value ) ;
1660
1661 } ) ;
1662
1663
1664
1665 // Assigning innerHTML to <PRE> in IE causes all linebreaks to be
1666
1667 // reduced to spaces.
1668
1669 // Assigning outerHTML to <PRE> in IE doesn't work if the <PRE> isn't
1670
1671 // contained in another node since the node reference is changed after
1672
1673 // outerHTML assignment.
1674
1675 // So, we need some hacks to workaround IE bugs here.
1676
1677 if ( FCKBrowserInfo.IsIE )
1678
1679 {
1680
1681 var temp = doc.createElement( 'div' ) ;
1682
1683 temp.appendChild( newBlock ) ;
1684
1685 newBlock.outerHTML = '<pre>\n' + results.join( '' ) + '</pre>' ;
1686
1687 newBlock = temp.removeChild( temp.firstChild ) ;
1688
1689 }
1690
1691 else
1692
1693 newBlock.innerHTML = results.join( '' ) ;
1694
1695
1696
1697 return newBlock ;
1698
1699 },
1700
1701
1702
1703 /**
1704
1705 * Merge a <pre> block with a previous <pre> block, if available.
1706
1707 */
1708
1709 _CheckAndMergePre : function( previousBlock, preBlock )
1710
1711 {
1712
1713 // Check if the previous block and the current block are next
1714
1715 // to each other.
1716
1717 if ( previousBlock != FCKDomTools.GetPreviousSourceElement( preBlock, true ) )
1718
1719 return ;
1720
1721
1722
1723 // Merge the previous <pre> block contents into the current <pre>
1724
1725 // block.
1726
1727 //
1728
1729 // Another thing to be careful here is that currentBlock might contain
1730
1731 // a '\n' at the beginning, and previousBlock might contain a '\n'
1732
1733 // towards the end. These new lines are not normally displayed but they
1734
1735 // become visible after merging.
1736
1737 var innerHTML = previousBlock.innerHTML.replace( /\n$/, '' ) + '\n\n' +
1738
1739 preBlock.innerHTML.replace( /^\n/, '' ) ;
1740
1741
1742
1743 // Buggy IE normalizes innerHTML from <pre>, breaking whitespaces.
1744
1745 if ( FCKBrowserInfo.IsIE )
1746
1747 preBlock.outerHTML = '<pre>' + innerHTML + '</pre>' ;
1748
1749 else
1750
1751 preBlock.innerHTML = innerHTML ;
1752
1753
1754
1755 // Remove the previous <pre> block.
1756
1757 //
1758
1759 // The preBlock must not be moved or deleted from the DOM tree. This
1760
1761 // guarantees the FCKDomRangeIterator in _ApplyBlockStyle would not
1762
1763 // get lost at the next iteration.
1764
1765 FCKDomTools.RemoveNode( previousBlock ) ;
1766
1767 },
1768
1769
1770
1771 _CheckAndSplitPre : function( newBlock )
1772
1773 {
1774
1775 var lastNewBlock ;
1776
1777
1778
1779 var cursor = newBlock.firstChild ;
1780
1781
1782
1783 // We are not splitting <br><br> at the beginning of the block, so
1784
1785 // we'll start from the second child.
1786
1787 cursor = cursor && cursor.nextSibling ;
1788
1789
1790
1791 while ( cursor )
1792
1793 {
1794
1795 var next = cursor.nextSibling ;
1796
1797
1798
1799 // If we have two <BR>s, and they're not at the beginning or the end,
1800
1801 // then we'll split up the contents following them into another block.
1802
1803 // Stop processing if we are at the last child couple.
1804
1805 if ( next && next.nextSibling && cursor.nodeName.IEquals( 'br' ) && next.nodeName.IEquals( 'br' ) )
1806
1807 {
1808
1809 // Remove the first <br>.
1810
1811 FCKDomTools.RemoveNode( cursor ) ;
1812
1813
1814
1815 // Move to the node after the second <br>.
1816
1817 cursor = next.nextSibling ;
1818
1819
1820
1821 // Remove the second <br>.
1822
1823 FCKDomTools.RemoveNode( next ) ;
1824
1825
1826
1827 // Create the block that will hold the child nodes from now on.
1828
1829 lastNewBlock = FCKDomTools.InsertAfterNode( lastNewBlock || newBlock, FCKDomTools.CloneElement( newBlock ) ) ;
1830
1831
1832
1833 continue ;
1834
1835 }
1836
1837
1838
1839 // If we split it, then start moving the nodes to the new block.
1840
1841 if ( lastNewBlock )
1842
1843 {
1844
1845 cursor = cursor.previousSibling ;
1846
1847 FCKDomTools.MoveNode(cursor.nextSibling, lastNewBlock ) ;
1848
1849 }
1850
1851
1852
1853 cursor = cursor.nextSibling ;
1854
1855 }
1856
1857 },
1858
1859
1860
1861 /**
1862
1863 * Apply an inline style to a FCKDomRange.
1864
1865 *
1866
1867 * TODO
1868
1869 * - Implement the "#" style handling.
1870
1871 * - Properly handle block containers like <div> and <blockquote>.
1872
1873 */
1874
1875 _ApplyBlockStyle : function( range, selectIt, updateRange )
1876
1877 {
1878
1879 // Bookmark the range so we can re-select it after processing.
1880
1881 var bookmark ;
1882
1883
1884
1885 if ( selectIt )
1886
1887 bookmark = range.CreateBookmark() ;
1888
1889
1890
1891 var iterator = new FCKDomRangeIterator( range ) ;
1892
1893 iterator.EnforceRealBlocks = true ;
1894
1895
1896
1897 var block ;
1898
1899 var doc = range.Window.document ;
1900
1901 var previousPreBlock ;
1902
1903
1904
1905 while( ( block = iterator.GetNextParagraph() ) ) // Only one =
1906
1907 {
1908
1909 // Create the new node right before the current one.
1910
1911 var newBlock = this.BuildElement( doc ) ;
1912
1913
1914
1915 // Check if we are changing from/to <pre>.
1916
1917 var newBlockIsPre = newBlock.nodeName.IEquals( 'pre' ) ;
1918
1919 var blockIsPre = block.nodeName.IEquals( 'pre' ) ;
1920
1921
1922
1923 var toPre = newBlockIsPre && !blockIsPre ;
1924
1925 var fromPre = !newBlockIsPre && blockIsPre ;
1926
1927
1928
1929 // Move everything from the current node to the new one.
1930
1931 if ( toPre )
1932
1933 newBlock = this._ToPre( doc, block, newBlock ) ;
1934
1935 else if ( fromPre )
1936
1937 newBlock = this._FromPre( doc, block, newBlock ) ;
1938
1939 else // Convering from a regular block to another regular block.
1940
1941 FCKDomTools.MoveChildren( block, newBlock ) ;
1942
1943
1944
1945 // Replace the current block.
1946
1947 block.parentNode.insertBefore( newBlock, block ) ;
1948
1949 FCKDomTools.RemoveNode( block ) ;
1950
1951
1952
1953 // Complete other tasks after inserting the node in the DOM.
1954
1955 if ( newBlockIsPre )
1956
1957 {
1958
1959 if ( previousPreBlock )
1960
1961 this._CheckAndMergePre( previousPreBlock, newBlock ) ; // Merge successive <pre> blocks.
1962
1963 previousPreBlock = newBlock ;
1964
1965 }
1966
1967 else if ( fromPre )
1968
1969 this._CheckAndSplitPre( newBlock ) ; // Split <br><br> in successive <pre>s.
1970
1971 }
1972
1973
1974
1975 // Re-select the original range.
1976
1977 if ( selectIt )
1978
1979 range.SelectBookmark( bookmark ) ;
1980
1981
1982
1983 if ( updateRange )
1984
1985 range.MoveToBookmark( bookmark ) ;
1986
1987 },
1988
1989
1990
1991 /**
1992
1993 * Apply an inline style to a FCKDomRange.
1994
1995 *
1996
1997 * TODO
1998
1999 * - Merge elements, when applying styles to similar elements that enclose
2000
2001 * the entire selection, outputing:
2002
2003 * <span style="color: #ff0000; background-color: #ffffff">XYZ</span>
2004
2005 * instead of:
2006
2007 * <span style="color: #ff0000;"><span style="background-color: #ffffff">XYZ</span></span>
2008
2009 */
2010
2011 _ApplyInlineStyle : function( range, selectIt, updateRange )
2012
2013 {
2014
2015 var doc = range.Window.document ;
2016
2017
2018
2019 if ( range.CheckIsCollapsed() )
2020
2021 {
2022
2023 // Create the element to be inserted in the DOM.
2024
2025 var collapsedElement = this.BuildElement( doc ) ;
2026
2027 range.InsertNode( collapsedElement ) ;
2028
2029 range.MoveToPosition( collapsedElement, 2 ) ;
2030
2031 range.Select() ;
2032
2033
2034
2035 return ;
2036
2037 }
2038
2039
2040
2041 // The general idea here is navigating through all nodes inside the
2042
2043 // current selection, working on distinct range blocks, defined by the
2044
2045 // DTD compatibility between the style element and the nodes inside the
2046
2047 // ranges.
2048
2049 //
2050
2051 // For example, suppose we have the following selection (where [ and ]
2052
2053 // are the boundaries), and we apply a <b> style there:
2054
2055 //
2056
2057 // <p>Here we [have <b>some</b> text.<p>
2058
2059 // <p>And some here] here.</p>
2060
2061 //
2062
2063 // Two different ranges will be detected:
2064
2065 //
2066
2067 // "have <b>some</b> text."
2068
2069 // "And some here"
2070
2071 //
2072
2073 // Both ranges will be extracted, moved to a <b> element, and
2074
2075 // re-inserted, resulting in the following output:
2076
2077 //
2078
2079 // <p>Here we [<b>have some text.</b><p>
2080
2081 // <p><b>And some here</b>] here.</p>
2082
2083 //
2084
2085 // Note that the <b> element at <b>some</b> is also removed because it
2086
2087 // is not needed anymore.
2088
2089
2090
2091 var elementName = this.Element ;
2092
2093
2094
2095 // Get the DTD definition for the element. Defaults to "span".
2096
2097 var elementDTD = FCK.DTD[ elementName ] || FCK.DTD.span ;
2098
2099
2100
2101 // Create the attribute list to be used later for element comparisons.
2102
2103 var styleAttribs = this._GetAttribsForComparison() ;
2104
2105 var styleNode ;
2106
2107
2108
2109 // Expand the range, if inside inline element boundaries.
2110
2111 range.Expand( 'inline_elements' ) ;
2112
2113
2114
2115 // Bookmark the range so we can re-select it after processing.
2116
2117 var bookmark = range.CreateBookmark( true ) ;
2118
2119
2120
2121 // The style will be applied within the bookmark boundaries.
2122
2123 var startNode = range.GetBookmarkNode( bookmark, true ) ;
2124
2125 var endNode = range.GetBookmarkNode( bookmark, false ) ;
2126
2127
2128
2129 // We'll be reusing the range to apply the styles. So, release it here
2130
2131 // to indicate that it has not been initialized.
2132
2133 range.Release( true ) ;
2134
2135
2136
2137 // Let's start the nodes lookup from the node right after the bookmark
2138
2139 // span.
2140
2141 var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
2142
2143
2144
2145 while ( currentNode )
2146
2147 {
2148
2149 var applyStyle = false ;
2150
2151
2152
2153 var nodeType = currentNode.nodeType ;
2154
2155 var nodeName = nodeType == 1 ? currentNode.nodeName.toLowerCase() : null ;
2156
2157
2158
2159 // Check if the current node can be a child of the style element.
2160
2161 if ( !nodeName || elementDTD[ nodeName ] )
2162
2163 {
2164
2165 // Check if the style element can be a child of the current
2166
2167 // node parent or if the element is not defined in the DTD.
2168
2169 if ( ( FCK.DTD[ currentNode.parentNode.nodeName.toLowerCase() ] || FCK.DTD.span )[ elementName ] || !FCK.DTD[ elementName ] )
2170
2171 {
2172
2173 // This node will be part of our range, so if it has not
2174
2175 // been started, place its start right before the node.
2176
2177 if ( !range.CheckHasRange() )
2178
2179 range.SetStart( currentNode, 3 ) ;
2180
2181
2182
2183 // Non element nodes, or empty elements can be added
2184
2185 // completely to the range.
2186
2187 if ( nodeType != 1 || currentNode.childNodes.length == 0 )
2188
2189 {
2190
2191 var includedNode = currentNode ;
2192
2193 var parentNode = includedNode.parentNode ;
2194
2195
2196
2197 // This node is about to be included completelly, but,
2198
2199 // if this is the last node in its parent, we must also
2200
2201 // check if the parent itself can be added completelly
2202
2203 // to the range.
2204
2205 while ( includedNode == parentNode.lastChild
2206
2207 && elementDTD[ parentNode.nodeName.toLowerCase() ] )
2208
2209 {
2210
2211 includedNode = parentNode ;
2212
2213 }
2214
2215
2216
2217 range.SetEnd( includedNode, 4 ) ;
2218
2219
2220
2221 // If the included node is the last node in its parent
2222
2223 // and its parent can't be inside the style node, apply
2224
2225 // the style immediately.
2226
2227 if ( includedNode == includedNode.parentNode.lastChild && !elementDTD[ includedNode.parentNode.nodeName.toLowerCase() ] )
2228
2229 applyStyle = true ;
2230
2231 }
2232
2233 else
2234
2235 {
2236
2237 // Element nodes will not be added directly. We need to
2238
2239 // check their children because the selection could end
2240
2241 // inside the node, so let's place the range end right
2242
2243 // before the element.
2244
2245 range.SetEnd( currentNode, 3 ) ;
2246
2247 }
2248
2249 }
2250
2251 else
2252
2253 applyStyle = true ;
2254
2255 }
2256
2257 else
2258
2259 applyStyle = true ;
2260
2261
2262
2263 // Get the next node to be processed.
2264
2265 currentNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
2266
2267
2268
2269 // If we have reached the end of the selection, just apply the
2270
2271 // style ot the range, and stop looping.
2272
2273 if ( currentNode == endNode )
2274
2275 {
2276
2277 currentNode = null ;
2278
2279 applyStyle = true ;
2280
2281 }
2282
2283
2284
2285 // Apply the style if we have something to which apply it.
2286
2287 if ( applyStyle && range.CheckHasRange() && !range.CheckIsCollapsed() )
2288
2289 {
2290
2291 // Build the style element, based on the style object definition.
2292
2293 styleNode = this.BuildElement( doc ) ;
2294
2295
2296
2297 // Move the contents of the range to the style element.
2298
2299 range.ExtractContents().AppendTo( styleNode ) ;
2300
2301
2302
2303 // If it is not empty.
2304
2305 if ( styleNode.innerHTML.RTrim().length > 0 )
2306
2307 {
2308
2309 // Insert it in the range position (it is collapsed after
2310
2311 // ExtractContents.
2312
2313 range.InsertNode( styleNode ) ;
2314
2315
2316
2317 // Here we do some cleanup, removing all duplicated
2318
2319 // elements from the style element.
2320
2321 this.RemoveFromElement( styleNode ) ;
2322
2323
2324
2325 // Let's merge our new style with its neighbors, if possible.
2326
2327 this._MergeSiblings( styleNode, this._GetAttribsForComparison() ) ;
2328
2329
2330
2331 // As the style system breaks text nodes constantly, let's normalize
2332
2333 // things for performance.
2334
2335 // With IE, some paragraphs get broken when calling normalize()
2336
2337 // repeatedly. Also, for IE, we must normalize body, not documentElement.
2338
2339 // IE is also known for having a "crash effect" with normalize().
2340
2341 // We should try to normalize with IE too in some way, somewhere.
2342
2343 if ( !FCKBrowserInfo.IsIE )
2344
2345 styleNode.normalize() ;
2346
2347 }
2348
2349
2350
2351 // Style applied, let's release the range, so it gets marked to
2352
2353 // re-initialization in the next loop.
2354
2355 range.Release( true ) ;
2356
2357 }
2358
2359 }
2360
2361
2362
2363 this._FixBookmarkStart( startNode ) ;
2364
2365
2366
2367 // Re-select the original range.
2368
2369 if ( selectIt )
2370
2371 range.SelectBookmark( bookmark ) ;
2372
2373
2374
2375 if ( updateRange )
2376
2377 range.MoveToBookmark( bookmark ) ;
2378
2379 },
2380
2381
2382
2383 _FixBookmarkStart : function( startNode )
2384
2385 {
2386
2387 // After appliying or removing an inline style, the start boundary of
2388
2389 // the selection must be placed inside all inline elements it is
2390
2391 // bordering.
2392
2393 var startSibling ;
2394
2395 while ( ( startSibling = startNode.nextSibling ) ) // Only one "=".
2396
2397 {
2398
2399 if ( startSibling.nodeType == 1
2400
2401 && FCKListsLib.InlineNonEmptyElements[ startSibling.nodeName.toLowerCase() ] )
2402
2403 {
2404
2405 // If it is an empty inline element, we can safely remove it.
2406
2407 if ( !startSibling.firstChild )
2408
2409 FCKDomTools.RemoveNode( startSibling ) ;
2410
2411 else
2412
2413 FCKDomTools.MoveNode( startNode, startSibling, true ) ;
2414
2415 continue ;
2416
2417 }
2418
2419
2420
2421 // Empty text nodes can be safely removed to not disturb.
2422
2423 if ( startSibling.nodeType == 3 && startSibling.length == 0 )
2424
2425 {
2426
2427 FCKDomTools.RemoveNode( startSibling ) ;
2428
2429 continue ;
2430
2431 }
2432
2433
2434
2435 break ;
2436
2437 }
2438
2439 },
2440
2441
2442
2443 /**
2444
2445 * Merge an element with its similar siblings.
2446
2447 * "attribs" is and object computed with _CreateAttribsForComparison.
2448
2449 */
2450
2451 _MergeSiblings : function( element, attribs )
2452
2453 {
2454
2455 if ( !element || element.nodeType != 1 || !FCKListsLib.InlineNonEmptyElements[ element.nodeName.toLowerCase() ] )
2456
2457 return ;
2458
2459
2460
2461 this._MergeNextSibling( element, attribs ) ;
2462
2463 this._MergePreviousSibling( element, attribs ) ;
2464
2465 },
2466
2467
2468
2469 /**
2470
2471 * Merge an element with its similar siblings after it.
2472
2473 * "attribs" is and object computed with _CreateAttribsForComparison.
2474
2475 */
2476
2477 _MergeNextSibling : function( element, attribs )
2478
2479 {
2480
2481 // Check the next sibling.
2482
2483 var sibling = element.nextSibling ;
2484
2485
2486
2487 // Check if the next sibling is a bookmark element. In this case, jump it.
2488
2489 var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
2490
2491 if ( hasBookmark )
2492
2493 sibling = sibling.nextSibling ;
2494
2495
2496
2497 if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
2498
2499 {
2500
2501 if ( !attribs )
2502
2503 attribs = this._CreateElementAttribsForComparison( element ) ;
2504
2505
2506
2507 if ( this._CheckAttributesMatch( sibling, attribs ) )
2508
2509 {
2510
2511 // Save the last child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
2512
2513 var innerSibling = element.lastChild ;
2514
2515
2516
2517 if ( hasBookmark )
2518
2519 FCKDomTools.MoveNode( element.nextSibling, element ) ;
2520
2521
2522
2523 // Move contents from the sibling.
2524
2525 FCKDomTools.MoveChildren( sibling, element ) ;
2526
2527 FCKDomTools.RemoveNode( sibling ) ;
2528
2529
2530
2531 // Now check the last inner child (see two comments above).
2532
2533 if ( innerSibling )
2534
2535 this._MergeNextSibling( innerSibling ) ;
2536
2537 }
2538
2539 }
2540
2541 },
2542
2543
2544
2545 /**
2546
2547 * Merge an element with its similar siblings before it.
2548
2549 * "attribs" is and object computed with _CreateAttribsForComparison.
2550
2551 */
2552
2553 _MergePreviousSibling : function( element, attribs )
2554
2555 {
2556
2557 // Check the previous sibling.
2558
2559 var sibling = element.previousSibling ;
2560
2561
2562
2563 // Check if the previous sibling is a bookmark element. In this case, jump it.
2564
2565 var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
2566
2567 if ( hasBookmark )
2568
2569 sibling = sibling.previousSibling ;
2570
2571
2572
2573 if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
2574
2575 {
2576
2577 if ( !attribs )
2578
2579 attribs = this._CreateElementAttribsForComparison( element ) ;
2580
2581
2582
2583 if ( this._CheckAttributesMatch( sibling, attribs ) )
2584
2585 {
2586
2587 // Save the first child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
2588
2589 var innerSibling = element.firstChild ;
2590
2591
2592
2593 if ( hasBookmark )
2594
2595 FCKDomTools.MoveNode( element.previousSibling, element, true ) ;
2596
2597
2598
2599 // Move contents to the sibling.
2600
2601 FCKDomTools.MoveChildren( sibling, element, true ) ;
2602
2603 FCKDomTools.RemoveNode( sibling ) ;
2604
2605
2606
2607 // Now check the first inner child (see two comments above).
2608
2609 if ( innerSibling )
2610
2611 this._MergePreviousSibling( innerSibling ) ;
2612
2613 }
2614
2615 }
2616
2617 },
2618
2619
2620
2621 /**
2622
2623 * Build the cssText based on the styles definition.
2624
2625 */
2626
2627 _GetStyleText : function()
2628
2629 {
2630
2631 var stylesDef = this._StyleDesc.Styles ;
2632
2633
2634
2635 // Builds the StyleText.
2636
2637 var stylesText = ( this._StyleDesc.Attributes ? this._StyleDesc.Attributes['style'] || '' : '' ) ;
2638
2639
2640
2641 if ( stylesText.length > 0 )
2642
2643 stylesText += ';' ;
2644
2645
2646
2647 for ( var style in stylesDef )
2648
2649 stylesText += style + ':' + stylesDef[style] + ';' ;
2650
2651
2652
2653 // Browsers make some changes to the style when applying them. So, here
2654
2655 // we normalize it to the browser format. We'll not do that if there
2656
2657 // are variables inside the style.
2658
2659 if ( stylesText.length > 0 && !( /#\(/.test( stylesText ) ) )
2660
2661 {
2662
2663 stylesText = FCKTools.NormalizeCssText( stylesText ) ;
2664
2665 }
2666
2667
2668
2669 return (this._GetStyleText = function() { return stylesText ; })() ;
2670
2671 },
2672
2673
2674
2675 /**
2676
2677 * Get the the collection used to compare the attributes defined in this
2678
2679 * style with attributes in an element. All information in it is lowercased.
2680
2681 */
2682
2683 _GetAttribsForComparison : function()
2684
2685 {
2686
2687 // If we have already computed it, just return it.
2688
2689 var attribs = this._GetAttribsForComparison_$ ;
2690
2691 if ( attribs )
2692
2693 return attribs ;
2694
2695
2696
2697 attribs = new Object() ;
2698
2699
2700
2701 // Loop through all defined attributes.
2702
2703 var styleAttribs = this._StyleDesc.Attributes ;
2704
2705 if ( styleAttribs )
2706
2707 {
2708
2709 for ( var styleAtt in styleAttribs )
2710
2711 {
2712
2713 attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase() ;
2714
2715 }
2716
2717 }
2718
2719
2720
2721 // Includes the style definitions.
2722
2723 if ( this._GetStyleText().length > 0 )
2724
2725 {
2726
2727 attribs['style'] = this._GetStyleText().toLowerCase() ;
2728
2729 }
2730
2731
2732
2733 // Appends the "length" information to the object.
2734
2735 FCKTools.AppendLengthProperty( attribs, '_length' ) ;
2736
2737
2738
2739 // Return it, saving it to the next request.
2740
2741 return ( this._GetAttribsForComparison_$ = attribs ) ;
2742
2743 },
2744
2745
2746
2747 /**
2748
2749 * Get the the collection used to compare the elements and attributes,
2750
2751 * defined in this style overrides, with other element. All information in
2752
2753 * it is lowercased.
2754
2755 */
2756
2757 _GetOverridesForComparison : function()
2758
2759 {
2760
2761 // If we have already computed it, just return it.
2762
2763 var overrides = this._GetOverridesForComparison_$ ;
2764
2765 if ( overrides )
2766
2767 return overrides ;
2768
2769
2770
2771 overrides = new Object() ;
2772
2773
2774
2775 var overridesDesc = this._StyleDesc.Overrides ;
2776
2777
2778
2779 if ( overridesDesc )
2780
2781 {
2782
2783 // The override description can be a string, object or array.
2784
2785 // Internally, well handle arrays only, so transform it if needed.
2786
2787 if ( !FCKTools.IsArray( overridesDesc ) )
2788
2789 overridesDesc = [ overridesDesc ] ;
2790
2791
2792
2793 // Loop through all override definitions.
2794
2795 for ( var i = 0 ; i < overridesDesc.length ; i++ )
2796
2797 {
2798
2799 var override = overridesDesc[i] ;
2800
2801 var elementName ;
2802
2803 var overrideEl ;
2804
2805 var attrs ;
2806
2807
2808
2809 // If can be a string with the element name.
2810
2811 if ( typeof override == 'string' )
2812
2813 elementName = override.toLowerCase() ;
2814
2815 // Or an object.
2816
2817 else
2818
2819 {
2820
2821 elementName = override.Element ? override.Element.toLowerCase() : this.Element ;
2822
2823 attrs = override.Attributes ;
2824
2825 }
2826
2827
2828
2829 // We can have more than one override definition for the same
2830
2831 // element name, so we attempt to simply append information to
2832
2833 // it if it already exists.
2834
2835 overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} ) ;
2836
2837
2838
2839 if ( attrs )
2840
2841 {
2842
2843 // The returning attributes list is an array, because we
2844
2845 // could have different override definitions for the same
2846
2847 // attribute name.
2848
2849 var overrideAttrs = ( overrideEl.Attributes = overrideEl.Attributes || new Array() ) ;
2850
2851 for ( var attName in attrs )
2852
2853 {
2854
2855 // Each item in the attributes array is also an array,
2856
2857 // where [0] is the attribute name and [1] is the
2858
2859 // override value.
2860
2861 overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] ) ;
2862
2863 }
2864
2865 }
2866
2867 }
2868
2869 }
2870
2871
2872
2873 return ( this._GetOverridesForComparison_$ = overrides ) ;
2874
2875 },
2876
2877
2878
2879 /*
2880
2881 * Create and object containing all attributes specified in an element,
2882
2883 * added by a "_length" property. All values are lowercased.
2884
2885 */
2886
2887 _CreateElementAttribsForComparison : function( element )
2888
2889 {
2890
2891 var attribs = new Object() ;
2892
2893 var attribsCount = 0 ;
2894
2895
2896
2897 for ( var i = 0 ; i < element.attributes.length ; i++ )
2898
2899 {
2900
2901 var att = element.attributes[i] ;
2902
2903
2904
2905 if ( att.specified )
2906
2907 {
2908
2909 attribs[ att.nodeName.toLowerCase() ] = FCKDomTools.GetAttributeValue( element, att ).toLowerCase() ;
2910
2911 attribsCount++ ;
2912
2913 }
2914
2915 }
2916
2917
2918
2919 attribs._length = attribsCount ;
2920
2921
2922
2923 return attribs ;
2924
2925 },
2926
2927
2928
2929 /**
2930
2931 * Checks is the element attributes have a perfect match with the style
2932
2933 * attributes.
2934
2935 */
2936
2937 _CheckAttributesMatch : function( element, styleAttribs )
2938
2939 {
2940
2941 // Loop through all specified attributes. The same number of
2942
2943 // attributes must be found and their values must match to
2944
2945 // declare them as equal.
2946
2947
2948
2949 var elementAttrbs = element.attributes ;
2950
2951 var matchCount = 0 ;
2952
2953
2954
2955 for ( var i = 0 ; i < elementAttrbs.length ; i++ )
2956
2957 {
2958
2959 var att = elementAttrbs[i] ;
2960
2961 if ( att.specified )
2962
2963 {
2964
2965 var attName = att.nodeName.toLowerCase() ;
2966
2967 var styleAtt = styleAttribs[ attName ] ;
2968
2969
2970
2971 // The attribute is not defined in the style.
2972
2973 if ( !styleAtt )
2974
2975 break ;
2976
2977
2978
2979 // The values are different.
2980
2981 if ( styleAtt != FCKDomTools.GetAttributeValue( element, att ).toLowerCase() )
2982
2983 break ;
2984
2985
2986
2987 matchCount++ ;
2988
2989 }
2990
2991 }
2992
2993
2994
2995 return ( matchCount == styleAttribs._length ) ;
2996
2997 }
2998
2999 } ;
3000
3001