2
0

crowi-form.js 12 KB

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