final move of files
[web.mtrack] / js / jquery.markitup.js
1 // ----------------------------------------------------------------------------
2 // markItUp! Universal MarkUp Engine, JQuery plugin
3 // v 1.1.7
4 // Dual licensed under the MIT and GPL licenses.
5 // ----------------------------------------------------------------------------
6 // Copyright (C) 2007-2010 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 (function($) {
28         $.fn.markItUp = function(settings, extraSettings) {
29                 var options, ctrlKey, shiftKey, altKey;
30                 ctrlKey = shiftKey = altKey = false;
31
32                 options = {     id:                                             '',
33                                         nameSpace:                              '',
34                                         root:                                   '',
35                                         previewInWindow:                '', // 'width=800, height=600, resizable=yes, scrollbars=yes'
36                                         previewAutoRefresh:             true,
37                                         previewPosition:                'after',
38                                         previewTemplatePath:    '~/templates/preview.html',
39                                         previewParserPath:              '',
40                                         previewParserVar:               'data',
41                                         resizeHandle:                   true,
42                                         beforeInsert:                   '',
43                                         afterInsert:                    '',
44                                         onEnter:                                {},
45                                         onShiftEnter:                   {},
46                                         onCtrlEnter:                    {},
47                                         onTab:                                  {},
48                                         markupSet:                      [       { /* set */ } ]
49                                 };
50                 $.extend(options, settings, extraSettings);
51
52                 // compute markItUp! path
53                 if (!options.root) {
54                         $('script').each(function(a, tag) {
55                                 miuScript = $(tag).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/);
56                                 if (miuScript !== null) {
57                                         options.root = miuScript[1];
58                                 }
59                         });
60                 }
61
62                 return this.each(function() {
63                         var $$, textarea, levels, scrollPosition, caretPosition, caretOffset,
64                                 clicked, hash, header, footer, previewWindow, template, iFrame, abort;
65                         $$ = $(this);
66                         textarea = this;
67                         levels = [];
68                         abort = false;
69                         scrollPosition = caretPosition = 0;
70                         caretOffset = -1;
71
72                         options.previewParserPath = localize(options.previewParserPath);
73                         options.previewTemplatePath = localize(options.previewTemplatePath);
74
75                         // apply the computed path to ~/
76                         function localize(data, inText) {
77                                 if (inText) {
78                                         return  data.replace(/("|')~\//g, "$1"+options.root);
79                                 }
80                                 return  data.replace(/^~\//, options.root);
81                         }
82
83                         // init and build editor
84                         function init() {
85                                 id = ''; nameSpace = '';
86                                 if (options.id) {
87                                         id = 'id="'+options.id+'"';
88                                 } else if ($$.attr("id")) {
89                                         id = 'id="markItUp'+($$.attr("id").substr(0, 1).toUpperCase())+($$.attr("id").substr(1))+'"';
90
91                                 }
92                                 if (options.nameSpace) {
93                                         nameSpace = 'class="'+options.nameSpace+'"';
94                                 }
95                                 $$.wrap('<div '+nameSpace+'></div>');
96                                 $$.wrap('<div '+id+' class="markItUp"></div>');
97                                 $$.wrap('<div class="markItUpContainer"></div>');
98                                 $$.addClass("markItUpEditor");
99
100                                 // add the header before the textarea
101                                 header = $('<div class="markItUpHeader"></div>').insertBefore($$);
102                                 $(dropMenus(options.markupSet)).appendTo(header);
103
104                                 // add the footer after the textarea
105                                 footer = $('<div class="markItUpFooter"></div>').insertAfter($$);
106
107                                 // add the resize handle after textarea
108                                 if (options.resizeHandle === true && $.browser.safari !== true) {
109                                         resizeHandle = $('<div class="markItUpResizeHandle"></div>')
110                                                 .insertAfter($$)
111                                                 .bind("mousedown", function(e) {
112                                                         var h = $$.height(), y = e.clientY, mouseMove, mouseUp;
113                                                         mouseMove = function(e) {
114                                                                 $$.css("height", Math.max(20, e.clientY+h-y)+"px");
115                                                                 return false;
116                                                         };
117                                                         mouseUp = function(e) {
118                                                                 $("html").unbind("mousemove", mouseMove).unbind("mouseup", mouseUp);
119                                                                 return false;
120                                                         };
121                                                         $("html").bind("mousemove", mouseMove).bind("mouseup", mouseUp);
122                                         });
123                                         footer.append(resizeHandle);
124                                 }
125
126                                 // listen key events
127                                 $$.keydown(keyPressed).keyup(keyPressed);
128                                 
129                                 // bind an event to catch external calls
130                                 $$.bind("insertion", function(e, settings) {
131                                         if (settings.target !== false) {
132                                                 get();
133                                         }
134                                         if (textarea === $.markItUp.focused) {
135                                                 markup(settings);
136                                         }
137                                 });
138
139                                 // remember the last focus
140                                 $$.focus(function() {
141                                         $.markItUp.focused = this;
142                                 });
143                         }
144
145                         // recursively build header with dropMenus from markupset
146                         function dropMenus(markupSet) {
147                                 var ul = $('<ul></ul>'), i = 0;
148                                 $('li:hover > ul', ul).css('display', 'block');
149                                 $.each(markupSet, function() {
150                                         var button = this, t = '', title, li, j;
151                                         title = (button.key) ? (button.name||'')+' [Ctrl+'+button.key+']' : (button.name||'');
152                                         key   = (button.key) ? 'accesskey="'+button.key+'"' : '';
153                                         if (button.separator) {
154                                                 li = $('<li class="markItUpSeparator">'+(button.separator||'')+'</li>').appendTo(ul);
155                                         } else {
156                                                 i++;
157                                                 for (j = levels.length -1; j >= 0; j--) {
158                                                         t += levels[j]+"-";
159                                                 }
160                                                 li = $('<li class="markItUpButton markItUpButton'+t+(i)+' '+(button.className||'')+'"><a href="" '+key+' title="'+title+'">'+(button.name||'')+'</a></li>')
161                                                 .bind("contextmenu", function() { // prevent contextmenu on mac and allow ctrl+click
162                                                         return false;
163                                                 }).click(function() {
164                                                         return false;
165                                                 }).mousedown(function() {
166                                                         if (button.call) {
167                                                                 eval(button.call)();
168                                                         }
169                                                         setTimeout(function() { markup(button) },1);
170                                                         return false;
171                                                 }).hover(function() {
172                                                                 $('> ul', this).show();
173                                                                 $(document).one('click', function() { // close dropmenu if click outside
174                                                                                 $('ul ul', header).hide();
175                                                                         }
176                                                                 );
177                                                         }, function() {
178                                                                 $('> ul', this).hide();
179                                                         }
180                                                 ).appendTo(ul);
181                                                 if (button.dropMenu) {
182                                                         levels.push(i);
183                                                         $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu));
184                                                 }
185                                         }
186                                 }); 
187                                 levels.pop();
188                                 return ul;
189                         }
190
191                         // markItUp! markups
192                         function magicMarkups(string) {
193                                 if (string) {
194                                         string = string.toString();
195                                         string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g,
196                                                 function(x, a) {
197                                                         var b = a.split('|!|');
198                                                         if (altKey === true) {
199                                                                 return (b[1] !== undefined) ? b[1] : b[0];
200                                                         } else {
201                                                                 return (b[1] === undefined) ? "" : b[0];
202                                                         }
203                                                 }
204                                         );
205                                         // [![prompt]!], [![prompt:!:value]!]
206                                         string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g,
207                                                 function(x, a) {
208                                                         var b = a.split(':!:');
209                                                         if (abort === true) {
210                                                                 return false;
211                                                         }
212                                                         value = prompt(b[0], (b[1]) ? b[1] : '');
213                                                         if (value === null) {
214                                                                 abort = true;
215                                                         }
216                                                         return value;
217                                                 }
218                                         );
219                                         return string;
220                                 }
221                                 return "";
222                         }
223
224                         // prepare action
225                         function prepare(action) {
226                                 if ($.isFunction(action)) {
227                                         action = action(hash);
228                                 }
229                                 return magicMarkups(action);
230                         }
231
232                         // build block to insert
233                         function build(string) {
234                                 openWith        = prepare(clicked.openWith);
235                                 placeHolder = prepare(clicked.placeHolder);
236                                 replaceWith = prepare(clicked.replaceWith);
237                                 closeWith       = prepare(clicked.closeWith);
238                                 if (replaceWith !== "") {
239                                         block = openWith + replaceWith + closeWith;
240                                 } else if (selection === '' && placeHolder !== '') {
241                                         block = openWith + placeHolder + closeWith;
242                                 } else {
243                                         block = openWith + (string||selection) + closeWith;
244                                 }
245                                 return {        block:block, 
246                                                         openWith:openWith, 
247                                                         replaceWith:replaceWith, 
248                                                         placeHolder:placeHolder,
249                                                         closeWith:closeWith
250                                         };
251                         }
252
253                         // define markup to insert
254                         function markup(button) {
255                                 var len, j, n, i;
256                                 hash = clicked = button;
257                                 get();
258
259                                 $.extend(hash, {        line:"", 
260                                                                         root:options.root,
261                                                                         textarea:textarea, 
262                                                                         selection:(selection||''), 
263                                                                         caretPosition:caretPosition,
264                                                                         ctrlKey:ctrlKey, 
265                                                                         shiftKey:shiftKey, 
266                                                                         altKey:altKey
267                                                                 }
268                                                         );
269                                 // callbacks before insertion
270                                 prepare(options.beforeInsert);
271                                 prepare(clicked.beforeInsert);
272                                 if (ctrlKey === true && shiftKey === true) {
273                                         prepare(clicked.beforeMultiInsert);
274                                 }                       
275                                 $.extend(hash, { line:1 });
276                                 
277                                 if (ctrlKey === true && shiftKey === true) {
278                                         lines = selection.split(/\r?\n/);
279                                         for (j = 0, n = lines.length, i = 0; i < n; i++) {
280                                                 if ($.trim(lines[i]) !== '') {
281                                                         $.extend(hash, { line:++j, selection:lines[i] } );
282                                                         lines[i] = build(lines[i]).block;
283                                                 } else {
284                                                         lines[i] = "";
285                                                 }
286                                         }
287                                         string = { block:lines.join('\n')};
288                                         start = caretPosition;
289                                         len = string.block.length + (($.browser.opera) ? n : 0);
290                                 } else if (ctrlKey === true) {
291                                         string = build(selection);
292                                         start = caretPosition + string.openWith.length;
293                                         len = string.block.length - string.openWith.length - string.closeWith.length;
294                                         len -= fixIeBug(string.block);
295                                 } else if (shiftKey === true) {
296                                         string = build(selection);
297                                         start = caretPosition;
298                                         len = string.block.length;
299                                         len -= fixIeBug(string.block);
300                                 } else {
301                                         string = build(selection);
302                                         start = caretPosition + string.block.length ;
303                                         len = 0;
304                                         start -= fixIeBug(string.block);
305                                 }
306                                 if ((selection === '' && string.replaceWith === '')) {
307                                         caretOffset += fixOperaBug(string.block);
308                                         
309                                         start = caretPosition + string.openWith.length;
310                                         len = string.block.length - string.openWith.length - string.closeWith.length;
311
312                                         caretOffset = $$.val().substring(caretPosition,  $$.val().length).length;
313                                         caretOffset -= fixOperaBug($$.val().substring(0, caretPosition));
314                                 }
315                                 $.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } );
316
317                                 if (string.block !== selection && abort === false) {
318                                         insert(string.block);
319                                         set(start, len);
320                                 } else {
321                                         caretOffset = -1;
322                                 }
323                                 get();
324
325                                 $.extend(hash, { line:'', selection:selection });
326
327                                 // callbacks after insertion
328                                 if (ctrlKey === true && shiftKey === true) {
329                                         prepare(clicked.afterMultiInsert);
330                                 }
331                                 prepare(clicked.afterInsert);
332                                 prepare(options.afterInsert);
333
334                                 // refresh preview if opened
335                                 if (previewWindow && options.previewAutoRefresh) {
336                                         refreshPreview(); 
337                                 }
338                                                                                                                                                                                                         
339                                 // reinit keyevent
340                                 shiftKey = altKey = ctrlKey = abort = false;
341                         }
342
343                         // Substract linefeed in Opera
344                         function fixOperaBug(string) {
345                                 if ($.browser.opera) {
346                                         return string.length - string.replace(/\n*/g, '').length;
347                                 }
348                                 return 0;
349                         }
350                         // Substract linefeed in IE
351                         function fixIeBug(string) {
352                                 if ($.browser.msie) {
353                                         return string.length - string.replace(/\r*/g, '').length;
354                                 }
355                                 return 0;
356                         }
357                                 
358                         // add markup
359                         function insert(block) {        
360                                 if (document.selection) {
361                                         var newSelection = document.selection.createRange();
362                                         newSelection.text = block;
363                                 } else {
364                                         $$.val($$.val().substring(0, caretPosition)     + block + $$.val().substring(caretPosition + selection.length, $$.val().length));
365                                 }
366                         }
367
368                         // set a selection
369                         function set(start, len) {
370                                 if (textarea.createTextRange){
371                                         // quick fix to make it work on Opera 9.5
372                                         if ($.browser.opera && $.browser.version >= 9.5 && len == 0) {
373                                                 return false;
374                                         }
375                                         range = textarea.createTextRange();
376                                         range.collapse(true);
377                                         range.moveStart('character', start); 
378                                         range.moveEnd('character', len); 
379                                         range.select();
380                                 } else if (textarea.setSelectionRange ){
381                                         textarea.setSelectionRange(start, start + len);
382                                 }
383                                 textarea.scrollTop = scrollPosition;
384                                 textarea.focus();
385                         }
386
387                         // get the selection
388                         function get() {
389                                 textarea.focus();
390
391                                 scrollPosition = textarea.scrollTop;
392                                 if (document.selection) {
393                                         selection = document.selection.createRange().text;
394                                         if ($.browser.msie) { // ie
395                                                 var range = document.selection.createRange(), rangeCopy = range.duplicate();
396                                                 rangeCopy.moveToElementText(textarea);
397                                                 caretPosition = -1;
398                                                 while(rangeCopy.inRange(range)) { // fix most of the ie bugs with linefeeds...
399                                                         rangeCopy.moveStart('character');
400                                                         caretPosition ++;
401                                                 }
402                                         } else { // opera
403                                                 caretPosition = textarea.selectionStart;
404                                         }
405                                 } else { // gecko & webkit
406                                         caretPosition = textarea.selectionStart;
407                                         selection = $$.val().substring(caretPosition, textarea.selectionEnd);
408                                 } 
409                                 return selection;
410                         }
411
412                         // open preview window
413                         function preview() {
414                                 if (!previewWindow || previewWindow.closed) {
415                                         if (options.previewInWindow) {
416                                                 previewWindow = window.open('', 'preview', options.previewInWindow);
417                                         } else {
418                                                 iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>');
419                                                 if (options.previewPosition == 'after') {
420                                                         iFrame.insertAfter(footer);
421                                                 } else {
422                                                         iFrame.insertBefore(header);
423                                                 }       
424                                                 previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1];
425                                         }
426                                 } else if (altKey === true) {
427                                         // Thx Stephen M. Redd for the IE8 fix
428                                         if (iFrame) {
429                                                 iFrame.remove();
430                                         } else {
431                                                 previewWindow.close();
432                                         }
433                                         previewWindow = iFrame = false;
434                                 }
435                                 if (!options.previewAutoRefresh) {
436                                         refreshPreview(); 
437                                 }
438                         }
439
440                         // refresh Preview window
441                         function refreshPreview() {
442                                 renderPreview();
443                         }
444
445                         function renderPreview() {              
446                                 var phtml;
447                                 if (options.previewParserPath !== '') {
448                                         $.ajax( {
449                                                 type: 'POST',
450                                                 url: options.previewParserPath,
451                                                 data: options.previewParserVar+'='+encodeURIComponent($$.val()),
452                                                 success: function(data) {
453                                                         writeInPreview( localize(data, 1) ); 
454                                                 }
455                                         } );
456                                 } else {
457                                         if (!template) {
458                                                 $.ajax( {
459                                                         url: options.previewTemplatePath,
460                                                         success: function(data) {
461                                                                 writeInPreview( localize(data, 1).replace(/<!-- content -->/g, $$.val()) );
462                                                         }
463                                                 } );
464                                         }
465                                 }
466                                 return false;
467                         }
468                         
469                         function writeInPreview(data) {
470                                 if (previewWindow.document) {                   
471                                         try {
472                                                 sp = previewWindow.document.documentElement.scrollTop
473                                         } catch(e) {
474                                                 sp = 0;
475                                         }       
476                                         previewWindow.document.open();
477                                         previewWindow.document.write(data);
478                                         previewWindow.document.close();
479                                         previewWindow.document.documentElement.scrollTop = sp;
480                                 }
481                                 if (options.previewInWindow) {
482                                         previewWindow.focus();
483                                 }
484                         }
485                         
486                         // set keys pressed
487                         function keyPressed(e) { 
488                                 shiftKey = e.shiftKey;
489                                 altKey = e.altKey;
490                                 ctrlKey = (!(e.altKey && e.ctrlKey)) ? e.ctrlKey : false;
491
492                                 if (e.type === 'keydown') {
493                                         if (ctrlKey === true) {
494                                                 li = $("a[accesskey="+String.fromCharCode(e.keyCode)+"]", header).parent('li');
495                                                 if (li.length !== 0) {
496                                                         ctrlKey = false;
497                                                         setTimeout(function() {
498                                                                 li.triggerHandler('mousedown');
499                                                         },1);
500                                                         return false;
501                                                 }
502                                         }
503                                         if (e.keyCode === 13 || e.keyCode === 10) { // Enter key
504                                                 if (ctrlKey === true) {  // Enter + Ctrl
505                                                         ctrlKey = false;
506                                                         markup(options.onCtrlEnter);
507                                                         return options.onCtrlEnter.keepDefault;
508                                                 } else if (shiftKey === true) { // Enter + Shift
509                                                         shiftKey = false;
510                                                         markup(options.onShiftEnter);
511                                                         return options.onShiftEnter.keepDefault;
512                                                 } else { // only Enter
513                                                         markup(options.onEnter);
514                                                         return options.onEnter.keepDefault;
515                                                 }
516                                         }
517                                         if (e.keyCode === 9) { // Tab key
518                                                 if (shiftKey == true || ctrlKey == true || altKey == true) { // Thx Dr Floob.
519                                                         return false; 
520                                                 }
521                                                 if (caretOffset !== -1) {
522                                                         get();
523                                                         caretOffset = $$.val().length - caretOffset;
524                                                         set(caretOffset, 0);
525                                                         caretOffset = -1;
526                                                         return false;
527                                                 } else {
528                                                         markup(options.onTab);
529                                                         return options.onTab.keepDefault;
530                                                 }
531                                         }
532                                 }
533                         }
534
535                         init();
536                 });
537         };
538
539         $.fn.markItUpRemove = function() {
540                 return this.each(function() {
541                                 var $$ = $(this).unbind().removeClass('markItUpEditor');
542                                 $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$);
543                         }
544                 );
545         };
546
547         $.markItUp = function(settings) {
548                 var options = { target:false };
549                 $.extend(options, settings);
550                 if (options.target) {
551                         return $(options.target).each(function() {
552                                 $(this).focus();
553                                 $(this).trigger('insertion', [options]);
554                         });
555                 } else {
556                         $('textarea').trigger('insertion', [options]);
557                 }
558         };
559 })(jQuery);