@@ -21,37 +21,7 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
21
21
}
22
22
}
23
23
24
- // fix a webkit bug, see: https://gist.github.com/shimondoodkin/1081133
25
- // this is set true when a blur occurs as the blur of the ta-bind triggers before the click
26
- var globalContentEditableBlur = false ;
27
- /* istanbul ignore next: Browser Un-Focus fix for webkit */
28
- if ( / A p p l e W e b K i t \/ ( [ \d . ] + ) / . exec ( navigator . userAgent ) ) { // detect webkit
29
- document . addEventListener ( "click" , function ( _event ) {
30
- var e = _event || window . event ;
31
- var curelement = e . target ;
32
- if ( globalContentEditableBlur && curelement !== null ) {
33
- var isEditable = false ;
34
- var tempEl = curelement ;
35
- while ( tempEl !== null && tempEl . tagName . toLowerCase ( ) !== 'html' && ! isEditable ) {
36
- isEditable = tempEl . contentEditable === 'true' ;
37
- tempEl = tempEl . parentNode ;
38
- }
39
- if ( ! isEditable ) {
40
- document . getElementById ( 'textAngular-editableFix-010203040506070809' ) . setSelectionRange ( 0 , 0 ) ; // set caret focus to an element that handles caret focus correctly.
41
- curelement . focus ( ) ; // focus the wanted element.
42
- }
43
- }
44
- globalContentEditableBlur = false ;
45
- } , false ) ; // add global click handler
46
- angular . element ( document ) . ready ( function ( ) {
47
- angular . element ( document . body ) . append ( angular . element ( '<input id="textAngular-editableFix-010203040506070809" style="width:1px;height:1px;border:none;margin:0;padding:0;position:absolute; top: -10000px; left: -10000px;" unselectable="on" tabIndex="-1">' ) ) ;
48
- } ) ;
49
- }
50
-
51
- // Gloabl to textAngular REGEXP vars for block and list elements.
52
-
53
- var BLOCKELEMENTS = / ^ ( a d d r e s s | a r t i c l e | a s i d e | a u d i o | b l o c k q u o t e | c a n v a s | d d | d i v | d l | f i e l d s e t | f i g c a p t i o n | f i g u r e | f o o t e r | f o r m | h 1 | h 2 | h 3 | h 4 | h 5 | h 6 | h e a d e r | h g r o u p | h r | n o s c r i p t | o l | o u t p u t | p | p r e | s e c t i o n | t a b l e | t f o o t | u l | v i d e o ) $ / ig;
54
- var LISTELEMENTS = / ^ ( u l | l i | o l ) $ / ig;
24
+
55
25
// IE version detection - http://stackoverflow.com/questions/4169160/javascript-ie-detection-why-not-use-simple-conditional-comments
56
26
// We need this as IE sometimes plays funny tricks with the contenteditable.
57
27
// ----------------------------------------------------------
@@ -84,7 +54,42 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
84
54
85
55
return ( ( rv > - 1 ) ? rv : undef ) ;
86
56
} ( ) ) ;
87
-
57
+ // detect webkit
58
+ var webkit = / A p p l e W e b K i t \/ ( [ \d . ] + ) / . test ( navigator . userAgent ) ;
59
+
60
+ // fix a webkit bug, see: https://gist.github.com/shimondoodkin/1081133
61
+ // this is set true when a blur occurs as the blur of the ta-bind triggers before the click
62
+ var globalContentEditableBlur = false ;
63
+ /* istanbul ignore next: Browser Un-Focus fix for webkit */
64
+ if ( webkit ) {
65
+ document . addEventListener ( "click" , function ( _event ) {
66
+ var e = _event || window . event ;
67
+ var curelement = e . target ;
68
+ if ( globalContentEditableBlur && curelement !== null ) {
69
+ var isEditable = false ;
70
+ var tempEl = curelement ;
71
+ while ( tempEl !== null && tempEl . tagName . toLowerCase ( ) !== 'html' && ! isEditable ) {
72
+ isEditable = tempEl . contentEditable === 'true' ;
73
+ tempEl = tempEl . parentNode ;
74
+ }
75
+ if ( ! isEditable ) {
76
+ document . getElementById ( 'textAngular-editableFix-010203040506070809' ) . setSelectionRange ( 0 , 0 ) ; // set caret focus to an element that handles caret focus correctly.
77
+ curelement . focus ( ) ; // focus the wanted element.
78
+ }
79
+ }
80
+ globalContentEditableBlur = false ;
81
+ } , false ) ; // add global click handler
82
+ angular . element ( document ) . ready ( function ( ) {
83
+ angular . element ( document . body ) . append ( angular . element ( '<input id="textAngular-editableFix-010203040506070809" style="width:1px;height:1px;border:none;margin:0;padding:0;position:absolute; top: -10000px; left: -10000px;" unselectable="on" tabIndex="-1">' ) ) ;
84
+ } ) ;
85
+ }
86
+
87
+ // Gloabl to textAngular REGEXP vars for block and list elements.
88
+
89
+ var BLOCKELEMENTS = / ^ ( a d d r e s s | a r t i c l e | a s i d e | a u d i o | b l o c k q u o t e | c a n v a s | d d | d i v | d l | f i e l d s e t | f i g c a p t i o n | f i g u r e | f o o t e r | f o r m | h 1 | h 2 | h 3 | h 4 | h 5 | h 6 | h e a d e r | h g r o u p | h r | n o s c r i p t | o l | o u t p u t | p | p r e | s e c t i o n | t a b l e | t f o o t | u l | v i d e o ) $ / ig;
90
+ var LISTELEMENTS = / ^ ( u l | l i | o l ) $ / ig;
91
+ var VALIDELEMENTS = / ^ ( a d d r e s s | a r t i c l e | a s i d e | a u d i o | b l o c k q u o t e | c a n v a s | d d | d i v | d l | f i e l d s e t | f i g c a p t i o n | f i g u r e | f o o t e r | f o r m | h 1 | h 2 | h 3 | h 4 | h 5 | h 6 | h e a d e r | h g r o u p | h r | n o s c r i p t | o l | o u t p u t | p | p r e | s e c t i o n | t a b l e | t f o o t | u l | v i d e o | l i ) $ / ig;
92
+
88
93
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Compatibility
89
94
/* istanbul ignore next: trim shim for older browsers */
90
95
if ( ! String . prototype . trim ) {
@@ -283,15 +288,21 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
283
288
angular . extend ( scope , angular . copy ( taOptions ) , {
284
289
// wraps the selection in the provided tag / execCommand function. Should only be called in WYSIWYG mode.
285
290
wrapSelection : function ( command , opt , isSelectableElementTool ) {
286
- // catch errors like FF erroring when you try to force an undo with nothing done
287
- _taExecCommand ( command , false , opt ) ;
288
- if ( isSelectableElementTool ) {
289
- // re-apply the selectable tool events
290
- scope [ 'reApplyOnSelectorHandlerstaTextElement' + _serial ] ( ) ;
291
+ if ( command . toLowerCase ( ) === "undo" ) {
292
+ scope [ '$undoTaBindtaTextElement' + _serial ] ( ) ;
293
+ } else if ( command . toLowerCase ( ) === "redo" ) {
294
+ scope [ '$redoTaBindtaTextElement' + _serial ] ( ) ;
295
+ } else {
296
+ // catch errors like FF erroring when you try to force an undo with nothing done
297
+ _taExecCommand ( command , false , opt ) ;
298
+ if ( isSelectableElementTool ) {
299
+ // re-apply the selectable tool events
300
+ scope [ 'reApplyOnSelectorHandlerstaTextElement' + _serial ] ( ) ;
301
+ }
302
+ // refocus on the shown display element, this fixes a display bug when using :focus styles to outline the box.
303
+ // You still have focus on the text/html input it just doesn't show up
304
+ scope . displayElements . text [ 0 ] . focus ( ) ;
291
305
}
292
- // refocus on the shown display element, this fixes a display bug when using :focus styles to outline the box.
293
- // You still have focus on the text/html input it just doesn't show up
294
- scope . displayElements . text [ 0 ] . focus ( ) ;
295
306
} ,
296
307
showHtml : false
297
308
} ) ;
@@ -999,7 +1010,9 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
999
1010
var _isReadonly = false ;
1000
1011
var _focussed = false ;
1001
1012
var _disableSanitizer = attrs . taUnsafeSanitizer || taOptions . disableSanitizer ;
1002
- var BLOCKED_KEYS = / ^ ( 9 | 1 9 | 2 0 | 2 7 | 3 3 | 3 4 | 3 5 | 3 6 | 3 7 | 3 8 | 3 9 | 4 0 | 4 5 | 4 6 | 1 1 2 | 1 1 3 | 1 1 4 | 1 1 5 | 1 1 6 | 1 1 7 | 1 1 8 | 1 1 9 | 1 2 0 | 1 2 1 | 1 2 2 | 1 2 3 | 1 4 4 | 1 4 5 ) $ / ;
1013
+ var _lastKey ;
1014
+ var BLOCKED_KEYS = / ^ ( 9 | 1 9 | 2 0 | 2 7 | 3 3 | 3 4 | 3 5 | 3 6 | 3 7 | 3 8 | 3 9 | 4 0 | 4 5 | 1 1 2 | 1 1 3 | 1 1 4 | 1 1 5 | 1 1 6 | 1 1 7 | 1 1 8 | 1 1 9 | 1 2 0 | 1 2 1 | 1 2 2 | 1 2 3 | 1 4 4 | 1 4 5 ) $ / ;
1015
+ var UNDO_TRIGGER_KEYS = / ^ ( 8 | 1 3 | 3 2 | 4 6 | 5 9 | 6 1 | 1 0 7 | 1 0 9 | 1 8 6 | 1 8 7 | 1 8 8 | 1 8 9 | 1 9 0 | 1 9 1 | 1 9 2 | 2 1 9 | 2 2 0 | 2 2 1 | 2 2 2 ) $ / ; // spaces, enter, delete, backspace, all punctuation
1003
1016
1004
1017
// defaults to the paragraph element, but we need the line-break or it doesn't allow you to type into the empty element
1005
1018
// non IE is '<p><br/></p>', ie is '<p></p>' as for once IE gets it correct...
@@ -1026,21 +1039,90 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
1026
1039
}
1027
1040
1028
1041
element . addClass ( 'ta-bind' ) ;
1029
-
1042
+
1043
+ var _undoKeyupTimeout ;
1044
+
1045
+ scope [ '$undoManager' + ( attrs . id || '' ) ] = ngModel . $undoManager = {
1046
+ _stack : [ ] ,
1047
+ _index : 0 ,
1048
+ _max : 1000 ,
1049
+ push : function ( value ) {
1050
+ if ( ( typeof value === "undefined" || value === null ) ||
1051
+ ( ( typeof this . current ( ) !== "undefined" && this . current ( ) !== null ) && value === this . current ( ) ) ) return value ;
1052
+ if ( this . _index < this . _stack . length - 1 ) {
1053
+ this . _stack = this . _stack . slice ( 0 , this . _index + 1 ) ;
1054
+ }
1055
+ this . _stack . push ( value ) ;
1056
+ if ( _undoKeyupTimeout ) $timeout . cancel ( _undoKeyupTimeout ) ;
1057
+ if ( this . _stack . length > this . _max ) this . _stack . shift ( ) ;
1058
+ this . _index = this . _stack . length - 1 ;
1059
+ return value ;
1060
+ } ,
1061
+ undo : function ( ) {
1062
+ return this . setToIndex ( this . _index - 1 ) ;
1063
+ } ,
1064
+ redo : function ( ) {
1065
+ return this . setToIndex ( this . _index + 1 ) ;
1066
+ } ,
1067
+ setToIndex : function ( index ) {
1068
+ if ( index < 0 || index > this . _stack . length - 1 ) {
1069
+ return undefined ;
1070
+ }
1071
+ this . _index = index ;
1072
+ return this . current ( ) ;
1073
+ } ,
1074
+ current : function ( ) {
1075
+ return this . _stack [ this . _index ] ;
1076
+ }
1077
+ } ;
1078
+
1079
+ var _undo = scope [ '$undoTaBind' + ( attrs . id || '' ) ] = function ( ) {
1080
+ /* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */
1081
+ if ( ! _isReadonly && _isContentEditable ) {
1082
+ var content = ngModel . $undoManager . undo ( ) ;
1083
+ if ( typeof content !== "undefined" && content !== null ) {
1084
+ element [ 0 ] . innerHTML = content ;
1085
+ _setViewValue ( content , false ) ;
1086
+ /* istanbul ignore else: browser catch */
1087
+ if ( element [ 0 ] . childNodes . length ) taSelection . setSelectionToElementEnd ( element [ 0 ] . childNodes [ element [ 0 ] . childNodes . length - 1 ] ) ;
1088
+ else taSelection . setSelectionToElementEnd ( element [ 0 ] ) ;
1089
+ }
1090
+ }
1091
+ } ;
1092
+
1093
+ var _redo = scope [ '$redoTaBind' + ( attrs . id || '' ) ] = function ( ) {
1094
+ /* istanbul ignore else: can't really test it due to all changes being ignored as well in readonly */
1095
+ if ( ! _isReadonly && _isContentEditable ) {
1096
+ var content = ngModel . $undoManager . redo ( ) ;
1097
+ if ( typeof content !== "undefined" && content !== null ) {
1098
+ element [ 0 ] . innerHTML = content ;
1099
+ _setViewValue ( content , false ) ;
1100
+ /* istanbul ignore else: browser catch */
1101
+ if ( element [ 0 ] . childNodes . length ) taSelection . setSelectionToElementEnd ( element [ 0 ] . childNodes [ element [ 0 ] . childNodes . length - 1 ] ) ;
1102
+ else taSelection . setSelectionToElementEnd ( element [ 0 ] ) ;
1103
+ }
1104
+ }
1105
+ } ;
1106
+
1030
1107
// in here we are undoing the converts used elsewhere to prevent the < > and & being displayed when they shouldn't in the code.
1031
1108
var _compileHtml = function ( ) {
1032
1109
if ( _isContentEditable ) return element [ 0 ] . innerHTML ;
1033
1110
if ( _isInputFriendly ) return element . val ( ) ;
1034
1111
throw ( 'textAngular Error: attempting to update non-editable taBind' ) ;
1035
1112
} ;
1036
1113
1037
- var _setViewValue = function ( val ) {
1114
+ var _setViewValue = function ( val , triggerUndo ) {
1115
+ if ( typeof triggerUndo === "undefined" || triggerUndo === null ) triggerUndo = true && _isContentEditable ; // if not contentEditable then the native undo/redo is fine
1038
1116
if ( ! val ) val = _compileHtml ( ) ;
1039
- if ( val === _defaultTest || val . match ( _trimTest ) ) {
1117
+ if ( val === _defaultTest || _trimTest . test ( val ) ) {
1040
1118
// this avoids us from tripping the ng-pristine flag if we click in and out with out typing
1041
1119
if ( ngModel . $viewValue !== '' ) ngModel . $setViewValue ( '' ) ;
1120
+ if ( triggerUndo && ngModel . $undoManager . current ( ) !== '' ) ngModel . $undoManager . push ( '' ) ;
1042
1121
} else {
1043
- if ( ngModel . $viewValue !== val ) ngModel . $setViewValue ( val ) ;
1122
+ if ( ngModel . $viewValue !== val ) {
1123
+ ngModel . $setViewValue ( val ) ;
1124
+ if ( triggerUndo ) ngModel . $undoManager . push ( val ) ;
1125
+ }
1044
1126
}
1045
1127
} ;
1046
1128
@@ -1184,16 +1266,35 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
1184
1266
else e . preventDefault ( ) ;
1185
1267
} ) ;
1186
1268
1269
+ element . on ( 'keydown' , function ( event , eventData ) {
1270
+ /* istanbul ignore else: this is for catching the jqLite testing*/
1271
+ if ( eventData ) angular . extend ( event , eventData ) ;
1272
+ /* istanbul ignore else: readonly check */
1273
+ if ( ! _isReadonly ) {
1274
+ if ( event . metaKey || event . ctrlKey ) {
1275
+ // covers ctrl/command + z
1276
+ if ( ( event . keyCode === 90 && ! event . shiftKey ) ) {
1277
+ _undo ( ) ;
1278
+ event . preventDefault ( ) ;
1279
+ // covers ctrl + y, command + shift + z
1280
+ } else if ( ( event . keyCode === 90 && event . shiftKey ) || ( event . keyCode === 89 && ! event . shiftKey ) ) {
1281
+ _redo ( ) ;
1282
+ event . preventDefault ( ) ;
1283
+ }
1284
+ }
1285
+ }
1286
+ } ) ;
1287
+
1187
1288
element . on ( 'keyup' , function ( event , eventData ) {
1188
1289
/* istanbul ignore else: this is for catching the jqLite testing*/
1189
1290
if ( eventData ) angular . extend ( event , eventData ) ;
1291
+ if ( _undoKeyupTimeout ) $timeout . cancel ( _undoKeyupTimeout ) ;
1190
1292
if ( ! _isReadonly && ! BLOCKED_KEYS . test ( event . keyCode ) ) {
1191
1293
// if enter - insert new taDefaultWrap, if shift+enter insert <br/>
1192
1294
if ( _defaultVal !== '' && event . keyCode === 13 ) {
1193
1295
if ( ! event . shiftKey ) {
1194
1296
// new paragraph, br should be caught correctly
1195
1297
var selection = taSelection . getSelectionElement ( ) ;
1196
- var VALIDELEMENTS = / ^ ( a d d r e s s | a r t i c l e | a s i d e | a u d i o | b l o c k q u o t e | c a n v a s | d d | d i v | d l | f i e l d s e t | f i g c a p t i o n | f i g u r e | f o o t e r | f o r m | h 1 | h 2 | h 3 | h 4 | h 5 | h 6 | h e a d e r | h g r o u p | h r | n o s c r i p t | o l | o u t p u t | p | p r e | s e c t i o n | t a b l e | t f o o t | u l | v i d e o | l i ) $ / ig;
1197
1298
while ( ! selection . tagName . match ( VALIDELEMENTS ) && selection !== element [ 0 ] ) {
1198
1299
selection = selection . parentNode ;
1199
1300
}
@@ -1209,7 +1310,10 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
1209
1310
element [ 0 ] . innerHTML = _defaultVal ;
1210
1311
taSelection . setSelectionToElementStart ( element . children ( ) [ 0 ] ) ;
1211
1312
}
1212
- _setViewValue ( val ) ;
1313
+ var triggerUndo = _lastKey !== event . keyCode && UNDO_TRIGGER_KEYS . test ( event . keyCode ) ;
1314
+ _setViewValue ( val , triggerUndo ) ;
1315
+ if ( ! triggerUndo ) _undoKeyupTimeout = $timeout ( function ( ) { ngModel . $undoManager . push ( val ) ; } , 250 ) ;
1316
+ _lastKey = event . keyCode ;
1213
1317
}
1214
1318
} ) ;
1215
1319
@@ -1264,6 +1368,9 @@ See README.md or https://github.com/fraywing/textAngular/wiki for requirements a
1264
1368
// because textAngular is bi-directional (which is awesome) we need to also sanitize values going in from the server
1265
1369
ngModel . $formatters . push ( _sanitize ) ;
1266
1370
ngModel . $formatters . push ( _validity ) ;
1371
+ ngModel . $formatters . push ( function ( value ) {
1372
+ return ngModel . $undoManager . push ( value || '' ) ;
1373
+ } ) ;
1267
1374
1268
1375
var selectorClickHandler = function ( event ) {
1269
1376
// emit the element-select event, pass the element
0 commit comments