Mercurial > public > sg101
comparison static/js/markitup/jquery.markitup.js @ 312:88b2b9cb8c1f
Fixing #142; cut over to the django.contrib.staticfiles app.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Thu, 27 Jan 2011 02:56:10 +0000 |
parents | |
children | c78c6e007e61 |
comparison
equal
deleted
inserted
replaced
311:b1c39788e511 | 312:88b2b9cb8c1f |
---|---|
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-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 }).focusin(function(){ | |
166 $$.focus(); | |
167 }).mousedown(function() { | |
168 if (button.call) { | |
169 eval(button.call)(); | |
170 } | |
171 setTimeout(function() { markup(button) },1); | |
172 return false; | |
173 }).hover(function() { | |
174 $('> ul', this).show(); | |
175 $(document).one('click', function() { // close dropmenu if click outside | |
176 $('ul ul', header).hide(); | |
177 } | |
178 ); | |
179 }, function() { | |
180 $('> ul', this).hide(); | |
181 } | |
182 ).appendTo(ul); | |
183 if (button.dropMenu) { | |
184 levels.push(i); | |
185 $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu)); | |
186 } | |
187 } | |
188 }); | |
189 levels.pop(); | |
190 return ul; | |
191 } | |
192 | |
193 // markItUp! markups | |
194 function magicMarkups(string) { | |
195 if (string) { | |
196 string = string.toString(); | |
197 string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g, | |
198 function(x, a) { | |
199 var b = a.split('|!|'); | |
200 if (altKey === true) { | |
201 return (b[1] !== undefined) ? b[1] : b[0]; | |
202 } else { | |
203 return (b[1] === undefined) ? "" : b[0]; | |
204 } | |
205 } | |
206 ); | |
207 // [![prompt]!], [![prompt:!:value]!] | |
208 string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g, | |
209 function(x, a) { | |
210 var b = a.split(':!:'); | |
211 if (abort === true) { | |
212 return false; | |
213 } | |
214 value = prompt(b[0], (b[1]) ? b[1] : ''); | |
215 if (value === null) { | |
216 abort = true; | |
217 } | |
218 return value; | |
219 } | |
220 ); | |
221 return string; | |
222 } | |
223 return ""; | |
224 } | |
225 | |
226 // prepare action | |
227 function prepare(action) { | |
228 if ($.isFunction(action)) { | |
229 action = action(hash); | |
230 } | |
231 return magicMarkups(action); | |
232 } | |
233 | |
234 // build block to insert | |
235 function build(string) { | |
236 openWith = prepare(clicked.openWith); | |
237 placeHolder = prepare(clicked.placeHolder); | |
238 replaceWith = prepare(clicked.replaceWith); | |
239 closeWith = prepare(clicked.closeWith); | |
240 if (replaceWith !== "") { | |
241 block = openWith + replaceWith + closeWith; | |
242 } else if (selection === '' && placeHolder !== '') { | |
243 block = openWith + placeHolder + closeWith; | |
244 } else { | |
245 block = openWith + (string||selection) + closeWith; | |
246 } | |
247 return { block:block, | |
248 openWith:openWith, | |
249 replaceWith:replaceWith, | |
250 placeHolder:placeHolder, | |
251 closeWith:closeWith | |
252 }; | |
253 } | |
254 | |
255 // define markup to insert | |
256 function markup(button) { | |
257 var len, j, n, i; | |
258 hash = clicked = button; | |
259 get(); | |
260 | |
261 $.extend(hash, { line:"", | |
262 root:options.root, | |
263 textarea:textarea, | |
264 selection:(selection||''), | |
265 caretPosition:caretPosition, | |
266 ctrlKey:ctrlKey, | |
267 shiftKey:shiftKey, | |
268 altKey:altKey | |
269 } | |
270 ); | |
271 // callbacks before insertion | |
272 prepare(options.beforeInsert); | |
273 prepare(clicked.beforeInsert); | |
274 if (ctrlKey === true && shiftKey === true) { | |
275 prepare(clicked.beforeMultiInsert); | |
276 } | |
277 $.extend(hash, { line:1 }); | |
278 | |
279 if (ctrlKey === true && shiftKey === true) { | |
280 lines = selection.split(/\r?\n/); | |
281 for (j = 0, n = lines.length, i = 0; i < n; i++) { | |
282 if ($.trim(lines[i]) !== '') { | |
283 $.extend(hash, { line:++j, selection:lines[i] } ); | |
284 lines[i] = build(lines[i]).block; | |
285 } else { | |
286 lines[i] = ""; | |
287 } | |
288 } | |
289 string = { block:lines.join('\n')}; | |
290 start = caretPosition; | |
291 len = string.block.length + (($.browser.opera) ? n-1 : 0); | |
292 } else if (ctrlKey === true) { | |
293 string = build(selection); | |
294 start = caretPosition + string.openWith.length; | |
295 len = string.block.length - string.openWith.length - string.closeWith.length; | |
296 len -= fixIeBug(string.block); | |
297 } else if (shiftKey === true) { | |
298 string = build(selection); | |
299 start = caretPosition; | |
300 len = string.block.length; | |
301 len -= fixIeBug(string.block); | |
302 } else { | |
303 string = build(selection); | |
304 start = caretPosition + string.block.length ; | |
305 len = 0; | |
306 start -= fixIeBug(string.block); | |
307 } | |
308 if ((selection === '' && string.replaceWith === '')) { | |
309 caretOffset += fixOperaBug(string.block); | |
310 | |
311 start = caretPosition + string.openWith.length; | |
312 len = string.block.length - string.openWith.length - string.closeWith.length; | |
313 | |
314 caretOffset = $$.val().substring(caretPosition, $$.val().length).length; | |
315 caretOffset -= fixOperaBug($$.val().substring(0, caretPosition)); | |
316 } | |
317 $.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } ); | |
318 | |
319 if (string.block !== selection && abort === false) { | |
320 insert(string.block); | |
321 set(start, len); | |
322 } else { | |
323 caretOffset = -1; | |
324 } | |
325 get(); | |
326 | |
327 $.extend(hash, { line:'', selection:selection }); | |
328 | |
329 // callbacks after insertion | |
330 if (ctrlKey === true && shiftKey === true) { | |
331 prepare(clicked.afterMultiInsert); | |
332 } | |
333 prepare(clicked.afterInsert); | |
334 prepare(options.afterInsert); | |
335 | |
336 // refresh preview if opened | |
337 if (previewWindow && options.previewAutoRefresh) { | |
338 refreshPreview(); | |
339 } | |
340 | |
341 // reinit keyevent | |
342 shiftKey = altKey = ctrlKey = abort = false; | |
343 } | |
344 | |
345 // Substract linefeed in Opera | |
346 function fixOperaBug(string) { | |
347 if ($.browser.opera) { | |
348 return string.length - string.replace(/\n*/g, '').length; | |
349 } | |
350 return 0; | |
351 } | |
352 // Substract linefeed in IE | |
353 function fixIeBug(string) { | |
354 if ($.browser.msie) { | |
355 return string.length - string.replace(/\r*/g, '').length; | |
356 } | |
357 return 0; | |
358 } | |
359 | |
360 // add markup | |
361 function insert(block) { | |
362 if (document.selection) { | |
363 var newSelection = document.selection.createRange(); | |
364 newSelection.text = block; | |
365 } else { | |
366 textarea.value = textarea.value.substring(0, caretPosition) + block + textarea.value.substring(caretPosition + selection.length, textarea.value.length); | |
367 } | |
368 } | |
369 | |
370 // set a selection | |
371 function set(start, len) { | |
372 if (textarea.createTextRange){ | |
373 // quick fix to make it work on Opera 9.5 | |
374 if ($.browser.opera && $.browser.version >= 9.5 && len == 0) { | |
375 return false; | |
376 } | |
377 range = textarea.createTextRange(); | |
378 range.collapse(true); | |
379 range.moveStart('character', start); | |
380 range.moveEnd('character', len); | |
381 range.select(); | |
382 } else if (textarea.setSelectionRange ){ | |
383 textarea.setSelectionRange(start, start + len); | |
384 } | |
385 textarea.scrollTop = scrollPosition; | |
386 textarea.focus(); | |
387 } | |
388 | |
389 // get the selection | |
390 function get() { | |
391 textarea.focus(); | |
392 | |
393 scrollPosition = textarea.scrollTop; | |
394 if (document.selection) { | |
395 selection = document.selection.createRange().text; | |
396 if ($.browser.msie) { // ie | |
397 var range = document.selection.createRange(), rangeCopy = range.duplicate(); | |
398 rangeCopy.moveToElementText(textarea); | |
399 caretPosition = -1; | |
400 while(rangeCopy.inRange(range)) { | |
401 rangeCopy.moveStart('character'); | |
402 caretPosition ++; | |
403 } | |
404 } else { // opera | |
405 caretPosition = textarea.selectionStart; | |
406 } | |
407 } else { // gecko & webkit | |
408 caretPosition = textarea.selectionStart; | |
409 selection = textarea.value.substring(caretPosition, textarea.selectionEnd); | |
410 } | |
411 return selection; | |
412 } | |
413 | |
414 // open preview window | |
415 function preview() { | |
416 if (!previewWindow || previewWindow.closed) { | |
417 if (options.previewInWindow) { | |
418 previewWindow = window.open('', 'preview', options.previewInWindow); | |
419 $(window).unload(function() { | |
420 previewWindow.close(); | |
421 }); | |
422 } else { | |
423 iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>'); | |
424 if (options.previewPosition == 'after') { | |
425 iFrame.insertAfter(footer); | |
426 } else { | |
427 iFrame.insertBefore(header); | |
428 } | |
429 previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1]; | |
430 } | |
431 } else if (altKey === true) { | |
432 if (iFrame) { | |
433 iFrame.remove(); | |
434 } else { | |
435 previewWindow.close(); | |
436 } | |
437 previewWindow = iFrame = false; | |
438 } | |
439 if (!options.previewAutoRefresh) { | |
440 refreshPreview(); | |
441 } | |
442 if (options.previewInWindow) { | |
443 previewWindow.focus(); | |
444 } | |
445 } | |
446 | |
447 // refresh Preview window | |
448 function refreshPreview() { | |
449 renderPreview(); | |
450 } | |
451 | |
452 function renderPreview() { | |
453 var phtml; | |
454 if (options.previewParserPath !== '') { | |
455 $.ajax( { | |
456 type: 'POST', | |
457 url: options.previewParserPath, | |
458 data: options.previewParserVar+'='+encodeURIComponent($$.val()), | |
459 success: function(data) { | |
460 writeInPreview( localize(data, 1) ); | |
461 } | |
462 } ); | |
463 } else { | |
464 if (!template) { | |
465 $.ajax( { | |
466 url: options.previewTemplatePath, | |
467 success: function(data) { | |
468 writeInPreview( localize(data, 1).replace(/<!-- content -->/g, $$.val()) ); | |
469 } | |
470 } ); | |
471 } | |
472 } | |
473 return false; | |
474 } | |
475 | |
476 function writeInPreview(data) { | |
477 if (previewWindow.document) { | |
478 try { | |
479 sp = previewWindow.document.documentElement.scrollTop | |
480 } catch(e) { | |
481 sp = 0; | |
482 } | |
483 previewWindow.document.open(); | |
484 previewWindow.document.write(data); | |
485 previewWindow.document.close(); | |
486 previewWindow.document.documentElement.scrollTop = sp; | |
487 } | |
488 } | |
489 | |
490 // set keys pressed | |
491 function keyPressed(e) { | |
492 shiftKey = e.shiftKey; | |
493 altKey = e.altKey; | |
494 ctrlKey = (!(e.altKey && e.ctrlKey)) ? e.ctrlKey : false; | |
495 | |
496 if (e.type === 'keydown') { | |
497 if (ctrlKey === true) { | |
498 li = $("a[accesskey="+String.fromCharCode(e.keyCode)+"]", header).parent('li'); | |
499 if (li.length !== 0) { | |
500 ctrlKey = false; | |
501 setTimeout(function() { | |
502 li.triggerHandler('mousedown'); | |
503 },1); | |
504 return false; | |
505 } | |
506 } | |
507 if (e.keyCode === 13 || e.keyCode === 10) { // Enter key | |
508 if (ctrlKey === true) { // Enter + Ctrl | |
509 ctrlKey = false; | |
510 markup(options.onCtrlEnter); | |
511 return options.onCtrlEnter.keepDefault; | |
512 } else if (shiftKey === true) { // Enter + Shift | |
513 shiftKey = false; | |
514 markup(options.onShiftEnter); | |
515 return options.onShiftEnter.keepDefault; | |
516 } else { // only Enter | |
517 markup(options.onEnter); | |
518 return options.onEnter.keepDefault; | |
519 } | |
520 } | |
521 if (e.keyCode === 9) { // Tab key | |
522 if (shiftKey == true || ctrlKey == true || altKey == true) { | |
523 return false; | |
524 } | |
525 if (caretOffset !== -1) { | |
526 get(); | |
527 caretOffset = $$.val().length - caretOffset; | |
528 set(caretOffset, 0); | |
529 caretOffset = -1; | |
530 return false; | |
531 } else { | |
532 markup(options.onTab); | |
533 return options.onTab.keepDefault; | |
534 } | |
535 } | |
536 } | |
537 } | |
538 | |
539 init(); | |
540 }); | |
541 }; | |
542 | |
543 $.fn.markItUpRemove = function() { | |
544 return this.each(function() { | |
545 var $$ = $(this).unbind().removeClass('markItUpEditor'); | |
546 $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$); | |
547 } | |
548 ); | |
549 }; | |
550 | |
551 $.markItUp = function(settings) { | |
552 var options = { target:false }; | |
553 $.extend(options, settings); | |
554 if (options.target) { | |
555 return $(options.target).each(function() { | |
556 $(this).focus(); | |
557 $(this).trigger('insertion', [options]); | |
558 }); | |
559 } else { | |
560 $('textarea').trigger('insertion', [options]); | |
561 } | |
562 }; | |
563 })(jQuery); |