@grafit/era-dependencies 1.0.0

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.
Files changed (72) hide show
  1. package/package.json +7 -0
  2. package/vendor/fonts/FontAwesome.otf +0 -0
  3. package/vendor/fonts/fontawesome-webfont.eot +0 -0
  4. package/vendor/fonts/fontawesome-webfont.svg +685 -0
  5. package/vendor/fonts/fontawesome-webfont.ttf +0 -0
  6. package/vendor/fonts/fontawesome-webfont.woff +0 -0
  7. package/vendor/fonts/fontawesome-webfont.woff2 +0 -0
  8. package/vendor/fonts/glyphicons-halflings-regular.eot +0 -0
  9. package/vendor/fonts/glyphicons-halflings-regular.svg +288 -0
  10. package/vendor/fonts/glyphicons-halflings-regular.ttf +0 -0
  11. package/vendor/fonts/glyphicons-halflings-regular.woff +0 -0
  12. package/vendor/fonts/glyphicons-halflings-regular.woff2 +0 -0
  13. package/vendor/scripts/angular/angular-cookies.js +322 -0
  14. package/vendor/scripts/angular/angular-file-upload.js +2087 -0
  15. package/vendor/scripts/angular/angular-filter.js +2287 -0
  16. package/vendor/scripts/angular/angular-locale_ru-ru.js +143 -0
  17. package/vendor/scripts/angular/angular-route.js +1069 -0
  18. package/vendor/scripts/angular/angular-sanitize.js +738 -0
  19. package/vendor/scripts/angular/angular-ui-router-0.2.18.js +4539 -0
  20. package/vendor/scripts/angular/angular.js +31768 -0
  21. package/vendor/scripts/angular/datetimepicker.js +578 -0
  22. package/vendor/scripts/angular/datetimepicker.templates.js +30 -0
  23. package/vendor/scripts/angular/mask.min.js +7 -0
  24. package/vendor/scripts/angular/ng-table.js +1518 -0
  25. package/vendor/scripts/angular/select.js +2356 -0
  26. package/vendor/scripts/angular/ui-bootstrap-tpls-2.1.3.js +7536 -0
  27. package/vendor/scripts/angular/uploader.js +3 -0
  28. package/vendor/scripts/bootbox.js +985 -0
  29. package/vendor/scripts/bootstrap.js +2377 -0
  30. package/vendor/scripts/es6-shim.js +3837 -0
  31. package/vendor/scripts/highchart/highcharts-more.src.js +3165 -0
  32. package/vendor/scripts/highchart/highstock.src.js +32008 -0
  33. package/vendor/scripts/highchart/modules/boost.src.js +2721 -0
  34. package/vendor/scripts/highchart/modules/exporting.src.js +951 -0
  35. package/vendor/scripts/jquery/jquery.js +11008 -0
  36. package/vendor/scripts/jquery.datetimepicker.full.js +2911 -0
  37. package/vendor/scripts/keycloak.js +2382 -0
  38. package/vendor/scripts/lodash.js +16733 -0
  39. package/vendor/scripts/moment-with-locales.js +12251 -0
  40. package/vendor/scripts/moment.js +4234 -0
  41. package/vendor/scripts/old/datepicker-ru.js +38 -0
  42. package/vendor/scripts/old/jquery-ui-1.11.1.js +16375 -0
  43. package/vendor/scripts/old/jquery.form.js +1278 -0
  44. package/vendor/scripts/perfect-scrollbar.js +1549 -0
  45. package/vendor/scripts/pickmeup/pickmeup-locales.js +11 -0
  46. package/vendor/scripts/pickmeup/pickmeup.js +1383 -0
  47. package/vendor/scripts/quill.js +9676 -0
  48. package/vendor/scripts/socket.io.min.js +3 -0
  49. package/vendor/scripts/textAngular/angular-spectrum-colorpicker.min.js +2 -0
  50. package/vendor/scripts/textAngular/spectrum.min.js +1 -0
  51. package/vendor/scripts/textAngular/textAngular-dropdownToggle.js +38 -0
  52. package/vendor/scripts/textAngular/textAngular-rangy.min.js +478 -0
  53. package/vendor/scripts/textAngular/textAngular-sanitize.min.js +322 -0
  54. package/vendor/scripts/textAngular/textAngular.min.js +1481 -0
  55. package/vendor/scripts/textAngular/textAngularSetup.js +1013 -0
  56. package/vendor/styles/bootstrap-theme.css +587 -0
  57. package/vendor/styles/bootstrap-theme.css.map +1 -0
  58. package/vendor/styles/bootstrap-theme.min.css +6 -0
  59. package/vendor/styles/bootstrap-theme.min.css.map +1 -0
  60. package/vendor/styles/bootstrap.css +6757 -0
  61. package/vendor/styles/bootstrap.css.map +1 -0
  62. package/vendor/styles/bootstrap.min.css +6 -0
  63. package/vendor/styles/bootstrap.min.css.map +1 -0
  64. package/vendor/styles/datetimepicker.css +115 -0
  65. package/vendor/styles/font-awesome.css +2199 -0
  66. package/vendor/styles/jquery.datetimepicker.min.css +1 -0
  67. package/vendor/styles/ng-table.css +136 -0
  68. package/vendor/styles/normalize.css +424 -0
  69. package/vendor/styles/perfect-scrollbar.css +165 -0
  70. package/vendor/styles/pickmeup.css +137 -0
  71. package/vendor/styles/spectrum.min.css +1 -0
  72. package/vendor/styles/textAngular.css +193 -0
