Просмотр файла assets/markitup/jquery.markitup.js

Размер файла: 20.1Kb
  1. // ----------------------------------------------------------------------------
  2. // markItUp! Universal MarkUp Engine, JQuery plugin
  3. // v 1.1.x
  4. // Dual licensed under the MIT and GPL licenses.
  5. // ----------------------------------------------------------------------------
  6. // Copyright (C) 2007-2011 Jay Salvat
  7. // http://markitup.jaysalvat.com/
  8. // ----------------------------------------------------------------------------
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. // ----------------------------------------------------------------------------
  27. (function($) {
  28. $.fn.markItUp = function(settings, extraSettings) {
  29. var options, ctrlKey, shiftKey, altKey;
  30. ctrlKey = shiftKey = altKey = false;
  31. options = { id: '',
  32. nameSpace: '',
  33. root: '',
  34. previewInWindow: '', // 'width=800, height=600, resizable=yes, scrollbars=yes'
  35. previewAutoRefresh: true,
  36. previewPosition: 'after',
  37. previewTemplatePath: '~/templates/preview.html',
  38. previewParser: false,
  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. // bind an event to catch external calls
  129. $$.bind("insertion", function(e, settings) {
  130. if (settings.target !== false) {
  131. get();
  132. }
  133. if (textarea === $.markItUp.focused) {
  134. markup(settings);
  135. }
  136. });
  137.  
  138. // remember the last focus
  139. $$.focus(function() {
  140. $.markItUp.focused = this;
  141. });
  142. }
  143.  
  144. // recursively build header with dropMenus from markupset
  145. function dropMenus(markupSet) {
  146. var ul = $('<ul></ul>'), i = 0;
  147. $('li:hover > ul', ul).css('display', 'block');
  148. $.each(markupSet, function() {
  149. var button = this, t = '', title, li, j;
  150. title = (button.key) ? (button.name||'')+' [Ctrl+'+button.key+']' : (button.name||'');
  151. key = (button.key) ? 'accesskey="'+button.key+'"' : '';
  152. if (button.separator) {
  153. li = $('<li class="markItUpSeparator">'+(button.separator||'')+'</li>').appendTo(ul);
  154. } else {
  155. i++;
  156. for (j = levels.length -1; j >= 0; j--) {
  157. t += levels[j]+"-";
  158. }
  159. li = $('<li class="markItUpButton markItUpButton'+t+(i)+' '+(button.className||'')+'"><a href="" '+key+' title="'+title+'">'+(button.name||'')+'</a></li>')
  160. .bind("contextmenu", function() { // prevent contextmenu on mac and allow ctrl+click
  161. return false;
  162. }).click(function() {
  163. return false;
  164. }).bind("focusin", function(){
  165. $$.focus();
  166. }).mouseup(function() {
  167. if (button.call) {
  168. eval(button.call)();
  169. }
  170. setTimeout(function() { markup(button) },1);
  171. return false;
  172. }).hover(function() {
  173. $('> ul', this).show();
  174. $(document).one('click', function() { // close dropmenu if click outside
  175. $('ul ul', header).hide();
  176. }
  177. );
  178. }, function() {
  179. $('> ul', this).hide();
  180. }
  181. ).appendTo(ul);
  182. if (button.dropMenu) {
  183. levels.push(i);
  184. $(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu));
  185. }
  186. }
  187. });
  188. levels.pop();
  189. return ul;
  190. }
  191.  
  192. // markItUp! markups
  193. function magicMarkups(string) {
  194. if (string) {
  195. string = string.toString();
  196. string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g,
  197. function(x, a) {
  198. var b = a.split('|!|');
  199. if (altKey === true) {
  200. return (b[1] !== undefined) ? b[1] : b[0];
  201. } else {
  202. return (b[1] === undefined) ? "" : b[0];
  203. }
  204. }
  205. );
  206. // [![prompt]!], [![prompt:!:value]!]
  207. string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g,
  208. function(x, a) {
  209. var b = a.split(':!:');
  210. if (abort === true) {
  211. return false;
  212. }
  213. value = prompt(b[0], (b[1]) ? b[1] : '');
  214. if (value === null) {
  215. abort = true;
  216. }
  217. return value;
  218. }
  219. );
  220. return string;
  221. }
  222. return "";
  223. }
  224.  
  225. // prepare action
  226. function prepare(action) {
  227. if ($.isFunction(action)) {
  228. action = action(hash);
  229. }
  230. return magicMarkups(action);
  231. }
  232.  
  233. // build block to insert
  234. function build(string) {
  235. var openWith = prepare(clicked.openWith);
  236. var placeHolder = prepare(clicked.placeHolder);
  237. var replaceWith = prepare(clicked.replaceWith);
  238. var closeWith = prepare(clicked.closeWith);
  239. var openBlockWith = prepare(clicked.openBlockWith);
  240. var closeBlockWith = prepare(clicked.closeBlockWith);
  241. var multiline = clicked.multiline;
  242. if (replaceWith !== "") {
  243. block = openWith + replaceWith + closeWith;
  244. } else if (selection === '' && placeHolder !== '') {
  245. block = openWith + placeHolder + closeWith;
  246. } else {
  247. string = string || selection;
  248.  
  249. var lines = [string], blocks = [];
  250. if (multiline === true) {
  251. lines = string.split(/\r?\n/);
  252. }
  253. for (var l = 0; l < lines.length; l++) {
  254. line = lines[l];
  255. var trailingSpaces;
  256. if (trailingSpaces = line.match(/ *$/)) {
  257. blocks.push(openWith + line.replace(/ *$/g, '') + closeWith + trailingSpaces);
  258. } else {
  259. blocks.push(openWith + line + closeWith);
  260. }
  261. }
  262. block = blocks.join("\n");
  263. }
  264.  
  265. block = openBlockWith + block + closeBlockWith;
  266.  
  267. return { block:block,
  268. openWith:openWith,
  269. replaceWith:replaceWith,
  270. placeHolder:placeHolder,
  271. closeWith:closeWith
  272. };
  273. }
  274.  
  275. // define markup to insert
  276. function markup(button) {
  277. var len, j, n, i;
  278. hash = clicked = button;
  279. get();
  280. $.extend(hash, { line:"",
  281. root:options.root,
  282. textarea:textarea,
  283. selection:(selection||''),
  284. caretPosition:caretPosition,
  285. ctrlKey:ctrlKey,
  286. shiftKey:shiftKey,
  287. altKey:altKey
  288. }
  289. );
  290. // callbacks before insertion
  291. prepare(options.beforeInsert);
  292. prepare(clicked.beforeInsert);
  293. if ((ctrlKey === true && shiftKey === true) || button.multiline === true) {
  294. prepare(clicked.beforeMultiInsert);
  295. }
  296. $.extend(hash, { line:1 });
  297.  
  298. if ((ctrlKey === true && shiftKey === true)) {
  299. lines = selection.split(/\r?\n/);
  300. for (j = 0, n = lines.length, i = 0; i < n; i++) {
  301. if ($.trim(lines[i]) !== '') {
  302. $.extend(hash, { line:++j, selection:lines[i] } );
  303. lines[i] = build(lines[i]).block;
  304. } else {
  305. lines[i] = "";
  306. }
  307. }
  308.  
  309. string = { block:lines.join('\n')};
  310. start = caretPosition;
  311. len = string.block.length + (($.browser.opera) ? n-1 : 0);
  312. } else if (ctrlKey === true) {
  313. string = build(selection);
  314. start = caretPosition + string.openWith.length;
  315. len = string.block.length - string.openWith.length - string.closeWith.length;
  316. len = len - (string.block.match(/ $/) ? 1 : 0);
  317. len -= fixIeBug(string.block);
  318. } else if (shiftKey === true) {
  319. string = build(selection);
  320. start = caretPosition;
  321. len = string.block.length;
  322. len -= fixIeBug(string.block);
  323. } else {
  324. string = build(selection);
  325. start = caretPosition + string.block.length ;
  326. len = 0;
  327. start -= fixIeBug(string.block);
  328. }
  329. if ((selection === '' && string.replaceWith === '')) {
  330. caretOffset += fixOperaBug(string.block);
  331. start = caretPosition + string.openWith.length;
  332. len = string.block.length - string.openWith.length - string.closeWith.length;
  333.  
  334. caretOffset = $$.val().substring(caretPosition, $$.val().length).length;
  335. caretOffset -= fixOperaBug($$.val().substring(0, caretPosition));
  336. }
  337. $.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } );
  338.  
  339. if (string.block !== selection && abort === false) {
  340. insert(string.block);
  341. set(start, len);
  342. } else {
  343. caretOffset = -1;
  344. }
  345. get();
  346.  
  347. $.extend(hash, { line:'', selection:selection });
  348.  
  349. // callbacks after insertion
  350. if ((ctrlKey === true && shiftKey === true) || button.multiline === true) {
  351. prepare(clicked.afterMultiInsert);
  352. }
  353. prepare(clicked.afterInsert);
  354. prepare(options.afterInsert);
  355.  
  356. // refresh preview if opened
  357. if (previewWindow && options.previewAutoRefresh) {
  358. refreshPreview();
  359. }
  360. // reinit keyevent
  361. shiftKey = altKey = ctrlKey = abort = false;
  362. }
  363.  
  364. // Substract linefeed in Opera
  365. function fixOperaBug(string) {
  366. if ($.browser.opera) {
  367. return string.length - string.replace(/\n*/g, '').length;
  368. }
  369. return 0;
  370. }
  371. // Substract linefeed in IE
  372. function fixIeBug(string) {
  373. if ($.browser.msie) {
  374. return string.length - string.replace(/\r*/g, '').length;
  375. }
  376. return 0;
  377. }
  378. // add markup
  379. function insert(block) {
  380. if (document.selection) {
  381. var newSelection = document.selection.createRange();
  382. newSelection.text = block;
  383. } else {
  384. textarea.value = textarea.value.substring(0, caretPosition) + block + textarea.value.substring(caretPosition + selection.length, textarea.value.length);
  385. }
  386. }
  387.  
  388. // set a selection
  389. function set(start, len) {
  390. if (textarea.createTextRange){
  391. // quick fix to make it work on Opera 9.5
  392. if ($.browser.opera && $.browser.version >= 9.5 && len == 0) {
  393. return false;
  394. }
  395. range = textarea.createTextRange();
  396. range.collapse(true);
  397. range.moveStart('character', start);
  398. range.moveEnd('character', len);
  399. range.select();
  400. } else if (textarea.setSelectionRange ){
  401. textarea.setSelectionRange(start, start + len);
  402. }
  403. textarea.scrollTop = scrollPosition;
  404. textarea.focus();
  405. }
  406.  
  407. // get the selection
  408. function get() {
  409. textarea.focus();
  410.  
  411. scrollPosition = textarea.scrollTop;
  412. if (document.selection) {
  413. selection = document.selection.createRange().text;
  414. if ($.browser.msie) { // ie
  415. var range = document.selection.createRange(), rangeCopy = range.duplicate();
  416. rangeCopy.moveToElementText(textarea);
  417. caretPosition = -1;
  418. while(rangeCopy.inRange(range)) {
  419. rangeCopy.moveStart('character');
  420. caretPosition ++;
  421. }
  422. } else { // opera
  423. caretPosition = textarea.selectionStart;
  424. }
  425. } else { // gecko & webkit
  426. caretPosition = textarea.selectionStart;
  427.  
  428. selection = textarea.value.substring(caretPosition, textarea.selectionEnd);
  429. }
  430. return selection;
  431. }
  432.  
  433. // open preview window
  434. function preview() {
  435. if (!previewWindow || previewWindow.closed) {
  436. if (options.previewInWindow) {
  437. previewWindow = window.open('', 'preview', options.previewInWindow);
  438. $(window).unload(function() {
  439. previewWindow.close();
  440. });
  441. } else {
  442. iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>');
  443. if (options.previewPosition == 'after') {
  444. iFrame.insertAfter(footer);
  445. } else {
  446. iFrame.insertBefore(header);
  447. }
  448. previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1];
  449. }
  450. } else if (altKey === true) {
  451. if (iFrame) {
  452. iFrame.remove();
  453. } else {
  454. previewWindow.close();
  455. }
  456. previewWindow = iFrame = false;
  457. }
  458. if (!options.previewAutoRefresh) {
  459. refreshPreview();
  460. }
  461. if (options.previewInWindow) {
  462. previewWindow.focus();
  463. }
  464. }
  465.  
  466. // refresh Preview window
  467. function refreshPreview() {
  468. renderPreview();
  469. }
  470.  
  471. function renderPreview() {
  472. var phtml;
  473. if (options.previewParser && typeof options.previewParser === 'function') {
  474. var data = options.previewParser( $$.val() );
  475. writeInPreview( localize(data, 1) );
  476. } else if (options.previewParserPath !== '') {
  477. $.ajax({
  478. type: 'POST',
  479. dataType: 'text',
  480. global: false,
  481. url: options.previewParserPath,
  482. data: options.previewParserVar+'='+encodeURIComponent($$.val()),
  483. success: function(data) {
  484. writeInPreview( localize(data, 1) );
  485. }
  486. });
  487. } else {
  488. if (!template) {
  489. $.ajax({
  490. url: options.previewTemplatePath,
  491. dataType: 'text',
  492. global: false,
  493. success: function(data) {
  494. writeInPreview( localize(data, 1).replace(/<!-- content -->/g, $$.val()) );
  495. }
  496. });
  497. }
  498. }
  499. return false;
  500. }
  501. function writeInPreview(data) {
  502. if (previewWindow.document) {
  503. try {
  504. sp = previewWindow.document.documentElement.scrollTop
  505. } catch(e) {
  506. sp = 0;
  507. }
  508. previewWindow.document.open();
  509. previewWindow.document.write(data);
  510. previewWindow.document.close();
  511. previewWindow.document.documentElement.scrollTop = sp;
  512. }
  513. }
  514. // set keys pressed
  515. function keyPressed(e) {
  516. shiftKey = e.shiftKey;
  517. altKey = e.altKey;
  518. ctrlKey = (!(e.altKey && e.ctrlKey)) ? (e.ctrlKey || e.metaKey) : false;
  519.  
  520. if (e.type === 'keydown') {
  521. if (ctrlKey === true) {
  522. li = $('a[accesskey="'+String.fromCharCode(e.keyCode)+'"]', header).parent('li');
  523. if (li.length !== 0) {
  524. ctrlKey = false;
  525. setTimeout(function() {
  526. li.triggerHandler('mouseup');
  527. },1);
  528. return false;
  529. }
  530. }
  531. if (e.keyCode === 13 || e.keyCode === 10) { // Enter key
  532. if (ctrlKey === true) { // Enter + Ctrl
  533. ctrlKey = false;
  534. markup(options.onCtrlEnter);
  535. return options.onCtrlEnter.keepDefault;
  536. } else if (shiftKey === true) { // Enter + Shift
  537. shiftKey = false;
  538. markup(options.onShiftEnter);
  539. return options.onShiftEnter.keepDefault;
  540. } else { // only Enter
  541. markup(options.onEnter);
  542. return options.onEnter.keepDefault;
  543. }
  544. }
  545. if (e.keyCode === 9) { // Tab key
  546. if (shiftKey == true || ctrlKey == true || altKey == true) {
  547. return false;
  548. }
  549. if (caretOffset !== -1) {
  550. get();
  551. caretOffset = $$.val().length - caretOffset;
  552. set(caretOffset, 0);
  553. caretOffset = -1;
  554. return false;
  555. } else {
  556. markup(options.onTab);
  557. return options.onTab.keepDefault;
  558. }
  559. }
  560. }
  561. }
  562.  
  563. init();
  564. });
  565. };
  566.  
  567. $.fn.markItUpRemove = function() {
  568. return this.each(function() {
  569. var $$ = $(this).unbind().removeClass('markItUpEditor');
  570. $$.parent('div').parent('div.markItUp').parent('div').replaceWith($$);
  571. }
  572. );
  573. };
  574.  
  575. $.markItUp = function(settings) {
  576. var options = { target:false };
  577. $.extend(options, settings);
  578. if (options.target) {
  579. return $(options.target).each(function() {
  580. $(this).focus();
  581. $(this).trigger('insertion', [options]);
  582. });
  583. } else {
  584. $('textarea').trigger('insertion', [options]);
  585. }
  586. };
  587. })(jQuery);
  588.  
  589. // ----------------------------------------------------------------------------
  590. // markItUp!
  591. // ----------------------------------------------------------------------------
  592. mySettings = {
  593. previewParserPath: '', // path to your BBCode parser
  594. markupSet: [
  595. {name:'Bold', key:'B', openWith:'[b]', closeWith:'[/b]'},
  596. {name:'Italic', key:'I', openWith:'[i]', closeWith:'[/i]'},
  597. {name:'Underline', key:'U', openWith:'[u]', closeWith:'[/u]'},
  598. {name:'Strike', key:'D', openWith:'[del]', closeWith:'[/del]'},
  599. {separator:'---------------' },
  600. {name:'Link', key:'L', openWith:'[url=[![Ссылка:!:http://]!]]', closeWith:'[/url]', placeHolder:'Текст ссылки...'},
  601. {name:'Colors', className:"colors",
  602. dropMenu: [
  603. {name:'Red', openWith:'[red]', closeWith:'[/red]', className:"col1-1" },
  604. {name:'Green', openWith:'[green]', closeWith:'[/green]', className:"col1-2" },
  605. {name:'Blue', openWith:'[blue]', closeWith:'[/blue]', className:"col1-3" }
  606. ]},
  607. {separator:'---------------' },
  608. {name:'Small', openWith:'[small]', closeWith:'[/small]' },
  609. {name:'Big', openWith:'[big]', closeWith:'[/big]' },
  610. {separator:'---------------' },
  611. {name:'Hide', openWith:'[hide]', closeWith:'[/hide]'},
  612. {name:'Quotes', openWith:'[q]', closeWith:'[/q]'},
  613. {name:'Code', openWith:'[code]', closeWith:'[/code]'},
  614. {separator:'---------------' },
  615. {name:'Clean', className:"clean", replaceWith:function(markitup) { return markitup.selection.replace(/\[(.*?)\]/g, "") } },
  616. {name:'Smiles', className:"smiles", dropMenu: [
  617. {name:':)', openWith:' :) ', className:"col1-1" },
  618. {name:':(', openWith:' :( ', className:"col1-2" },
  619. {name:':E', openWith:' :E ', className:"col1-3" },
  620. {name:':hello', openWith:' :hello ', className:"col2-1" },
  621. {name:':cry', openWith:' :cry ', className:"col2-2" },
  622. {name:':obana', openWith:' :obana ', className:"col2-3" },
  623. {name:':infat', openWith:' :infat ', className:"col3-1" },
  624. {name:':klass', openWith:' :klass ', className:"col3-2" },
  625. {name:':krut', openWith:' :krut ', className:"col3-3" }
  626. ]},
  627. {name:'Nextpage', openWith:'[nextpage]'}
  628. ]
  629. }
  630.  
  631. $(document).ready(function() {
  632. $('#markItUp').markItUp(mySettings);
  633. });