PageRenderTime 98ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/components/com_easyblog/classes/markitup/jquery.markitup.js

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