mod/forum/amd/src/inpage_reply.js

  1. // This file is part of Moodle - http://moodle.org/
  2. //
  3. // Moodle is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. //
  8. // Moodle is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  15. /**
  16. * This module handles the in page replying to forum posts.
  17. *
  18. * @module mod_forum/inpage_reply
  19. * @copyright 2019 Peter Dias
  20. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21. */
  22. define([
  23. 'jquery',
  24. 'core/templates',
  25. 'core/notification',
  26. 'mod_forum/repository',
  27. 'mod_forum/selectors',
  28. 'core_form/changechecker',
  29. ], function(
  30. $,
  31. Templates,
  32. Notification,
  33. Repository,
  34. Selectors,
  35. FormChangeChecker
  36. ) {
  37. var DISPLAYCONSTANTS = {
  38. NESTED_V2: 4,
  39. THREADED: 2,
  40. NESTED: 3,
  41. FLAT_OLDEST_FIRST: 1,
  42. FLAT_NEWEST_FIRST: -1
  43. };
  44. var EVENTS = {
  45. POST_CREATED: 'mod_forum-post-created'
  46. };
  47. /**
  48. * Moodle formats taken from the FORMAT_* constants declared in lib/weblib.php.
  49. * @type {Object}
  50. */
  51. var CONTENT_FORMATS = {
  52. MOODLE: 0
  53. };
  54. /**
  55. * Show the loading icon for the submit button.
  56. *
  57. * @param {Object} button The submit button element
  58. */
  59. var showSubmitButtonLoadingIcon = function(button) {
  60. var textContainer = button.find(Selectors.post.inpageSubmitBtnText);
  61. var loadingIconContainer = button.find(Selectors.post.loadingIconContainer);
  62. var width = button.outerWidth();
  63. // Fix the width so that the button size doesn't change when we show the loading icon.
  64. button.css('width', width);
  65. textContainer.addClass('hidden');
  66. loadingIconContainer.removeClass('hidden');
  67. };
  68. /**
  69. * Hide the loading icon for the submit button.
  70. *
  71. * @param {Object} button The submit button element
  72. */
  73. var hideSubmitButtonLoadingIcon = function(button) {
  74. var textContainer = button.find(Selectors.post.inpageSubmitBtnText);
  75. var loadingIconContainer = button.find(Selectors.post.loadingIconContainer);
  76. // Reset the width back to it's default.
  77. button.css('width', '');
  78. textContainer.removeClass('hidden');
  79. loadingIconContainer.addClass('hidden');
  80. };
  81. /**
  82. * Register the event listeners for the submit/cancel buttons of the in page reply.
  83. *
  84. * @param {Object} root The discussion container element.
  85. */
  86. var registerEventListeners = function(root) {
  87. root.on('click', Selectors.post.inpageSubmitBtn, function(e) {
  88. e.preventDefault();
  89. var submitButton = $(e.currentTarget);
  90. var allButtons = submitButton.parent().find(Selectors.post.inpageReplyButton);
  91. var form = submitButton.parents(Selectors.post.inpageReplyForm).get(0);
  92. var message = form.elements.post.value.trim();
  93. // For now, we consider the inline reply post written using the FORMAT_MOODLE (because a textarea is displayed).
  94. // In the future, other formats should be supported, letting users to use their preferred editor and format.
  95. var messageformat = CONTENT_FORMATS.MOODLE;
  96. // The message post will be converted from messageformat to FORMAT_HTML.
  97. var topreferredformat = true;
  98. var postid = form.elements.reply.value;
  99. var subject = form.elements.subject.value;
  100. var currentRoot = submitButton.closest(Selectors.post.post);
  101. var isprivatereply = form.elements.privatereply != undefined ? form.elements.privatereply.checked : false;
  102. var modeSelector = root.find(Selectors.post.modeSelect);
  103. var mode = modeSelector.length ? parseInt(modeSelector.get(0).value) : null;
  104. var newid;
  105. if (message.length) {
  106. showSubmitButtonLoadingIcon(submitButton);
  107. allButtons.prop('disabled', true);
  108. Repository.addDiscussionPost(postid, subject, message, messageformat, isprivatereply, topreferredformat)
  109. .then(function(context) {
  110. var message = context.messages.reduce(function(carry, message) {
  111. if (message.type == 'success') {
  112. carry += '<p>' + message.message + '</p>';
  113. }
  114. return carry;
  115. }, '');
  116. Notification.addNotification({
  117. message: message,
  118. type: "success"
  119. });
  120. return context;
  121. })
  122. .then(function(context) {
  123. form.reset();
  124. var post = context.post;
  125. newid = post.id;
  126. switch (mode) {
  127. case DISPLAYCONSTANTS.NESTED_V2:
  128. var capabilities = post.capabilities;
  129. var currentAuthorName = currentRoot.children()
  130. .not(Selectors.post.repliesContainer)
  131. .find(Selectors.post.authorName)
  132. .text();
  133. post.parentauthorname = currentAuthorName;
  134. post.showactionmenu = capabilities.view ||
  135. capabilities.controlreadstatus ||
  136. capabilities.edit ||
  137. capabilities.split ||
  138. capabilities.delete ||
  139. capabilities.export ||
  140. post.urls.viewparent;
  141. return Templates.render('mod_forum/forum_discussion_nested_v2_post_reply', post);
  142. case DISPLAYCONSTANTS.THREADED:
  143. return Templates.render('mod_forum/forum_discussion_threaded_post', post);
  144. case DISPLAYCONSTANTS.NESTED:
  145. return Templates.render('mod_forum/forum_discussion_nested_post', post);
  146. default:
  147. return Templates.render('mod_forum/forum_discussion_post', post);
  148. }
  149. })
  150. .then(function(html, js) {
  151. var repliesnode = currentRoot.find(Selectors.post.repliesContainer).first();
  152. if (mode == DISPLAYCONSTANTS.FLAT_NEWEST_FIRST) {
  153. return Templates.prependNodeContents(repliesnode, html, js);
  154. } else {
  155. return Templates.appendNodeContents(repliesnode, html, js);
  156. }
  157. })
  158. .then(function() {
  159. submitButton.trigger(EVENTS.POST_CREATED, newid);
  160. hideSubmitButtonLoadingIcon(submitButton);
  161. allButtons.prop('disabled', false);
  162. // Tell formchangechecker we submitted the form.
  163. FormChangeChecker.resetFormDirtyState(submitButton[0]);
  164. return currentRoot.find(Selectors.post.inpageReplyContent).hide();
  165. })
  166. .then(function() {
  167. location.href = "#p" + newid;
  168. // Reload the page, say if threshold is being set by user those would get reflected through the templates.
  169. location.reload();
  170. })
  171. .catch(function(error) {
  172. hideSubmitButtonLoadingIcon(submitButton);
  173. allButtons.prop('disabled', false);
  174. return Notification.exception(error);
  175. });
  176. }
  177. });
  178. root.on('click', Selectors.post.inpageCancelButton, function(e) {
  179. // Tell formchangechecker to reset the form state.
  180. FormChangeChecker.resetFormDirtyState(e.currentTarget);
  181. });
  182. };
  183. return {
  184. init: function(root) {
  185. registerEventListeners(root);
  186. },
  187. CONTENT_FORMATS: CONTENT_FORMATS,
  188. EVENTS: EVENTS
  189. };
  190. });