2
0

crowi-form.js 9.9 KB

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