]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/jquery/markitup/jquery.markitup.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / jquery / markitup / jquery.markitup.js
1 // ----------------------------------------------------------------------------
2 // markItUp! Universal MarkUp Engine, JQuery plugin
3 // v 1.1.x
4 // Dual licensed under the MIT and GPL licenses.
5 // ----------------------------------------------------------------------------
6 // Copyright (C) 2007-2011 Jay Salvat
7 // http://markitup.jaysalvat.com/
8 // ----------------------------------------------------------------------------
9 // Permission is hereby granted, free of charge, to any person obtaining a copy
10 // of this software and associated documentation files (the "Software"), to deal
11 // in the Software without restriction, including without limitation the rights
12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 // copies of the Software, and to permit persons to whom the Software is
14 // furnished to do so, subject to the following conditions:
15 // 
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
18 // 
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 // THE SOFTWARE.
26 // ----------------------------------------------------------------------------
27 /*
28     Modified on 4-26-12 by SugarCRM
29     get and insert functions modifed for better IE8 compatibility.
30     Various global varaible cleanups
31  */
32 (function($) {
33         $.fn.markItUp = function(settings, extraSettings) {
34                 var options, ctrlKey, shiftKey, altKey, selection, caretPosition;
35                 ctrlKey = shiftKey = altKey = false;
36         
37                 options = {     id:                                             '',
38                                         nameSpace:                              '',
39                                         root:                                   '',
40                                         previewInWindow:                '', // 'width=800, height=600, resizable=yes, scrollbars=yes'
41                                         previewAutoRefresh:             true,
42                                         previewPosition:                'after',
43                                         previewTemplatePath:    '~/templates/preview.html',
44                                         previewParser:                  false,
45                                         previewParserPath:              '',
46                                         previewParserVar:               'data',
47                                         resizeHandle:                   true,
48                                         beforeInsert:                   '',
49                                         afterInsert:                    '',
50                                         onEnter:                                {},
51                                         onShiftEnter:                   {},
52                                         onCtrlEnter:                    {},
53                                         onTab:                                  {},
54                                         markupSet:                      [       { /* set */ } ]
55                                 };
56                 $.extend(options, settings, extraSettings);
57
58                 // compute markItUp! path
59                 if (!options.root) {
60                         $('script').each(function(a, tag) {
61                                 var miuScript = $(tag).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/);
62                                 if (miuScript !== null) {
63                                         options.root = miuScript[1];
64                                 }
65                         });
66                 }
67
68                 return this.each(function() {
69                         var $$, textarea, levels, scrollPosition, caretPosition, caretOffset,
70                                 clicked, hash, header, footer, previewWindow, template, iFrame, abort;
71                         $$ = $(this);
72                         textarea = this;
73                         levels = [];
74                         abort = false;
75                         scrollPosition = caretPosition = 0;
76                         caretOffset = -1;
77
78                         options.previewParserPath = localize(options.previewParserPath);
79                         options.previewTemplatePath = localize(options.previewTemplatePath);
80
81                         // apply the computed path to ~/
82                         function localize(data, inText) {
83                                 if (inText) {
84                                         return  data.replace(/("|')~\//g, "$1"+options.root);
85                                 }
86                                 return  data.replace(/^~\//, options.root);
87                         }
88
89                         // init and build editor
90                         function init() {
91                                 id = ''; nameSpace = '';
92                                 if (options.id) {
93                                         id = 'id="'+options.id+'"';
94                                 } else if ($$.attr("id")) {
95                                         id = 'id="markItUp'+($$.attr("id").substr(0, 1).toUpperCase())+($$.attr("id").substr(1))+'"';
96
97                                 }
98                                 if (options.nameSpace) {
99                                         nameSpace = 'class="'+options.nameSpace+'"';
100                                 }
101                                 $$.wrap('<div '+nameSpace+'></div>');
102                                 $$.wrap('<div '+id+' class="markItUp"></div>');
103                                 $$.wrap('<div class="markItUpContainer"></div>');
104                                 $$.addClass("markItUpEditor");
105
106                                 // add the header before the textarea
107                                 header = $('<div class="markItUpHeader"></div>').insertBefore($$);
108                                 $(dropMenus(options.markupSet)).appendTo(header);
109
110                                 // add the footer after the textarea
111                                 footer = $('<div class="markItUpFooter"></div>').insertAfter($$);
112
113                                 // add the resize handle after textarea
114                                 if (options.resizeHandle === true && $.browser.safari !== true) {
115                                         var resizeHandle = $('<div class="markItUpResizeHandle"></div>')
116                                                 .insertAfter($$)
117                                                 .bind("mousedown", function(e) {
118                                                         var h = $$.height(), y = e.clientY, mouseMove, mouseUp;
119                                                         mouseMove = function(e) {
120                                                                 $$.css("height", Math.max(20, e.clientY+h-y)+"px");
121                                                                 return false;
122                                                         };
123                                                         mouseUp = function(e) {
124                                                                 $("html").unbind("mousemove", mouseMove).unbind("mouseup", mouseUp);
125                                                                 return false;
126                                                         };
127                                                         $("html").bind("mousemove", mouseMove).bind("mouseup", mouseUp);
128                                         });
129                                         footer.append(resizeHandle);
130                                 }
131
132                                 // listen key events
133                                 $$.keydown(keyPressed).keyup(keyPressed);
134                                 
135                                 // bind an event to catch external calls
136                                 $$.bind("insertion", function(e, settings) {
137                                         if (settings.target !== false) {
138                                                 get();
139                                         }
140                                         if (textarea === $.markItUp.focused) {
141                                                 markup(settings);
142                                         }
143                                 });
144
145                                 // remember the last focus
146                                 $$.focus(function() {
147                                         $.markItUp.focused = this;
148                                 });
149                         }
150
151                         // recursively build header with dropMenus from markupset
152                         function dropMenus(markupSet) {
153                                 var ul = $('<ul></ul>'), i = 0;
154                                 $('li:hover > ul', ul).css('display', 'block');
155                                 $.each(markupSet, function() {
156                                         var button = this, t = '', title, li, j, key;
157                                         title = (button.key) ? (button.name||'')+' [Ctrl+'+button.key+']' : (button.name||'');
158                                         key   = (button.key) ? 'accesskey="'+button.key+'"' : '';
159                                         if (button.separator) {
160                                                 li = $('<li class="markItUpSeparator">'+(button.separator||'')+'</li>').appendTo(ul);
161                                         } else {
162                                                 i++;
163                                                 for (j = levels.length -1; j >= 0; j--) {
164                                                         t += levels[j]+"-";
165                                                 }
166                                                 li = $('<li class="markItUpButton markItUpButton'+t+(i)+' '+(button.className||'')+'"><a href="" '+key+' title="'+title+'">'+(button.name||'')+'</a></li>')
167                                                 .bind("contextmenu", function() { // prevent contextmenu on mac and allow ctrl+click
168                                                         return false;
169                                                 }).click(function() {
170                                                         return false;
171                                                 }).bind("focusin", function(){
172                                                         $$.focus();
173                                                 }).mouseup(function() {
174                                                         if (button.call) {
175                                                                 eval(button.call)();
176                                                         }
177                                                         setTimeout(function() { markup(button) },1);
178                                                         return false;
179                                                 }).hover(function() {
180                                                                 $('> ul', this).show();
181                                                                 $(document).one('click', function() { // close dropmenu if click outside
182                                                                                 $('ul ul', header).hide();
183                                                                         }
184                                                                 );
185                                                         }, function() {
186                                                                 $('> ul', this).hide();
187                                                         }
188                                                 ).appendTo(ul);
189                                                 if (button.dropMenu) {
190                                                         levels.push(i);
191                                                         $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu));
192                                                 }
193                                         }
194                                 }); 
195                                 levels.pop();
196                                 return ul;
197                         }
198
199                         // markItUp! markups
200                         function magicMarkups(string) {
201                                 if (string) {
202                                         string = string.toString();
203                                         string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g,
204                                                 function(x, a) {
205                                                         var b = a.split('|!|');
206                                                         if (altKey === true) {
207                                                                 return (b[1] !== undefined) ? b[1] : b[0];
208                                                         } else {
209                                                                 return (b[1] === undefined) ? "" : b[0];
210                                                         }
211                                                 }
212                                         );
213                                         // [![prompt]!], [![prompt:!:value]!]
214                                         string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g,
215                                                 function(x, a) {
216                                                         var b = a.split(':!:');
217                                                         if (abort === true) {
218                                                                 return false;
219                                                         }
220                                                         value = prompt(b[0], (b[1]) ? b[1] : '');
221                                                         if (value === null) {
222                                                                 abort = true;
223                                                         }
224                                                         return value;
225                                                 }
226                                         );
227                                         return string;
228                                 }
229                                 return "";
230                         }
231
232                         // prepare action
233                         function prepare(action) {
234                                 if ($.isFunction(action)) {
235                                         action = action(hash);
236                                 }
237                                 return magicMarkups(action);
238                         }
239
240                         // build block to insert
241                         function build(string) {
242                                 var block, line;
243                                 var openWith                    = prepare(clicked.openWith);
244                                 var placeHolder                 = prepare(clicked.placeHolder);
245                                 var replaceWith                 = prepare(clicked.replaceWith);
246                                 var closeWith                   = prepare(clicked.closeWith);
247                                 var openBlockWith               = prepare(clicked.openBlockWith);
248                                 var closeBlockWith              = prepare(clicked.closeBlockWith);
249                                 var multiline                   = clicked.multiline;
250                                 
251                                 if (replaceWith !== "") {
252                                         block = openWith + replaceWith + closeWith;
253                                 } else if (selection === '' && placeHolder !== '') {
254                                         block = openWith + placeHolder + closeWith;
255                                 } else {
256                                         string = string || selection;
257
258                                         var lines = selection.split(/\r?\n/), blocks = [];
259                                         
260                                         for (var l=0; l < lines.length; l++) {
261                                                 line = lines[l];
262                                                 var trailingSpaces;
263                                                 if (trailingSpaces = line.match(/ *$/)) {
264                                                         blocks.push(openWith + line.replace(/ *$/g, '') + closeWith + trailingSpaces);
265                                                 } else {
266                                                         blocks.push(openWith + line + closeWith);
267                                                 }
268                                         }
269                                         
270                                         block = blocks.join("\n");
271                                 }
272
273                                 block = openBlockWith + block + closeBlockWith;
274
275                                 return {        block:block, 
276                                                         openWith:openWith, 
277                                                         replaceWith:replaceWith, 
278                                                         placeHolder:placeHolder,
279                                                         closeWith:closeWith
280                                         };
281                         }
282
283                         // define markup to insert
284                         function markup(button) {
285                                 var len, j, n, i, lines, string, start;
286                                 hash = clicked = button;
287                                 get();
288                                 $.extend(hash, {        line:"", 
289                                                                         root:options.root,
290                                                                         textarea:textarea, 
291                                                                         selection:(selection||''), 
292                                                                         caretPosition:caretPosition,
293                                                                         ctrlKey:ctrlKey, 
294                                                                         shiftKey:shiftKey, 
295                                                                         altKey:altKey
296                                                                 }
297                                                         );
298                                 // callbacks before insertion
299                                 prepare(options.beforeInsert);
300                                 prepare(clicked.beforeInsert);
301                                 if ((ctrlKey === true && shiftKey === true) || button.multiline === true) {
302                                         prepare(clicked.beforeMultiInsert);
303                                 }                       
304                                 $.extend(hash, { line:1 });
305
306                                 if ((ctrlKey === true && shiftKey === true)) {
307                                         lines = selection.split(/\r?\n/);
308                                         for (j = 0, n = lines.length, i = 0; i < n; i++) {
309                                                 if ($.trim(lines[i]) !== '') {
310                                                         $.extend(hash, { line:++j, selection:lines[i] } );
311                                                         lines[i] = build(lines[i]).block;
312                                                 } else {
313                                                         lines[i] = "";
314                                                 }
315                                         }
316                                         string = { block:lines.join('\n')};
317                                         start = caretPosition;
318                                         len = string.block.length + (($.browser.opera) ? n-1 : 0);
319                                 } else if (ctrlKey === true) {
320                                         string = build(selection);
321                                         start = caretPosition + string.openWith.length;
322                                         len = string.block.length - string.openWith.length - string.closeWith.length;
323                                         len = len - (string.block.match(/ $/) ? 1 : 0);
324                                         len -= fixIeBug(string.block);
325                                 } else if (shiftKey === true) {
326                                         string = build(selection);
327                                         start = caretPosition;
328                                         len = string.block.length;
329                                         len -= fixIeBug(string.block);
330                                 } else {
331                                         string = build(selection);
332                                         start = caretPosition + string.block.length ;
333                                         len = 0;
334                                         start -= fixIeBug(string.block);
335                                 }
336                                 if ((selection === '' && string.replaceWith === '')) {
337                                         caretOffset += fixOperaBug(string.block);
338                                         
339                                         start = caretPosition + string.openWith.length;
340                                         len = string.block.length - string.openWith.length - string.closeWith.length;
341
342                                         caretOffset = $$.val().substring(caretPosition,  $$.val().length).length;
343                                         caretOffset -= fixOperaBug($$.val().substring(0, caretPosition));
344                                 }
345                                 $.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } );
346
347                                 if (string.block !== selection && abort === false) {
348                                         insert(string.block);
349                                         set(start, len);
350                                 } else {
351                                         caretOffset = -1;
352                                 }
353                                 get();
354
355                                 $.extend(hash, { line:'', selection:selection });
356
357                                 // callbacks after insertion
358                                 if ((ctrlKey === true && shiftKey === true) || button.multiline === true) {
359                                         prepare(clicked.afterMultiInsert);
360                                 }
361                                 prepare(clicked.afterInsert);
362                                 prepare(options.afterInsert);
363
364                                 // refresh preview if opened
365                                 if (previewWindow && options.previewAutoRefresh) {
366                                         refreshPreview(); 
367                                 }
368                                                                                                                                                                                                         
369                                 // reinit keyevent
370                                 shiftKey = altKey = ctrlKey = abort = false;
371                         }
372
373                         // Substract linefeed in Opera
374                         function fixOperaBug(string) {
375                                 if ($.browser.opera) {
376                                         return string.length - string.replace(/\n*/g, '').length;
377                                 }
378                                 return 0;
379                         }
380                         // Substract linefeed in IE
381                         function fixIeBug(string) {
382                                 if ($.browser.msie) {
383                                         return string.length - string.replace(/\r*/g, '').length;
384                                 }
385                                 return 0;
386                         }
387                                 
388                         // add markup
389                         function insert(block) {        
390                                 textarea.value =  textarea.value.substring(0, caretPosition)  + block
391                     + textarea.value.substring(caretPosition + selection.length, textarea.value.length);
392                         }
393
394                         // set a selection
395                         function set(start, len) {
396                                 var range;
397                                 if (textarea.createTextRange){
398                                         // quick fix to make it work on Opera 9.5
399                                         if ($.browser.opera && $.browser.version >= 9.5 && len == 0) {
400                                                 return false;
401                                         }
402                                         range = textarea.createTextRange();
403                                         range.collapse(true);
404                                         range.moveStart('character', start); 
405                                         range.moveEnd('character', len); 
406                                         range.select();
407                                 } else if (textarea.setSelectionRange ){
408                                         textarea.setSelectionRange(start, start + len);
409                                 }
410                                 textarea.scrollTop = scrollPosition;
411                                 textarea.focus();
412                         }
413
414                         // get the selection
415                         function get() {
416                                 textarea.focus();
417
418                                 scrollPosition = textarea.scrollTop;
419                                 if (document.selection) {
420                                         selection = document.selection.createRange().text;
421                                         if ($.browser.msie) { // ie
422                                                 var range = document.selection.createRange(),
423                                                         rangeCopy = range.duplicate();
424                                                 //If nothing is selected, we have to use a different technique to find the caret position
425                                                 if (document.selection.type =="None")
426                                                 {
427                                                         var sel = range;
428                             range = textarea.createTextRange();
429                             rangeCopy = range.duplicate();
430                                                         range.moveToBookmark(sel.getBookmark());
431                             rangeCopy.setEndPoint("EndToStart", range);
432                                                         if (rangeCopy.parentElement() == textarea)
433                                 caretPosition = rangeCopy.text.length;
434                                                 }
435                                                 else {
436                                                         rangeCopy.moveToElementText(textarea);
437                             caretPosition = -1;
438                             var inrange = false;
439                             while(rangeCopy.inRange(range, inrange)) {
440                                 rangeCopy.moveStart('character');
441                                 caretPosition ++;
442                             }
443                                                 }
444                                         } else { // opera
445                                                 caretPosition = textarea.selectionStart;
446                                         }
447                                 } else { // gecko & webkit
448                                         caretPosition = textarea.selectionStart;
449
450                                         selection = textarea.value.substring(caretPosition, textarea.selectionEnd);
451                                 } 
452                                 return selection;
453                         }
454
455                         // open preview window
456                         function preview() {
457                                 if (!previewWindow || previewWindow.closed) {
458                                         if (options.previewInWindow) {
459                                                 previewWindow = window.open('', 'preview', options.previewInWindow);
460                                                 $(window).unload(function() {
461                                                         previewWindow.close();
462                                                 });
463                                         } else {
464                                                 iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>');
465                                                 if (options.previewPosition == 'after') {
466                                                         iFrame.insertAfter(footer);
467                                                 } else {
468                                                         iFrame.insertBefore(header);
469                                                 }       
470                                                 previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1];
471                                         }
472                                 } else if (altKey === true) {
473                                         if (iFrame) {
474                                                 iFrame.remove();
475                                         } else {
476                                                 previewWindow.close();
477                                         }
478                                         previewWindow = iFrame = false;
479                                 }
480                                 if (!options.previewAutoRefresh) {
481                                         refreshPreview(); 
482                                 }
483                                 if (options.previewInWindow) {
484                                         previewWindow.focus();
485                                 }
486                         }
487
488                         // refresh Preview window
489                         function refreshPreview() {
490                                 renderPreview();
491                         }
492
493                         function renderPreview() {              
494                                 var phtml;
495                                 if (options.previewParser && typeof options.previewParser === 'function') {
496                                         var data = options.previewParser( $$.val() );
497                                         writeInPreview( localize(data, 1) ); 
498                                 } else if (options.previewParserPath !== '') {
499                                         $.ajax({
500                                                 type: 'POST',
501                                                 dataType: 'text',
502                                                 global: false,
503                                                 url: options.previewParserPath,
504                                                 data: options.previewParserVar+'='+encodeURIComponent($$.val()),
505                                                 success: function(data) {
506                                                         writeInPreview( localize(data, 1) ); 
507                                                 }
508                                         });
509                                 } else {
510                                         if (!template) {
511                                                 $.ajax({
512                                                         url: options.previewTemplatePath,
513                                                         dataType: 'text',
514                                                         global: false,
515                                                         success: function(data) {
516                                                                 writeInPreview( localize(data, 1).replace(/<!-- content -->/g, $$.val()) );
517                                                         }
518                                                 });
519                                         }
520                                 }
521                                 return false;
522                         }
523                         
524                         function writeInPreview(data) {
525                                 if (previewWindow.document) {                   
526                                         try {
527                                                 sp = previewWindow.document.documentElement.scrollTop
528                                         } catch(e) {
529                                                 sp = 0;
530                                         }       
531                                         previewWindow.document.open();
532                                         previewWindow.document.write(data);
533                                         previewWindow.document.close();
534                                         previewWindow.document.documentElement.scrollTop = sp;
535                                 }
536                         }
537                         
538                         // set keys pressed
539                         function keyPressed(e) { 
540                                 shiftKey = e.shiftKey;
541                                 altKey = e.altKey;
542                                 ctrlKey = (!(e.altKey && e.ctrlKey)) ? (e.ctrlKey || e.metaKey) : false;
543
544                                 if (e.type === 'keydown') {
545                                         if (ctrlKey === true) {
546                                                 li = $('a[accesskey="'+String.fromCharCode(e.keyCode)+'"]', header).parent('li');
547                                                 if (li.length !== 0) {
548                                                         ctrlKey = false;
549                                                         setTimeout(function() {
550                                                                 li.triggerHandler('mouseup');
551                                                         },1);
552                                                         return false;
553                                                 }
554                                         }
555                                         if (e.keyCode === 13 || e.keyCode === 10) { // Enter key
556                                                 if (ctrlKey === true) {  // Enter + Ctrl
557                                                         ctrlKey = false;
558                                                         markup(options.onCtrlEnter);
559                                                         return options.onCtrlEnter.keepDefault;
560                                                 } else if (shiftKey === true) { // Enter + Shift
561                                                         shiftKey = false;
562                                                         markup(options.onShiftEnter);
563                                                         return options.onShiftEnter.keepDefault;
564                                                 } else { // only Enter
565                                                         markup(options.onEnter);
566                                                         return options.onEnter.keepDefault;
567                                                 }
568                                         }
569                                         if (e.keyCode === 9) { // Tab key
570                                                 if (shiftKey == true || ctrlKey == true || altKey == true) {
571                                                         return false; 
572                                                 }
573                                                 if (caretOffset !== -1) {
574                                                         get();
575                                                         caretOffset = $$.val().length - caretOffset;
576                                                         set(caretOffset, 0);
577                                                         caretOffset = -1;
578                                                         return false;
579                                                 } else {
580                                                         markup(options.onTab);
581                                                         return options.onTab.keepDefault;
582                                                 }
583                                         }
584                                 }
585                         }
586
587                         init();
588                 });
589         };
590
591         $.fn.markItUpRemove = function() {
592                 return this.each(function() {
593                                 var $$ = $(this).unbind().removeClass('markItUpEditor');
594                                 $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$);
595                         }
596                 );
597         };
598
599         $.markItUp = function(settings) {
600                 var options = { target:false };
601                 $.extend(options, settings);
602                 if (options.target) {
603                         return $(options.target).each(function() {
604                                 $(this).focus();
605                                 $(this).trigger('insertion', [options]);
606                         });
607                 } else {
608                         $('textarea').trigger('insertion', [options]);
609                 }
610         };
611 })(jQuery);