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