diff --git a/mod/forum/amd/build/grades/expandconversation.min.js b/mod/forum/amd/build/grades/expandconversation.min.js
new file mode 100644
index 00000000000..2e6fe41cb0e
--- /dev/null
+++ b/mod/forum/amd/build/grades/expandconversation.min.js
@@ -0,0 +1,2 @@
+define ("mod_forum/grades/expandconversation",["exports","./grader/selectors","mod_forum/repository","core/notification","core/templates","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.registerEventListeners=void 0;b=i(b);c=h(c);e=h(e);f=i(f);g=i(g);function h(a){return a&&a.__esModule?a:{default:a}}function i(a){if(a&&a.__esModule){return a}else{var b={};if(null!=a){for(var c in a){if(Object.prototype.hasOwnProperty.call(a,c)){var d=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(a,c):{};if(d.get||d.set){Object.defineProperty(b,c,d)}else{b[c]=a[c]}}}}b.default=a;return b}}function j(a,b){return m(a)||l(a,b)||k()}function k(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}function l(a,b){var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function m(a){if(Array.isArray(a))return a}function n(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function o(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){n(h,d,e,f,g,"next",a)}function g(a){n(h,d,e,f,g,"throw",a)}f(void 0)})}}var p=function(a){return a.closest(b.expandConversation)},q=function(){var a=o(regeneratorRuntime.mark(function a(b){var d,h,i,k,l,m,n,o;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:d=b.dataset.postid;h=b.dataset.discussionid;i=b.dataset.name;a.next=5;return Promise.all([c.default.getDiscussionPosts(parseInt(h)),f.create({title:i,large:!0,type:f.types.CANCEL})]);case 5:k=a.sent;l=j(k,2);m=l[0];n=l[1];n.getRoot().on(g.hidden,function(){n.destroy()});n.show();o=e.default.render("mod_forum/grades/grader/discussion/post_modal",m);n.setBody(o);o.then(function(){var a=n.getRoot()[0].querySelector("#p".concat(d));if(a){a.scrollIntoView({behavior:"smooth"})}return});case 14:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}();a.registerEventListeners=function registerEventListeners(a){a.addEventListener("click",function(a){var b=p(a.target);if(b){a.preventDefault();try{q(b)}catch(a){(0,d.exception)(a)}}})}});
+//# sourceMappingURL=expandconversation.min.js.map
diff --git a/mod/forum/amd/build/grades/expandconversation.min.js.map b/mod/forum/amd/build/grades/expandconversation.min.js.map
new file mode 100644
index 00000000000..6006f1a7e4b
--- /dev/null
+++ b/mod/forum/amd/build/grades/expandconversation.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../../src/grades/expandconversation.js"],"names":["findGradableNode","node","closest","ForumSelectors","expandConversation","showPostInContext","rootNode","postId","dataset","postid","discussionId","discussionid","discussionName","name","Promise","all","Repository","getDiscussionPosts","parseInt","Modal","create","title","large","type","types","CANCEL","allPosts","modal","getRoot","on","ModalEvents","hidden","destroy","show","templatePromise","Templates","render","setBody","then","relevantPost","querySelector","scrollIntoView","behavior","registerEventListeners","addEventListener","e","target","preventDefault","err"],"mappings":"0SAuBA,OACA,OAEA,OACA,OACA,O,4lCAQMA,CAAAA,CAAgB,CAAG,SAAAC,CAAI,QAAIA,CAAAA,CAAI,CAACC,OAAL,CAAaC,CAAc,CAACC,kBAA5B,CAAJ,C,CAOvBC,CAAiB,4CAAG,WAAMC,CAAN,uGAChBC,CADgB,CACPD,CAAQ,CAACE,OAAT,CAAiBC,MADV,CAEhBC,CAFgB,CAEDJ,CAAQ,CAACE,OAAT,CAAiBG,YAFhB,CAGhBC,CAHgB,CAGCN,CAAQ,CAACE,OAAT,CAAiBK,IAHlB,gBAQZC,CAAAA,OAAO,CAACC,GAAR,CAAY,CAClBC,UAAWC,kBAAX,CAA8BC,QAAQ,CAACR,CAAD,CAAtC,CADkB,CAElBS,CAAK,CAACC,MAAN,CAAa,CACTC,KAAK,CAAET,CADE,CAETU,KAAK,GAFI,CAGTC,IAAI,CAAEJ,CAAK,CAACK,KAAN,CAAYC,MAHT,CAAb,CAFkB,CAAZ,CARY,0BAMlBC,CANkB,MAOlBC,CAPkB,MAkBtBA,CAAK,CAACC,OAAN,GAAgBC,EAAhB,CAAmBC,CAAW,CAACC,MAA/B,CAAuC,UAAW,CAE9CJ,CAAK,CAACK,OAAN,EACH,CAHD,EAKAL,CAAK,CAACM,IAAN,GAGMC,CA1BgB,CA0BEC,UAAUC,MAAV,CAAiB,+CAAjB,CAAkEV,CAAlE,CA1BF,CA2BtBC,CAAK,CAACU,OAAN,CAAcH,CAAd,EAEAA,CAAe,CAACI,IAAhB,CAAqB,UAAM,CACvB,GAAMC,CAAAA,CAAY,CAAGZ,CAAK,CAACC,OAAN,GAAgB,CAAhB,EAAmBY,aAAnB,aAAsCjC,CAAtC,EAArB,CACA,GAAIgC,CAAJ,CAAkB,CACdA,CAAY,CAACE,cAAb,CAA4B,CAACC,QAAQ,CAAE,QAAX,CAA5B,CACH,CAED,MACH,CAPD,EA7BsB,yCAAH,uD,0BA4Ce,QAAzBC,CAAAA,sBAAyB,CAAArC,CAAQ,CAAI,CAC9CA,CAAQ,CAACsC,gBAAT,CAA0B,OAA1B,CAAmC,SAAAC,CAAC,CAAI,CACpC,GAAMvC,CAAAA,CAAQ,CAAGN,CAAgB,CAAC6C,CAAC,CAACC,MAAH,CAAjC,CAEA,GAAIxC,CAAJ,CAAc,CACVuC,CAAC,CAACE,cAAF,GAEA,GAAI,CACA1C,CAAiB,CAACC,CAAD,CACpB,CAAC,MAAO0C,CAAP,CAAY,CACV,gBAAcA,CAAd,CACH,CACJ,CACJ,CAZD,CAaH,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/expandconversation\n * @package mod_forum\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as ForumSelectors from './grader/selectors';\nimport Repository from 'mod_forum/repository';\nimport {exception as showException} from \"core/notification\";\nimport Templates from 'core/templates';\nimport * as Modal from 'core/modal_factory';\nimport * as ModalEvents from 'core/modal_events';\n\n/**\n * Find the Node containing the gradable details from the provided node by searching up the tree.\n *\n * @param {HTMLElement} node\n * @returns {HTMLElement}\n */\nconst findGradableNode = node => node.closest(ForumSelectors.expandConversation);\n\n/**\n * Show the post in context in a modal.\n *\n * @param {HTMLElement} rootNode The button thas clicked\n */\nconst showPostInContext = async rootNode => {\n const postId = rootNode.dataset.postid;\n const discussionId = rootNode.dataset.discussionid;\n const discussionName = rootNode.dataset.name;\n\n const [\n allPosts,\n modal,\n ] = await Promise.all([\n Repository.getDiscussionPosts(parseInt(discussionId)),\n Modal.create({\n title: discussionName,\n large: true,\n type: Modal.types.CANCEL\n }),\n ]);\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Destroy when hidden.\n modal.destroy();\n });\n\n modal.show();\n\n // Note: We do not use await here because it messes with the Modal transitions.\n const templatePromise = Templates.render('mod_forum/grades/grader/discussion/post_modal', allPosts);\n modal.setBody(templatePromise);\n // eslint-disable-next-line promise/catch-or-return\n templatePromise.then(() => {\n const relevantPost = modal.getRoot()[0].querySelector(`#p${postId}`);\n if (relevantPost) {\n relevantPost.scrollIntoView({behavior: \"smooth\"});\n }\n\n return;\n });\n};\n\n/**\n * Register event listeners for the expand conversations button.\n *\n * @param {HTMLElement} rootNode The root to listen to.\n */\nexport const registerEventListeners = rootNode => {\n rootNode.addEventListener('click', e => {\n const rootNode = findGradableNode(e.target);\n\n if (rootNode) {\n e.preventDefault();\n\n try {\n showPostInContext(rootNode);\n } catch (err) {\n showException(err);\n }\n }\n });\n};\n"],"file":"expandconversation.min.js"}
\ No newline at end of file
diff --git a/mod/forum/amd/build/grades/grader/selectors.min.js b/mod/forum/amd/build/grades/grader/selectors.min.js
index 0dd37e9d26a..791d1c9426f 100644
--- a/mod/forum/amd/build/grades/grader/selectors.min.js
+++ b/mod/forum/amd/build/grades/grader/selectors.min.js
@@ -1,2 +1,2 @@
-define ("mod_forum/grades/grader/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={launch:"[data-grade-action=\"launch\"]",gradableItem:"[data-gradable-itemtype]",gradableItems:{wholeForum:"[data-gradable-itemtype=\"forum\"]"}};return a.default});
+define ("mod_forum/grades/grader/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={launch:"[data-grade-action=\"launch\"]",gradableItem:"[data-gradable-itemtype]",gradableItems:{wholeForum:"[data-gradable-itemtype=\"forum\"]"},expandConversation:"[data-action=\"view-context\"]",posts:"[data-region=\"posts\"]"};return a.default});
//# sourceMappingURL=selectors.min.js.map
diff --git a/mod/forum/amd/build/grades/grader/selectors.min.js.map b/mod/forum/amd/build/grades/grader/selectors.min.js.map
index 9e751259720..ec64fb37b1b 100644
--- a/mod/forum/amd/build/grades/grader/selectors.min.js.map
+++ b/mod/forum/amd/build/grades/grader/selectors.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../../../src/grades/grader/selectors.js"],"names":["launch","gradableItem","gradableItems","wholeForum"],"mappings":"6JAuBe,CACXA,MAAM,CAAE,gCADG,CAEXC,YAAY,CAAE,0BAFH,CAGXC,aAAa,CAAE,CACXC,UAAU,CAAE,oCADD,CAHJ,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader/selectors\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n launch: '[data-grade-action=\"launch\"]',\n gradableItem: '[data-gradable-itemtype]',\n gradableItems: {\n wholeForum: '[data-gradable-itemtype=\"forum\"]',\n },\n};\n"],"file":"selectors.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/grades/grader/selectors.js"],"names":["launch","gradableItem","gradableItems","wholeForum","expandConversation","posts"],"mappings":"6JAuBe,CACXA,MAAM,CAAE,gCADG,CAEXC,YAAY,CAAE,0BAFH,CAGXC,aAAa,CAAE,CACXC,UAAU,CAAE,oCADD,CAHJ,CAMXC,kBAAkB,CAAE,gCANT,CAOXC,KAAK,CAAE,yBAPI,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will tie together all of the different calls the gradable module will make.\n *\n * @module mod_forum/grades/grader/selectors\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n launch: '[data-grade-action=\"launch\"]',\n gradableItem: '[data-gradable-itemtype]',\n gradableItems: {\n wholeForum: '[data-gradable-itemtype=\"forum\"]',\n },\n expandConversation: '[data-action=\"view-context\"]',\n posts: '[data-region=\"posts\"]',\n};\n"],"file":"selectors.min.js"}
\ No newline at end of file
diff --git a/mod/forum/amd/build/repository.min.js b/mod/forum/amd/build/repository.min.js
index 4518127126d..cea397ef4d7 100644
--- a/mod/forum/amd/build/repository.min.js
+++ b/mod/forum/amd/build/repository.min.js
@@ -1,2 +1,2 @@
-define ("mod_forum/repository",["core/ajax"],function(a){return{setDiscussionSubscriptionState:function setDiscussionSubscriptionState(b,c,d){return a.call([{methodname:"mod_forum_set_subscription_state",args:{forumid:b,discussionid:c,targetstate:d}}])[0]},addDiscussionPost:function addDiscussionPost(b,c,d,e,f,g){return a.call([{methodname:"mod_forum_add_discussion_post",args:{postid:b,message:d,messageformat:e,subject:c,options:[{name:"private",value:f},{name:"topreferredformat",value:g}]}}])[0]},setDiscussionLockState:function setDiscussionLockState(b,c,d){return a.call([{methodname:"mod_forum_set_lock_state",args:{forumid:b,discussionid:c,targetstate:d}}])[0]},setFavouriteDiscussionState:function setFavouriteDiscussionState(b,c,d){return a.call([{methodname:"mod_forum_toggle_favourite_state",args:{discussionid:c,targetstate:d}}])[0]},setPinDiscussionState:function setPinDiscussionState(b,c,d){return a.call([{methodname:"mod_forum_set_pin_state",args:{discussionid:c,targetstate:d}}])[0]},getDiscussionByUserID:function getDiscussionByUserID(b,c){var d=2.\n\n/**\n * Forum repository class to encapsulate all of the AJAX requests that subscribe or unsubscribe\n * can be sent for forum.\n *\n * @module mod_forum/repository\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/ajax'], function(Ajax) {\n /**\n * Set the subscription state for a discussion in a forum.\n *\n * @param {number} forumId ID of the forum the discussion belongs to\n * @param {number} discussionId ID of the discussion with the subscription state\n * @param {boolean} targetState Set the subscribed state. True == subscribed; false == unsubscribed.\n * @return {object} jQuery promise\n */\n var setDiscussionSubscriptionState = function(forumId, discussionId, targetState) {\n var request = {\n methodname: 'mod_forum_set_subscription_state',\n args: {\n forumid: forumId,\n discussionid: discussionId,\n targetstate: targetState\n }\n };\n return Ajax.call([request])[0];\n };\n\n var addDiscussionPost = function(postid, subject, message, messageformat, isprivatereply, topreferredformat) {\n var request = {\n methodname: 'mod_forum_add_discussion_post',\n args: {\n postid: postid,\n message: message,\n messageformat: messageformat,\n subject: subject,\n options: [{\n name: \"private\",\n value: isprivatereply,\n }, {\n name: \"topreferredformat\",\n value: topreferredformat,\n }]\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Set the favourite state for a discussion in a forum.\n *\n * @param {number} forumId ID of the forum the discussion belongs to\n * @param {number} discussionId ID of the discussion with the subscription state\n * @param {null|date} targetState Set the favourite state. True == favourited; false == unfavourited.\n * @return {object} jQuery promise\n */\n var setFavouriteDiscussionState = function(forumId, discussionId, targetState) {\n var request = {\n methodname: 'mod_forum_toggle_favourite_state',\n args: {\n discussionid: discussionId,\n targetstate: targetState\n }\n };\n return Ajax.call([request])[0];\n };\n\n var setDiscussionLockState = function(forumId, discussionId, targetState) {\n var request = {\n methodname: 'mod_forum_set_lock_state',\n args: {\n forumid: forumId,\n discussionid: discussionId,\n targetstate: targetState}\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Set the pinned state for the discussion provided.\n *\n * @param {number} forumid\n * @param {number} discussionid\n * @param {boolean} targetstate\n * @return {*|Promise}\n */\n var setPinDiscussionState = function(forumid, discussionid, targetstate) {\n var request = {\n methodname: 'mod_forum_set_pin_state',\n args: {\n discussionid: discussionid,\n targetstate: targetstate\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get the discussions for the user and cmid provided.\n *\n * @param {number} userid\n * @param {number} cmid\n * @param {string} sortby\n * @param {string} sortdirection\n * @return {*|Promise}\n */\n var getDiscussionByUserID = function(userid, cmid, sortby = 'modified', sortdirection = 'DESC') {\n var request = {\n methodname: 'mod_forum_get_discussion_posts_by_userid',\n args: {\n userid: userid,\n cmid: cmid,\n sortby: sortby,\n sortdirection: sortdirection,\n },\n };\n return Ajax.call([request])[0];\n };\n\n return {\n setDiscussionSubscriptionState: setDiscussionSubscriptionState,\n addDiscussionPost: addDiscussionPost,\n setDiscussionLockState: setDiscussionLockState,\n setFavouriteDiscussionState: setFavouriteDiscussionState,\n setPinDiscussionState: setPinDiscussionState,\n getDiscussionByUserID: getDiscussionByUserID\n };\n});\n"],"file":"repository.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/repository.js"],"names":["define","Ajax","setDiscussionSubscriptionState","forumId","discussionId","targetState","call","methodname","args","forumid","discussionid","targetstate","addDiscussionPost","postid","subject","message","messageformat","isprivatereply","topreferredformat","options","name","value","setDiscussionLockState","setFavouriteDiscussionState","setPinDiscussionState","getDiscussionByUserID","userid","cmid","sortby","sortdirection","getDiscussionPosts"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Forum repository class to encapsulate all of the AJAX requests that subscribe or unsubscribe\n * can be sent for forum.\n *\n * @module mod_forum/repository\n * @package mod_forum\n * @copyright 2019 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['core/ajax'], function(Ajax) {\n /**\n * Set the subscription state for a discussion in a forum.\n *\n * @param {number} forumId ID of the forum the discussion belongs to\n * @param {number} discussionId ID of the discussion with the subscription state\n * @param {boolean} targetState Set the subscribed state. True == subscribed; false == unsubscribed.\n * @return {object} jQuery promise\n */\n var setDiscussionSubscriptionState = function(forumId, discussionId, targetState) {\n var request = {\n methodname: 'mod_forum_set_subscription_state',\n args: {\n forumid: forumId,\n discussionid: discussionId,\n targetstate: targetState\n }\n };\n return Ajax.call([request])[0];\n };\n\n var addDiscussionPost = function(postid, subject, message, messageformat, isprivatereply, topreferredformat) {\n var request = {\n methodname: 'mod_forum_add_discussion_post',\n args: {\n postid: postid,\n message: message,\n messageformat: messageformat,\n subject: subject,\n options: [{\n name: \"private\",\n value: isprivatereply,\n }, {\n name: \"topreferredformat\",\n value: topreferredformat,\n }]\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Set the favourite state for a discussion in a forum.\n *\n * @param {number} forumId ID of the forum the discussion belongs to\n * @param {number} discussionId ID of the discussion with the subscription state\n * @param {null|date} targetState Set the favourite state. True == favourited; false == unfavourited.\n * @return {object} jQuery promise\n */\n var setFavouriteDiscussionState = function(forumId, discussionId, targetState) {\n var request = {\n methodname: 'mod_forum_toggle_favourite_state',\n args: {\n discussionid: discussionId,\n targetstate: targetState\n }\n };\n return Ajax.call([request])[0];\n };\n\n var setDiscussionLockState = function(forumId, discussionId, targetState) {\n var request = {\n methodname: 'mod_forum_set_lock_state',\n args: {\n forumid: forumId,\n discussionid: discussionId,\n targetstate: targetState}\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Set the pinned state for the discussion provided.\n *\n * @param {number} forumid\n * @param {number} discussionid\n * @param {boolean} targetstate\n * @return {*|Promise}\n */\n var setPinDiscussionState = function(forumid, discussionid, targetstate) {\n var request = {\n methodname: 'mod_forum_set_pin_state',\n args: {\n discussionid: discussionid,\n targetstate: targetstate\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get the discussions for the user and cmid provided.\n *\n * @param {number} userid\n * @param {number} cmid\n * @param {string} sortby\n * @param {string} sortdirection\n * @return {*|Promise}\n */\n var getDiscussionByUserID = function(userid, cmid, sortby = 'modified', sortdirection = 'DESC') {\n var request = {\n methodname: 'mod_forum_get_discussion_posts_by_userid',\n args: {\n userid: userid,\n cmid: cmid,\n sortby: sortby,\n sortdirection: sortdirection,\n },\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get the posts for the discussion ID provided.\n *\n * @param {number} discussionId\n * @return {*|Promise}\n */\n var getDiscussionPosts = function(discussionId) {\n var request = {\n methodname: 'mod_forum_get_discussion_posts',\n args: {\n discussionid: discussionId,\n },\n };\n return Ajax.call([request])[0];\n };\n\n return {\n setDiscussionSubscriptionState: setDiscussionSubscriptionState,\n addDiscussionPost: addDiscussionPost,\n setDiscussionLockState: setDiscussionLockState,\n setFavouriteDiscussionState: setFavouriteDiscussionState,\n setPinDiscussionState: setPinDiscussionState,\n getDiscussionByUserID: getDiscussionByUserID,\n getDiscussionPosts: getDiscussionPosts,\n };\n});\n"],"file":"repository.min.js"}
\ No newline at end of file
diff --git a/mod/forum/amd/src/grades/expandconversation.js b/mod/forum/amd/src/grades/expandconversation.js
new file mode 100644
index 00000000000..c6cbcc53747
--- /dev/null
+++ b/mod/forum/amd/src/grades/expandconversation.js
@@ -0,0 +1,102 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+/**
+ * This module will tie together all of the different calls the gradable module will make.
+ *
+ * @module mod_forum/grades/expandconversation
+ * @package mod_forum
+ * @copyright 2019 Mathew May
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+import * as ForumSelectors from './grader/selectors';
+import Repository from 'mod_forum/repository';
+import {exception as showException} from "core/notification";
+import Templates from 'core/templates';
+import * as Modal from 'core/modal_factory';
+import * as ModalEvents from 'core/modal_events';
+
+/**
+ * Find the Node containing the gradable details from the provided node by searching up the tree.
+ *
+ * @param {HTMLElement} node
+ * @returns {HTMLElement}
+ */
+const findGradableNode = node => node.closest(ForumSelectors.expandConversation);
+
+/**
+ * Show the post in context in a modal.
+ *
+ * @param {HTMLElement} rootNode The button thas clicked
+ */
+const showPostInContext = async rootNode => {
+ const postId = rootNode.dataset.postid;
+ const discussionId = rootNode.dataset.discussionid;
+ const discussionName = rootNode.dataset.name;
+
+ const [
+ allPosts,
+ modal,
+ ] = await Promise.all([
+ Repository.getDiscussionPosts(parseInt(discussionId)),
+ Modal.create({
+ title: discussionName,
+ large: true,
+ type: Modal.types.CANCEL
+ }),
+ ]);
+
+ // Handle hidden event.
+ modal.getRoot().on(ModalEvents.hidden, function() {
+ // Destroy when hidden.
+ modal.destroy();
+ });
+
+ modal.show();
+
+ // Note: We do not use await here because it messes with the Modal transitions.
+ const templatePromise = Templates.render('mod_forum/grades/grader/discussion/post_modal', allPosts);
+ modal.setBody(templatePromise);
+ // eslint-disable-next-line promise/catch-or-return
+ templatePromise.then(() => {
+ const relevantPost = modal.getRoot()[0].querySelector(`#p${postId}`);
+ if (relevantPost) {
+ relevantPost.scrollIntoView({behavior: "smooth"});
+ }
+
+ return;
+ });
+};
+
+/**
+ * Register event listeners for the expand conversations button.
+ *
+ * @param {HTMLElement} rootNode The root to listen to.
+ */
+export const registerEventListeners = rootNode => {
+ rootNode.addEventListener('click', e => {
+ const rootNode = findGradableNode(e.target);
+
+ if (rootNode) {
+ e.preventDefault();
+
+ try {
+ showPostInContext(rootNode);
+ } catch (err) {
+ showException(err);
+ }
+ }
+ });
+};
diff --git a/mod/forum/amd/src/grades/grader/selectors.js b/mod/forum/amd/src/grades/grader/selectors.js
index 5a4ed490f0f..65e87d2c1dc 100644
--- a/mod/forum/amd/src/grades/grader/selectors.js
+++ b/mod/forum/amd/src/grades/grader/selectors.js
@@ -27,4 +27,6 @@ export default {
gradableItems: {
wholeForum: '[data-gradable-itemtype="forum"]',
},
+ expandConversation: '[data-action="view-context"]',
+ posts: '[data-region="posts"]',
};
diff --git a/mod/forum/amd/src/repository.js b/mod/forum/amd/src/repository.js
index 5cb8cd0ab1a..fceeef73649 100644
--- a/mod/forum/amd/src/repository.js
+++ b/mod/forum/amd/src/repository.js
@@ -134,12 +134,29 @@ define(['core/ajax'], function(Ajax) {
return Ajax.call([request])[0];
};
+ /**
+ * Get the posts for the discussion ID provided.
+ *
+ * @param {number} discussionId
+ * @return {*|Promise}
+ */
+ var getDiscussionPosts = function(discussionId) {
+ var request = {
+ methodname: 'mod_forum_get_discussion_posts',
+ args: {
+ discussionid: discussionId,
+ },
+ };
+ return Ajax.call([request])[0];
+ };
+
return {
setDiscussionSubscriptionState: setDiscussionSubscriptionState,
addDiscussionPost: addDiscussionPost,
setDiscussionLockState: setDiscussionLockState,
setFavouriteDiscussionState: setFavouriteDiscussionState,
setPinDiscussionState: setPinDiscussionState,
- getDiscussionByUserID: getDiscussionByUserID
+ getDiscussionByUserID: getDiscussionByUserID,
+ getDiscussionPosts: getDiscussionPosts,
};
});
diff --git a/mod/forum/db/services.php b/mod/forum/db/services.php
index 78c4e9f94b5..fba90c7a19b 100644
--- a/mod/forum/db/services.php
+++ b/mod/forum/db/services.php
@@ -43,6 +43,7 @@ $functions = array(
'classpath' => 'mod/forum/externallib.php',
'description' => 'Returns a list of forum posts for a discussion.',
'type' => 'read',
+ 'ajax' => true,
'capabilities' => 'mod/forum:viewdiscussion, mod/forum:viewqandawithoutposting',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
diff --git a/mod/forum/externallib.php b/mod/forum/externallib.php
index b692db429c6..47f5bc346bc 100644
--- a/mod/forum/externallib.php
+++ b/mod/forum/externallib.php
@@ -188,6 +188,8 @@ class mod_forum_external extends external_api {
$forumvault = $vaultfactory->get_forum_vault();
$forum = $forumvault->get_from_id($discussion->get_forum_id());
+ $context = $forum->get_context();
+ self::validate_context($context);
$sortby = $params['sortby'];
$sortdirection = $params['sortdirection'];
diff --git a/mod/forum/templates/grades/grader/discussion/post_modal.mustache b/mod/forum/templates/grades/grader/discussion/post_modal.mustache
new file mode 100644
index 00000000000..ab2b4ff77a6
--- /dev/null
+++ b/mod/forum/templates/grades/grader/discussion/post_modal.mustache
@@ -0,0 +1,36 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_forum/forum_discussion_post_modal
+
+ Template to render a single post from a discussion.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Example context (json):
+ {
+ }
+}}
+