crowi-form.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. $(function() {
  2. // preview watch
  3. $('[data-toggle="popover"]').popover();
  4. var originalContent = $('#form-body').val();
  5. var prevContent = "";
  6. var watchTimer = setInterval(function() {
  7. var content = $('#form-body').val();
  8. if (prevContent != content) {
  9. var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
  10. renderer.render();
  11. prevContent = content;
  12. }
  13. }, 500);
  14. var getCurrentLine = function(event) {
  15. var $target = $(event.target);
  16. var text = $target.val();
  17. var pos = $target.selection('getPos');
  18. if (text === null || pos.start !== pos.end) {
  19. return null;
  20. }
  21. var startPos = text.lastIndexOf("\n", pos.start - 1) + 1;
  22. var endPos = text.indexOf("\n", pos.start);
  23. if (endPos === -1) {
  24. endPos = text.length;
  25. }
  26. return {
  27. text: text.slice(startPos, endPos),
  28. start: startPos,
  29. end: endPos,
  30. caret: pos.start,
  31. endOfLine: !$.trim(text.slice(pos.start, endPos))
  32. };
  33. };
  34. var getPrevLine = function(event) {
  35. var $target = $(event.target);
  36. var currentLine = getCurrentLine(event);
  37. var text = $target.val().slice(0, currentLine.start);
  38. var startPos = text.lastIndexOf("\n", currentLine.start - 2) + 1;
  39. var endPos = currentLine.start;
  40. return {
  41. text: text.slice(startPos, endPos),
  42. start: startPos,
  43. end: endPos
  44. };
  45. };
  46. var handleTabKey = function(event) {
  47. event.preventDefault();
  48. var $target = $(event.target);
  49. var currentLine = getCurrentLine(event);
  50. var text = $target.val();
  51. var pos = $target.selection('getPos');
  52. if (currentLine) {
  53. $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
  54. }
  55. if (event.shiftKey === true) {
  56. if (currentLine && currentLine.text.charAt(0) === '|') {
  57. // prev cell in table
  58. var newPos = text.lastIndexOf('|', pos.start - 1);
  59. if (newPos > 0) {
  60. $target.selection('setPos', {start: newPos - 1, end: newPos - 1});
  61. }
  62. } else {
  63. // re indent
  64. var reindentedText = $target.selection().replace(/^ {1,4}/gm, '');
  65. var reindentedCount = $target.selection().length - reindentedText.length;
  66. $target.selection('replace', {text: reindentedText, mode: 'before'});
  67. if (currentLine) {
  68. $target.selection('setPos', {start: pos.start - reindentedCount, end: pos.start - reindentedCount});
  69. }
  70. }
  71. } else {
  72. if (currentLine && currentLine.text.charAt(0) === '|') {
  73. // next cell in table
  74. var newPos = text.indexOf('|', pos.start + 1);
  75. if (newPos < 0 || newPos === text.lastIndexOf('|', currentLine.end - 1)) {
  76. $target.selection('setPos', {start: currentLine.end, end: currentLine.end});
  77. } else {
  78. $target.selection('setPos', {start: newPos + 2, end: newPos + 2});
  79. }
  80. } else {
  81. // indent
  82. $target.selection('replace', {
  83. text: ' ' + $target.selection().split("\n").join("\n "),
  84. mode: 'before'
  85. });
  86. if (currentLine) {
  87. $target.selection('setPos', {start: pos.start + 4, end: pos.start + 4});
  88. }
  89. }
  90. }
  91. $target.trigger('input');
  92. };
  93. var handleEnterKey = function(event) {
  94. if (event.metaKey || event.ctrlKey || event.shiftKey) {
  95. return;
  96. }
  97. var currentLine = getCurrentLine(event);
  98. if (!currentLine || currentLine.start === currentLine.caret) {
  99. return;
  100. }
  101. var $target = $(event.target);
  102. var match = currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)\s*\S/);
  103. if (match) {
  104. // smart indent with list
  105. if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) (?:\[(?:x| )\] ))\s*$/)) {
  106. // empty task list
  107. $target.selection('setPos', {start: currentLine.start, end: (currentLine.end - 1)});
  108. return;
  109. }
  110. event.preventDefault();
  111. var listMark = match[1].replace(/\[x\]/, '[ ]');
  112. var listMarkMatch = listMark.match(/^(\s*)(\d+)\./);
  113. if (listMarkMatch) {
  114. var indent = listMarkMatch[1];
  115. var num = parseInt(listMarkMatch[2]);
  116. if (num !== 1) {
  117. listMark = listMark.replace(/\s*\d+/, indent + (num +1));
  118. }
  119. }
  120. $target.selection('insert', {text: "\n" + listMark, mode: 'before'});
  121. } else if (currentLine.text.match(/^(\s*(?:-|\+|\*|\d+\.) )/)) {
  122. // remove list
  123. $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
  124. } else if (currentLine.text.match(/^.*\|\s*$/)) {
  125. // new row for table
  126. if (currentLine.text.match(/^[\|\s]+$/)) {
  127. $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
  128. return;
  129. }
  130. if (!currentLine.endOfLine) {
  131. return;
  132. }
  133. event.preventDefault();
  134. var row = [];
  135. var cellbarMatch = currentLine.text.match(/\|/g);
  136. for (var i = 0; i < cellbarMatch.length; i++) {
  137. row.push('|');
  138. }
  139. var prevLine = getPrevLine(event);
  140. if (!prevLine || (!currentLine.text.match(/---/) && !prevLine.text.match(/\|/g))) {
  141. $target.selection('insert', {text: "\n" + row.join(' --- ') + "\n" + row.join(' '), mode: 'before'});
  142. $target.selection('setPos', {start: currentLine.caret + 6 * row.length - 1, end: currentLine.caret + 6 * row.length - 1});
  143. } else {
  144. $target.selection('insert', {text: "\n" + row.join(' '), mode: 'before'});
  145. $target.selection('setPos', {start: currentLine.caret + 3, end: currentLine.caret + 3});
  146. }
  147. }
  148. $target.trigger('input');
  149. };
  150. var handleEscapeKey = function(event) {
  151. event.preventDefault();
  152. var $target = $(event.target);
  153. $target.blur();
  154. };
  155. var handleSpaceKey = function(event) {
  156. // keybind: alt + shift + space
  157. if (!(event.shiftKey && event.altKey)) {
  158. return;
  159. }
  160. var currentLine = getCurrentLine(event);
  161. if (!currentLine) {
  162. return;
  163. }
  164. var $target = $(event.target);
  165. var match = currentLine.text.match(/^(\s*)(-|\+|\*|\d+\.) (?:\[(x| )\] )(.*)/);
  166. if (match) {
  167. event.preventDefault();
  168. var checkMark = (match[3] == ' ') ? 'x' : ' ';
  169. var replaceTo = match[1] + match[2] + ' [' + checkMark + '] ' + match[4];
  170. $target.selection('setPos', {start: currentLine.start, end: currentLine.end});
  171. $target.selection('replace', {text: replaceTo, mode: 'keep'});
  172. $target.selection('setPos', {start: currentLine.caret, end: currentLine.caret});
  173. $target.trigger('input');
  174. }
  175. };
  176. // markdown helper inspired by 'esarea'.
  177. // see: https://github.com/fukayatsu/esarea
  178. $('textarea#form-body').on('keydown', function(event) {
  179. switch (event.which || event.keyCode) {
  180. case 9:
  181. handleTabKey(event);
  182. break;
  183. case 13:
  184. handleEnterKey(event);
  185. break;
  186. case 27:
  187. handleEscapeKey(event);
  188. break;
  189. case 32:
  190. handleSpaceKey(event);
  191. break;
  192. default:
  193. }
  194. });
  195. var handlePasteEvent = function(event) {
  196. var currentLine = getCurrentLine(event);
  197. if (!currentLine) {
  198. return false;
  199. }
  200. var $target = $(event.target);
  201. var pasteText = event.clipboardData.getData('text');
  202. var match = currentLine.text.match(/^(\s*(?:>|\-|\+|\*|\d+\.) (?:\[(?:x| )\] )?)/);
  203. if (match) {
  204. if (pasteText.match(/(?:\r\n|\r|\n)/)) {
  205. pasteText = pasteText.replace(/(\r\n|\r|\n)/g, "$1" + match[1]);
  206. }
  207. }
  208. $target.selection('insert', {text: pasteText, mode: 'after'});
  209. var newPos = currentLine.end + pasteText.length;
  210. $target.selection('setPos', {start: newPos, end: newPos});
  211. return true;
  212. };
  213. document.getElementById('form-body').addEventListener('paste', function(event) {
  214. if (handlePasteEvent(event)) {
  215. event.preventDefault();
  216. }
  217. });
  218. var unbindInlineAttachment = function($form) {
  219. $form.unbind('.inlineattach');
  220. };
  221. var bindInlineAttachment = function($form, attachmentOption) {
  222. var $this = $form;
  223. var editor = createEditorInstance($form);
  224. var inlineattach = new inlineAttachment(attachmentOption, editor);
  225. $form.bind({
  226. 'paste.inlineattach': function(e) {
  227. inlineattach.onPaste(e.originalEvent);
  228. },
  229. 'drop.inlineattach': function(e) {
  230. e.stopPropagation();
  231. e.preventDefault();
  232. inlineattach.onDrop(e.originalEvent);
  233. },
  234. 'dragenter.inlineattach dragover.inlineattach': function(e) {
  235. e.stopPropagation();
  236. e.preventDefault();
  237. }
  238. });
  239. };
  240. var createEditorInstance = function($form) {
  241. var $this = $form;
  242. return {
  243. getValue: function() {
  244. return $this.val();
  245. },
  246. insertValue: function(val) {
  247. inlineAttachment.util.insertTextAtCursor($this[0], val);
  248. },
  249. setValue: function(val) {
  250. $this.val(val);
  251. }
  252. };
  253. };
  254. var $inputForm = $('form.uploadable textarea#form-body');
  255. if ($inputForm.length > 0) {
  256. var pageId = $('#content-main').data('page-id') || 0;
  257. var attachmentOption = {
  258. uploadUrl: '/_api/attachment/page/' + pageId,
  259. extraParams: {
  260. path: location.pathname
  261. },
  262. progressText: '(Uploading file...)',
  263. urlText: "\n![file]({filename})\n"
  264. };
  265. attachmentOption.onFileUploadResponse = function(res) {
  266. var result = JSON.parse(res.response);
  267. if (result.status && result.pageCreated) {
  268. var page = result.page,
  269. pageId = page._id;
  270. $('#content-main').data('page-id', page._id);
  271. $('#page-form [name="pageForm[currentRevision]"]').val(page.revision._id)
  272. unbindInlineAttachment($inputForm);
  273. attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
  274. bindInlineAttachment($inputForm, attachmentOption);
  275. }
  276. return true;
  277. };
  278. bindInlineAttachment($inputForm, attachmentOption);
  279. $('textarea#form-body').on('dragenter dragover', function() {
  280. $(this).addClass('dragover');
  281. });
  282. $('textarea#form-body').on('drop dragleave dragend', function() {
  283. $(this).removeClass('dragover');
  284. });
  285. }
  286. });