scratch – Diff between revs 58 and 125

Subversion Repositories:
Rev:
Only display areas with differencesIgnore whitespace
Rev 58 Rev 125
1 /** 1 /**
2 * Trumbowyg v2.5.1 - A lightweight WYSIWYG editor 2 * Trumbowyg v2.6.0 - A lightweight WYSIWYG editor
3 * Trumbowyg core file 3 * Trumbowyg core file
4 * ------------------------ 4 * ------------------------
5 * @link http://alex-d.github.io/Trumbowyg 5 * @link http://alex-d.github.io/Trumbowyg
6 * @license MIT 6 * @license MIT
7 * @author Alexandre Demode (Alex-D) 7 * @author Alexandre Demode (Alex-D)
8 * Twitter : @AlexandreDemode 8 * Twitter : @AlexandreDemode
9 * Website : alex-d.fr 9 * Website : alex-d.fr
10 */ 10 */
11   11  
12 jQuery.trumbowyg = { 12 jQuery.trumbowyg = {
13 langs: { 13 langs: {
14 en: { 14 en: {
15 viewHTML: 'View HTML', 15 viewHTML: 'View HTML',
16   16  
17 undo: 'Undo', 17 undo: 'Undo',
18 redo: 'Redo', 18 redo: 'Redo',
19   19  
20 formatting: 'Formatting', 20 formatting: 'Formatting',
21 p: 'Paragraph', 21 p: 'Paragraph',
22 blockquote: 'Quote', 22 blockquote: 'Quote',
23 code: 'Code', 23 code: 'Code',
24 header: 'Header', 24 header: 'Header',
25   25  
26 bold: 'Bold', 26 bold: 'Bold',
27 italic: 'Italic', 27 italic: 'Italic',
28 strikethrough: 'Stroke', 28 strikethrough: 'Stroke',
29 underline: 'Underline', 29 underline: 'Underline',
30   30  
31 strong: 'Strong', 31 strong: 'Strong',
32 em: 'Emphasis', 32 em: 'Emphasis',
33 del: 'Deleted', 33 del: 'Deleted',
34   34  
35 superscript: 'Superscript', 35 superscript: 'Superscript',
36 subscript: 'Subscript', 36 subscript: 'Subscript',
37   37  
38 unorderedList: 'Unordered list', 38 unorderedList: 'Unordered list',
39 orderedList: 'Ordered list', 39 orderedList: 'Ordered list',
40   40  
41 insertImage: 'Insert Image', 41 insertImage: 'Insert Image',
42 link: 'Link', 42 link: 'Link',
43 createLink: 'Insert link', 43 createLink: 'Insert link',
44 unlink: 'Remove link', 44 unlink: 'Remove link',
45   45  
46 justifyLeft: 'Align Left', 46 justifyLeft: 'Align Left',
47 justifyCenter: 'Align Center', 47 justifyCenter: 'Align Center',
48 justifyRight: 'Align Right', 48 justifyRight: 'Align Right',
49 justifyFull: 'Align Justify', 49 justifyFull: 'Align Justify',
50   50  
51 horizontalRule: 'Insert horizontal rule', 51 horizontalRule: 'Insert horizontal rule',
52 removeformat: 'Remove format', 52 removeformat: 'Remove format',
53   53  
54 fullscreen: 'Fullscreen', 54 fullscreen: 'Fullscreen',
55   55  
56 close: 'Close', 56 close: 'Close',
57   57  
58 submit: 'Confirm', 58 submit: 'Confirm',
59 reset: 'Cancel', 59 reset: 'Cancel',
60   60  
61 required: 'Required', 61 required: 'Required',
62 description: 'Description', 62 description: 'Description',
63 title: 'Title', 63 title: 'Title',
64 text: 'Text', 64 text: 'Text',
65 target: 'Target' 65 target: 'Target'
66 } 66 }
67 }, 67 },
68   68  
69 // Plugins 69 // Plugins
70 plugins: {}, 70 plugins: {},
71   71  
72 // SVG Path globally 72 // SVG Path globally
73 svgPath: null, 73 svgPath: null,
74   74  
75 hideButtonTexts: null 75 hideButtonTexts: null
76 }; 76 };
77   77  
78   78  
79 (function (navigator, window, document, $) { 79 (function (navigator, window, document, $) {
80 'use strict'; 80 'use strict';
81   81  
82 $.fn.trumbowyg = function (options, params) { 82 $.fn.trumbowyg = function (options, params) {
83 var trumbowygDataName = 'trumbowyg'; 83 var trumbowygDataName = 'trumbowyg';
84 if (options === Object(options) || !options) { 84 if (options === Object(options) || !options) {
85 return this.each(function () { 85 return this.each(function () {
86 if (!$(this).data(trumbowygDataName)) { 86 if (!$(this).data(trumbowygDataName)) {
87 $(this).data(trumbowygDataName, new Trumbowyg(this, options)); 87 $(this).data(trumbowygDataName, new Trumbowyg(this, options));
88 } 88 }
89 }); 89 });
90 } 90 }
91 if (this.length === 1) { 91 if (this.length === 1) {
92 try { 92 try {
93 var t = $(this).data(trumbowygDataName); 93 var t = $(this).data(trumbowygDataName);
94 switch (options) { 94 switch (options) {
95 // Exec command 95 // Exec command
96 case 'execCmd': 96 case 'execCmd':
97 return t.execCmd(params.cmd, params.param, params.forceCss); 97 return t.execCmd(params.cmd, params.param, params.forceCss);
98   98  
99 // Modal box 99 // Modal box
100 case 'openModal': 100 case 'openModal':
101 return t.openModal(params.title, params.content); 101 return t.openModal(params.title, params.content);
102 case 'closeModal': 102 case 'closeModal':
103 return t.closeModal(); 103 return t.closeModal();
104 case 'openModalInsert': 104 case 'openModalInsert':
105 return t.openModalInsert(params.title, params.fields, params.callback); 105 return t.openModalInsert(params.title, params.fields, params.callback);
106   106  
107 // Range 107 // Range
108 case 'saveRange': 108 case 'saveRange':
109 return t.saveRange(); 109 return t.saveRange();
110 case 'getRange': 110 case 'getRange':
111 return t.range; 111 return t.range;
112 case 'getRangeText': 112 case 'getRangeText':
113 return t.getRangeText(); 113 return t.getRangeText();
114 case 'restoreRange': 114 case 'restoreRange':
115 return t.restoreRange(); 115 return t.restoreRange();
116   116  
117 // Enable/disable 117 // Enable/disable
118 case 'enable': 118 case 'enable':
119 return t.toggleDisable(false); 119 return t.toggleDisable(false);
120 case 'disable': 120 case 'disable':
121 return t.toggleDisable(true); 121 return t.toggleDisable(true);
122   122  
123 // Destroy 123 // Destroy
124 case 'destroy': 124 case 'destroy':
125 return t.destroy(); 125 return t.destroy();
126   126  
127 // Empty 127 // Empty
128 case 'empty': 128 case 'empty':
129 return t.empty(); 129 return t.empty();
130   130  
131 // HTML 131 // HTML
132 case 'html': 132 case 'html':
133 return t.html(params); 133 return t.html(params);
134 } 134 }
135 } catch (c) { 135 } catch (c) {
136 } 136 }
137 } 137 }
138   138  
139 return false; 139 return false;
140 }; 140 };
141   141  
142 // @param: editorElem is the DOM element 142 // @param: editorElem is the DOM element
143 var Trumbowyg = function (editorElem, options) { 143 var Trumbowyg = function (editorElem, options) {
144 var t = this, 144 var t = this,
145 trumbowygIconsId = 'trumbowyg-icons'; 145 trumbowygIconsId = 'trumbowyg-icons';
146   146  
147 // Get the document of the element. It use to makes the plugin 147 // Get the document of the element. It use to makes the plugin
148 // compatible on iframes. 148 // compatible on iframes.
149 t.doc = editorElem.ownerDocument || document; 149 t.doc = editorElem.ownerDocument || document;
150   150  
151 // jQuery object of the editor 151 // jQuery object of the editor
152 t.$ta = $(editorElem); // $ta : Textarea 152 t.$ta = $(editorElem); // $ta : Textarea
153 t.$c = $(editorElem); // $c : creator 153 t.$c = $(editorElem); // $c : creator
154   154  
155 options = options || {}; 155 options = options || {};
156   156  
157 // Localization management 157 // Localization management
158 if (options.lang != null || $.trumbowyg.langs[options.lang] != null) { 158 if (options.lang != null || $.trumbowyg.langs[options.lang] != null) {
159 t.lang = $.extend(true, {}, $.trumbowyg.langs.en, $.trumbowyg.langs[options.lang]); 159 t.lang = $.extend(true, {}, $.trumbowyg.langs.en, $.trumbowyg.langs[options.lang]);
160 } else { 160 } else {
161 t.lang = $.trumbowyg.langs.en; 161 t.lang = $.trumbowyg.langs.en;
162 } 162 }
163   163  
164 t.hideButtonTexts = $.trumbowyg.hideButtonTexts != null ? $.trumbowyg.hideButtonTexts : options.hideButtonTexts; 164 t.hideButtonTexts = $.trumbowyg.hideButtonTexts != null ? $.trumbowyg.hideButtonTexts : options.hideButtonTexts;
165   165  
166 // SVG path 166 // SVG path
167 var svgPathOption = $.trumbowyg.svgPath != null ? $.trumbowyg.svgPath : options.svgPath; 167 var svgPathOption = $.trumbowyg.svgPath != null ? $.trumbowyg.svgPath : options.svgPath;
168 t.hasSvg = svgPathOption !== false; 168 t.hasSvg = svgPathOption !== false;
169 t.svgPath = !!t.doc.querySelector('base') ? window.location.href.split('#')[0] : ''; 169 t.svgPath = !!t.doc.querySelector('base') ? window.location.href.split('#')[0] : '';
170 if ($('#' + trumbowygIconsId, t.doc).length === 0 && svgPathOption !== false) { 170 if ($('#' + trumbowygIconsId, t.doc).length === 0 && svgPathOption !== false) {
171 if (svgPathOption == null) { 171 if (svgPathOption == null) {
172 try { 172 try {
173 throw new Error(); 173 throw new Error();
174 } catch (e) { 174 } catch (e) {
175 var stackLines = e.stack.split('\n'); 175 var stackLines = e.stack.split('\n');
176   176  
177 for (var i in stackLines) { 177 for (var i in stackLines) {
178 if (!stackLines[i].match(/http[s]?:\/\//)) { 178 if (!stackLines[i].match(/http[s]?:\/\//)) {
179 continue; 179 continue;
180 } 180 }
181 svgPathOption = stackLines[Number(i)].match(/((http[s]?:\/\/.+\/)([^\/]+\.js))(\?.*)?:/)[1].split('/'); 181 svgPathOption = stackLines[Number(i)].match(/((http[s]?:\/\/.+\/)([^\/]+\.js))(\?.*)?:/)[1].split('/');
182 svgPathOption.pop(); 182 svgPathOption.pop();
183 svgPathOption = svgPathOption.join('/') + '/ui/icons.svg'; 183 svgPathOption = svgPathOption.join('/') + '/ui/icons.svg';
184 break; 184 break;
185 } 185 }
186 } 186 }
187 } 187 }
188   188  
189 var div = t.doc.createElement('div'); 189 var div = t.doc.createElement('div');
190 div.id = trumbowygIconsId; 190 div.id = trumbowygIconsId;
191 t.doc.body.insertBefore(div, t.doc.body.childNodes[0]); 191 t.doc.body.insertBefore(div, t.doc.body.childNodes[0]);
192 $.ajax({ 192 $.ajax({
193 async: true, 193 async: true,
194 type: 'GET', 194 type: 'GET',
195 contentType: 'application/x-www-form-urlencoded; charset=UTF-8', 195 contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
196 dataType: 'xml', 196 dataType: 'xml',
-   197 crossDomain : true,
197 url: svgPathOption, 198 url: svgPathOption,
198 data: null, 199 data: null,
199 beforeSend: null, 200 beforeSend: null,
200 complete: null, 201 complete: null,
201 success: function (data) { 202 success: function (data) {
202 div.innerHTML = new XMLSerializer().serializeToString(data.documentElement); 203 div.innerHTML = new XMLSerializer().serializeToString(data.documentElement);
203 } 204 }
204 }); 205 });
205 } 206 }
206   207  
207   208  
208 /** 209 /**
209 * When the button is associated to a empty object 210 * When the button is associated to a empty object
210 * fn and title attributs are defined from the button key value 211 * fn and title attributs are defined from the button key value
211 * 212 *
212 * For example 213 * For example
213 * foo: {} 214 * foo: {}
214 * is equivalent to : 215 * is equivalent to :
215 * foo: { 216 * foo: {
216 * fn: 'foo', 217 * fn: 'foo',
217 * title: this.lang.foo 218 * title: this.lang.foo
218 * } 219 * }
219 */ 220 */
220 var h = t.lang.header, // Header translation 221 var h = t.lang.header, // Header translation
221 isBlinkFunction = function () { 222 isBlinkFunction = function () {
222 return (window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window; 223 return (window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window;
223 }; 224 };
224 t.btnsDef = { 225 t.btnsDef = {
225 viewHTML: { 226 viewHTML: {
226 fn: 'toggle' 227 fn: 'toggle'
227 }, 228 },
228   229  
229 undo: { 230 undo: {
230 isSupported: isBlinkFunction, 231 isSupported: isBlinkFunction,
231 key: 'Z' 232 key: 'Z'
232 }, 233 },
233 redo: { 234 redo: {
234 isSupported: isBlinkFunction, 235 isSupported: isBlinkFunction,
235 key: 'Y' 236 key: 'Y'
236 }, 237 },
237   238  
238 p: { 239 p: {
239 fn: 'formatBlock' 240 fn: 'formatBlock'
240 }, 241 },
241 blockquote: { 242 blockquote: {
242 fn: 'formatBlock' 243 fn: 'formatBlock'
243 }, 244 },
244 h1: { 245 h1: {
245 fn: 'formatBlock', 246 fn: 'formatBlock',
246 title: h + ' 1' 247 title: h + ' 1'
247 }, 248 },
248 h2: { 249 h2: {
249 fn: 'formatBlock', 250 fn: 'formatBlock',
250 title: h + ' 2' 251 title: h + ' 2'
251 }, 252 },
252 h3: { 253 h3: {
253 fn: 'formatBlock', 254 fn: 'formatBlock',
254 title: h + ' 3' 255 title: h + ' 3'
255 }, 256 },
256 h4: { 257 h4: {
257 fn: 'formatBlock', 258 fn: 'formatBlock',
258 title: h + ' 4' 259 title: h + ' 4'
259 }, 260 },
260 subscript: { 261 subscript: {
261 tag: 'sub' 262 tag: 'sub'
262 }, 263 },
263 superscript: { 264 superscript: {
264 tag: 'sup' 265 tag: 'sup'
265 }, 266 },
266   267  
267 bold: { 268 bold: {
268 key: 'B', 269 key: 'B',
269 tag: 'b' 270 tag: 'b'
270 }, 271 },
271 italic: { 272 italic: {
272 key: 'I', 273 key: 'I',
273 tag: 'i' 274 tag: 'i'
274 }, 275 },
275 underline: { 276 underline: {
276 tag: 'u' 277 tag: 'u'
277 }, 278 },
278 strikethrough: { 279 strikethrough: {
279 tag: 'strike' 280 tag: 'strike'
280 }, 281 },
281   282  
282 strong: { 283 strong: {
283 fn: 'bold', 284 fn: 'bold',
284 key: 'B' 285 key: 'B'
285 }, 286 },
286 em: { 287 em: {
287 fn: 'italic', 288 fn: 'italic',
288 key: 'I' 289 key: 'I'
289 }, 290 },
290 del: { 291 del: {
291 fn: 'strikethrough' 292 fn: 'strikethrough'
292 }, 293 },
293   294  
294 createLink: { 295 createLink: {
295 key: 'K', 296 key: 'K',
296 tag: 'a' 297 tag: 'a'
297 }, 298 },
298 unlink: {}, 299 unlink: {},
299   300  
300 insertImage: {}, 301 insertImage: {},
301   302  
302 justifyLeft: { 303 justifyLeft: {
303 tag: 'left', 304 tag: 'left',
304 forceCss: true 305 forceCss: true
305 }, 306 },
306 justifyCenter: { 307 justifyCenter: {
307 tag: 'center', 308 tag: 'center',
308 forceCss: true 309 forceCss: true
309 }, 310 },
310 justifyRight: { 311 justifyRight: {
311 tag: 'right', 312 tag: 'right',
312 forceCss: true 313 forceCss: true
313 }, 314 },
314 justifyFull: { 315 justifyFull: {
315 tag: 'justify', 316 tag: 'justify',
316 forceCss: true 317 forceCss: true
317 }, 318 },
318   319  
319 unorderedList: { 320 unorderedList: {
320 fn: 'insertUnorderedList', 321 fn: 'insertUnorderedList',
321 tag: 'ul' 322 tag: 'ul'
322 }, 323 },
323 orderedList: { 324 orderedList: {
324 fn: 'insertOrderedList', 325 fn: 'insertOrderedList',
325 tag: 'ol' 326 tag: 'ol'
326 }, 327 },
327   328  
328 horizontalRule: { 329 horizontalRule: {
329 fn: 'insertHorizontalRule' 330 fn: 'insertHorizontalRule'
330 }, 331 },
331   332  
332 removeformat: {}, 333 removeformat: {},
333   334  
334 fullscreen: { 335 fullscreen: {
335 class: 'trumbowyg-not-disable' 336 class: 'trumbowyg-not-disable'
336 }, 337 },
337 close: { 338 close: {
338 fn: 'destroy', 339 fn: 'destroy',
339 class: 'trumbowyg-not-disable' 340 class: 'trumbowyg-not-disable'
340 }, 341 },
341   342  
342 // Dropdowns 343 // Dropdowns
343 formatting: { 344 formatting: {
344 dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4'], 345 dropdown: ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4'],
345 ico: 'p' 346 ico: 'p'
346 }, 347 },
347 link: { 348 link: {
348 dropdown: ['createLink', 'unlink'] 349 dropdown: ['createLink', 'unlink']
349 } 350 }
350 }; 351 };
351   352  
352 // Defaults Options 353 // Defaults Options
353 t.o = $.extend(true, {}, { 354 t.o = $.extend(true, {}, {
354 lang: 'en', 355 lang: 'en',
355   356  
356 fixedBtnPane: false, 357 fixedBtnPane: false,
357 fixedFullWidth: false, 358 fixedFullWidth: false,
358 autogrow: false, 359 autogrow: false,
359   360  
360 prefix: 'trumbowyg-', 361 prefix: 'trumbowyg-',
361   362  
362 semantic: true, 363 semantic: true,
363 resetCss: false, 364 resetCss: false,
364 removeformatPasted: false, 365 removeformatPasted: false,
365 tagsToRemove: [], 366 tagsToRemove: [],
366   367  
367 btnsGrps: { 368 btnsGrps: {
368 design: ['bold', 'italic', 'underline', 'strikethrough'], 369 design: ['bold', 'italic', 'underline', 'strikethrough'],
369 semantic: ['strong', 'em', 'del'], 370 semantic: ['strong', 'em', 'del'],
370 justify: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], 371 justify: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
371 lists: ['unorderedList', 'orderedList'] 372 lists: ['unorderedList', 'orderedList']
372 }, 373 },
373 btns: [ 374 btns: [
374 ['viewHTML'], 375 ['viewHTML'],
375 ['undo', 'redo'], 376 ['undo', 'redo'],
376 ['formatting'], 377 ['formatting'],
377 'btnGrp-semantic', 378 'btnGrp-semantic',
378 ['superscript', 'subscript'], 379 ['superscript', 'subscript'],
379 ['link'], 380 ['link'],
380 ['insertImage'], 381 ['insertImage'],
381 'btnGrp-justify', 382 'btnGrp-justify',
382 'btnGrp-lists', 383 'btnGrp-lists',
383 ['horizontalRule'], 384 ['horizontalRule'],
384 ['removeformat'], 385 ['removeformat'],
385 ['fullscreen'] 386 ['fullscreen']
386 ], 387 ],
387 // For custom button definitions 388 // For custom button definitions
388 btnsDef: {}, 389 btnsDef: {},
389   390  
390 inlineElementsSelector: 'a,abbr,acronym,b,caption,cite,code,col,dfn,dir,dt,dd,em,font,hr,i,kbd,li,q,span,strikeout,strong,sub,sup,u', 391 inlineElementsSelector: 'a,abbr,acronym,b,caption,cite,code,col,dfn,dir,dt,dd,em,font,hr,i,kbd,li,q,span,strikeout,strong,sub,sup,u',
391   392  
392 pasteHandlers: [], 393 pasteHandlers: [],
393   394  
394 imgDblClickHandler: function () { 395 imgDblClickHandler: function () {
395 var $img = $(this), 396 var $img = $(this),
396 src = $img.attr('src'), 397 src = $img.attr('src'),
397 base64 = '(Base64)'; 398 base64 = '(Base64)';
398   399  
399 if (src.indexOf('data:image') === 0) { 400 if (src.indexOf('data:image') === 0) {
400 src = base64; 401 src = base64;
401 } 402 }
402   403  
403 t.openModalInsert(t.lang.insertImage, { 404 t.openModalInsert(t.lang.insertImage, {
404 url: { 405 url: {
405 label: 'URL', 406 label: 'URL',
406 value: src, 407 value: src,
407 required: true 408 required: true
408 }, 409 },
409 alt: { 410 alt: {
410 label: t.lang.description, 411 label: t.lang.description,
411 value: $img.attr('alt') 412 value: $img.attr('alt')
412 } 413 }
413 }, function (v) { 414 }, function (v) {
414 if (v.src !== base64) { 415 if (v.src !== base64) {
415 $img.attr({ 416 $img.attr({
416 src: v.src 417 src: v.src
417 }); 418 });
418 } 419 }
419 $img.attr({ 420 $img.attr({
420 alt: v.alt 421 alt: v.alt
421 }); 422 });
422 return true; 423 return true;
423 }); 424 });
424 return false; 425 return false;
425 }, 426 },
426   427  
427 plugins: {} 428 plugins: {}
428 }, options); 429 }, options);
429   430  
430 t.disabled = t.o.disabled || (editorElem.nodeName === 'TEXTAREA' && editorElem.disabled); 431 t.disabled = t.o.disabled || (editorElem.nodeName === 'TEXTAREA' && editorElem.disabled);
431   432  
432 if (options.btns) { 433 if (options.btns) {
433 t.o.btns = options.btns; 434 t.o.btns = options.btns;
434 } else if (!t.o.semantic) { 435 } else if (!t.o.semantic) {
435 t.o.btns[4] = 'btnGrp-design'; 436 t.o.btns[4] = 'btnGrp-design';
436 } 437 }
437   438  
438 $.each(t.o.btnsDef, function (btnName, btnDef) { 439 $.each(t.o.btnsDef, function (btnName, btnDef) {
439 t.addBtnDef(btnName, btnDef); 440 t.addBtnDef(btnName, btnDef);
440 }); 441 });
441   442  
442 // put this here in the event it would be merged in with options 443 // put this here in the event it would be merged in with options
443 t.eventNamespace = 'trumbowyg-event'; 444 t.eventNamespace = 'trumbowyg-event';
444   445  
445 // Keyboard shortcuts are load in this array 446 // Keyboard shortcuts are load in this array
446 t.keys = []; 447 t.keys = [];
447   448  
448 // Tag to button dynamically hydrated 449 // Tag to button dynamically hydrated
449 t.tagToButton = {}; 450 t.tagToButton = {};
450 t.tagHandlers = []; 451 t.tagHandlers = [];
451   452  
452 // Admit multiple paste handlers 453 // Admit multiple paste handlers
453 t.pasteHandlers = [].concat(t.o.pasteHandlers); 454 t.pasteHandlers = [].concat(t.o.pasteHandlers);
454   455  
455 // Check if browser is IE 456 // Check if browser is IE
456 t.isIE = (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') !== -1); 457 t.isIE = (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') !== -1);
457   458  
458 t.init(); 459 t.init();
459 }; 460 };
460   461  
461 Trumbowyg.prototype = { 462 Trumbowyg.prototype = {
462 init: function () { 463 init: function () {
463 var t = this; 464 var t = this;
464 t.height = t.$ta.height(); 465 t.height = t.$ta.height();
465   466  
466 t.initPlugins(); 467 t.initPlugins();
467   468  
468 try { 469 try {
469 // Disable image resize, try-catch for old IE 470 // Disable image resize, try-catch for old IE
470 t.doc.execCommand('enableObjectResizing', false, false); 471 t.doc.execCommand('enableObjectResizing', false, false);
471 t.doc.execCommand('defaultParagraphSeparator', false, 'p'); 472 t.doc.execCommand('defaultParagraphSeparator', false, 'p');
472 } catch (e) { 473 } catch (e) {
473 } 474 }
474   475  
475 t.buildEditor(); 476 t.buildEditor();
476 t.buildBtnPane(); 477 t.buildBtnPane();
477   478  
478 t.fixedBtnPaneEvents(); 479 t.fixedBtnPaneEvents();
479   480  
480 t.buildOverlay(); 481 t.buildOverlay();
481   482  
482 setTimeout(function () { 483 setTimeout(function () {
483 if (t.disabled) { 484 if (t.disabled) {
484 t.toggleDisable(true); 485 t.toggleDisable(true);
485 } 486 }
486 t.$c.trigger('tbwinit'); 487 t.$c.trigger('tbwinit');
487 }); 488 });
488 }, 489 },
489   490  
490 addBtnDef: function (btnName, btnDef) { 491 addBtnDef: function (btnName, btnDef) {
491 this.btnsDef[btnName] = btnDef; 492 this.btnsDef[btnName] = btnDef;
492 }, 493 },
493   494  
494 buildEditor: function () { 495 buildEditor: function () {
495 var t = this, 496 var t = this,
496 prefix = t.o.prefix, 497 prefix = t.o.prefix,
497 html = ''; 498 html = '';
498   499  
499 t.$box = $('<div/>', { 500 t.$box = $('<div/>', {
500 class: prefix + 'box ' + prefix + 'editor-visible ' + prefix + t.o.lang + ' trumbowyg' 501 class: prefix + 'box ' + prefix + 'editor-visible ' + prefix + t.o.lang + ' trumbowyg'
501 }); 502 });
502   503  
503 // $ta = Textarea 504 // $ta = Textarea
504 // $ed = Editor 505 // $ed = Editor
505 t.isTextarea = t.$ta.is('textarea'); 506 t.isTextarea = t.$ta.is('textarea');
506 if (t.isTextarea) { 507 if (t.isTextarea) {
507 html = t.$ta.val(); 508 html = t.$ta.val();
508 t.$ed = $('<div/>'); 509 t.$ed = $('<div/>');
509 t.$box 510 t.$box
510 .insertAfter(t.$ta) 511 .insertAfter(t.$ta)
511 .append(t.$ed, t.$ta); 512 .append(t.$ed, t.$ta);
512 } else { 513 } else {
513 t.$ed = t.$ta; 514 t.$ed = t.$ta;
514 html = t.$ed.html(); 515 html = t.$ed.html();
515   516  
516 t.$ta = $('<textarea/>', { 517 t.$ta = $('<textarea/>', {
517 name: t.$ta.attr('id'), 518 name: t.$ta.attr('id'),
518 height: t.height 519 height: t.height
519 }).val(html); 520 }).val(html);
520   521  
521 t.$box 522 t.$box
522 .insertAfter(t.$ed) 523 .insertAfter(t.$ed)
523 .append(t.$ta, t.$ed); 524 .append(t.$ta, t.$ed);
524 t.syncCode(); 525 t.syncCode();
525 } 526 }
526   527  
527 t.$ta 528 t.$ta
528 .addClass(prefix + 'textarea') 529 .addClass(prefix + 'textarea')
529 .attr('tabindex', -1) 530 .attr('tabindex', -1)
530 ; 531 ;
531   532  
532 t.$ed 533 t.$ed
533 .addClass(prefix + 'editor') 534 .addClass(prefix + 'editor')
534 .attr({ 535 .attr({
535 contenteditable: true, 536 contenteditable: true,
536 dir: t.lang._dir || 'ltr' 537 dir: t.lang._dir || 'ltr'
537 }) 538 })
538 .html(html) 539 .html(html)
539 ; 540 ;
540   541  
541 if (t.o.tabindex) { 542 if (t.o.tabindex) {
542 t.$ed.attr('tabindex', t.o.tabindex); 543 t.$ed.attr('tabindex', t.o.tabindex);
543 } 544 }
544   545  
545 if (t.$c.is('[placeholder]')) { 546 if (t.$c.is('[placeholder]')) {
546 t.$ed.attr('placeholder', t.$c.attr('placeholder')); 547 t.$ed.attr('placeholder', t.$c.attr('placeholder'));
547 } 548 }
548   549  
549 if (t.o.resetCss) { 550 if (t.o.resetCss) {
550 t.$ed.addClass(prefix + 'reset-css'); 551 t.$ed.addClass(prefix + 'reset-css');
551 } 552 }
552   553  
553 if (!t.o.autogrow) { 554 if (!t.o.autogrow) {
554 t.$ta.add(t.$ed).css({ 555 t.$ta.add(t.$ed).css({
555 height: t.height 556 height: t.height
556 }); 557 });
557 } 558 }
558   559  
559 t.semanticCode(); 560 t.semanticCode();
560   561  
561   562  
562 var ctrl = false, 563 var ctrl = false,
563 composition = false, 564 composition = false,
564 debounceButtonPaneStatus, 565 debounceButtonPaneStatus,
565 updateEventName = t.isIE ? 'keyup' : 'input'; 566 updateEventName = t.isIE ? 'keyup' : 'input';
566   567  
567 t.$ed 568 t.$ed
568 .on('dblclick', 'img', t.o.imgDblClickHandler) 569 .on('dblclick', 'img', t.o.imgDblClickHandler)
569 .on('keydown', function (e) { 570 .on('keydown', function (e) {
570 if (e.ctrlKey) { 571 if (e.ctrlKey) {
571 ctrl = true; 572 ctrl = true;
572 var key = t.keys[String.fromCharCode(e.which).toUpperCase()]; 573 var key = t.keys[String.fromCharCode(e.which).toUpperCase()];
573   574  
574 try { 575 try {
575 t.execCmd(key.fn, key.param); 576 t.execCmd(key.fn, key.param);
576 return false; 577 return false;
577 } catch (c) { 578 } catch (c) {
578 } 579 }
579 } 580 }
580 }) 581 })
581 .on('compositionstart compositionupdate', function () { 582 .on('compositionstart compositionupdate', function () {
582 composition = true; 583 composition = true;
583 }) 584 })
584 .on(updateEventName + ' compositionend', function (e) { 585 .on(updateEventName + ' compositionend', function (e) {
585 if (e.type === 'compositionend') { 586 if (e.type === 'compositionend') {
586 composition = false; 587 composition = false;
587 } else if(composition) { 588 } else if(composition) {
588 return; 589 return;
589 } 590 }
590   591  
591 var keyCode = e.which; 592 var keyCode = e.which;
592   593  
593 if (keyCode >= 37 && keyCode <= 40) { 594 if (keyCode >= 37 && keyCode <= 40) {
594 return; 595 return;
595 } 596 }
596   597  
597 if (e.ctrlKey && (keyCode === 89 || keyCode === 90)) { 598 if (e.ctrlKey && (keyCode === 89 || keyCode === 90)) {
598 t.$c.trigger('tbwchange'); 599 t.$c.trigger('tbwchange');
599 } else if (!ctrl && keyCode !== 17) { 600 } else if (!ctrl && keyCode !== 17) {
600 t.semanticCode(false, keyCode === 13); 601 t.semanticCode(false, keyCode === 13);
601 t.$c.trigger('tbwchange'); 602 t.$c.trigger('tbwchange');
602 } else if (typeof e.which === 'undefined') { 603 } else if (typeof e.which === 'undefined') {
603 t.semanticCode(false, false, true); 604 t.semanticCode(false, false, true);
604 } 605 }
605   606  
606 setTimeout(function () { 607 setTimeout(function () {
607 ctrl = false; 608 ctrl = false;
608 }, 200); 609 }, 200);
609 }) 610 })
610 .on('mouseup keydown keyup', function () { 611 .on('mouseup keydown keyup', function () {
611 clearTimeout(debounceButtonPaneStatus); 612 clearTimeout(debounceButtonPaneStatus);
612 debounceButtonPaneStatus = setTimeout(function () { 613 debounceButtonPaneStatus = setTimeout(function () {
613 t.updateButtonPaneStatus(); 614 t.updateButtonPaneStatus();
614 }, 50); 615 }, 50);
615 }) 616 })
616 .on('focus blur', function (e) { 617 .on('focus blur', function (e) {
617 t.$c.trigger('tbw' + e.type); 618 t.$c.trigger('tbw' + e.type);
618 if (e.type === 'blur') { 619 if (e.type === 'blur') {
619 $('.' + prefix + 'active-button', t.$btnPane).removeClass(prefix + 'active-button ' + prefix + 'active'); 620 $('.' + prefix + 'active-button', t.$btnPane).removeClass(prefix + 'active-button ' + prefix + 'active');
620 } 621 }
621 }) 622 })
622 .on('cut', function () { 623 .on('cut', function () {
623 setTimeout(function () { 624 setTimeout(function () {
624 t.semanticCode(false, true); 625 t.semanticCode(false, true);
625 t.$c.trigger('tbwchange'); 626 t.$c.trigger('tbwchange');
626 }, 0); 627 }, 0);
627 }) 628 })
628 .on('paste', function (e) { 629 .on('paste', function (e) {
629 if (t.o.removeformatPasted) { 630 if (t.o.removeformatPasted) {
630 e.preventDefault(); 631 e.preventDefault();
631   632  
632 try { 633 try {
633 // IE 634 // IE
634 var text = window.clipboardData.getData('Text'); 635 var text = window.clipboardData.getData('Text');
635   636  
636 try { 637 try {
637 // <= IE10 638 // <= IE10
638 t.doc.selection.createRange().pasteHTML(text); 639 t.doc.selection.createRange().pasteHTML(text);
639 } catch (c) { 640 } catch (c) {
640 // IE 11 641 // IE 11
641 t.doc.getSelection().getRangeAt(0).insertNode(t.doc.createTextNode(text)); 642 t.doc.getSelection().getRangeAt(0).insertNode(t.doc.createTextNode(text));
642 } 643 }
643 } catch (d) { 644 } catch (d) {
644 // Not IE 645 // Not IE
645 t.execCmd('insertText', (e.originalEvent || e).clipboardData.getData('text/plain')); 646 t.execCmd('insertText', (e.originalEvent || e).clipboardData.getData('text/plain'));
646 } 647 }
647 } 648 }
648   649  
649 // Call pasteHandlers 650 // Call pasteHandlers
650 $.each(t.pasteHandlers, function (i, pasteHandler) { 651 $.each(t.pasteHandlers, function (i, pasteHandler) {
651 pasteHandler(e); 652 pasteHandler(e);
652 }); 653 });
653   654  
654 setTimeout(function () { 655 setTimeout(function () {
655 t.semanticCode(false, true); 656 t.semanticCode(false, true);
656 t.$c.trigger('tbwpaste', e); 657 t.$c.trigger('tbwpaste', e);
657 }, 0); 658 }, 0);
658 }); 659 });
659 t.$ta.on('keyup paste', function () { 660 t.$ta.on('keyup paste', function () {
660 t.$c.trigger('tbwchange'); 661 t.$c.trigger('tbwchange');
661 }); 662 });
662   663  
663 t.$box.on('keydown', function (e) { 664 t.$box.on('keydown', function (e) {
664 if (e.which === 27 && $('.' + prefix + 'modal-box', t.$box).length === 1) { 665 if (e.which === 27 && $('.' + prefix + 'modal-box', t.$box).length === 1) {
665 t.closeModal(); 666 t.closeModal();
666 return false; 667 return false;
667 } 668 }
668 }); 669 });
669 }, 670 },
670   671  
671   672  
672 // Build button pane, use o.btns option 673 // Build button pane, use o.btns option
673 buildBtnPane: function () { 674 buildBtnPane: function () {
674 var t = this, 675 var t = this,
675 prefix = t.o.prefix; 676 prefix = t.o.prefix;
676   677  
677 var $btnPane = t.$btnPane = $('<div/>', { 678 var $btnPane = t.$btnPane = $('<div/>', {
678 class: prefix + 'button-pane' 679 class: prefix + 'button-pane'
679 }); 680 });
680   681  
681 $.each(t.o.btns, function (i, btnGrps) { 682 $.each(t.o.btns, function (i, btnGrps) {
682 // Managment of group of buttons 683 // Managment of group of buttons
683 try { 684 try {
684 var b = btnGrps.split('btnGrp-'); 685 var b = btnGrps.split('btnGrp-');
685 if (b[1] != null) { 686 if (b[1] != null) {
686 btnGrps = t.o.btnsGrps[b[1]]; 687 btnGrps = t.o.btnsGrps[b[1]];
687 } 688 }
688 } catch (c) { 689 } catch (c) {
689 } 690 }
690   691  
691 if (!$.isArray(btnGrps)) { 692 if (!$.isArray(btnGrps)) {
692 btnGrps = [btnGrps]; 693 btnGrps = [btnGrps];
693 } 694 }
694   695  
695 var $btnGroup = $('<div/>', { 696 var $btnGroup = $('<div/>', {
696 class: prefix + 'button-group ' + ((btnGrps.indexOf('fullscreen') >= 0) ? prefix + 'right' : '') 697 class: prefix + 'button-group ' + ((btnGrps.indexOf('fullscreen') >= 0) ? prefix + 'right' : '')
697 }); 698 });
698 $.each(btnGrps, function (i, btn) { 699 $.each(btnGrps, function (i, btn) {
699 try { // Prevent buildBtn error 700 try { // Prevent buildBtn error
700 var $item; 701 var $item;
701   702  
702 if (t.isSupportedBtn(btn)) { // It's a supported button 703 if (t.isSupportedBtn(btn)) { // It's a supported button
703 $item = t.buildBtn(btn); 704 $item = t.buildBtn(btn);
704 } 705 }
705   706  
706 $btnGroup.append($item); 707 $btnGroup.append($item);
707 } catch (c) { 708 } catch (c) {
708 } 709 }
709 }); 710 });
710 $btnPane.append($btnGroup); 711 $btnPane.append($btnGroup);
711 }); 712 });
712   713  
713 t.$box.prepend($btnPane); 714 t.$box.prepend($btnPane);
714 }, 715 },
715   716  
716   717  
717 // Build a button and his action 718 // Build a button and his action
718 buildBtn: function (btnName) { // btnName is name of the button 719 buildBtn: function (btnName) { // btnName is name of the button
719 var t = this, 720 var t = this,
720 prefix = t.o.prefix, 721 prefix = t.o.prefix,
721 btn = t.btnsDef[btnName], 722 btn = t.btnsDef[btnName],
722 isDropdown = btn.dropdown, 723 isDropdown = btn.dropdown,
723 hasIcon = btn.hasIcon != null ? btn.hasIcon : true, 724 hasIcon = btn.hasIcon != null ? btn.hasIcon : true,
724 textDef = t.lang[btnName] || btnName, 725 textDef = t.lang[btnName] || btnName,
725   726  
726 $btn = $('<button/>', { 727 $btn = $('<button/>', {
727 type: 'button', 728 type: 'button',
728 class: prefix + btnName + '-button ' + (btn.class || '') + (!hasIcon ? ' ' + prefix + 'textual-button' : ''), 729 class: prefix + btnName + '-button ' + (btn.class || '') + (!hasIcon ? ' ' + prefix + 'textual-button' : ''),
729 html: t.hasSvg && hasIcon ? 730 html: t.hasSvg && hasIcon ?
730 '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' : 731 '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' :
731 t.hideButtonTexts ? '' : (btn.text || btn.title || t.lang[btnName] || btnName), 732 t.hideButtonTexts ? '' : (btn.text || btn.title || t.lang[btnName] || btnName),
732 title: (btn.title || btn.text || textDef) + ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : ''), 733 title: (btn.title || btn.text || textDef) + ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : ''),
733 tabindex: -1, 734 tabindex: -1,
734 mousedown: function () { 735 mousedown: function () {
735 if (!isDropdown || $('.' + btnName + '-' + prefix + 'dropdown', t.$box).is(':hidden')) { 736 if (!isDropdown || $('.' + btnName + '-' + prefix + 'dropdown', t.$box).is(':hidden')) {
736 $('body', t.doc).trigger('mousedown'); 737 $('body', t.doc).trigger('mousedown');
737 } 738 }
738   739  
739 if (t.$btnPane.hasClass(prefix + 'disable') && !$(this).hasClass(prefix + 'active') && !$(this).hasClass(prefix + 'not-disable')) { 740 if (t.$btnPane.hasClass(prefix + 'disable') && !$(this).hasClass(prefix + 'active') && !$(this).hasClass(prefix + 'not-disable')) {
740 return false; 741 return false;
741 } 742 }
742   743  
743 t.execCmd((isDropdown ? 'dropdown' : false) || btn.fn || btnName, btn.param || btnName, btn.forceCss || false); 744 t.execCmd((isDropdown ? 'dropdown' : false) || btn.fn || btnName, btn.param || btnName, btn.forceCss || false);
744   745  
745 return false; 746 return false;
746 } 747 }
747 }); 748 });
748   749  
749 if (isDropdown) { 750 if (isDropdown) {
750 $btn.addClass(prefix + 'open-dropdown'); 751 $btn.addClass(prefix + 'open-dropdown');
751 var dropdownPrefix = prefix + 'dropdown', 752 var dropdownPrefix = prefix + 'dropdown',
752 $dropdown = $('<div/>', { // the dropdown 753 $dropdown = $('<div/>', { // the dropdown
753 class: dropdownPrefix + '-' + btnName + ' ' + dropdownPrefix + ' ' + prefix + 'fixed-top', 754 class: dropdownPrefix + '-' + btnName + ' ' + dropdownPrefix + ' ' + prefix + 'fixed-top',
754 'data-dropdown': btnName 755 'data-dropdown': btnName
755 }); 756 });
756 $.each(isDropdown, function (i, def) { 757 $.each(isDropdown, function (i, def) {
757 if (t.btnsDef[def] && t.isSupportedBtn(def)) { 758 if (t.btnsDef[def] && t.isSupportedBtn(def)) {
758 $dropdown.append(t.buildSubBtn(def)); 759 $dropdown.append(t.buildSubBtn(def));
759 } 760 }
760 }); 761 });
761 t.$box.append($dropdown.hide()); 762 t.$box.append($dropdown.hide());
762 } else if (btn.key) { 763 } else if (btn.key) {
763 t.keys[btn.key] = { 764 t.keys[btn.key] = {
764 fn: btn.fn || btnName, 765 fn: btn.fn || btnName,
765 param: btn.param || btnName 766 param: btn.param || btnName
766 }; 767 };
767 } 768 }
768   769  
769 if (!isDropdown) { 770 if (!isDropdown) {
770 t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName; 771 t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName;
771 } 772 }
772   773  
773 return $btn; 774 return $btn;
774 }, 775 },
775 // Build a button for dropdown menu 776 // Build a button for dropdown menu
776 // @param n : name of the subbutton 777 // @param n : name of the subbutton
777 buildSubBtn: function (btnName) { 778 buildSubBtn: function (btnName) {
778 var t = this, 779 var t = this,
779 prefix = t.o.prefix, 780 prefix = t.o.prefix,
780 btn = t.btnsDef[btnName], 781 btn = t.btnsDef[btnName],
781 hasIcon = btn.hasIcon != null ? btn.hasIcon : true; 782 hasIcon = btn.hasIcon != null ? btn.hasIcon : true;
782   783  
783 if (btn.key) { 784 if (btn.key) {
784 t.keys[btn.key] = { 785 t.keys[btn.key] = {
785 fn: btn.fn || btnName, 786 fn: btn.fn || btnName,
786 param: btn.param || btnName 787 param: btn.param || btnName
787 }; 788 };
788 } 789 }
789   790  
790 t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName; 791 t.tagToButton[(btn.tag || btnName).toLowerCase()] = btnName;
791   792  
792 return $('<button/>', { 793 return $('<button/>', {
793 type: 'button', 794 type: 'button',
794 class: prefix + btnName + '-dropdown-button' + (btn.ico ? ' ' + prefix + btn.ico + '-button' : ''), 795 class: prefix + btnName + '-dropdown-button' + (btn.ico ? ' ' + prefix + btn.ico + '-button' : ''),
795 html: t.hasSvg && hasIcon ? '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' + (btn.text || btn.title || t.lang[btnName] || btnName) : (btn.text || btn.title || t.lang[btnName] || btnName), 796 html: t.hasSvg && hasIcon ? '<svg><use xlink:href="' + t.svgPath + '#' + prefix + (btn.ico || btnName).replace(/([A-Z]+)/g, '-$1').toLowerCase() + '"/></svg>' + (btn.text || btn.title || t.lang[btnName] || btnName) : (btn.text || btn.title || t.lang[btnName] || btnName),
796 title: ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : null), 797 title: ((btn.key) ? ' (Ctrl + ' + btn.key + ')' : null),
797 style: btn.style || null, 798 style: btn.style || null,
798 mousedown: function () { 799 mousedown: function () {
799 $('body', t.doc).trigger('mousedown'); 800 $('body', t.doc).trigger('mousedown');
800   801  
801 t.execCmd(btn.fn || btnName, btn.param || btnName, btn.forceCss || false); 802 t.execCmd(btn.fn || btnName, btn.param || btnName, btn.forceCss || false);
802   803  
803 return false; 804 return false;
804 } 805 }
805 }); 806 });
806 }, 807 },
807 // Check if button is supported 808 // Check if button is supported
808 isSupportedBtn: function (b) { 809 isSupportedBtn: function (b) {
809 try { 810 try {
810 return this.btnsDef[b].isSupported(); 811 return this.btnsDef[b].isSupported();
811 } catch (c) { 812 } catch (c) {
812 } 813 }
813 return true; 814 return true;
814 }, 815 },
815   816  
816 // Build overlay for modal box 817 // Build overlay for modal box
817 buildOverlay: function () { 818 buildOverlay: function () {
818 var t = this; 819 var t = this;
819 t.$overlay = $('<div/>', { 820 t.$overlay = $('<div/>', {
820 class: t.o.prefix + 'overlay' 821 class: t.o.prefix + 'overlay'
821 }).css({ 822 }).css({
822 top: t.$btnPane.outerHeight(), 823 top: t.$btnPane.outerHeight(),
823 height: (t.$ed.outerHeight() + 1) + 'px' 824 height: (t.$ed.outerHeight() + 1) + 'px'
824 }).appendTo(t.$box); 825 }).appendTo(t.$box);
825 return t.$overlay; 826 return t.$overlay;
826 }, 827 },
827 showOverlay: function () { 828 showOverlay: function () {
828 var t = this; 829 var t = this;
829 $(window).trigger('scroll'); 830 $(window).trigger('scroll');
830 t.$overlay.fadeIn(200); 831 t.$overlay.fadeIn(200);
831 t.$box.addClass(t.o.prefix + 'box-blur'); 832 t.$box.addClass(t.o.prefix + 'box-blur');
832 }, 833 },
833 hideOverlay: function () { 834 hideOverlay: function () {
834 var t = this; 835 var t = this;
835 t.$overlay.fadeOut(50); 836 t.$overlay.fadeOut(50);
836 t.$box.removeClass(t.o.prefix + 'box-blur'); 837 t.$box.removeClass(t.o.prefix + 'box-blur');
837 }, 838 },
838   839  
839 // Management of fixed button pane 840 // Management of fixed button pane
840 fixedBtnPaneEvents: function () { 841 fixedBtnPaneEvents: function () {
841 var t = this, 842 var t = this,
842 fixedFullWidth = t.o.fixedFullWidth, 843 fixedFullWidth = t.o.fixedFullWidth,
843 $box = t.$box; 844 $box = t.$box;
844   845  
845 if (!t.o.fixedBtnPane) { 846 if (!t.o.fixedBtnPane) {
846 return; 847 return;
847 } 848 }
848   849  
849 t.isFixed = false; 850 t.isFixed = false;
850   851  
851 $(window) 852 $(window)
852 .on('scroll.'+t.eventNamespace+' resize.'+t.eventNamespace, function () { 853 .on('scroll.'+t.eventNamespace+' resize.'+t.eventNamespace, function () {
853 if (!$box) { 854 if (!$box) {
854 return; 855 return;
855 } 856 }
856   857  
857 t.syncCode(); 858 t.syncCode();
858   859  
859 var scrollTop = $(window).scrollTop(), 860 var scrollTop = $(window).scrollTop(),
860 offset = $box.offset().top + 1, 861 offset = $box.offset().top + 1,
861 bp = t.$btnPane, 862 bp = t.$btnPane,
862 oh = bp.outerHeight() - 2; 863 oh = bp.outerHeight() - 2;
863   864  
864 if ((scrollTop - offset > 0) && ((scrollTop - offset - t.height) < 0)) { 865 if ((scrollTop - offset > 0) && ((scrollTop - offset - t.height) < 0)) {
865 if (!t.isFixed) { 866 if (!t.isFixed) {
866 t.isFixed = true; 867 t.isFixed = true;
867 bp.css({ 868 bp.css({
868 position: 'fixed', 869 position: 'fixed',
869 top: 0, 870 top: 0,
870 left: fixedFullWidth ? '0' : 'auto', 871 left: fixedFullWidth ? '0' : 'auto',
871 zIndex: 7 872 zIndex: 7
872 }); 873 });
873 $([t.$ta, t.$ed]).css({marginTop: bp.height()}); 874 $([t.$ta, t.$ed]).css({marginTop: bp.height()});
874 } 875 }
875 bp.css({ 876 bp.css({
876 width: fixedFullWidth ? '100%' : (($box.width() - 1) + 'px') 877 width: fixedFullWidth ? '100%' : (($box.width() - 1) + 'px')
877 }); 878 });
878   879  
879 $('.' + t.o.prefix + 'fixed-top', $box).css({ 880 $('.' + t.o.prefix + 'fixed-top', $box).css({
880 position: fixedFullWidth ? 'fixed' : 'absolute', 881 position: fixedFullWidth ? 'fixed' : 'absolute',
881 top: fixedFullWidth ? oh : oh + (scrollTop - offset) + 'px', 882 top: fixedFullWidth ? oh : oh + (scrollTop - offset) + 'px',
882 zIndex: 15 883 zIndex: 15
883 }); 884 });
884 } else if (t.isFixed) { 885 } else if (t.isFixed) {
885 t.isFixed = false; 886 t.isFixed = false;
886 bp.removeAttr('style'); 887 bp.removeAttr('style');
887 $([t.$ta, t.$ed]).css({marginTop: 0}); 888 $([t.$ta, t.$ed]).css({marginTop: 0});
888 $('.' + t.o.prefix + 'fixed-top', $box).css({ 889 $('.' + t.o.prefix + 'fixed-top', $box).css({
889 position: 'absolute', 890 position: 'absolute',
890 top: oh 891 top: oh
891 }); 892 });
892 } 893 }
893 }); 894 });
894 }, 895 },
895   896  
896 // Disable editor 897 // Disable editor
897 toggleDisable: function (disable) { 898 toggleDisable: function (disable) {
898 var t = this, 899 var t = this,
899 prefix = t.o.prefix; 900 prefix = t.o.prefix;
900   901  
901 t.disabled = disable; 902 t.disabled = disable;
902   903  
903 if (disable) { 904 if (disable) {
904 t.$ta.attr('disabled', true); 905 t.$ta.attr('disabled', true);
905 } else { 906 } else {
906 t.$ta.removeAttr('disabled'); 907 t.$ta.removeAttr('disabled');
907 } 908 }
908 t.$box.toggleClass(prefix + 'disabled', disable); 909 t.$box.toggleClass(prefix + 'disabled', disable);
909 t.$ed.attr('contenteditable', !disable); 910 t.$ed.attr('contenteditable', !disable);
910 }, 911 },
911   912  
912 // Destroy the editor 913 // Destroy the editor
913 destroy: function () { 914 destroy: function () {
914 var t = this, 915 var t = this,
915 prefix = t.o.prefix, 916 prefix = t.o.prefix,
916 height = t.height; 917 height = t.height;
917   918  
918 if (t.isTextarea) { 919 if (t.isTextarea) {
919 t.$box.after( 920 t.$box.after(
920 t.$ta 921 t.$ta
921 .css({height: height}) 922 .css({height: height})
922 .val(t.html()) 923 .val(t.html())
923 .removeClass(prefix + 'textarea') 924 .removeClass(prefix + 'textarea')
924 .show() 925 .show()
925 ); 926 );
926 } else { 927 } else {
927 t.$box.after( 928 t.$box.after(
928 t.$ed 929 t.$ed
929 .css({height: height}) 930 .css({height: height})
930 .removeClass(prefix + 'editor') 931 .removeClass(prefix + 'editor')
931 .removeAttr('contenteditable') 932 .removeAttr('contenteditable')
932 .html(t.html()) 933 .html(t.html())
933 .show() 934 .show()
934 ); 935 );
935 } 936 }
936   937  
937 t.$ed.off('dblclick', 'img'); 938 t.$ed.off('dblclick', 'img');
938   939  
939 t.destroyPlugins(); 940 t.destroyPlugins();
940   941  
941 t.$box.remove(); 942 t.$box.remove();
942 t.$c.removeData('trumbowyg'); 943 t.$c.removeData('trumbowyg');
943 $('body').removeClass(prefix + 'body-fullscreen'); 944 $('body').removeClass(prefix + 'body-fullscreen');
944 t.$c.trigger('tbwclose'); 945 t.$c.trigger('tbwclose');
945 $(window).off('scroll.'+t.eventNamespace+' resize.'+t.eventNamespace); 946 $(window).off('scroll.'+t.eventNamespace+' resize.'+t.eventNamespace);
946 }, 947 },
947   948  
948   949  
949 // Empty the editor 950 // Empty the editor
950 empty: function () { 951 empty: function () {
951 this.$ta.val(''); 952 this.$ta.val('');
952 this.syncCode(true); 953 this.syncCode(true);
953 }, 954 },
954   955  
955   956  
956 // Function call when click on viewHTML button 957 // Function call when click on viewHTML button
957 toggle: function () { 958 toggle: function () {
958 var t = this, 959 var t = this,
959 prefix = t.o.prefix; 960 prefix = t.o.prefix;
960 t.semanticCode(false, true); 961 t.semanticCode(false, true);
961 setTimeout(function () { 962 setTimeout(function () {
962 t.doc.activeElement.blur(); 963 t.doc.activeElement.blur();
963 t.$box.toggleClass(prefix + 'editor-hidden ' + prefix + 'editor-visible'); 964 t.$box.toggleClass(prefix + 'editor-hidden ' + prefix + 'editor-visible');
964 t.$btnPane.toggleClass(prefix + 'disable'); 965 t.$btnPane.toggleClass(prefix + 'disable');
965 $('.' + prefix + 'viewHTML-button', t.$btnPane).toggleClass(prefix + 'active'); 966 $('.' + prefix + 'viewHTML-button', t.$btnPane).toggleClass(prefix + 'active');
966 if (t.$box.hasClass(prefix + 'editor-visible')) { 967 if (t.$box.hasClass(prefix + 'editor-visible')) {
967 t.$ta.attr('tabindex', -1); 968 t.$ta.attr('tabindex', -1);
968 } else { 969 } else {
969 t.$ta.removeAttr('tabindex'); 970 t.$ta.removeAttr('tabindex');
970 } 971 }
971 }, 0); 972 }, 0);
972 }, 973 },
973   974  
974 // Open dropdown when click on a button which open that 975 // Open dropdown when click on a button which open that
975 dropdown: function (name) { 976 dropdown: function (name) {
976 var t = this, 977 var t = this,
977 d = t.doc, 978 d = t.doc,
978 prefix = t.o.prefix, 979 prefix = t.o.prefix,
979 $dropdown = $('[data-dropdown=' + name + ']', t.$box), 980 $dropdown = $('[data-dropdown=' + name + ']', t.$box),
980 $btn = $('.' + prefix + name + '-button', t.$btnPane), 981 $btn = $('.' + prefix + name + '-button', t.$btnPane),
981 show = $dropdown.is(':hidden'); 982 show = $dropdown.is(':hidden');
982   983  
983 $('body', d).trigger('mousedown'); 984 $('body', d).trigger('mousedown');
984   985  
985 if (show) { 986 if (show) {
986 var o = $btn.offset().left; 987 var o = $btn.offset().left;
987 $btn.addClass(prefix + 'active'); 988 $btn.addClass(prefix + 'active');
988   989  
989 $dropdown.css({ 990 $dropdown.css({
990 position: 'absolute', 991 position: 'absolute',
991 top: $btn.offset().top - t.$btnPane.offset().top + $btn.outerHeight(), 992 top: $btn.offset().top - t.$btnPane.offset().top + $btn.outerHeight(),
992 left: (t.o.fixedFullWidth && t.isFixed) ? o + 'px' : (o - t.$btnPane.offset().left) + 'px' 993 left: (t.o.fixedFullWidth && t.isFixed) ? o + 'px' : (o - t.$btnPane.offset().left) + 'px'
993 }).show(); 994 }).show();
994   995  
995 $(window).trigger('scroll'); 996 $(window).trigger('scroll');
996   997  
997 $('body', d).on('mousedown.'+t.eventNamespace, function (e) { 998 $('body', d).on('mousedown.'+t.eventNamespace, function (e) {
998 if (!$dropdown.is(e.target)) { 999 if (!$dropdown.is(e.target)) {
999 $('.' + prefix + 'dropdown', d).hide(); 1000 $('.' + prefix + 'dropdown', d).hide();
1000 $('.' + prefix + 'active', d).removeClass(prefix + 'active'); 1001 $('.' + prefix + 'active', d).removeClass(prefix + 'active');
1001 $('body', d).off('mousedown.'+t.eventNamespace); 1002 $('body', d).off('mousedown.'+t.eventNamespace);
1002 } 1003 }
1003 }); 1004 });
1004 } 1005 }
1005 }, 1006 },
1006   1007  
1007   1008  
1008 // HTML Code management 1009 // HTML Code management
1009 html: function (html) { 1010 html: function (html) {
1010 var t = this; 1011 var t = this;
1011 if (html != null) { 1012 if (html != null) {
1012 t.$ta.val(html); 1013 t.$ta.val(html);
1013 t.syncCode(true); 1014 t.syncCode(true);
1014 return t; 1015 return t;
1015 } 1016 }
1016 return t.$ta.val(); 1017 return t.$ta.val();
1017 }, 1018 },
1018 syncTextarea: function () { 1019 syncTextarea: function () {
1019 var t = this; 1020 var t = this;
1020 t.$ta.val(t.$ed.text().trim().length > 0 || t.$ed.find('hr,img,embed,iframe,input').length > 0 ? t.$ed.html() : ''); 1021 t.$ta.val(t.$ed.text().trim().length > 0 || t.$ed.find('hr,img,embed,iframe,input').length > 0 ? t.$ed.html() : '');
1021 }, 1022 },
1022 syncCode: function (force) { 1023 syncCode: function (force) {
1023 var t = this; 1024 var t = this;
1024 if (!force && t.$ed.is(':visible')) { 1025 if (!force && t.$ed.is(':visible')) {
1025 t.syncTextarea(); 1026 t.syncTextarea();
1026 } else { 1027 } else {
-   1028 // wrap the content in a div it's easier to get the innerhtml
-   1029 var html = '<div>' + t.$ta.val() + '</div>';
-   1030 //scrub the html before loading into the doc
-   1031 html = $(t.o.tagsToRemove.join(','), html).remove().end().html();
1027 t.$ed.html(t.$ta.val()); 1032 t.$ed.html(html);
1028 } 1033 }
1029   1034  
1030 if (t.o.autogrow) { 1035 if (t.o.autogrow) {
1031 t.height = t.$ed.height(); 1036 t.height = t.$ed.height();
1032 if (t.height !== t.$ta.css('height')) { 1037 if (t.height !== t.$ta.css('height')) {
1033 t.$ta.css({height: t.height}); 1038 t.$ta.css({height: t.height});
1034 t.$c.trigger('tbwresize'); 1039 t.$c.trigger('tbwresize');
1035 } 1040 }
1036 } 1041 }
1037 }, 1042 },
1038   1043  
1039 // Analyse and update to semantic code 1044 // Analyse and update to semantic code
1040 // @param force : force to sync code from textarea 1045 // @param force : force to sync code from textarea
1041 // @param full : wrap text nodes in <p> 1046 // @param full : wrap text nodes in <p>
1042 // @param keepRange : leave selection range as it is 1047 // @param keepRange : leave selection range as it is
1043 semanticCode: function (force, full, keepRange) { 1048 semanticCode: function (force, full, keepRange) {
1044 var t = this; 1049 var t = this;
1045 t.saveRange(); 1050 t.saveRange();
1046 t.syncCode(force); 1051 t.syncCode(force);
1047   -  
1048 $(t.o.tagsToRemove.join(','), t.$ed).remove(); -  
1049   1052  
1050 if (t.o.semantic) { 1053 if (t.o.semantic) {
1051 t.semanticTag('b', 'strong'); 1054 t.semanticTag('b', 'strong');
1052 t.semanticTag('i', 'em'); 1055 t.semanticTag('i', 'em');
1053   1056  
1054 if (full) { 1057 if (full) {
1055 var inlineElementsSelector = t.o.inlineElementsSelector, 1058 var inlineElementsSelector = t.o.inlineElementsSelector,
1056 blockElementsSelector = ':not(' + inlineElementsSelector + ')'; 1059 blockElementsSelector = ':not(' + inlineElementsSelector + ')';
1057   1060  
1058 // Wrap text nodes in span for easier processing 1061 // Wrap text nodes in span for easier processing
1059 t.$ed.contents().filter(function () { 1062 t.$ed.contents().filter(function () {
1060 return this.nodeType === 3 && this.nodeValue.trim().length > 0; 1063 return this.nodeType === 3 && this.nodeValue.trim().length > 0;
1061 }).wrap('<span data-tbw/>'); 1064 }).wrap('<span data-tbw/>');
1062   1065  
1063 // Wrap groups of inline elements in paragraphs (recursive) 1066 // Wrap groups of inline elements in paragraphs (recursive)
1064 var wrapInlinesInParagraphsFrom = function ($from) { 1067 var wrapInlinesInParagraphsFrom = function ($from) {
1065 if ($from.length !== 0) { 1068 if ($from.length !== 0) {
1066 var $finalParagraph = $from.nextUntil(blockElementsSelector).addBack().wrapAll('<p/>').parent(), 1069 var $finalParagraph = $from.nextUntil(blockElementsSelector).addBack().wrapAll('<p/>').parent(),
1067 $nextElement = $finalParagraph.nextAll(inlineElementsSelector).first(); 1070 $nextElement = $finalParagraph.nextAll(inlineElementsSelector).first();
1068 $finalParagraph.next('br').remove(); 1071 $finalParagraph.next('br').remove();
1069 wrapInlinesInParagraphsFrom($nextElement); 1072 wrapInlinesInParagraphsFrom($nextElement);
1070 } 1073 }
1071 }; 1074 };
1072 wrapInlinesInParagraphsFrom(t.$ed.children(inlineElementsSelector).first()); 1075 wrapInlinesInParagraphsFrom(t.$ed.children(inlineElementsSelector).first());
1073   1076  
1074 t.semanticTag('div', 'p', true); 1077 t.semanticTag('div', 'p', true);
1075   1078  
1076 // Unwrap paragraphs content, containing nothing usefull 1079 // Unwrap paragraphs content, containing nothing usefull
1077 t.$ed.find('p').filter(function () { 1080 t.$ed.find('p').filter(function () {
1078 // Don't remove currently being edited element 1081 // Don't remove currently being edited element
1079 if (t.range && this === t.range.startContainer) { 1082 if (t.range && this === t.range.startContainer) {
1080 return false; 1083 return false;
1081 } 1084 }
1082 return $(this).text().trim().length === 0 && $(this).children().not('br,span').length === 0; 1085 return $(this).text().trim().length === 0 && $(this).children().not('br,span').length === 0;
1083 }).contents().unwrap(); 1086 }).contents().unwrap();
1084   1087  
1085 // Get rid of temporial span's 1088 // Get rid of temporial span's
1086 $('[data-tbw]', t.$ed).contents().unwrap(); 1089 $('[data-tbw]', t.$ed).contents().unwrap();
1087   1090  
1088 // Remove empty <p> 1091 // Remove empty <p>
1089 t.$ed.find('p:empty').remove(); 1092 t.$ed.find('p:empty').remove();
1090 } 1093 }
1091   1094  
1092 if (!keepRange) { 1095 if (!keepRange) {
1093 t.restoreRange(); 1096 t.restoreRange();
1094 } 1097 }
1095   1098  
1096 t.syncTextarea(); 1099 t.syncTextarea();
1097 } 1100 }
1098 }, 1101 },
1099   1102  
1100 semanticTag: function (oldTag, newTag, copyAttributes) { 1103 semanticTag: function (oldTag, newTag, copyAttributes) {
1101 $(oldTag, this.$ed).each(function () { 1104 $(oldTag, this.$ed).each(function () {
1102 var $oldTag = $(this); 1105 var $oldTag = $(this);
1103 $oldTag.wrap('<' + newTag + '/>'); 1106 $oldTag.wrap('<' + newTag + '/>');
1104 if (copyAttributes) { 1107 if (copyAttributes) {
1105 $.each($oldTag.prop('attributes'), function () { 1108 $.each($oldTag.prop('attributes'), function () {
1106 $oldTag.parent().attr(this.name, this.value); 1109 $oldTag.parent().attr(this.name, this.value);
1107 }); 1110 });
1108 } 1111 }
1109 $oldTag.contents().unwrap(); 1112 $oldTag.contents().unwrap();
1110 }); 1113 });
1111 }, 1114 },
1112   1115  
1113 // Function call when user click on "Insert Link" 1116 // Function call when user click on "Insert Link"
1114 createLink: function () { 1117 createLink: function () {
1115 var t = this, 1118 var t = this,
1116 documentSelection = t.doc.getSelection(), 1119 documentSelection = t.doc.getSelection(),
1117 node = documentSelection.focusNode, 1120 node = documentSelection.focusNode,
1118 url, 1121 url,
1119 title, 1122 title,
1120 target; 1123 target;
1121   1124  
1122 while (['A', 'DIV'].indexOf(node.nodeName) < 0) { 1125 while (['A', 'DIV'].indexOf(node.nodeName) < 0) {
1123 node = node.parentNode; 1126 node = node.parentNode;
1124 } 1127 }
1125   1128  
1126 if (node && node.nodeName === 'A') { 1129 if (node && node.nodeName === 'A') {
1127 var $a = $(node); 1130 var $a = $(node);
1128 url = $a.attr('href'); 1131 url = $a.attr('href');
1129 title = $a.attr('title'); 1132 title = $a.attr('title');
1130 target = $a.attr('target'); 1133 target = $a.attr('target');
1131 var range = t.doc.createRange(); 1134 var range = t.doc.createRange();
1132 range.selectNode(node); 1135 range.selectNode(node);
-   1136 documentSelection.removeAllRanges();
1133 documentSelection.addRange(range); 1137 documentSelection.addRange(range);
1134 } 1138 }
1135   1139  
1136 t.saveRange(); 1140 t.saveRange();
1137   1141  
1138 t.openModalInsert(t.lang.createLink, { 1142 t.openModalInsert(t.lang.createLink, {
1139 url: { 1143 url: {
1140 label: 'URL', 1144 label: 'URL',
1141 required: true, 1145 required: true,
1142 value: url 1146 value: url
1143 }, 1147 },
1144 title: { 1148 title: {
1145 label: t.lang.title, 1149 label: t.lang.title,
1146 value: title 1150 value: title
1147 }, 1151 },
1148 text: { 1152 text: {
1149 label: t.lang.text, 1153 label: t.lang.text,
1150 value: t.getRangeText() 1154 value: t.getRangeText()
1151 }, 1155 },
1152 target: { 1156 target: {
1153 label: t.lang.target, 1157 label: t.lang.target,
1154 value: target 1158 value: target
1155 } 1159 }
1156 }, function (v) { // v is value 1160 }, function (v) { // v is value
1157 var link = $(['<a href="', v.url, '">', v.text, '</a>'].join('')); 1161 var link = $(['<a href="', v.url, '">', v.text, '</a>'].join(''));
1158 if (v.title.length > 0) { 1162 if (v.title.length > 0) {
1159 link.attr('title', v.title); 1163 link.attr('title', v.title);
1160 } 1164 }
1161 if (v.target.length > 0) { 1165 if (v.target.length > 0) {
1162 link.attr('target', v.target); 1166 link.attr('target', v.target);
1163 } 1167 }
1164 t.range.deleteContents(); 1168 t.range.deleteContents();
1165 t.range.insertNode(link[0]); 1169 t.range.insertNode(link[0]);
1166 return true; 1170 return true;
1167 }); 1171 });
1168 }, 1172 },
1169 unlink: function () { 1173 unlink: function () {
1170 var t = this, 1174 var t = this,
1171 documentSelection = t.doc.getSelection(), 1175 documentSelection = t.doc.getSelection(),
1172 node = documentSelection.focusNode; 1176 node = documentSelection.focusNode;
1173   1177  
1174 if (documentSelection.isCollapsed) { 1178 if (documentSelection.isCollapsed) {
1175 while (['A', 'DIV'].indexOf(node.nodeName) < 0) { 1179 while (['A', 'DIV'].indexOf(node.nodeName) < 0) {
1176 node = node.parentNode; 1180 node = node.parentNode;
1177 } 1181 }
1178   1182  
1179 if (node && node.nodeName === 'A') { 1183 if (node && node.nodeName === 'A') {
1180 var range = t.doc.createRange(); 1184 var range = t.doc.createRange();
1181 range.selectNode(node); 1185 range.selectNode(node);
-   1186 documentSelection.removeAllRanges();
1182 documentSelection.addRange(range); 1187 documentSelection.addRange(range);
1183 } 1188 }
1184 } 1189 }
1185 t.execCmd('unlink', undefined, undefined, true); 1190 t.execCmd('unlink', undefined, undefined, true);
1186 }, 1191 },
1187 insertImage: function () { 1192 insertImage: function () {
1188 var t = this; 1193 var t = this;
1189 t.saveRange(); 1194 t.saveRange();
1190 t.openModalInsert(t.lang.insertImage, { 1195 t.openModalInsert(t.lang.insertImage, {
1191 url: { 1196 url: {
1192 label: 'URL', 1197 label: 'URL',
1193 required: true 1198 required: true
1194 }, 1199 },
1195 alt: { 1200 alt: {
1196 label: t.lang.description, 1201 label: t.lang.description,
1197 value: t.getRangeText() 1202 value: t.getRangeText()
1198 } 1203 }
1199 }, function (v) { // v are values 1204 }, function (v) { // v are values
1200 t.execCmd('insertImage', v.url); 1205 t.execCmd('insertImage', v.url);
1201 $('img[src="' + v.url + '"]:not([alt])', t.$box).attr('alt', v.alt); 1206 $('img[src="' + v.url + '"]:not([alt])', t.$box).attr('alt', v.alt);
1202 return true; 1207 return true;
1203 }); 1208 });
1204 }, 1209 },
1205 fullscreen: function () { 1210 fullscreen: function () {
1206 var t = this, 1211 var t = this,
1207 prefix = t.o.prefix, 1212 prefix = t.o.prefix,
1208 fullscreenCssClass = prefix + 'fullscreen', 1213 fullscreenCssClass = prefix + 'fullscreen',
1209 isFullscreen; 1214 isFullscreen;
1210   1215  
1211 t.$box.toggleClass(fullscreenCssClass); 1216 t.$box.toggleClass(fullscreenCssClass);
1212 isFullscreen = t.$box.hasClass(fullscreenCssClass); 1217 isFullscreen = t.$box.hasClass(fullscreenCssClass);
1213 $('body').toggleClass(prefix + 'body-fullscreen', isFullscreen); 1218 $('body').toggleClass(prefix + 'body-fullscreen', isFullscreen);
1214 $(window).trigger('scroll'); 1219 $(window).trigger('scroll');
1215 t.$c.trigger('tbw' + (isFullscreen ? 'open' : 'close') + 'fullscreen'); 1220 t.$c.trigger('tbw' + (isFullscreen ? 'open' : 'close') + 'fullscreen');
1216 }, 1221 },
1217   1222  
1218   1223  
1219 /* 1224 /*
1220 * Call method of trumbowyg if exist 1225 * Call method of trumbowyg if exist
1221 * else try to call anonymous function 1226 * else try to call anonymous function
1222 * and finaly native execCommand 1227 * and finaly native execCommand
1223 */ 1228 */
1224 execCmd: function (cmd, param, forceCss, skipTrumbowyg) { 1229 execCmd: function (cmd, param, forceCss, skipTrumbowyg) {
1225 var t = this; 1230 var t = this;
1226 skipTrumbowyg = !!skipTrumbowyg || ''; 1231 skipTrumbowyg = !!skipTrumbowyg || '';
1227   1232  
1228 if (cmd !== 'dropdown') { 1233 if (cmd !== 'dropdown') {
1229 t.$ed.focus(); 1234 t.$ed.focus();
1230 } 1235 }
1231   1236  
1232 try { 1237 try {
1233 t.doc.execCommand('styleWithCSS', false, forceCss || false); 1238 t.doc.execCommand('styleWithCSS', false, forceCss || false);
1234 } catch (c) { 1239 } catch (c) {
1235 } 1240 }
1236   1241  
1237 try { 1242 try {
1238 t[cmd + skipTrumbowyg](param); 1243 t[cmd + skipTrumbowyg](param);
1239 } catch (c) { 1244 } catch (c) {
1240 try { 1245 try {
1241 cmd(param); 1246 cmd(param);
1242 } catch (e2) { 1247 } catch (e2) {
1243 if (cmd === 'insertHorizontalRule') { 1248 if (cmd === 'insertHorizontalRule') {
1244 param = undefined; 1249 param = undefined;
1245 } else if (cmd === 'formatBlock' && t.isIE) { 1250 } else if (cmd === 'formatBlock' && t.isIE) {
1246 param = '<' + param + '>'; 1251 param = '<' + param + '>';
1247 } 1252 }
1248   1253  
1249 t.doc.execCommand(cmd, false, param); 1254 t.doc.execCommand(cmd, false, param);
1250   1255  
1251 t.syncCode(); 1256 t.syncCode();
1252 t.semanticCode(false, true); 1257 t.semanticCode(false, true);
1253 } 1258 }
1254   1259  
1255 if (cmd !== 'dropdown') { 1260 if (cmd !== 'dropdown') {
1256 t.updateButtonPaneStatus(); 1261 t.updateButtonPaneStatus();
1257 t.$c.trigger('tbwchange'); 1262 t.$c.trigger('tbwchange');
1258 } 1263 }
1259 } 1264 }
1260 }, 1265 },
1261   1266  
1262   1267  
1263 // Open a modal box 1268 // Open a modal box
1264 openModal: function (title, content) { 1269 openModal: function (title, content) {
1265 var t = this, 1270 var t = this,
1266 prefix = t.o.prefix; 1271 prefix = t.o.prefix;
1267   1272  
1268 // No open a modal box when exist other modal box 1273 // No open a modal box when exist other modal box
1269 if ($('.' + prefix + 'modal-box', t.$box).length > 0) { 1274 if ($('.' + prefix + 'modal-box', t.$box).length > 0) {
1270 return false; 1275 return false;
1271 } 1276 }
1272   1277  
1273 t.saveRange(); 1278 t.saveRange();
1274 t.showOverlay(); 1279 t.showOverlay();
1275   1280  
1276 // Disable all btnPane btns 1281 // Disable all btnPane btns
1277 t.$btnPane.addClass(prefix + 'disable'); 1282 t.$btnPane.addClass(prefix + 'disable');
1278   1283  
1279 // Build out of ModalBox, it's the mask for animations 1284 // Build out of ModalBox, it's the mask for animations
1280 var $modal = $('<div/>', { 1285 var $modal = $('<div/>', {
1281 class: prefix + 'modal ' + prefix + 'fixed-top' 1286 class: prefix + 'modal ' + prefix + 'fixed-top'
1282 }).css({ 1287 }).css({
1283 top: t.$btnPane.height() 1288 top: t.$btnPane.height()
1284 }).appendTo(t.$box); 1289 }).appendTo(t.$box);
1285   1290  
1286 // Click on overlay close modal by cancelling them 1291 // Click on overlay close modal by cancelling them
1287 t.$overlay.one('click', function () { 1292 t.$overlay.one('click', function () {
1288 $modal.trigger('tbwcancel'); 1293 $modal.trigger('tbwcancel');
1289 return false; 1294 return false;
1290 }); 1295 });
1291   1296  
1292 // Build the form 1297 // Build the form
1293 var $form = $('<form/>', { 1298 var $form = $('<form/>', {
1294 action: '', 1299 action: '',
1295 html: content 1300 html: content
1296 }) 1301 })
1297 .on('submit', function () { 1302 .on('submit', function () {
1298 $modal.trigger('tbwconfirm'); 1303 $modal.trigger('tbwconfirm');
1299 return false; 1304 return false;
1300 }) 1305 })
1301 .on('reset', function () { 1306 .on('reset', function () {
1302 $modal.trigger('tbwcancel'); 1307 $modal.trigger('tbwcancel');
1303 return false; 1308 return false;
1304 }); 1309 });
1305   1310  
1306   1311  
1307 // Build ModalBox and animate to show them 1312 // Build ModalBox and animate to show them
1308 var $box = $('<div/>', { 1313 var $box = $('<div/>', {
1309 class: prefix + 'modal-box', 1314 class: prefix + 'modal-box',
1310 html: $form 1315 html: $form
1311 }) 1316 })
1312 .css({ 1317 .css({
1313 top: '-' + t.$btnPane.outerHeight() + 'px', 1318 top: '-' + t.$btnPane.outerHeight() + 'px',
1314 opacity: 0 1319 opacity: 0
1315 }) 1320 })
1316 .appendTo($modal) 1321 .appendTo($modal)
1317 .animate({ 1322 .animate({
1318 top: 0, 1323 top: 0,
1319 opacity: 1 1324 opacity: 1
1320 }, 100); 1325 }, 100);
1321   1326  
1322   1327  
1323 // Append title 1328 // Append title
1324 $('<span/>', { 1329 $('<span/>', {
1325 text: title, 1330 text: title,
1326 class: prefix + 'modal-title' 1331 class: prefix + 'modal-title'
1327 }).prependTo($box); 1332 }).prependTo($box);
1328   1333  
1329 $modal.height($box.outerHeight() + 10); 1334 $modal.height($box.outerHeight() + 10);
1330   1335  
1331   1336  
1332 // Focus in modal box 1337 // Focus in modal box
1333 $('input:first', $box).focus(); 1338 $('input:first', $box).focus();
1334   1339  
1335   1340  
1336 // Append Confirm and Cancel buttons 1341 // Append Confirm and Cancel buttons
1337 t.buildModalBtn('submit', $box); 1342 t.buildModalBtn('submit', $box);
1338 t.buildModalBtn('reset', $box); 1343 t.buildModalBtn('reset', $box);
1339   1344  
1340   1345  
1341 $(window).trigger('scroll'); 1346 $(window).trigger('scroll');
1342   1347  
1343 return $modal; 1348 return $modal;
1344 }, 1349 },
1345 // @param n is name of modal 1350 // @param n is name of modal
1346 buildModalBtn: function (n, $modal) { 1351 buildModalBtn: function (n, $modal) {
1347 var t = this, 1352 var t = this,
1348 prefix = t.o.prefix; 1353 prefix = t.o.prefix;
1349   1354  
1350 return $('<button/>', { 1355 return $('<button/>', {
1351 class: prefix + 'modal-button ' + prefix + 'modal-' + n, 1356 class: prefix + 'modal-button ' + prefix + 'modal-' + n,
1352 type: n, 1357 type: n,
1353 text: t.lang[n] || n 1358 text: t.lang[n] || n
1354 }).appendTo($('form', $modal)); 1359 }).appendTo($('form', $modal));
1355 }, 1360 },
1356 // close current modal box 1361 // close current modal box
1357 closeModal: function () { 1362 closeModal: function () {
1358 var t = this, 1363 var t = this,
1359 prefix = t.o.prefix; 1364 prefix = t.o.prefix;
1360   1365  
1361 t.$btnPane.removeClass(prefix + 'disable'); 1366 t.$btnPane.removeClass(prefix + 'disable');
1362 t.$overlay.off(); 1367 t.$overlay.off();
1363   1368  
1364 // Find the modal box 1369 // Find the modal box
1365 var $modalBox = $('.' + prefix + 'modal-box', t.$box); 1370 var $modalBox = $('.' + prefix + 'modal-box', t.$box);
1366   1371  
1367 $modalBox.animate({ 1372 $modalBox.animate({
1368 top: '-' + $modalBox.height() 1373 top: '-' + $modalBox.height()
1369 }, 100, function () { 1374 }, 100, function () {
1370 $modalBox.parent().remove(); 1375 $modalBox.parent().remove();
1371 t.hideOverlay(); 1376 t.hideOverlay();
1372 }); 1377 });
1373   1378  
1374 t.restoreRange(); 1379 t.restoreRange();
1375 }, 1380 },
1376 // Preformated build and management modal 1381 // Preformated build and management modal
1377 openModalInsert: function (title, fields, cmd) { 1382 openModalInsert: function (title, fields, cmd) {
1378 var t = this, 1383 var t = this,
1379 prefix = t.o.prefix, 1384 prefix = t.o.prefix,
1380 lg = t.lang, 1385 lg = t.lang,
1381 html = '', 1386 html = '',
1382 CONFIRM_EVENT = 'tbwconfirm'; 1387 CONFIRM_EVENT = 'tbwconfirm';
1383   1388  
1384 $.each(fields, function (fieldName, field) { 1389 $.each(fields, function (fieldName, field) {
1385 var l = field.label, 1390 var l = field.label,
1386 n = field.name || fieldName, 1391 n = field.name || fieldName,
1387 a = field.attributes || {}; 1392 a = field.attributes || {};
1388   1393  
1389 var attr = Object.keys(a).map(function (prop) { 1394 var attr = Object.keys(a).map(function (prop) {
1390 return prop + '="' + a[prop] + '"'; 1395 return prop + '="' + a[prop] + '"';
1391 }).join(' '); 1396 }).join(' ');
1392   1397  
1393 html += '<label><input type="' + (field.type || 'text') + '" name="' + n + '" value="' + (field.value || '').replace(/"/g, '&quot;') + '"' + attr + '><span class="' + prefix + 'input-infos"><span>' + 1398 html += '<label><input type="' + (field.type || 'text') + '" name="' + n + '" value="' + (field.value || '').replace(/"/g, '&quot;') + '"' + attr + '><span class="' + prefix + 'input-infos"><span>' +
1394 ((!l) ? (lg[fieldName] ? lg[fieldName] : fieldName) : (lg[l] ? lg[l] : l)) + 1399 ((!l) ? (lg[fieldName] ? lg[fieldName] : fieldName) : (lg[l] ? lg[l] : l)) +
1395 '</span></span></label>'; 1400 '</span></span></label>';
1396 }); 1401 });
1397   1402  
1398 return t.openModal(title, html) 1403 return t.openModal(title, html)
1399 .on(CONFIRM_EVENT, function () { 1404 .on(CONFIRM_EVENT, function () {
1400 var $form = $('form', $(this)), 1405 var $form = $('form', $(this)),
1401 valid = true, 1406 valid = true,
1402 values = {}; 1407 values = {};
1403   1408  
1404 $.each(fields, function (fieldName, field) { 1409 $.each(fields, function (fieldName, field) {
1405 var $field = $('input[name="' + fieldName + '"]', $form), 1410 var $field = $('input[name="' + fieldName + '"]', $form),
1406 inputType = $field.attr('type'); 1411 inputType = $field.attr('type');
1407   1412  
1408 if (inputType.toLowerCase() === 'checkbox') { 1413 if (inputType.toLowerCase() === 'checkbox') {
1409 values[fieldName] = $field.is(':checked'); 1414 values[fieldName] = $field.is(':checked');
1410 } else { 1415 } else {
1411 values[fieldName] = $.trim($field.val()); 1416 values[fieldName] = $.trim($field.val());
1412 } 1417 }
1413 // Validate value 1418 // Validate value
1414 if (field.required && values[fieldName] === '') { 1419 if (field.required && values[fieldName] === '') {
1415 valid = false; 1420 valid = false;
1416 t.addErrorOnModalField($field, t.lang.required); 1421 t.addErrorOnModalField($field, t.lang.required);
1417 } else if (field.pattern && !field.pattern.test(values[fieldName])) { 1422 } else if (field.pattern && !field.pattern.test(values[fieldName])) {
1418 valid = false; 1423 valid = false;
1419 t.addErrorOnModalField($field, field.patternError); 1424 t.addErrorOnModalField($field, field.patternError);
1420 } 1425 }
1421 }); 1426 });
1422   1427  
1423 if (valid) { 1428 if (valid) {
1424 t.restoreRange(); 1429 t.restoreRange();
1425   1430  
1426 if (cmd(values, fields)) { 1431 if (cmd(values, fields)) {
1427 t.syncCode(); 1432 t.syncCode();
1428 t.$c.trigger('tbwchange'); 1433 t.$c.trigger('tbwchange');
1429 t.closeModal(); 1434 t.closeModal();
1430 $(this).off(CONFIRM_EVENT); 1435 $(this).off(CONFIRM_EVENT);
1431 } 1436 }
1432 } 1437 }
1433 }) 1438 })
1434 .one('tbwcancel', function () { 1439 .one('tbwcancel', function () {
1435 $(this).off(CONFIRM_EVENT); 1440 $(this).off(CONFIRM_EVENT);
1436 t.closeModal(); 1441 t.closeModal();
1437 }); 1442 });
1438 }, 1443 },
1439 addErrorOnModalField: function ($field, err) { 1444 addErrorOnModalField: function ($field, err) {
1440 var prefix = this.o.prefix, 1445 var prefix = this.o.prefix,
1441 $label = $field.parent(); 1446 $label = $field.parent();
1442   1447  
1443 $field 1448 $field
1444 .on('change keyup', function () { 1449 .on('change keyup', function () {
1445 $label.removeClass(prefix + 'input-error'); 1450 $label.removeClass(prefix + 'input-error');
1446 }); 1451 });
1447   1452  
1448 $label 1453 $label
1449 .addClass(prefix + 'input-error') 1454 .addClass(prefix + 'input-error')
1450 .find('input+span') 1455 .find('input+span')
1451 .append( 1456 .append(
1452 $('<span/>', { 1457 $('<span/>', {
1453 class: prefix + 'msg-error', 1458 class: prefix + 'msg-error',
1454 text: err 1459 text: err
1455 }) 1460 })
1456 ); 1461 );
1457 }, 1462 },
1458   1463  
1459   1464  
1460 // Range management 1465 // Range management
1461 saveRange: function () { 1466 saveRange: function () {
1462 var t = this, 1467 var t = this,
1463 documentSelection = t.doc.getSelection(); 1468 documentSelection = t.doc.getSelection();
1464   1469  
1465 t.range = null; 1470 t.range = null;
1466   1471  
1467 if (documentSelection.rangeCount) { 1472 if (documentSelection.rangeCount) {
1468 var savedRange = t.range = documentSelection.getRangeAt(0), 1473 var savedRange = t.range = documentSelection.getRangeAt(0),
1469 range = t.doc.createRange(), 1474 range = t.doc.createRange(),
1470 rangeStart; 1475 rangeStart;
1471 range.selectNodeContents(t.$ed[0]); 1476 range.selectNodeContents(t.$ed[0]);
1472 range.setEnd(savedRange.startContainer, savedRange.startOffset); 1477 range.setEnd(savedRange.startContainer, savedRange.startOffset);
1473 rangeStart = (range + '').length; 1478 rangeStart = (range + '').length;
1474 t.metaRange = { 1479 t.metaRange = {
1475 start: rangeStart, 1480 start: rangeStart,
1476 end: rangeStart + (savedRange + '').length 1481 end: rangeStart + (savedRange + '').length
1477 }; 1482 };
1478 } 1483 }
1479 }, 1484 },
1480 restoreRange: function () { 1485 restoreRange: function () {
1481 var t = this, 1486 var t = this,
1482 metaRange = t.metaRange, 1487 metaRange = t.metaRange,
1483 savedRange = t.range, 1488 savedRange = t.range,
1484 documentSelection = t.doc.getSelection(), 1489 documentSelection = t.doc.getSelection(),
1485 range; 1490 range;
1486   1491  
1487 if (!savedRange) { 1492 if (!savedRange) {
1488 return; 1493 return;
1489 } 1494 }
1490   1495  
1491 if (metaRange && metaRange.start !== metaRange.end) { // Algorithm from http://jsfiddle.net/WeWy7/3/ 1496 if (metaRange && metaRange.start !== metaRange.end) { // Algorithm from http://jsfiddle.net/WeWy7/3/
1492 var charIndex = 0, 1497 var charIndex = 0,
1493 nodeStack = [t.$ed[0]], 1498 nodeStack = [t.$ed[0]],
1494 node, 1499 node,
1495 foundStart = false, 1500 foundStart = false,
1496 stop = false; 1501 stop = false;
1497   1502  
1498 range = t.doc.createRange(); 1503 range = t.doc.createRange();
1499   1504  
1500 while (!stop && (node = nodeStack.pop())) { 1505 while (!stop && (node = nodeStack.pop())) {
1501 if (node.nodeType === 3) { 1506 if (node.nodeType === 3) {
1502 var nextCharIndex = charIndex + node.length; 1507 var nextCharIndex = charIndex + node.length;
1503 if (!foundStart && metaRange.start >= charIndex && metaRange.start <= nextCharIndex) { 1508 if (!foundStart && metaRange.start >= charIndex && metaRange.start <= nextCharIndex) {
1504 range.setStart(node, metaRange.start - charIndex); 1509 range.setStart(node, metaRange.start - charIndex);
1505 foundStart = true; 1510 foundStart = true;
1506 } 1511 }
1507 if (foundStart && metaRange.end >= charIndex && metaRange.end <= nextCharIndex) { 1512 if (foundStart && metaRange.end >= charIndex && metaRange.end <= nextCharIndex) {
1508 range.setEnd(node, metaRange.end - charIndex); 1513 range.setEnd(node, metaRange.end - charIndex);
1509 stop = true; 1514 stop = true;
1510 } 1515 }
1511 charIndex = nextCharIndex; 1516 charIndex = nextCharIndex;
1512 } else { 1517 } else {
1513 var cn = node.childNodes, 1518 var cn = node.childNodes,
1514 i = cn.length; 1519 i = cn.length;
1515   1520  
1516 while (i > 0) { 1521 while (i > 0) {
1517 i -= 1; 1522 i -= 1;
1518 nodeStack.push(cn[i]); 1523 nodeStack.push(cn[i]);
1519 } 1524 }
1520 } 1525 }
1521 } 1526 }
1522 } 1527 }
1523   1528  
1524 documentSelection.removeAllRanges(); 1529 documentSelection.removeAllRanges();
1525 documentSelection.addRange(range || savedRange); 1530 documentSelection.addRange(range || savedRange);
1526 }, 1531 },
1527 getRangeText: function () { 1532 getRangeText: function () {
1528 return this.range + ''; 1533 return this.range + '';
1529 }, 1534 },
1530   1535  
1531 updateButtonPaneStatus: function () { 1536 updateButtonPaneStatus: function () {
1532 var t = this, 1537 var t = this,
1533 prefix = t.o.prefix, 1538 prefix = t.o.prefix,
1534 tags = t.getTagsRecursive(t.doc.getSelection().focusNode), 1539 tags = t.getTagsRecursive(t.doc.getSelection().focusNode),
1535 activeClasses = prefix + 'active-button ' + prefix + 'active'; 1540 activeClasses = prefix + 'active-button ' + prefix + 'active';
1536   1541  
1537 $('.' + prefix + 'active-button', t.$btnPane).removeClass(activeClasses); 1542 $('.' + prefix + 'active-button', t.$btnPane).removeClass(activeClasses);
1538 $.each(tags, function (i, tag) { 1543 $.each(tags, function (i, tag) {
1539 var btnName = t.tagToButton[tag.toLowerCase()], 1544 var btnName = t.tagToButton[tag.toLowerCase()],
1540 $btn = $('.' + prefix + btnName + '-button', t.$btnPane); 1545 $btn = $('.' + prefix + btnName + '-button', t.$btnPane);
1541   1546  
1542 if ($btn.length > 0) { 1547 if ($btn.length > 0) {
1543 $btn.addClass(activeClasses); 1548 $btn.addClass(activeClasses);
1544 } else { 1549 } else {
1545 try { 1550 try {
1546 $btn = $('.' + prefix + 'dropdown .' + prefix + btnName + '-dropdown-button', t.$box); 1551 $btn = $('.' + prefix + 'dropdown .' + prefix + btnName + '-dropdown-button', t.$box);
1547 var dropdownBtnName = $btn.parent().data('dropdown'); 1552 var dropdownBtnName = $btn.parent().data('dropdown');
1548 $('.' + prefix + dropdownBtnName + '-button', t.$box).addClass(activeClasses); 1553 $('.' + prefix + dropdownBtnName + '-button', t.$box).addClass(activeClasses);
1549 } catch (e) { 1554 } catch (e) {
1550 } 1555 }
1551 } 1556 }
1552 }); 1557 });
1553 }, 1558 },
1554 getTagsRecursive: function (element, tags) { 1559 getTagsRecursive: function (element, tags) {
1555 var t = this; 1560 var t = this;
1556 tags = tags || (element && element.tagName ? [element.tagName] : []); 1561 tags = tags || (element && element.tagName ? [element.tagName] : []);
1557   1562  
1558 if (element && element.parentNode) { 1563 if (element && element.parentNode) {
1559 element = element.parentNode; 1564 element = element.parentNode;
1560 } else { 1565 } else {
1561 return tags; 1566 return tags;
1562 } 1567 }
1563   1568  
1564 var tag = element.tagName; 1569 var tag = element.tagName;
1565 if (tag === 'DIV') { 1570 if (tag === 'DIV') {
1566 return tags; 1571 return tags;
1567 } 1572 }
1568 if (tag === 'P' && element.style.textAlign !== '') { 1573 if (tag === 'P' && element.style.textAlign !== '') {
1569 tags.push(element.style.textAlign); 1574 tags.push(element.style.textAlign);
1570 } 1575 }
1571   1576  
1572 $.each(t.tagHandlers, function (i, tagHandler) { 1577 $.each(t.tagHandlers, function (i, tagHandler) {
1573 tags = tags.concat(tagHandler(element, t)); 1578 tags = tags.concat(tagHandler(element, t));
1574 }); 1579 });
1575   1580  
1576 tags.push(tag); 1581 tags.push(tag);
1577   1582  
1578 return t.getTagsRecursive(element, tags); 1583 return t.getTagsRecursive(element, tags);
1579 }, 1584 },
1580   1585  
1581 // Plugins 1586 // Plugins
1582 initPlugins: function () { 1587 initPlugins: function () {
1583 var t = this; 1588 var t = this;
1584 t.loadedPlugins = []; 1589 t.loadedPlugins = [];
1585 $.each($.trumbowyg.plugins, function (name, plugin) { 1590 $.each($.trumbowyg.plugins, function (name, plugin) {
1586 if (!plugin.shouldInit || plugin.shouldInit(t)) { 1591 if (!plugin.shouldInit || plugin.shouldInit(t)) {
1587 plugin.init(t); 1592 plugin.init(t);
1588 if (plugin.tagHandler) { 1593 if (plugin.tagHandler) {
1589 t.tagHandlers.push(plugin.tagHandler); 1594 t.tagHandlers.push(plugin.tagHandler);
1590 } 1595 }
1591 t.loadedPlugins.push(plugin); 1596 t.loadedPlugins.push(plugin);
1592 } 1597 }
1593 }); 1598 });
1594 }, 1599 },
1595 destroyPlugins: function () { 1600 destroyPlugins: function () {
1596 $.each(this.loadedPlugins, function (i, plugin) { 1601 $.each(this.loadedPlugins, function (i, plugin) {
1597 if (plugin.destroy) { 1602 if (plugin.destroy) {
1598 plugin.destroy(); 1603 plugin.destroy();
1599 } 1604 }
1600 }); 1605 });
1601 } 1606 }
1602 }; 1607 };
1603 })(navigator, window, document, jQuery); 1608 })(navigator, window, document, jQuery);
1604   1609