7567f50dfa72b9e46d302f6d63884ec8c2d60042
1 // ----------------------------------------------------------------------------
2 // markItUp! Universal MarkUp Engine, JQuery plugin
3 // v 1.1.14 ( c014800b - 02/06/2014 )
4 // Dual licensed under the MIT and GPL licenses.
5 // ----------------------------------------------------------------------------
6 // Copyright (C) 2007-2012 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:
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
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
26 // ----------------------------------------------------------------------------
28 $.fn
.markItUp = function(settings
, extraSettings
) {
29 var method
, params
, options
, ctrlKey
, shiftKey
, altKey
; ctrlKey
= shiftKey
= altKey
= false;
31 if (typeof settings
== 'string') {
33 params
= extraSettings
;
39 previewHandler
: false,
40 previewInWindow
: '', // 'width=800, height=600, resizable=yes, scrollbars=yes'
42 previewAutoRefresh
: true,
43 previewPosition
: 'after',
44 previewTemplatePath
: '~/templates/preview.html',
46 previewParserPath
: '',
47 previewParserVar
: 'data',
48 previewParserAjaxType
: 'POST',
56 markupSet
: [ { /* set */ } ]
58 $.extend(options
, settings
, extraSettings
);
60 // compute markItUp! path
62 $('script').each(function(a
, tag
) {
63 miuScript
= $(tag
).get(0).src
.match(/(.*)jquery\.markitup(\.pack)?\.js$/);
64 if (miuScript
!== null) {
65 options
.root
= miuScript
[1];
70 // Quick patch to keep compatibility with jQuery 1.9
71 var uaMatch = function(ua
) {
72 ua
= ua
.toLowerCase();
74 var match
= /(chrome)[ \/]([\w.]+)/.exec(ua
) ||
75 /(webkit)[ \/]([\w.]+)/.exec(ua
) ||
76 /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua
) ||
77 /(msie) ([\w.]+)/.exec(ua
) ||
78 ua
.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua
) ||
82 browser
: match
[ 1 ] || "",
83 version
: match
[ 2 ] || "0"
86 var matched
= uaMatch( navigator
.userAgent
);
89 if (matched
.browser
) {
90 browser
[matched
.browser
] = true;
91 browser
.version
= matched
.version
;
94 browser
.webkit
= true;
95 } else if (browser
.webkit
) {
96 browser
.safari
= true;
99 return this.each(function() {
100 var $$, textarea
, levels
, scrollPosition
, caretPosition
, caretOffset
,
101 clicked
, hash
, header
, footer
, previewWindow
, template
, iFrame
, abort
;
106 scrollPosition
= caretPosition
= 0;
109 options
.previewParserPath
= localize(options
.previewParserPath
);
110 options
.previewTemplatePath
= localize(options
.previewTemplatePath
);
121 $.error('Method ' + method
+ ' does not exist on jQuery.markItUp');
126 // apply the computed path to ~/
127 function localize(data
, inText
) {
129 return data
.replace(/("|')~\//g, "$1"+options
.root
);
131 return data
.replace(/^~\//, options
.root
);
134 // init and build editor
136 id
= ''; nameSpace
= '';
138 id
= 'id="'+options
.id
+'"';
139 } else if ($$.attr("id")) {
140 id
= 'id="markItUp'+($$.attr("id").substr(0, 1).toUpperCase())+($$.attr("id").substr(1))+'"';
143 if (options
.nameSpace
) {
144 nameSpace
= 'class="'+options
.nameSpace
+'"';
146 $$.wrap('<div '+nameSpace
+'></div>');
147 $$.wrap('<div '+id
+' class="markItUp"></div>');
148 $$.wrap('<div class="markItUpContainer"></div>');
149 $$.addClass("markItUpEditor");
151 // add the header before the textarea
152 header
= $('<div class="markItUpHeader"></div>').insertBefore($$);
153 $(dropMenus(options
.markupSet
)).appendTo(header
);
155 // add the footer after the textarea
156 footer
= $('<div class="markItUpFooter"></div>').insertAfter($$);
158 // add the resize handle after textarea
159 if (options
.resizeHandle
=== true && browser
.safari
!== true) {
160 resizeHandle
= $('<div class="markItUpResizeHandle"></div>')
162 .bind("mousedown.markItUp", function(e
) {
163 var h
= $$.height(), y
= e
.clientY
, mouseMove
, mouseUp
;
164 mouseMove = function(e
) {
165 $$.css("height", Math
.max(20, e
.clientY
+h
-y
)+"px");
168 mouseUp = function(e
) {
169 $("html").unbind("mousemove.markItUp", mouseMove
).unbind("mouseup.markItUp", mouseUp
);
172 $("html").bind("mousemove.markItUp", mouseMove
).bind("mouseup.markItUp", mouseUp
);
174 footer
.append(resizeHandle
);
178 $$.bind('keydown.markItUp', keyPressed
).bind('keyup', keyPressed
);
180 // bind an event to catch external calls
181 $$.bind("insertion.markItUp", function(e
, settings
) {
182 if (settings
.target
!== false) {
185 if (textarea
=== $.markItUp
.focused
) {
190 // remember the last focus
191 $$.bind('focus.markItUp', function() {
192 $.markItUp
.focused
= this;
195 if (options
.previewInElement
) {
200 // recursively build header with dropMenus from markupset
201 function dropMenus(markupSet
) {
202 var ul
= $('<ul></ul>'), i
= 0;
203 $('li:hover > ul', ul
).css('display', 'block');
204 $.each(markupSet
, function() {
205 var button
= this, t
= '', title
, li
, j
;
206 button
.title
? title
= (button
.key
) ? (button
.title
||'')+' [Ctrl+'+button
.key
+']' : (button
.title
||'') : title
= (button
.key
) ? (button
.name
||'')+' [Ctrl+'+button
.key
+']' : (button
.name
||'');
207 key
= (button
.key
) ? 'accesskey="'+button
.key
+'"' : '';
208 if (button
.separator
) {
209 li
= $('<li class="markItUpSeparator">'+(button
.separator
||'')+'</li>').appendTo(ul
);
212 for (j
= levels
.length
-1; j
>= 0; j
--) {
215 li
= $('<li class="markItUpButton markItUpButton'+t
+(i
)+' '+(button
.className
||'')+'"><a href="#" '+key
+' title="'+title
+'">'+(button
.name
||'')+'</a></li>')
216 .bind("contextmenu.markItUp", function() { // prevent contextmenu on mac and allow ctrl+click
218 }).bind('click.markItUp', function(e
) {
220 }).bind("focusin.markItUp", function(){
222 }).bind('mouseup', function(e
) {
224 eval(button
.call
)(e
); // Pass the mouseup event to custom delegate
226 setTimeout(function() { markup(button
) },1);
228 }).bind('mouseenter.markItUp', function() {
229 $('> ul', this).show();
230 $(document
).one('click', function() { // close dropmenu if click outside
231 $('ul ul', header
).hide();
234 }).bind('mouseleave.markItUp', function() {
235 $('> ul', this).hide();
237 if (button
.dropMenu
) {
239 $(li
).addClass('markItUpDropMenu').append(dropMenus(button
.dropMenu
));
248 function magicMarkups(string
) {
250 string
= string
.toString();
251 string
= string
.replace(/\(\!\(([\s\S]*?)\)\!\)/g,
253 var b
= a
.split('|!|');
254 if (altKey
=== true) {
255 return (b
[1] !== undefined) ? b
[1] : b
[0];
257 return (b
[1] === undefined) ? "" : b
[0];
261 // [![prompt]!], [![prompt:!:value]!]
262 string
= string
.replace(/\[\!\[([\s\S]*?)\]\!\]/g,
264 var b
= a
.split(':!:');
265 if (abort
=== true) {
268 value
= prompt(b
[0], (b
[1]) ? b
[1] : '');
269 if (value
=== null) {
281 function prepare(action
) {
282 if ($.isFunction(action
)) {
283 action
= action(hash
);
285 return magicMarkups(action
);
288 // build block to insert
289 function build(string
) {
290 var openWith
= prepare(clicked
.openWith
);
291 var placeHolder
= prepare(clicked
.placeHolder
);
292 var replaceWith
= prepare(clicked
.replaceWith
);
293 var closeWith
= prepare(clicked
.closeWith
);
294 var openBlockWith
= prepare(clicked
.openBlockWith
);
295 var closeBlockWith
= prepare(clicked
.closeBlockWith
);
296 var multiline
= clicked
.multiline
;
298 if (replaceWith
!== "") {
299 block
= openWith
+ replaceWith
+ closeWith
;
300 } else if (selection
=== '' && placeHolder
!== '') {
301 block
= openWith
+ placeHolder
+ closeWith
;
303 string
= string
|| selection
;
305 var lines
= [string
], blocks
= [];
307 if (multiline
=== true) {
308 lines
= string
.split(/\r?\n/);
311 for (var l
= 0; l
< lines
.length
; l
++) {
314 if (trailingSpaces
= line
.match(/ *$/)) {
315 blocks
.push(openWith
+ line
.replace(/ *$/g
, '') + closeWith
+ trailingSpaces
);
317 blocks
.push(openWith
+ line
+ closeWith
);
321 block
= blocks
.join("\n");
324 block
= openBlockWith
+ block
+ closeBlockWith
;
326 return { block
:block
,
327 openBlockWith
:openBlockWith
,
329 replaceWith
:replaceWith
,
330 placeHolder
:placeHolder
,
332 closeBlockWith
:closeBlockWith
336 // define markup to insert
337 function markup(button
) {
339 hash
= clicked
= button
;
341 $.extend(hash
, { line
:"",
344 selection
:(selection
||''),
345 caretPosition
:caretPosition
,
351 // callbacks before insertion
352 prepare(options
.beforeInsert
);
353 prepare(clicked
.beforeInsert
);
354 if ((ctrlKey
=== true && shiftKey
=== true) || button
.multiline
=== true) {
355 prepare(clicked
.beforeMultiInsert
);
357 $.extend(hash
, { line
:1 });
359 if ((ctrlKey
=== true && shiftKey
=== true)) {
360 lines
= selection
.split(/\r?\n/);
361 for (j
= 0, n
= lines
.length
, i
= 0; i
< n
; i
++) {
362 if ($.trim(lines
[i
]) !== '') {
363 $.extend(hash
, { line
:++j
, selection
:lines
[i
] } );
364 lines
[i
] = build(lines
[i
]).block
;
370 string
= { block
:lines
.join('\n')};
371 start
= caretPosition
;
372 len
= string
.block
.length
+ ((browser
.opera
) ? n
-1 : 0);
373 } else if (ctrlKey
=== true) {
374 string
= build(selection
);
375 start
= caretPosition
+ string
.openWith
.length
;
376 len
= string
.block
.length
- string
.openWith
.length
- string
.closeWith
.length
;
377 len
= len
- (string
.block
.match(/ $/) ? 1 : 0);
378 len
-= fixIeBug(string
.block
);
379 } else if (shiftKey
=== true) {
380 string
= build(selection
);
381 start
= caretPosition
;
382 len
= string
.block
.length
;
383 len
-= fixIeBug(string
.block
);
385 string
= build(selection
);
386 start
= caretPosition
+ string
.block
.length
;
388 start
-= fixIeBug(string
.block
);
390 if ((selection
=== '' && string
.replaceWith
=== '')) {
391 caretOffset
+= fixOperaBug(string
.block
);
393 start
= caretPosition
+ string
.openBlockWith
.length
+ string
.openWith
.length
;
394 len
= string
.block
.length
- string
.openBlockWith
.length
- string
.openWith
.length
- string
.closeWith
.length
- string
.closeBlockWith
.length
;
396 caretOffset
= $$.val().substring(caretPosition
, $$.val().length
).length
;
397 caretOffset
-= fixOperaBug($$.val().substring(0, caretPosition
));
399 $.extend(hash
, { caretPosition
:caretPosition
, scrollPosition
:scrollPosition
} );
401 if (string
.block
!== selection
&& abort
=== false) {
402 insert(string
.block
);
409 $.extend(hash
, { line
:'', selection
:selection
});
411 // callbacks after insertion
412 if ((ctrlKey
=== true && shiftKey
=== true) || button
.multiline
=== true) {
413 prepare(clicked
.afterMultiInsert
);
415 prepare(clicked
.afterInsert
);
416 prepare(options
.afterInsert
);
418 // refresh preview if opened
419 if (previewWindow
&& options
.previewAutoRefresh
) {
424 shiftKey
= altKey
= ctrlKey
= abort
= false;
427 // Substract linefeed in Opera
428 function fixOperaBug(string
) {
430 return string
.length
- string
.replace(/\n*/g, '').length
;
434 // Substract linefeed in IE
435 function fixIeBug(string
) {
437 return string
.length
- string
.replace(/\r*/g, '').length
;
443 function insert(block
) {
444 if (document
.selection
) {
445 var newSelection
= document
.selection
.createRange();
446 newSelection
.text
= block
;
448 textarea
.value
= textarea
.value
.substring(0, caretPosition
) + block
+ textarea
.value
.substring(caretPosition
+ selection
.length
, textarea
.value
.length
);
453 function set(start
, len
) {
454 if (textarea
.createTextRange
){
455 // quick fix to make it work on Opera 9.5
456 if (browser
.opera
&& browser
.version
>= 9.5 && len
== 0) {
459 range
= textarea
.createTextRange();
460 range
.collapse(true);
461 range
.moveStart('character', start
);
462 range
.moveEnd('character', len
);
464 } else if (textarea
.setSelectionRange
){
465 textarea
.setSelectionRange(start
, start
+ len
);
467 textarea
.scrollTop
= scrollPosition
;
475 scrollPosition
= textarea
.scrollTop
;
476 if (document
.selection
) {
477 selection
= document
.selection
.createRange().text
;
478 if (browser
.msie
) { // ie
479 var range
= document
.selection
.createRange(), rangeCopy
= range
.duplicate();
480 rangeCopy
.moveToElementText(textarea
);
482 while(rangeCopy
.inRange(range
)) {
483 rangeCopy
.moveStart('character');
487 caretPosition
= textarea
.selectionStart
;
489 } else { // gecko & webkit
490 caretPosition
= textarea
.selectionStart
;
492 selection
= textarea
.value
.substring(caretPosition
, textarea
.selectionEnd
);
497 // open preview window
499 if (typeof options
.previewHandler
=== 'function') {
500 previewWindow
= true;
501 } else if (options
.previewInElement
) {
502 previewWindow
= $(options
.previewInElement
);
503 } else if (!previewWindow
|| previewWindow
.closed
) {
504 if (options
.previewInWindow
) {
505 previewWindow
= window
.open('', 'preview', options
.previewInWindow
);
506 $(window
).unload(function() {
507 previewWindow
.close();
510 iFrame
= $('<iframe class="markItUpPreviewFrame"></iframe>');
511 if (options
.previewPosition
== 'after') {
512 iFrame
.insertAfter(footer
);
514 iFrame
.insertBefore(header
);
516 previewWindow
= iFrame
[iFrame
.length
- 1].contentWindow
|| frame
[iFrame
.length
- 1];
518 } else if (altKey
=== true) {
522 previewWindow
.close();
524 previewWindow
= iFrame
= false;
526 if (!options
.previewAutoRefresh
) {
529 if (options
.previewInWindow
) {
530 previewWindow
.focus();
534 // refresh Preview window
535 function refreshPreview() {
539 function renderPreview() {
541 if (options
.previewHandler
&& typeof options
.previewHandler
=== 'function') {
542 options
.previewHandler( $$.val() );
543 } else if (options
.previewParser
&& typeof options
.previewParser
=== 'function') {
544 var data
= options
.previewParser( $$.val() );
545 writeInPreview(localize(data
, 1) );
546 } else if (options
.previewParserPath
!== '') {
548 type
: options
.previewParserAjaxType
,
551 url
: options
.previewParserPath
,
552 data
: options
.previewParserVar
+'='+encodeURIComponent($$.val()),
553 success: function(data
) {
554 writeInPreview( localize(data
, 1) );
560 url
: options
.previewTemplatePath
,
563 success: function(data
) {
564 writeInPreview( localize(data
, 1).replace(/<!-- content -->/g, $$.val()) );
572 function writeInPreview(data
) {
573 if (options
.previewInElement
) {
574 $(options
.previewInElement
).html(data
);
575 } else if (previewWindow
&& previewWindow
.document
) {
577 sp
= previewWindow
.document
.documentElement
.scrollTop
581 previewWindow
.document
.open();
582 previewWindow
.document
.write(data
);
583 previewWindow
.document
.close();
584 previewWindow
.document
.documentElement
.scrollTop
= sp
;
589 function keyPressed(e
) {
590 shiftKey
= e
.shiftKey
;
592 ctrlKey
= (!(e
.altKey
&& e
.ctrlKey
)) ? (e
.ctrlKey
|| e
.metaKey
) : false;
594 if (e
.type
=== 'keydown') {
595 if (ctrlKey
=== true) {
596 li
= $('a[accesskey="'+((e
.keyCode
== 13) ? '\\n' : String
.fromCharCode(e
.keyCode
))+'"]', header
).parent('li');
597 if (li
.length
!== 0) {
599 setTimeout(function() {
600 li
.triggerHandler('mouseup');
605 if (e
.keyCode
=== 13 || e
.keyCode
=== 10) { // Enter key
606 if (ctrlKey
=== true) { // Enter + Ctrl
608 markup(options
.onCtrlEnter
);
609 return options
.onCtrlEnter
.keepDefault
;
610 } else if (shiftKey
=== true) { // Enter + Shift
612 markup(options
.onShiftEnter
);
613 return options
.onShiftEnter
.keepDefault
;
614 } else { // only Enter
615 markup(options
.onEnter
);
616 return options
.onEnter
.keepDefault
;
619 if (e
.keyCode
=== 9) { // Tab key
620 if (shiftKey
== true || ctrlKey
== true || altKey
== true) {
623 if (caretOffset
!== -1) {
625 caretOffset
= $$.val().length
- caretOffset
;
630 markup(options
.onTab
);
631 return options
.onTab
.keepDefault
;
638 $$.unbind(".markItUp").removeClass('markItUpEditor');
639 $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$);
641 var relativeRef
= $$.parent('div').parent('div.markItUp').parent('div');
642 if (relativeRef
.length
) {
643 relativeRef
.replaceWith($$);
646 $$.data('markItUp', null);
653 $.fn
.markItUpRemove = function() {
654 return this.each(function() {
655 $(this).markItUp('remove');
660 $.markItUp = function(settings
) {
661 var options
= { target
:false };
662 $.extend(options
, settings
);
663 if (options
.target
) {
664 return $(options
.target
).each(function() {
666 $(this).trigger('insertion', [options
]);
669 $('textarea').trigger('insertion', [options
]);