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