1/** 2 * Binds a TinyMCE widget to <textarea> elements. 3 */ 4angular.module('ui.tinymce', []) 5 .value('uiTinymceConfig', {}) 6 .directive('uiTinymce', ['$rootScope', '$compile', '$timeout', '$window', '$sce', 'uiTinymceConfig', function($rootScope, $compile, $timeout, $window, $sce, uiTinymceConfig) { 7 uiTinymceConfig = uiTinymceConfig || {}; 8 var generatedIds = 0; 9 var ID_ATTR = 'ui-tinymce'; 10 if (uiTinymceConfig.baseUrl) { 11 tinymce.baseURL = uiTinymceConfig.baseUrl; 12 } 13 14 return { 15 require: ['ngModel', '^?form'], 16 priority: 599, 17 link: function(scope, element, attrs, ctrls) { 18 if (!$window.tinymce) { 19 return; 20 } 21 22 var ngModel = ctrls[0], 23 form = ctrls[1] || null; 24 25 var expression, options = { 26 debounce: true 27 }, tinyInstance, 28 updateView = function(editor) { 29 var content = editor.getContent({format: options.format}).trim(); 30 content = $sce.trustAsHtml(content); 31 32 ngModel.$setViewValue(content); 33 if (!$rootScope.$$phase) { 34 scope.$digest(); 35 } 36 }; 37 38 function toggleDisable(disabled) { 39 if (disabled) { 40 ensureInstance(); 41 42 if (tinyInstance) { 43 tinyInstance.getBody().setAttribute('contenteditable', false); 44 } 45 } else { 46 ensureInstance(); 47 48 if (tinyInstance && !tinyInstance.settings.readonly) { 49 tinyInstance.getBody().setAttribute('contenteditable', true); 50 } 51 } 52 } 53 54 // generate an ID 55 attrs.$set('id', ID_ATTR + '-' + generatedIds++); 56 57 expression = {}; 58 59 angular.extend(expression, scope.$eval(attrs.uiTinymce)); 60 61 //Debounce update and save action 62 var debouncedUpdate = (function(debouncedUpdateDelay) { 63 var debouncedUpdateTimer; 64 return function(ed) { 65 $timeout.cancel(debouncedUpdateTimer); 66 debouncedUpdateTimer = $timeout(function() { 67 return (function(ed) { 68 if (ed.isDirty()) { 69 ed.save(); 70 updateView(ed); 71 } 72 })(ed); 73 }, debouncedUpdateDelay); 74 }; 75 })(400); 76 77 var setupOptions = { 78 // Update model when calling setContent 79 // (such as from the source editor popup) 80 setup: function(ed) { 81 ed.on('init', function() { 82 ngModel.$render(); 83 ngModel.$setPristine(); 84 ngModel.$setUntouched(); 85 if (form) { 86 form.$setPristine(); 87 } 88 }); 89 90 // Update model when: 91 // - a button has been clicked [ExecCommand] 92 // - the editor content has been modified [change] 93 // - the node has changed [NodeChange] 94 // - an object has been resized (table, image) [ObjectResized] 95 ed.on('ExecCommand change NodeChange ObjectResized', function() { 96 if (!options.debounce) { 97 ed.save(); 98 updateView(ed); 99 return; 100 } 101 debouncedUpdate(ed); 102 }); 103 104 ed.on('blur', function() { 105 element[0].blur(); 106 ngModel.$setTouched(); 107 if (!$rootScope.$$phase) { 108 scope.$digest(); 109 } 110 }); 111 112 ed.on('remove', function() { 113 element.remove(); 114 }); 115 116 if (uiTinymceConfig.setup) { 117 uiTinymceConfig.setup(ed, { 118 updateView: updateView 119 }); 120 } 121 122 if (expression.setup) { 123 expression.setup(ed, { 124 updateView: updateView 125 }); 126 } 127 }, 128 format: expression.format || 'html', 129 selector: '#' + attrs.id 130 }; 131 // extend options with initial uiTinymceConfig and 132 // options from directive attribute value 133 angular.extend(options, uiTinymceConfig, expression, setupOptions); 134 // Wrapped in $timeout due to $tinymce:refresh implementation, requires 135 // element to be present in DOM before instantiating editor when 136 // re-rendering directive 137 $timeout(function() { 138 if (options.baseURL){ 139 tinymce.baseURL = options.baseURL; 140 } 141 tinymce.init(options); 142 toggleDisable(scope.$eval(attrs.ngDisabled)); 143 }); 144 145 ngModel.$formatters.unshift(function(modelValue) { 146 return modelValue ? $sce.trustAsHtml(modelValue) : ''; 147 }); 148 149 ngModel.$parsers.unshift(function(viewValue) { 150 return viewValue ? $sce.getTrustedHtml(viewValue) : ''; 151 }); 152 153 ngModel.$render = function() { 154 ensureInstance(); 155 156 var viewValue = ngModel.$viewValue ? 157 $sce.getTrustedHtml(ngModel.$viewValue) : ''; 158 159 // instance.getDoc() check is a guard against null value 160 // when destruction & recreation of instances happen 161 if (tinyInstance && 162 tinyInstance.getDoc() 163 ) { 164 tinyInstance.setContent(viewValue); 165 // Triggering change event due to TinyMCE not firing event & 166 // becoming out of sync for change callbacks 167 tinyInstance.fire('change'); 168 } 169 }; 170 171 attrs.$observe('disabled', toggleDisable); 172 173 // This block is because of TinyMCE not playing well with removal and 174 // recreation of instances, requiring instances to have different 175 // selectors in order to render new instances properly 176 scope.$on('$tinymce:refresh', function(e, id) { 177 var eid = attrs.id; 178 if (angular.isUndefined(id) || id === eid) { 179 var parentElement = element.parent(); 180 var clonedElement = element.clone(); 181 clonedElement.removeAttr('id'); 182 clonedElement.removeAttr('style'); 183 clonedElement.removeAttr('aria-hidden'); 184 tinymce.execCommand('mceRemoveEditor', false, eid); 185 parentElement.append($compile(clonedElement)(scope)); 186 } 187 }); 188 189 scope.$on('$destroy', function() { 190 ensureInstance(); 191 192 if (tinyInstance) { 193 tinyInstance.remove(); 194 tinyInstance = null; 195 } 196 }); 197 198 function ensureInstance() { 199 if (!tinyInstance) { 200 tinyInstance = tinymce.get(attrs.id); 201 } 202 } 203 } 204 }; 205 }]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.