Mercurial > public > sg101
comparison media/js/markitup/jquery.markitup.js @ 1:dbd703f7d63a
Initial import of sg101 stuff from private repository.
author | gremmie |
---|---|
date | Mon, 06 Apr 2009 02:43:12 +0000 |
parents | |
children | afb65fa947f1 |
comparison
equal
deleted
inserted
replaced
0:900ba3c7b765 | 1:dbd703f7d63a |
---|---|
1 // ---------------------------------------------------------------------------- | |
2 // markItUp! Universal MarkUp Engine, JQuery plugin | |
3 // v 1.1.5 | |
4 // Dual licensed under the MIT and GPL licenses. | |
5 // ---------------------------------------------------------------------------- | |
6 // Copyright (C) 2007-2008 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 }).mouseup(function() { | |
166 if (button.call) { | |
167 eval(button.call)(); | |
168 } | |
169 markup(button); | |
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 | |
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 if (iFrame) { | |
428 iFrame.remove(); | |
429 } | |
430 previewWindow.close(); | |
431 previewWindow = iFrame = false; | |
432 } | |
433 if (!options.previewAutoRefresh) { | |
434 refreshPreview(); | |
435 } | |
436 } | |
437 | |
438 // refresh Preview window | |
439 function refreshPreview() { | |
440 if (previewWindow.document) { | |
441 try { | |
442 sp = previewWindow.document.documentElement.scrollTop | |
443 } catch(e) { | |
444 sp = 0; | |
445 } | |
446 previewWindow.document.open(); | |
447 previewWindow.document.write(renderPreview()); | |
448 previewWindow.document.close(); | |
449 previewWindow.document.documentElement.scrollTop = sp; | |
450 } | |
451 if (options.previewInWindow) { | |
452 previewWindow.focus(); | |
453 } | |
454 } | |
455 | |
456 function renderPreview() { | |
457 if (options.previewParserPath !== '') { | |
458 $.ajax( { | |
459 type: 'POST', | |
460 async: false, | |
461 url: options.previewParserPath, | |
462 data: options.previewParserVar+'='+encodeURIComponent($$.val()), | |
463 success: function(data) { | |
464 phtml = localize(data, 1); | |
465 } | |
466 } ); | |
467 } else { | |
468 if (!template) { | |
469 $.ajax( { | |
470 async: false, | |
471 url: options.previewTemplatePath, | |
472 success: function(data) { | |
473 template = localize(data, 1); | |
474 } | |
475 } ); | |
476 } | |
477 phtml = template.replace(/<!-- content -->/g, $$.val()); | |
478 } | |
479 return phtml; | |
480 } | |
481 | |
482 // set keys pressed | |
483 function keyPressed(e) { | |
484 shiftKey = e.shiftKey; | |
485 altKey = e.altKey; | |
486 ctrlKey = (!(e.altKey && e.ctrlKey)) ? e.ctrlKey : false; | |
487 | |
488 if (e.type === 'keydown') { | |
489 if (ctrlKey === true) { | |
490 li = $("a[accesskey="+String.fromCharCode(e.keyCode)+"]", header).parent('li'); | |
491 if (li.length !== 0) { | |
492 ctrlKey = false; | |
493 li.triggerHandler('mouseup'); | |
494 return false; | |
495 } | |
496 } | |
497 if (e.keyCode === 13 || e.keyCode === 10) { // Enter key | |
498 if (ctrlKey === true) { // Enter + Ctrl | |
499 ctrlKey = false; | |
500 markup(options.onCtrlEnter); | |
501 return options.onCtrlEnter.keepDefault; | |
502 } else if (shiftKey === true) { // Enter + Shift | |
503 shiftKey = false; | |
504 markup(options.onShiftEnter); | |
505 return options.onShiftEnter.keepDefault; | |
506 } else { // only Enter | |
507 markup(options.onEnter); | |
508 return options.onEnter.keepDefault; | |
509 } | |
510 } | |
511 if (e.keyCode === 9) { // Tab key | |
512 if (shiftKey == true || ctrlKey == true || altKey == true) { // Thx Dr Floob. | |
513 return false; | |
514 } | |
515 if (caretOffset !== -1) { | |
516 get(); | |
517 caretOffset = $$.val().length - caretOffset; | |
518 set(caretOffset, 0); | |
519 caretOffset = -1; | |
520 return false; | |
521 } else { | |
522 markup(options.onTab); | |
523 return options.onTab.keepDefault; | |
524 } | |
525 } | |
526 } | |
527 } | |
528 | |
529 init(); | |
530 }); | |
531 }; | |
532 | |
533 $.fn.markItUpRemove = function() { | |
534 return this.each(function() { | |
535 $$ = $(this).unbind().removeClass('markItUpEditor'); | |
536 $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$); | |
537 } | |
538 ); | |
539 }; | |
540 | |
541 $.markItUp = function(settings) { | |
542 var options = { target:false }; | |
543 $.extend(options, settings); | |
544 if (options.target) { | |
545 return $(options.target).each(function() { | |
546 $(this).focus(); | |
547 $(this).trigger('insertion', [options]); | |
548 }); | |
549 } else { | |
550 $('textarea').trigger('insertion', [options]); | |
551 } | |
552 }; | |
553 })(jQuery); |