@@ -0,0 +1,1013 @@
1
+
2
+ // tests against the current jqLite/jquery implementation if this can be an element
3
+ function validElementString(string){
4
+ try{
5
+ return angular.element(string).length !== 0;
6
+ }catch(any){
7
+ return false;
8
+ }
9
+ }
10
+ // setup the global contstant functions for setting up the toolbar
11
+
12
+ // all tool definitions
13
+ var taTools = {};
14
+ /*
15
+ A tool definition is an object with the following key/value parameters:
16
+ action: [function(deferred, restoreSelection)]
17
+ a function that is executed on clicking on the button - this will allways be executed using ng-click and will
18
+ overwrite any ng-click value in the display attribute.
19
+ The function is passed a deferred object ($q.defer()), if this is wanted to be used `return false;` from the action and
20
+ manually call `deferred.resolve();` elsewhere to notify the editor that the action has finished.
21
+ restoreSelection is only defined if the rangy library is included and it can be called as `restoreSelection()` to restore the users
22
+ selection in the WYSIWYG editor.
23
+ display: [string]?
24
+ Optional, an HTML element to be displayed as the button. The `scope` of the button is the tool definition object with some additional functions
25
+ If set this will cause buttontext and iconclass to be ignored
26
+ class: [string]?
27
+ Optional, if set will override the taOptions.classes.toolbarButton class.
28
+ buttontext: [string]?
29
+ if this is defined it will replace the contents of the element contained in the `display` element
30
+ iconclass: [string]?
31
+ if this is defined an icon (<i>) will be appended to the `display` element with this string as it's class
32
+ tooltiptext: [string]?
33
+ Optional, a plain text description of the action, used for the title attribute of the action button in the toolbar by default.
34
+ activestate: [function(commonElement)]?
35
+ this function is called on every caret movement, if it returns true then the class taOptions.classes.toolbarButtonActive
36
+ will be applied to the `display` element, else the class will be removed
37
+ disabled: [function()]?
38
+ if this function returns true then the tool will have the class taOptions.classes.disabled applied to it, else it will be removed
39
+ Other functions available on the scope are:
40
+ name: [string]
41
+ the name of the tool, this is the first parameter passed into taRegisterTool
42
+ isDisabled: [function()]
43
+ returns true if the tool is disabled, false if it isn't
44
+ displayActiveToolClass: [function(boolean)]
45
+ returns true if the tool is 'active' in the currently focussed toolbar
46
+ onElementSelect: [Object]
47
+ This object contains the following key/value pairs and is used to trigger the ta-element-select event
48
+ element: [String]
49
+ an element name, will only trigger the onElementSelect action if the tagName of the element matches this string
50
+ filter: [function(element)]?
51
+ an optional filter that returns a boolean, if true it will trigger the onElementSelect.
52
+ action: [function(event, element, editorScope)]
53
+ the action that should be executed if the onElementSelect function runs
54
+ */
55
+ // name and toolDefinition to add into the tools available to be added on the toolbar
56
+ function registerTextAngularTool(name, toolDefinition){
57
+ if(!name || name === '' || taTools.hasOwnProperty(name)) throw('textAngular Error: A unique name is required for a Tool Definition');
58
+ if(
59
+ (toolDefinition.display && (toolDefinition.display === '' || !validElementString(toolDefinition.display))) ||
60
+ (!toolDefinition.display && !toolDefinition.buttontext && !toolDefinition.iconclass)
61
+ )
62
+ throw('textAngular Error: Tool Definition for "' + name + '" does not have a valid display/iconclass/buttontext value');
63
+ taTools[name] = toolDefinition;
64
+ }
65
+
66
+ angular.module('textAngularSetup', [])
67
+ .constant('taRegisterTool', registerTextAngularTool)
68
+ .value('taTools', taTools)
69
+ // Here we set up the global display defaults, to set your own use a angular $provider#decorator.
70
+ .value('taOptions', {
71
+ //////////////////////////////////////////////////////////////////////////////////////
72
+ // forceTextAngularSanitize
73
+ // set false to allow the textAngular-sanitize provider to be replaced
74
+ // with angular-sanitize or a custom provider.
75
+ forceTextAngularSanitize: true,
76
+ ///////////////////////////////////////////////////////////////////////////////////////
77
+ // keyMappings
78
+ // allow customizable keyMappings for specialized key boards or languages
79
+ //
80
+ // keyMappings provides key mappings that are attached to a given commandKeyCode.
81
+ // To modify a specific keyboard binding, simply provide function which returns true
82
+ // for the event you wish to map to.
83
+ // Or to disable a specific keyboard binding, provide a function which returns false.
84
+ // Note: 'RedoKey' and 'UndoKey' are internally bound to the redo and undo functionality.
85
+ // At present, the following commandKeyCodes are in use:
86
+ // 98, 'TabKey', 'ShiftTabKey', 105, 117, 'UndoKey', 'RedoKey'
87
+ //
88
+ // To map to an new commandKeyCode, add a new key mapping such as:
89
+ // {commandKeyCode: 'CustomKey', testForKey: function (event) {
90
+ // if (event.keyCode=57 && event.ctrlKey && !event.shiftKey && !event.altKey) return true;
91
+ // } }
92
+ // to the keyMappings. This example maps ctrl+9 to 'CustomKey'
93
+ // Then where taRegisterTool(...) is called, add a commandKeyCode: 'CustomKey' and your
94
+ // tool will be bound to ctrl+9.
95
+ //
96
+ // To disble one of the already bound commandKeyCodes such as 'RedoKey' or 'UndoKey' add:
97
+ // {commandKeyCode: 'RedoKey', testForKey: function (event) { return false; } },
98
+ // {commandKeyCode: 'UndoKey', testForKey: function (event) { return false; } },
99
+ // to disable them.
100
+ //
101
+ keyMappings : [],
102
+ toolbar: [
103
+ ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'quote'],
104
+ ['bold', 'italics', 'underline', 'strikeThrough', 'ul', 'ol', 'redo', 'undo', 'clear'],
105
+ ['justifyLeft','justifyCenter','justifyRight','justifyFull','indent','outdent'],
106
+ ['html', 'insertImage', 'insertLink', 'insertVideo', 'wordcount', 'charcount']
107
+ ],
108
+ classes: {
109
+ focussed: "focussed",
110
+ toolbar: "btn-toolbar",
111
+ toolbarGroup: "btn-group",
112
+ toolbarButton: "btn btn-default",
113
+ toolbarButtonActive: "active",
114
+ disabled: "disabled",
115
+ textEditor: 'form-control',
116
+ htmlEditor: 'form-control'
117
+ },
118
+ defaultTagAttributes : {
119
+ a: {target:""}
120
+ },
121
+ setup: {
122
+ // wysiwyg mode
123
+ textEditorSetup: function($element){ /* Do some processing here */ },
124
+ // raw html
125
+ htmlEditorSetup: function($element){ /* Do some processing here */ }
126
+ },
127
+ defaultFileDropHandler:
128
+ /* istanbul ignore next: untestable image processing */
129
+ function(file, insertAction){
130
+ var reader = new FileReader();
131
+ if(file.type.substring(0, 5) === 'image'){
132
+ reader.onload = function() {
133
+ if(reader.result !== '') insertAction('insertImage', reader.result, true);
134
+ };
135
+
136
+ reader.readAsDataURL(file);
137
+ // NOTE: For async procedures return a promise and resolve it when the editor should update the model.
138
+ return true;
139
+ }
140
+ return false;
141
+ }
142
+ })
143
+
144
+ // This is the element selector string that is used to catch click events within a taBind, prevents the default and $emits a 'ta-element-select' event
145
+ // these are individually used in an angular.element().find() call. What can go here depends on whether you have full jQuery loaded or just jQLite with angularjs.
146
+ // div is only used as div.ta-insert-video caught in filter.
147
+ .value('taSelectableElements', ['a','img'])
148
+
149
+ // This is an array of objects with the following options:
150
+ // selector: <string> a jqLite or jQuery selector string
151
+ // customAttribute: <string> an attribute to search for
152
+ // renderLogic: <function(element)>
153
+ // Both or one of selector and customAttribute must be defined.
154
+ .value('taCustomRenderers', [
155
+ {
156
+ // Parse back out: '<div class="ta-insert-video" ta-insert-video src="' + urlLink + '" allowfullscreen="true" width="300" frameborder="0" height="250"></div>'
157
+ // To correct video element. For now only support youtube
158
+ selector: 'img',
159
+ customAttribute: 'ta-insert-video',
160
+ renderLogic: function(element){
161
+ var iframe = angular.element('<iframe></iframe>');
162
+ var attributes = element.prop("attributes");
163
+ // loop through element attributes and apply them on iframe
164
+ angular.forEach(attributes, function(attr) {
165
+ iframe.attr(attr.name, attr.value);
166
+ });
167
+ iframe.attr('src', iframe.attr('ta-insert-video'));
168
+ element.replaceWith(iframe);
169
+ }
170
+ }
171
+ ])
172
+
173
+ .value('taTranslations', {
174
+ // moved to sub-elements
175
+ //toggleHTML: "Toggle HTML",
176
+ //insertImage: "Please enter a image URL to insert",
177
+ //insertLink: "Please enter a URL to insert",
178
+ //insertVideo: "Please enter a youtube URL to embed",
179
+ html: {
180
+ tooltip: 'Toggle html / Rich Text'
181
+ },
182
+ // tooltip for heading - might be worth splitting
183
+ heading: {
184
+ tooltip: 'Heading '
185
+ },
186
+ p: {
187
+ tooltip: 'Paragraph'
188
+ },
189
+ pre: {
190
+ tooltip: 'Preformatted text'
191
+ },
192
+ ul: {
193
+ tooltip: 'Unordered List'
194
+ },
195
+ ol: {
196
+ tooltip: 'Ordered List'
197
+ },
198
+ quote: {
199
+ tooltip: 'Quote/unquote selection or paragraph'
200
+ },
201
+ undo: {
202
+ tooltip: 'Undo'
203
+ },
204
+ redo: {
205
+ tooltip: 'Redo'
206
+ },
207
+ bold: {
208
+ tooltip: 'Bold'
209
+ },
210
+ italic: {
211
+ tooltip: 'Italic'
212
+ },
213
+ underline: {
214
+ tooltip: 'Underline'
215
+ },
216
+ strikeThrough:{
217
+ tooltip: 'Strikethrough'
218
+ },
219
+ justifyLeft: {
220
+ tooltip: 'Align text left'
221
+ },
222
+ justifyRight: {
223
+ tooltip: 'Align text right'
224
+ },
225
+ justifyFull: {
226
+ tooltip: 'Justify text'
227
+ },
228
+ justifyCenter: {
229
+ tooltip: 'Center'
230
+ },
231
+ indent: {
232
+ tooltip: 'Increase indent'
233
+ },
234
+ outdent: {
235
+ tooltip: 'Decrease indent'
236
+ },
237
+ clear: {
238
+ tooltip: 'Clear formatting'
239
+ },
240
+ insertImage: {
241
+ dialogPrompt: 'Please enter an image URL to insert',
242
+ tooltip: 'Insert image',
243
+ hotkey: 'the - possibly language dependent hotkey ... for some future implementation'
244
+ },
245
+ insertVideo: {
246
+ tooltip: 'Insert video',
247
+ dialogPrompt: 'Please enter a youtube URL to embed'
248
+ },
249
+ insertLink: {
250
+ tooltip: 'Insert / edit link',
251
+ dialogPrompt: "Please enter a URL to insert"
252
+ },
253
+ editLink: {
254
+ reLinkButton: {
255
+ tooltip: "Relink"
256
+ },
257
+ unLinkButton: {
258
+ tooltip: "Unlink"
259
+ },
260
+ targetToggle: {
261
+ buttontext: "Open in New Window"
262
+ }
263
+ },
264
+ wordcount: {
265
+ tooltip: 'Display words Count'
266
+ },
267
+ charcount: {
268
+ tooltip: 'Display characters Count'
269
+ }
270
+ })
271
+ .factory('taToolFunctions', ['$window','taTranslations', function($window, taTranslations) {
272
+ return {
273
+ imgOnSelectAction: function(event, $element, editorScope){
274
+ // setup the editor toolbar
275
+ // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic/display
276
+ var finishEdit = function(){
277
+ editorScope.updateTaBindtaTextElement();
278
+ editorScope.hidePopover();
279
+ };
280
+ event.preventDefault();
281
+ editorScope.displayElements.popover.css('width', '375px');
282
+ var container = editorScope.displayElements.popoverContainer;
283
+ container.empty();
284
+ var buttonGroup = angular.element('<div class="btn-group" style="padding-right: 6px;">');
285
+ var fullButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">100% </button>');
286
+ fullButton.on('click', function(event){
287
+ event.preventDefault();
288
+ $element.css({
289
+ 'width': '100%',
290
+ 'height': ''
291
+ });
292
+ finishEdit();
293
+ });
294
+ var halfButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">50% </button>');
295
+ halfButton.on('click', function(event){
296
+ event.preventDefault();
297
+ $element.css({
298
+ 'width': '50%',
299
+ 'height': ''
300
+ });
301
+ finishEdit();
302
+ });
303
+ var quartButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">25% </button>');
304
+ quartButton.on('click', function(event){
305
+ event.preventDefault();
306
+ $element.css({
307
+ 'width': '25%',
308
+ 'height': ''
309
+ });
310
+ finishEdit();
311
+ });
312
+ var resetButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1">Reset</button>');
313
+ resetButton.on('click', function(event){
314
+ event.preventDefault();
315
+ $element.css({
316
+ width: '',
317
+ height: ''
318
+ });
319
+ finishEdit();
320
+ });
321
+ buttonGroup.append(fullButton);
322
+ buttonGroup.append(halfButton);
323
+ buttonGroup.append(quartButton);
324
+ buttonGroup.append(resetButton);
325
+ container.append(buttonGroup);
326
+
327
+ buttonGroup = angular.element('<div class="btn-group" style="padding-right: 6px;">');
328
+ var floatLeft = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-left"></i></button>');
329
+ floatLeft.on('click', function(event){
330
+ event.preventDefault();
331
+ // webkit
332
+ $element.css('float', 'left');
333
+ // firefox
334
+ $element.css('cssFloat', 'left');
335
+ // IE < 8
336
+ $element.css('styleFloat', 'left');
337
+ finishEdit();
338
+ });
339
+ var floatRight = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-right"></i></button>');
340
+ floatRight.on('click', function(event){
341
+ event.preventDefault();
342
+ // webkit
343
+ $element.css('float', 'right');
344
+ // firefox
345
+ $element.css('cssFloat', 'right');
346
+ // IE < 8
347
+ $element.css('styleFloat', 'right');
348
+ finishEdit();
349
+ });
350
+ var floatNone = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-align-justify"></i></button>');
351
+ floatNone.on('click', function(event){
352
+ event.preventDefault();
353
+ // webkit
354
+ $element.css('float', '');
355
+ // firefox
356
+ $element.css('cssFloat', '');
357
+ // IE < 8
358
+ $element.css('styleFloat', '');
359
+ finishEdit();
360
+ });
361
+ buttonGroup.append(floatLeft);
362
+ buttonGroup.append(floatNone);
363
+ buttonGroup.append(floatRight);
364
+ container.append(buttonGroup);
365
+
366
+ buttonGroup = angular.element('<div class="btn-group">');
367
+ var remove = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" unselectable="on" tabindex="-1"><i class="fa fa-trash-o"></i></button>');
368
+ remove.on('click', function(event){
369
+ event.preventDefault();
370
+ $element.remove();
371
+ finishEdit();
372
+ });
373
+ buttonGroup.append(remove);
374
+ container.append(buttonGroup);
375
+
376
+ editorScope.showPopover($element);
377
+ editorScope.showResizeOverlay($element);
378
+ },
379
+ aOnSelectAction: function(event, $element, editorScope){
380
+ // setup the editor toolbar
381
+ // Credit to the work at http://hackerwins.github.io/summernote/ for this editbar logic
382
+ event.preventDefault();
383
+ editorScope.displayElements.popover.css('width', '436px');
384
+ var container = editorScope.displayElements.popoverContainer;
385
+ container.empty();
386
+ container.css('line-height', '28px');
387
+ var link = angular.element('<a href="' + $element.attr('href') + '" target="_blank">' + $element.attr('href') + '</a>');
388
+ link.css({
389
+ 'display': 'inline-block',
390
+ 'max-width': '200px',
391
+ 'overflow': 'hidden',
392
+ 'text-overflow': 'ellipsis',
393
+ 'white-space': 'nowrap',
394
+ 'vertical-align': 'middle'
395
+ });
396
+ container.append(link);
397
+ var buttonGroup = angular.element('<div class="btn-group pull-right">');
398
+ var reLinkButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on" title="' + taTranslations.editLink.reLinkButton.tooltip + '"><i class="fa fa-edit icon-edit"></i></button>');
399
+ reLinkButton.on('click', function(event){
400
+ event.preventDefault();
401
+ var urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, $element.attr('href'));
402
+ if(urlLink && urlLink !== '' && urlLink !== 'http://'){
403
+ $element.attr('href', urlLink);
404
+ editorScope.updateTaBindtaTextElement();
405
+ }
406
+ editorScope.hidePopover();
407
+ });
408
+ buttonGroup.append(reLinkButton);
409
+ var unLinkButton = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on" title="' + taTranslations.editLink.unLinkButton.tooltip + '"><i class="fa fa-unlink icon-unlink"></i></button>');
410
+ // directly before this click event is fired a digest is fired off whereby the reference to $element is orphaned off
411
+ unLinkButton.on('click', function(event){
412
+ event.preventDefault();
413
+ $element.replaceWith($element.contents());
414
+ editorScope.updateTaBindtaTextElement();
415
+ editorScope.hidePopover();
416
+ });
417
+ buttonGroup.append(unLinkButton);
418
+ var targetToggle = angular.element('<button type="button" class="btn btn-default btn-sm btn-small" tabindex="-1" unselectable="on">' + taTranslations.editLink.targetToggle.buttontext + '</button>');
419
+ if($element.attr('target') === '_blank'){
420
+ targetToggle.addClass('active');
421
+ }
422
+ targetToggle.on('click', function(event){
423
+ event.preventDefault();
424
+ $element.attr('target', ($element.attr('target') === '_blank') ? '' : '_blank');
425
+ targetToggle.toggleClass('active');
426
+ editorScope.updateTaBindtaTextElement();
427
+ });
428
+ buttonGroup.append(targetToggle);
429
+ container.append(buttonGroup);
430
+ editorScope.showPopover($element);
431
+ },
432
+ extractYoutubeVideoId: function(url) {
433
+ var re = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/i;
434
+ var match = url.match(re);
435
+ return (match && match[1]) || null;
436
+ }
437
+ };
438
+ }])
439
+ .run(['taRegisterTool', '$window', 'taTranslations', 'taSelection', 'taToolFunctions', '$sanitize', 'taOptions', '$log',
440
+ function(taRegisterTool, $window, taTranslations, taSelection, taToolFunctions, $sanitize, taOptions, $log){
441
+ // test for the version of $sanitize that is in use
442
+ // You can disable this check by setting taOptions.textAngularSanitize == false
443
+ var gv = {}; $sanitize('', gv);
444
+ /* istanbul ignore next, throws error */
445
+ if ((taOptions.forceTextAngularSanitize===true) && (gv.version !== 'taSanitize')) {
446
+ throw angular.$$minErr('textAngular')("textAngularSetup", "The textAngular-sanitize provider has been replaced by another -- have you included angular-sanitize by mistake?");
447
+ }
448
+ taRegisterTool("html", {
449
+ iconclass: 'fa fa-code',
450
+ tooltiptext: taTranslations.html.tooltip,
451
+ action: function(){
452
+ this.$editor().switchView();
453
+ },
454
+ activeState: function(){
455
+ return this.$editor().showHtml;
456
+ }
457
+ });
458
+ // add the Header tools
459
+ // convenience functions so that the loop works correctly
460
+ var _retActiveStateFunction = function(q){
461
+ return function(){ return this.$editor().queryFormatBlockState(q); };
462
+ };
463
+ var headerAction = function(){
464
+ return this.$editor().wrapSelection("formatBlock", "<" + this.name.toUpperCase() +">");
465
+ };
466
+ angular.forEach(['h1','h2','h3','h4','h5','h6'], function(h){
467
+ taRegisterTool(h.toLowerCase(), {
468
+ buttontext: h.toUpperCase(),
469
+ tooltiptext: taTranslations.heading.tooltip + h.charAt(1),
470
+ action: headerAction,
471
+ activeState: _retActiveStateFunction(h.toLowerCase())
472
+ });
473
+ });
474
+ taRegisterTool('p', {
475
+ buttontext: 'P',
476
+ tooltiptext: taTranslations.p.tooltip,
477
+ action: function(){
478
+ return this.$editor().wrapSelection("formatBlock", "<P>");
479
+ },
480
+ activeState: function(){ return this.$editor().queryFormatBlockState('p'); }
481
+ });
482
+ // key: pre -> taTranslations[key].tooltip, taTranslations[key].buttontext
483
+ taRegisterTool('pre', {
484
+ buttontext: 'pre',
485
+ tooltiptext: taTranslations.pre.tooltip,
486
+ action: function(){
487
+ return this.$editor().wrapSelection("formatBlock", "<PRE>");
488
+ },
489
+ activeState: function(){ return this.$editor().queryFormatBlockState('pre'); }
490
+ });
491
+ taRegisterTool('ul', {
492
+ iconclass: 'fa fa-list-ul',
493
+ tooltiptext: taTranslations.ul.tooltip,
494
+ action: function(){
495
+ return this.$editor().wrapSelection("insertUnorderedList", null);
496
+ },
497
+ activeState: function(){ return this.$editor().queryCommandState('insertUnorderedList'); }
498
+ });
499
+ taRegisterTool('ol', {
500
+ iconclass: 'fa fa-list-ol',
501
+ tooltiptext: taTranslations.ol.tooltip,
502
+ action: function(){
503
+ return this.$editor().wrapSelection("insertOrderedList", null);
504
+ },
505
+ activeState: function(){ return this.$editor().queryCommandState('insertOrderedList'); }
506
+ });
507
+ taRegisterTool('quote', {
508
+ iconclass: 'fa fa-quote-right',
509
+ tooltiptext: taTranslations.quote.tooltip,
510
+ action: function(){
511
+ return this.$editor().wrapSelection("formatBlock", "<BLOCKQUOTE>");
512
+ },
513
+ activeState: function(){ return this.$editor().queryFormatBlockState('blockquote'); }
514
+ });
515
+ taRegisterTool('undo', {
516
+ iconclass: 'fa fa-undo',
517
+ tooltiptext: taTranslations.undo.tooltip,
518
+ action: function(){
519
+ return this.$editor().wrapSelection("undo", null);
520
+ }
521
+ });
522
+ taRegisterTool('redo', {
523
+ iconclass: 'fa fa-repeat',
524
+ tooltiptext: taTranslations.redo.tooltip,
525
+ action: function(){
526
+ return this.$editor().wrapSelection("redo", null);
527
+ }
528
+ });
529
+ taRegisterTool('bold', {
530
+ iconclass: 'fa fa-bold',
531
+ tooltiptext: taTranslations.bold.tooltip,
532
+ action: function(){
533
+ return this.$editor().wrapSelection("bold", null);
534
+ },
535
+ activeState: function(){
536
+ return this.$editor().queryCommandState('bold');
537
+ },
538
+ commandKeyCode: 98
539
+ });
540
+ taRegisterTool('justifyLeft', {
541
+ iconclass: 'fa fa-align-left',
542
+ tooltiptext: taTranslations.justifyLeft.tooltip,
543
+ action: function(){
544
+ return this.$editor().wrapSelection("justifyLeft", null);
545
+ },
546
+ activeState: function(commonElement){
547
+ /* istanbul ignore next: */
548
+ if (commonElement && commonElement.nodeName === '#document') return false;
549
+ var result = false;
550
+ if (commonElement) {
551
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
552
+ // so we do try catch here...
553
+ try {
554
+ result =
555
+ commonElement.css('text-align') === 'left' ||
556
+ commonElement.attr('align') === 'left' ||
557
+ (
558
+ commonElement.css('text-align') !== 'right' &&
559
+ commonElement.css('text-align') !== 'center' &&
560
+ commonElement.css('text-align') !== 'justify' && !this.$editor().queryCommandState('justifyRight') && !this.$editor().queryCommandState('justifyCenter')
561
+ ) && !this.$editor().queryCommandState('justifyFull');
562
+ } catch(e) {
563
+ /* istanbul ignore next: error handler */
564
+ //console.log(e);
565
+ result = false;
566
+ }
567
+ }
568
+ result = result || this.$editor().queryCommandState('justifyLeft');
569
+ return result;
570
+ }
571
+ });
572
+ taRegisterTool('justifyRight', {
573
+ iconclass: 'fa fa-align-right',
574
+ tooltiptext: taTranslations.justifyRight.tooltip,
575
+ action: function(){
576
+ return this.$editor().wrapSelection("justifyRight", null);
577
+ },
578
+ activeState: function(commonElement){
579
+ /* istanbul ignore next: */
580
+ if (commonElement && commonElement.nodeName === '#document') return false;
581
+ var result = false;
582
+ if(commonElement) {
583
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
584
+ // so we do try catch here...
585
+ try {
586
+ result = commonElement.css('text-align') === 'right';
587
+ } catch(e) {
588
+ /* istanbul ignore next: error handler */
589
+ //console.log(e);
590
+ result = false;
591
+ }
592
+ }
593
+ result = result || this.$editor().queryCommandState('justifyRight');
594
+ return result;
595
+ }
596
+ });
597
+ taRegisterTool('justifyFull', {
598
+ iconclass: 'fa fa-align-justify',
599
+ tooltiptext: taTranslations.justifyFull.tooltip,
600
+ action: function(){
601
+ return this.$editor().wrapSelection("justifyFull", null);
602
+ },
603
+ activeState: function(commonElement){
604
+ var result = false;
605
+ if(commonElement) {
606
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
607
+ // so we do try catch here...
608
+ try {
609
+ result = commonElement.css('text-align') === 'justify';
610
+ } catch(e) {
611
+ /* istanbul ignore next: error handler */
612
+ //console.log(e);
613
+ result = false;
614
+ }
615
+ }
616
+ result = result || this.$editor().queryCommandState('justifyFull');
617
+ return result;
618
+ }
619
+ });
620
+ taRegisterTool('justifyCenter', {
621
+ iconclass: 'fa fa-align-center',
622
+ tooltiptext: taTranslations.justifyCenter.tooltip,
623
+ action: function(){
624
+ return this.$editor().wrapSelection("justifyCenter", null);
625
+ },
626
+ activeState: function(commonElement){
627
+ /* istanbul ignore next: */
628
+ if (commonElement && commonElement.nodeName === '#document') return false;
629
+ var result = false;
630
+ if(commonElement) {
631
+ // commonELement.css('text-align') can throw an error 'Cannot read property 'defaultView' of null' in rare conditions
632
+ // so we do try catch here...
633
+ try {
634
+ result = commonElement.css('text-align') === 'center';
635
+ } catch(e) {
636
+ /* istanbul ignore next: error handler */
637
+ //console.log(e);
638
+ result = false;
639
+ }
640
+
641
+ }
642
+ result = result || this.$editor().queryCommandState('justifyCenter');
643
+ return result;
644
+ }
645
+ });
646
+ taRegisterTool('indent', {
647
+ iconclass: 'fa fa-indent',
648
+ tooltiptext: taTranslations.indent.tooltip,
649
+ action: function(){
650
+ return this.$editor().wrapSelection("indent", null);
651
+ },
652
+ activeState: function(){
653
+ return this.$editor().queryFormatBlockState('blockquote');
654
+ },
655
+ commandKeyCode: 'TabKey'
656
+ });
657
+ taRegisterTool('outdent', {
658
+ iconclass: 'fa fa-outdent',
659
+ tooltiptext: taTranslations.outdent.tooltip,
660
+ action: function(){
661
+ return this.$editor().wrapSelection("outdent", null);
662
+ },
663
+ activeState: function(){
664
+ return false;
665
+ },
666
+ commandKeyCode: 'ShiftTabKey'
667
+ });
668
+ taRegisterTool('italics', {
669
+ iconclass: 'fa fa-italic',
670
+ tooltiptext: taTranslations.italic.tooltip,
671
+ action: function(){
672
+ return this.$editor().wrapSelection("italic", null);
673
+ },
674
+ activeState: function(){
675
+ return this.$editor().queryCommandState('italic');
676
+ },
677
+ commandKeyCode: 105
678
+ });
679
+ taRegisterTool('underline', {
680
+ iconclass: 'fa fa-underline',
681
+ tooltiptext: taTranslations.underline.tooltip,
682
+ action: function(){
683
+ return this.$editor().wrapSelection("underline", null);
684
+ },
685
+ activeState: function(){
686
+ return this.$editor().queryCommandState('underline');
687
+ },
688
+ commandKeyCode: 117
689
+ });
690
+ taRegisterTool('strikeThrough', {
691
+ iconclass: 'fa fa-strikethrough',
692
+ tooltiptext: taTranslations.strikeThrough.tooltip,
693
+ action: function(){
694
+ return this.$editor().wrapSelection("strikeThrough", null);
695
+ },
696
+ activeState: function(){
697
+ return document.queryCommandState('strikeThrough');
698
+ }
699
+ });
700
+ taRegisterTool('clear', {
701
+ iconclass: 'fa fa-ban',
702
+ tooltiptext: taTranslations.clear.tooltip,
703
+ action: function(deferred, restoreSelection){
704
+ var i, selectedElements, elementsSeen;
705
+
706
+ this.$editor().wrapSelection("removeFormat", null);
707
+ var possibleNodes = angular.element(taSelection.getSelectionElement());
708
+ selectedElements = taSelection.getAllSelectedElements();
709
+ //$log.log('selectedElements:', selectedElements);
710
+ // remove lists
711
+ var removeListElements = function(list, pe){
712
+ list = angular.element(list);
713
+ var prevElement = pe;
714
+ if (!pe) {
715
+ prevElement = list;
716
+ }
717
+ angular.forEach(list.children(), function(liElem){
718
+ if (liElem.tagName.toLowerCase() === 'ul' ||
719
+ liElem.tagName.toLowerCase() === 'ol') {
720
+ prevElement = removeListElements(liElem, prevElement);
721
+ } else {
722
+ var newElem = angular.element('<p></p>');
723
+ newElem.html(angular.element(liElem).html());
724
+ prevElement.after(newElem);
725
+ prevElement = newElem;
726
+ }
727
+ });
728
+ list.remove();
729
+ return prevElement;
730
+ };
731
+
732
+ angular.forEach(selectedElements, function(element) {
733
+ if (element.nodeName.toLowerCase() === 'ul' ||
734
+ element.nodeName.toLowerCase() === 'ol') {
735
+ //console.log('removeListElements', element);
736
+ removeListElements(element);
737
+ }
738
+ });
739
+
740
+ angular.forEach(possibleNodes.find("ul"), removeListElements);
741
+ angular.forEach(possibleNodes.find("ol"), removeListElements);
742
+
743
+ // clear out all class attributes. These do not seem to be cleared via removeFormat
744
+ var $editor = this.$editor();
745
+ var recursiveRemoveClass = function(node){
746
+ node = angular.element(node);
747
+ /* istanbul ignore next: this is not triggered in tests any longer since we now never select the whole displayELement */
748
+ if(node[0] !== $editor.displayElements.text[0]) {
749
+ node.removeAttr('class');
750
+ }
751
+ angular.forEach(node.children(), recursiveRemoveClass);
752
+ };
753
+ angular.forEach(possibleNodes, recursiveRemoveClass);
754
+ // check if in list. If not in list then use formatBlock option
755
+ if(possibleNodes[0] && possibleNodes[0].tagName.toLowerCase() !== 'li' &&
756
+ possibleNodes[0].tagName.toLowerCase() !== 'ol' &&
757
+ possibleNodes[0].tagName.toLowerCase() !== 'ul' &&
758
+ possibleNodes[0].getAttribute("contenteditable") !== "true") {
759
+ this.$editor().wrapSelection("formatBlock", "default");
760
+ }
761
+ restoreSelection();
762
+ }
763
+ });
764
+
765
+ /* jshint -W099 */
766
+ /****************************
767
+ // we don't use this code - since the previous way CLEAR is expected to work does not clear partially selected <li>
768
+
769
+ var removeListElement = function(listE){
770
+ console.log(listE);
771
+ var _list = listE.parentNode.childNodes;
772
+ console.log('_list', _list);
773
+ var _preLis = [], _postLis = [], _found = false;
774
+ for (i = 0; i < _list.length; i++) {
775
+ if (_list[i] === listE) {
776
+ _found = true;
777
+ } else if (!_found) _preLis.push(_list[i]);
778
+ else _postLis.push(_list[i]);
779
+ }
780
+ var _parent = angular.element(listE.parentNode);
781
+ var newElem = angular.element('<p></p>');
782
+ newElem.html(angular.element(listE).html());
783
+ if (_preLis.length === 0 || _postLis.length === 0) {
784
+ if (_postLis.length === 0) _parent.after(newElem);
785
+ else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]);
786
+
787
+ if (_preLis.length === 0 && _postLis.length === 0) _parent.remove();
788
+ else angular.element(listE).remove();
789
+ } else {
790
+ var _firstList = angular.element('<' + _parent[0].tagName + '></' + _parent[0].tagName + '>');
791
+ var _secondList = angular.element('<' + _parent[0].tagName + '></' + _parent[0].tagName + '>');
792
+ for (i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i]));
793
+ for (i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i]));
794
+ _parent.after(_secondList);
795
+ _parent.after(newElem);
796
+ _parent.after(_firstList);
797
+ _parent.remove();
798
+ }
799
+ taSelection.setSelectionToElementEnd(newElem[0]);
800
+ };
801
+
802
+ elementsSeen = [];
803
+ if (selectedElements.length !==0) console.log(selectedElements);
804
+ angular.forEach(selectedElements, function (element) {
805
+ if (elementsSeen.indexOf(element) !== -1 || elementsSeen.indexOf(element.parentElement) !== -1) {
806
+ return;
807
+ }
808
+ elementsSeen.push(element);
809
+ if (element.nodeName.toLowerCase() === 'li') {
810
+ console.log('removeListElement', element);
811
+ removeListElement(element);
812
+ }
813
+ else if (element.parentElement && element.parentElement.nodeName.toLowerCase() === 'li') {
814
+ console.log('removeListElement', element.parentElement);
815
+ elementsSeen.push(element.parentElement);
816
+ removeListElement(element.parentElement);
817
+ }
818
+ });
819
+ **********************/
820
+
821
+ /**********************
822
+ if(possibleNodes[0].tagName.toLowerCase() === 'li'){
823
+ var _list = possibleNodes[0].parentNode.childNodes;
824
+ var _preLis = [], _postLis = [], _found = false;
825
+ for(i = 0; i < _list.length; i++){
826
+ if(_list[i] === possibleNodes[0]){
827
+ _found = true;
828
+ }else if(!_found) _preLis.push(_list[i]);
829
+ else _postLis.push(_list[i]);
830
+ }
831
+ var _parent = angular.element(possibleNodes[0].parentNode);
832
+ var newElem = angular.element('<p></p>');
833
+ newElem.html(angular.element(possibleNodes[0]).html());
834
+ if(_preLis.length === 0 || _postLis.length === 0){
835
+ if(_postLis.length === 0) _parent.after(newElem);
836
+ else _parent[0].parentNode.insertBefore(newElem[0], _parent[0]);
837
+
838
+ if(_preLis.length === 0 && _postLis.length === 0) _parent.remove();
839
+ else angular.element(possibleNodes[0]).remove();
840
+ }else{
841
+ var _firstList = angular.element('<'+_parent[0].tagName+'></'+_parent[0].tagName+'>');
842
+ var _secondList = angular.element('<'+_parent[0].tagName+'></'+_parent[0].tagName+'>');
843
+ for(i = 0; i < _preLis.length; i++) _firstList.append(angular.element(_preLis[i]));
844
+ for(i = 0; i < _postLis.length; i++) _secondList.append(angular.element(_postLis[i]));
845
+ _parent.after(_secondList);
846
+ _parent.after(newElem);
847
+ _parent.after(_firstList);
848
+ _parent.remove();
849
+ }
850
+ taSelection.setSelectionToElementEnd(newElem[0]);
851
+ }
852
+ *******************/
853
+
854
+
855
+ /* istanbul ignore next: if it's javascript don't worry - though probably should show some kind of error message */
856
+ var blockJavascript = function (link) {
857
+ if (link.toLowerCase().indexOf('javascript')!==-1) {
858
+ return true;
859
+ }
860
+ return false;
861
+ };
862
+
863
+ taRegisterTool('insertImage', {
864
+ iconclass: 'fa fa-picture-o',
865
+ tooltiptext: taTranslations.insertImage.tooltip,
866
+ action: function(){
867
+ var imageLink;
868
+ imageLink = $window.prompt(taTranslations.insertImage.dialogPrompt, 'http://');
869
+ if(imageLink && imageLink !== '' && imageLink !== 'http://'){
870
+ /* istanbul ignore next: don't know how to test this... since it needs a dialogPrompt */
871
+ // block javascript here
872
+ if (!blockJavascript(imageLink)) {
873
+ if (taSelection.getSelectionElement().tagName && taSelection.getSelectionElement().tagName.toLowerCase() === 'a') {
874
+ // due to differences in implementation between FireFox and Chrome, we must move the
875
+ // insertion point past the <a> element, otherwise FireFox inserts inside the <a>
876
+ // With this change, both FireFox and Chrome behave the same way!
877
+ taSelection.setSelectionAfterElement(taSelection.getSelectionElement());
878
+ }
879
+ // In the past we used the simple statement:
880
+ //return this.$editor().wrapSelection('insertImage', imageLink, true);
881
+ //
882
+ // However on Firefox only, when the content is empty this is a problem
883
+ // See Issue #1201
884
+ // Investigation reveals that Firefox only inserts a <p> only!!!!
885
+ // So now we use insertHTML here and all is fine.
886
+ // NOTE: this is what 'insertImage' is supposed to do anyway!
887
+ var embed = '<img src="' + imageLink + '">';
888
+ return this.$editor().wrapSelection('insertHTML', embed, true);
889
+ }
890
+ }
891
+ },
892
+ onElementSelect: {
893
+ element: 'img',
894
+ action: taToolFunctions.imgOnSelectAction
895
+ }
896
+ });
897
+ taRegisterTool('insertVideo', {
898
+ iconclass: 'fa fa-youtube-play',
899
+ tooltiptext: taTranslations.insertVideo.tooltip,
900
+ action: function(){
901
+ var urlPrompt;
902
+ urlPrompt = $window.prompt(taTranslations.insertVideo.dialogPrompt, 'https://');
903
+ // block javascript here
904
+ /* istanbul ignore else: if it's javascript don't worry - though probably should show some kind of error message */
905
+ if (!blockJavascript(urlPrompt)) {
906
+
907
+ if (urlPrompt && urlPrompt !== '' && urlPrompt !== 'https://') {
908
+
909
+ videoId = taToolFunctions.extractYoutubeVideoId(urlPrompt);
910
+
911
+ /* istanbul ignore else: if it's invalid don't worry - though probably should show some kind of error message */
912
+ if (videoId) {
913
+ // create the embed link
914
+ var urlLink = "https://www.youtube.com/embed/" + videoId;
915
+ // create the HTML
916
+ // for all options see: http://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
917
+ // maxresdefault.jpg seems to be undefined on some.
918
+ var embed = '<img class="ta-insert-video" src="https://img.youtube.com/vi/' + videoId + '/hqdefault.jpg" ta-insert-video="' + urlLink + '" contenteditable="false" allowfullscreen="true" frameborder="0" />';
919
+ /* istanbul ignore next: don't know how to test this... since it needs a dialogPrompt */
920
+ if (taSelection.getSelectionElement().tagName && taSelection.getSelectionElement().tagName.toLowerCase() === 'a') {
921
+ // due to differences in implementation between FireFox and Chrome, we must move the
922
+ // insertion point past the <a> element, otherwise FireFox inserts inside the <a>
923
+ // With this change, both FireFox and Chrome behave the same way!
924
+ taSelection.setSelectionAfterElement(taSelection.getSelectionElement());
925
+ }
926
+ // insert
927
+ return this.$editor().wrapSelection('insertHTML', embed, true);
928
+ }
929
+ }
930
+ }
931
+ },
932
+ onElementSelect: {
933
+ element: 'img',
934
+ onlyWithAttrs: ['ta-insert-video'],
935
+ action: taToolFunctions.imgOnSelectAction
936
+ }
937
+ });
938
+ taRegisterTool('insertLink', {
939
+ tooltiptext: taTranslations.insertLink.tooltip,
940
+ iconclass: 'fa fa-link',
941
+ action: function(){
942
+ var urlLink;
943
+ // if this link has already been set, we need to just edit the existing link
944
+ /* istanbul ignore if: we do not test this */
945
+ if (taSelection.getSelectionElement().tagName && taSelection.getSelectionElement().tagName.toLowerCase() === 'a') {
946
+ urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, taSelection.getSelectionElement().href);
947
+ } else {
948
+ urlLink = $window.prompt(taTranslations.insertLink.dialogPrompt, 'http://');
949
+ }
950
+ if(urlLink && urlLink !== '' && urlLink !== 'http://'){
951
+ // block javascript here
952
+ /* istanbul ignore else: if it's javascript don't worry - though probably should show some kind of error message */
953
+ if (!blockJavascript(urlLink)) {
954
+ return this.$editor().wrapSelection('createLink', urlLink, true);
955
+ }
956
+ }
957
+ },
958
+ activeState: function(commonElement){
959
+ if(commonElement) return commonElement[0].tagName === 'A';
960
+ return false;
961
+ },
962
+ onElementSelect: {
963
+ element: 'a',
964
+ action: taToolFunctions.aOnSelectAction
965
+ }
966
+ });
967
+ taRegisterTool('wordcount', {
968
+ display: '<div id="toolbarWC" style="display:block; min-width:100px;">Words: <span ng-bind="wordcount"></span></div>',
969
+ disabled: true,
970
+ wordcount: 0,
971
+ activeState: function(){ // this fires on keyup
972
+ var textElement = this.$editor().displayElements.text;
973
+ /* istanbul ignore next: will default to '' when undefined */
974
+ var workingHTML = textElement[0].innerHTML || '';
975
+ var noOfWords = 0;
976
+
977
+ /* istanbul ignore if: will default to '' when undefined */
978
+ if (workingHTML.replace(/\s*<[^>]*?>\s*/g, '') !== '') {
979
+ if (workingHTML.trim() !== '') {
980
+ noOfWords = workingHTML.replace(/<\/?(b|i|em|strong|span|u|strikethrough|a|img|small|sub|sup|label)( [^>*?])?>/gi, '') // remove inline tags without adding spaces
981
+ .replace(/(<[^>]*?>\s*<[^>]*?>)/ig, ' ') // replace adjacent tags with possible space between with a space
982
+ .replace(/(<[^>]*?>)/ig, '') // remove any singular tags
983
+ .replace(/\s+/ig, ' ') // condense spacing
984
+ .match(/\S+/g).length; // count remaining non-space strings
985
+ }
986
+ }
987
+
988
+ //Set current scope
989
+ this.wordcount = noOfWords;
990
+ //Set editor scope
991
+ this.$editor().wordcount = noOfWords;
992
+
993
+ return false;
994
+ }
995
+ });
996
+ taRegisterTool('charcount', {
997
+ display: '<div id="toolbarCC" style="display:block; min-width:120px;">Characters: <span ng-bind="charcount"></span></div>',
998
+ disabled: true,
999
+ charcount: 0,
1000
+ activeState: function(){ // this fires on keyup
1001
+ var textElement = this.$editor().displayElements.text;
1002
+ var sourceText = textElement[0].innerText || textElement[0].textContent; // to cover the non-jquery use case.
1003
+
1004
+ // Caculate number of chars
1005
+ var noOfChars = sourceText.replace(/(\r\n|\n|\r)/gm,"").replace(/^\s+/g,' ').replace(/\s+$/g, ' ').length;
1006
+ //Set current scope
1007
+ this.charcount = noOfChars;
1008
+ //Set editor scope
1009
+ this.$editor().charcount = noOfChars;
1010
+ return false;
1011
+ }
1012
+ });
1013
+ }]);