From 9b4f09bad2d6e4af7492ea65124e571b21b5d919 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 11 Mar 2019 09:22:57 +0800 Subject: [PATCH] MDL-64956 mod_forum: Inpage reply * Basic functionality for an inpage reply. With advanced link that takes you to the previous "Reply page". * Updated behat function to handle the additional step * Updated templates to have some identifiers on where one is storing the replies post * Updated discussion_post to use the new vault functions to get the corresponding records * Jumpto newly created post --- mod/forum/amd/build/inpage_reply.min.js | 1 + mod/forum/amd/build/posts_list.min.js | 1 + mod/forum/amd/build/repository.min.js | 2 +- mod/forum/amd/build/selectors.min.js | 2 +- mod/forum/amd/src/inpage_reply.js | 111 ++++++++++++++++++ mod/forum/amd/src/posts_list.js | 78 ++++++++++++ mod/forum/amd/src/repository.js | 14 +++ mod/forum/amd/src/selectors.js | 13 +- mod/forum/db/services.php | 1 + mod/forum/externallib.php | 51 ++++++-- mod/forum/lang/en/forum.php | 2 + mod/forum/post.php | 10 +- mod/forum/templates/discussion_list.mustache | 44 +++---- mod/forum/templates/forum_discussion.mustache | 8 +- .../templates/forum_discussion_post.mustache | 22 ++-- .../forum_discussion_threaded_post.mustache | 11 +- .../forum_discussion_threaded_posts.mustache | 2 +- mod/forum/templates/inpage_reply.mustache | 56 +++++++++ mod/forum/tests/behat/behat_mod_forum.php | 22 ++++ .../tests/behat/discussion_display.feature | 2 +- mod/forum/tests/behat/inpage_reply.feature | 34 ++++++ 21 files changed, 428 insertions(+), 59 deletions(-) create mode 100644 mod/forum/amd/build/inpage_reply.min.js create mode 100644 mod/forum/amd/build/posts_list.min.js create mode 100644 mod/forum/amd/src/inpage_reply.js create mode 100644 mod/forum/amd/src/posts_list.js create mode 100644 mod/forum/templates/inpage_reply.mustache create mode 100644 mod/forum/tests/behat/inpage_reply.feature diff --git a/mod/forum/amd/build/inpage_reply.min.js b/mod/forum/amd/build/inpage_reply.min.js new file mode 100644 index 00000000000..fa33552edb2 --- /dev/null +++ b/mod/forum/amd/build/inpage_reply.min.js @@ -0,0 +1 @@ +define(["jquery","core/templates","core/notification","mod_forum/repository","mod_forum/selectors"],function(a,b,c,d,e){var f={THREADED:2,NESTED:3,FLAT_OLDEST_FIRST:1,FLAT_NEWEST_FIRST:-1},g=function(g){g.on("click",e.post.inpageSubmitBtn,function(h){h.preventDefault();var i,j=a(h.currentTarget).parents(e.post.inpageReplyForm).get(0),k=j.elements.post.value,l=j.elements.reply.value,m=j.elements.subject.value,n=a(h.currentTarget).parents(e.post.forumContent),o=parseInt(g.find(e.post.modeSelect).get(0).value);d.addDiscussionPost(l,m,k).then(function(a){var c=a.post;switch(i=c.id,o){case f.THREADED:return b.render("mod_forum/forum_discussion_threaded_post",c);case f.NESTED:return b.render("mod_forum/forum_discussion_nested_post",c);default:return b.render("mod_forum/forum_discussion_post",c)}}).then(function(a,c){var d;return o!=f.FLAT_OLDEST_FIRST&&o!=f.FLAT_NEWEST_FIRST||(d=n.parents(e.post.repliesContainer).children().get(0)),void 0==d&&(d=n.siblings(e.post.repliesContainer).children().get(0)),o==f.FLAT_NEWEST_FIRST?b.prependNodeContents(d,a,c):b.appendNodeContents(d,a,c)}).then(function(){return n.find(e.post.inpageReplyContent).hide()}).then(function(){var b="[data-target='"+i+"-target']";a(b).length&&a("body").animate({scrollTop:a(b).offset().top-60})}).fail(c.exception)})};return{init:function(a){g(a)}}}); \ No newline at end of file diff --git a/mod/forum/amd/build/posts_list.min.js b/mod/forum/amd/build/posts_list.min.js new file mode 100644 index 00000000000..0908103247a --- /dev/null +++ b/mod/forum/amd/build/posts_list.min.js @@ -0,0 +1 @@ +define(["jquery","core/templates","core/notification","mod_forum/selectors","mod_forum/inpage_reply"],function(a,b,c,d,e){var f=function(e){e.on("click",d.post.inpageReplyLink,function(e){e.preventDefault();var f=a(e.currentTarget).parents(d.post.forumCoreContent),g=a(e.currentTarget).parents(d.post.forumContent),h={postid:a(g).data("post-id"),reply_url:a(e.currentTarget).attr("href"),sesskey:M.cfg.sesskey};if(g.find(d.post.inpageReplyContent).length){var i=g.find(d.post.inpageReplyContent);i.toggle(),i.is(":visible")&&i.find("textarea").focus()}else b.render("mod_forum/inpage_reply",h).then(function(a,c){return b.appendNodeContents(f,a,c)}).then(function(){g.find(d.post.inpageReplyContent).toggle().find("textarea").focus()}).fail(c.exception)})};return{init:function(a){f(a),e.init(a)}}}); \ 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 2b3fcee891d..3c7817fac2e 100644 --- a/mod/forum/amd/build/repository.min.js +++ b/mod/forum/amd/build/repository.min.js @@ -1 +1 @@ -define(["core/ajax"],function(a){var b=function(b,c,d){var e={methodname:"mod_forum_set_subscription_state",args:{forumid:b,discussionid:c,targetstate:d}};return a.call([e])[0]};return{setDiscussionSubscriptionState:b}}); \ No newline at end of file +define(["core/ajax"],function(a){var b=function(b,c,d){var e={methodname:"mod_forum_set_subscription_state",args:{forumid:b,discussionid:c,targetstate:d}};return a.call([e])[0]},c=function(b,c,d){var e={methodname:"mod_forum_add_discussion_post",args:{postid:b,message:d,subject:c}};return a.call([e])[0]};return{setDiscussionSubscriptionState:b,addDiscussionPost:c}}); \ No newline at end of file diff --git a/mod/forum/amd/build/selectors.min.js b/mod/forum/amd/build/selectors.min.js index bb3a8f98d58..ffe5084bdc6 100644 --- a/mod/forum/amd/build/selectors.min.js +++ b/mod/forum/amd/build/selectors.min.js @@ -1 +1 @@ -define([],function(){return{subscription:{toggle:"[data-type='subscription-toggle'][data-action='toggle']"},post:{post:'[data-region="post"]',action:'[data-region="post-action"]',actionsContainer:'[data-region="post-actions-container"]'}}}); \ No newline at end of file +define([],function(){return{subscription:{toggle:"[data-type='subscription-toggle'][data-action='toggle']"},pin:{toggle:".pindiscussion [data-action='toggle']"},post:{post:'[data-region="post"]',action:'[data-region="post-action"]',actionsContainer:'[data-region="post-actions-container"]',forumCoreContent:"[data-region-content='forum-post-core']",forumContent:"[data-content='forum-post']",inpageReplyLink:"[data-action='collapsible-link']",inpageReplyContent:"[data-content='inpage-reply-content']",inpageReplyForm:"form[data-content='inpage-reply-form']",inpageSubmitBtn:"[data-action='forum-inpage-submit']",repliesContainer:"[data-region='replies-container']",modeSelect:"select[name='mode']"}}}); \ No newline at end of file diff --git a/mod/forum/amd/src/inpage_reply.js b/mod/forum/amd/src/inpage_reply.js new file mode 100644 index 00000000000..d67da7b797f --- /dev/null +++ b/mod/forum/amd/src/inpage_reply.js @@ -0,0 +1,111 @@ +// 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 is the highest level module for the calendar. It is + * responsible for initialising all of the components required for + * the calendar to run. It also coordinates the interaction between + * components by listening for and responding to different events + * triggered within the calendar UI. + * + * @module mod_forum/posts_list + * @package mod_forum + * @copyright 2019 Peter Dias + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define([ + 'jquery', + 'core/templates', + 'core/notification', + 'mod_forum/repository', + 'mod_forum/selectors', + ], function( + $, + Templates, + Notification, + Repository, + Selectors + ) { + + var DISPLAYCONSTANTS = { + THREADED: 2, + NESTED: 3, + FLAT_OLDEST_FIRST: 1, + FLAT_NEWEST_FIRST: -1 + }; + + var registerEventListeners = function(root) { + root.on('click', Selectors.post.inpageSubmitBtn, function(e) { + e.preventDefault(); + var form = $(e.currentTarget).parents(Selectors.post.inpageReplyForm).get(0); + var message = form.elements.post.value; + var postid = form.elements.reply.value; + var subject = form.elements.subject.value; + var currentRoot = $(e.currentTarget).parents(Selectors.post.forumContent); + var mode = parseInt(root.find(Selectors.post.modeSelect).get(0).value); + var newid; + + Repository.addDiscussionPost(postid, subject, message) + .then(function(context) { + var post = context.post; + newid = post.id; + switch (mode) { + case DISPLAYCONSTANTS.THREADED: + return Templates.render('mod_forum/forum_discussion_threaded_post', post); + case DISPLAYCONSTANTS.NESTED: + return Templates.render('mod_forum/forum_discussion_nested_post', post); + default: + return Templates.render('mod_forum/forum_discussion_post', post); + } + }) + .then(function(html, js) { + var repliesnode; + + // Try and get the replies-container which can either be a sibling OR parent if it's flat + if (mode == DISPLAYCONSTANTS.FLAT_OLDEST_FIRST || mode == DISPLAYCONSTANTS.FLAT_NEWEST_FIRST) { + repliesnode = currentRoot.parents(Selectors.post.repliesContainer).children().get(0); + } + + if (repliesnode == undefined) { + repliesnode = currentRoot.siblings(Selectors.post.repliesContainer).children().get(0); + } + + if (mode == DISPLAYCONSTANTS.FLAT_NEWEST_FIRST) { + return Templates.prependNodeContents(repliesnode, html, js); + } else { + return Templates.appendNodeContents(repliesnode, html, js); + } + }) + .then(function() { + return currentRoot.find(Selectors.post.inpageReplyContent).hide(); + }) + .then(function() { + var target = "[data-target='" + newid + "-target']"; + if ($(target).length) { + $('body').animate({scrollTop: $(target).offset().top - 60}); + } + return; + }) + .fail(Notification.exception); + }); + }; + + return { + init: function(root) { + registerEventListeners(root); + + } + }; +}); diff --git a/mod/forum/amd/src/posts_list.js b/mod/forum/amd/src/posts_list.js new file mode 100644 index 00000000000..4575e4626f0 --- /dev/null +++ b/mod/forum/amd/src/posts_list.js @@ -0,0 +1,78 @@ +// 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 is the highest level module for the calendar. It is + * responsible for initialising all of the components required for + * the calendar to run. It also coordinates the interaction between + * components by listening for and responding to different events + * triggered within the calendar UI. + * + * @module mod_forum/posts_list + * @package mod_forum + * @copyright 2019 Peter Dias + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define([ + 'jquery', + 'core/templates', + 'core/notification', + 'mod_forum/selectors', + 'mod_forum/inpage_reply', + ], function( + $, + Templates, + Notification, + Selectors, + InPageReply + ) { + + var registerEventListeners = function(root) { + root.on('click', Selectors.post.inpageReplyLink, function(e) { + e.preventDefault(); + var currentTarget = $(e.currentTarget).parents(Selectors.post.forumCoreContent); + var currentRoot = $(e.currentTarget).parents(Selectors.post.forumContent); + var context = { + postid: $(currentRoot).data('post-id'), + reply_url: $(e.currentTarget).attr('href'), + sesskey: M.cfg.sesskey + }; + + if (!currentRoot.find(Selectors.post.inpageReplyContent).length) { + Templates.render('mod_forum/inpage_reply', context) + .then(function(html, js) { + return Templates.appendNodeContents(currentTarget, html, js); + }) + .then(function() { + currentRoot.find(Selectors.post.inpageReplyContent).toggle().find('textarea').focus(); + }) + .fail(Notification.exception); + } else { + var form = currentRoot.find(Selectors.post.inpageReplyContent); + form.toggle(); + if (form.is(':visible')) { + form.find('textarea').focus(); + } + } + }); + }; + + return { + init: function(root) { + registerEventListeners(root); + InPageReply.init(root); + } + }; +}); diff --git a/mod/forum/amd/src/repository.js b/mod/forum/amd/src/repository.js index c09a6d868cc..d8a375847d2 100644 --- a/mod/forum/amd/src/repository.js +++ b/mod/forum/amd/src/repository.js @@ -43,7 +43,21 @@ define(['core/ajax'], function(Ajax) { return Ajax.call([request])[0]; }; + var addDiscussionPost = function(postid, subject, message) { + var request = { + methodname: 'mod_forum_add_discussion_post', + args: { + postid: postid, + message: message, + subject: subject + } + }; + + return Ajax.call([request])[0]; + }; + return { setDiscussionSubscriptionState: setDiscussionSubscriptionState, + addDiscussionPost: addDiscussionPost }; }); diff --git a/mod/forum/amd/src/selectors.js b/mod/forum/amd/src/selectors.js index b8d935a1460..ebc63a90866 100644 --- a/mod/forum/amd/src/selectors.js +++ b/mod/forum/amd/src/selectors.js @@ -26,10 +26,21 @@ define([], function() { subscription: { toggle: "[data-type='subscription-toggle'][data-action='toggle']", }, + pin: { + toggle: ".pindiscussion [data-action='toggle']", + }, post: { post: '[data-region="post"]', action: '[data-region="post-action"]', - actionsContainer: '[data-region="post-actions-container"]' + actionsContainer: '[data-region="post-actions-container"]', + forumCoreContent: "[data-region-content='forum-post-core']", + forumContent: "[data-content='forum-post']", + inpageReplyLink: "[data-action='collapsible-link']", + inpageReplyContent: "[data-content='inpage-reply-content']", + inpageReplyForm: "form[data-content='inpage-reply-form']", + inpageSubmitBtn: "[data-action='forum-inpage-submit']", + repliesContainer: "[data-region='replies-container']", + modeSelect: "select[name='mode']", } }; }); diff --git a/mod/forum/db/services.php b/mod/forum/db/services.php index 39072206066..e2bafac8ed2 100644 --- a/mod/forum/db/services.php +++ b/mod/forum/db/services.php @@ -93,6 +93,7 @@ $functions = array( 'classpath' => 'mod/forum/externallib.php', 'description' => 'Create new posts into an existing discussion.', 'type' => 'write', + 'ajax' => true, 'capabilities' => 'mod/forum:replypost', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) ), diff --git a/mod/forum/externallib.php b/mod/forum/externallib.php index 72bc0146dbb..cfc5b4bfb8f 100644 --- a/mod/forum/externallib.php +++ b/mod/forum/externallib.php @@ -27,6 +27,8 @@ defined('MOODLE_INTERNAL') || die; require_once("$CFG->libdir/externallib.php"); +use mod_forum\local\exporters\post as post_exporter; + class mod_forum_external extends external_api { /** @@ -917,6 +919,16 @@ class mod_forum_external extends external_api { global $DB, $CFG, $USER; require_once($CFG->dirroot . "/mod/forum/lib.php"); + // Get all the factories that are required. + $vaultfactory = mod_forum\local\container::get_vault_factory(); + $entityfactory = mod_forum\local\container::get_entity_factory(); + $datamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory(); + $managerfactory = mod_forum\local\container::get_manager_factory(); + $discussionvault = $vaultfactory->get_discussion_vault(); + $forumvault = $vaultfactory->get_forum_vault(); + $discussiondatamapper = $datamapperfactory->get_discussion_data_mapper(); + $forumdatamapper = $datamapperfactory->get_forum_data_mapper(); + $params = self::validate_parameters(self::add_discussion_post_parameters(), array( 'postid' => $postid, @@ -932,14 +944,18 @@ class mod_forum_external extends external_api { throw new moodle_exception('invalidparentpostid', 'forum'); } - if (!$discussion = $DB->get_record("forum_discussions", array("id" => $parent->discussion))) { + if (!$discussion = $discussionvault->get_from_id($parent->discussion)) { throw new moodle_exception('notpartofdiscussion', 'forum'); } // Request and permission validation. - $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST); - list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum'); + $forum = $forumvault->get_from_id($discussion->get_forum_id()); + $capabilitymanager = $managerfactory->get_capability_manager($forum); + $course = $forum->get_course_record(); + $cm = $forum->get_course_module_record(); + $discussionrecord = $discussiondatamapper->to_legacy_object($discussion); + $forumrecord = $forumdatamapper->to_legacy_object($forum); $context = context_module::instance($cm->id); self::validate_context($context); @@ -975,16 +991,16 @@ class mod_forum_external extends external_api { $options[$name] = $value; } - if (!forum_user_can_post($forum, $discussion, $USER, $cm, $course, $context)) { + if (!$capabilitymanager->can_post_in_discussion($USER, $discussion)) { throw new moodle_exception('nopostforum', 'forum'); } - $thresholdwarning = forum_check_throttling($forum, $cm); + $thresholdwarning = forum_check_throttling($forumrecord, $cm); forum_check_blocking_threshold($thresholdwarning); // Create the post. $post = new stdClass(); - $post->discussion = $discussion->id; + $post->discussion = $discussion->get_id(); $post->parent = $parent->id; $post->subject = $params['subject']; $post->message = $params['message']; @@ -1004,33 +1020,41 @@ class mod_forum_external extends external_api { 'context' => $context, 'objectid' => $post->id, 'other' => array( - 'discussionid' => $discussion->id, - 'forumid' => $forum->id, - 'forumtype' => $forum->type, + 'discussionid' => $discussion->get_id(), + 'forumid' => $forum->get_id(), + 'forumtype' => $forum->get_type(), ) ); $event = \mod_forum\event\post_created::create($params); $event->add_record_snapshot('forum_posts', $post); - $event->add_record_snapshot('forum_discussions', $discussion); + $event->add_record_snapshot('forum_discussions', $discussionrecord); $event->trigger(); // Update completion state. $completion = new completion_info($course); if ($completion->is_enabled($cm) && - ($forum->completionreplies || $forum->completionposts)) { + ($forum->get_completion_replies() || $forum->get_completion_posts())) { $completion->update_state($cm, COMPLETION_COMPLETE); } $settings = new stdClass(); $settings->discussionsubscribe = $options['discussionsubscribe']; - forum_post_subscription($settings, $forum, $discussion); + forum_post_subscription($settings, $forumrecord, $discussionrecord); } else { throw new moodle_exception('couldnotadd', 'forum'); } + + $builderfactory = \mod_forum\local\container::get_builder_factory(); + $exportedpostsbuilder = $builderfactory->get_exported_posts_builder(); + $postentity = $entityfactory->get_post_from_stdClass($post); + $exportedposts = $exportedpostsbuilder->build($USER, [$forum], [$discussion], [$postentity]); + $exportedpost = $exportedposts[0]; + $result = array(); $result['postid'] = $postid; $result['warnings'] = $warnings; + $result['post'] = $exportedpost; return $result; } @@ -1044,7 +1068,8 @@ class mod_forum_external extends external_api { return new external_single_structure( array( 'postid' => new external_value(PARAM_INT, 'new post id'), - 'warnings' => new external_warnings() + 'warnings' => new external_warnings(), + 'post' => post_exporter::get_read_structure() ) ); } diff --git a/mod/forum/lang/en/forum.php b/mod/forum/lang/en/forum.php index 0d40c174bcc..b1765400ddf 100644 --- a/mod/forum/lang/en/forum.php +++ b/mod/forum/lang/en/forum.php @@ -28,6 +28,7 @@ $string['addanewdiscussion'] = 'Add a new discussion topic'; $string['addanewquestion'] = 'Add a new question'; $string['addanewtopic'] = 'Add a new topic'; $string['advancedsearch'] = 'Advanced search'; +$string['advanced'] = 'Advanced'; $string['allforums'] = 'All forums'; $string['allowdiscussions'] = 'Can a {$a} post to this forum?'; $string['allowsallsubscribe'] = 'This forum allows everyone to choose whether to subscribe or not'; @@ -525,6 +526,7 @@ $string['replies'] = 'Replies'; $string['repliesmany'] = '{$a} replies so far'; $string['repliesone'] = '{$a} reply so far'; $string['reply'] = 'Reply'; +$string['replyplaceholder'] = 'Write your reply...'; $string['replyforum'] = 'Reply to forum'; $string['replytopostbyemail'] = 'You can reply to this via email.'; $string['replytouser'] = 'Use email address in reply'; diff --git a/mod/forum/post.php b/mod/forum/post.php index 4daa039ea3d..4aa76e77057 100644 --- a/mod/forum/post.php +++ b/mod/forum/post.php @@ -34,6 +34,8 @@ $prune = optional_param('prune', 0, PARAM_INT); $name = optional_param('name', '', PARAM_CLEAN); $confirm = optional_param('confirm', 0, PARAM_INT); $groupid = optional_param('groupid', null, PARAM_INT); +$subject = optional_param('subject', '', PARAM_TEXT); +$prefilledpost = optional_param('post', '', PARAM_TEXT); $PAGE->set_url('/mod/forum/post.php', array( 'reply' => $reply, @@ -163,9 +165,9 @@ if (!empty($forum)) { $post->forum = $forum->id; $post->discussion = 0; // Ie discussion # not defined yet. $post->parent = 0; - $post->subject = ''; + $post->subject = $subject; $post->userid = $USER->id; - $post->message = ''; + $post->message = $prefilledpost; $post->messageformat = editors_get_preferred_format(); $post->messagetrust = 0; $post->groupid = $groupid; @@ -249,10 +251,10 @@ if (!empty($forum)) { $post->forum = $forum->id; $post->discussion = $parent->discussion; $post->parent = $parent->id; - $post->subject = $parent->subject; + $post->subject = $subject ? $subject : $parent->subject; $post->userid = $USER->id; - $post->message = ''; $post->parentpostauthor = $parent->userid; + $post->message = $prefilledpost; $canreplyprivately = $capabilitymanager->can_reply_privately_to_post($USER, $parententity); $post->groupid = ($discussion->groupid == -1) ? 0 : $discussion->groupid; diff --git a/mod/forum/templates/discussion_list.mustache b/mod/forum/templates/discussion_list.mustache index bbc91625379..58b0cc38586 100644 --- a/mod/forum/templates/discussion_list.mustache +++ b/mod/forum/templates/discussion_list.mustache @@ -58,7 +58,6 @@ {{$discussion_list_header}} -     {{#str}}discussion, mod_forum{{/str}} {{#str}}startedby, mod_forum{{/str}} @@ -75,6 +74,7 @@ {{/forum.userstate.tracked}} {{/forum.capabilities.viewdiscussions}} {{#str}}lastpost, mod_forum{{/str}} +   {{#forum.capabilities.subscribe}} {{/forum.capabilities.subscribe}} @@ -91,27 +91,6 @@ {{#pix}}i/pinned, mod_forum, {{#str}}discussionpinned, mod_forum{{/str}}{{/pix}} {{/discussion.pinned}} - - {{#discussion.timed.istimed}} -
- {{#pix}} - i/calendar, moodle, - {{#discussion.times.start}} -{{! }}{{#str}} displaystart, mod_forum {{/str}}: {{#userdate}}{{.}}, {{#str}}strftimerecentfull {{/str}}{{/userdate}} - {{/discussion.times.start}} - {{#discussion.times.end}} -{{! }}{{#str}} displayend, mod_forum {{/str}}: {{#userdate}}{{.}}, {{#str}} strftimerecentfull {{/str}}{{/userdate}} - {{/discussion.times.end}} - {{#discussion.timed.visible}} -{{! }}{{#str}} timedvisible, mod_forum {{/str}} - {{/discussion.timed.visible}} - {{^discussion.timed.visible}} -{{! }}{{#str}} timedhidden, mod_forum {{/str}} - {{/discussion.timed.visible}} - {{/pix}} -
- {{/discussion.timed.istimed}} - {{{discussion.name}}} @@ -208,6 +187,27 @@ {{/latestpostid}} + + {{#discussion.timed.istimed}} +
+ {{#pix}} + i/calendar, moodle, + {{#discussion.times.start}} + {{! }}{{#str}} displaystart, mod_forum {{/str}}: {{#userdate}}{{.}}, {{#str}}strftimerecentfull {{/str}}{{/userdate}} + {{/discussion.times.start}} + {{#discussion.times.end}} + {{! }}{{#str}} displayend, mod_forum {{/str}}: {{#userdate}}{{.}}, {{#str}} strftimerecentfull {{/str}}{{/userdate}} + {{/discussion.times.end}} + {{#discussion.timed.visible}} + {{! }}{{#str}} timedvisible, mod_forum {{/str}} + {{/discussion.timed.visible}} + {{^discussion.timed.visible}} + {{! }}{{#str}} timedhidden, mod_forum {{/str}} + {{/discussion.timed.visible}} + {{/pix}} +
+ {{/discussion.timed.istimed}} + {{#discussion}} {{> mod_forum/discussion_subscription_toggle}} diff --git a/mod/forum/templates/forum_discussion.mustache b/mod/forum/templates/forum_discussion.mustache index 9ba956c665a..816b492d1dd 100644 --- a/mod/forum/templates/forum_discussion.mustache +++ b/mod/forum/templates/forum_discussion.mustache @@ -30,7 +30,7 @@ } }} -
+
{{#html}} {{{subscribe}}} {{{neighbourlinks}}} @@ -52,8 +52,10 @@ {{#html.neighbourlinks}}{{{.}}}{{/html.neighbourlinks}}
{{#js}} -require(['jquery', 'mod_forum/discussion'], function($, Discussion) { - var root = $('#discussion-container-{{uniqid}}'); +require(['jquery', 'mod_forum/discussion', 'mod_forum/posts_list'], function($, Discussion, PostsList) { + var root = $("[data-content='forum-discussion']"); Discussion.init(root); + PostsList.init(root); + var root = $('#discussion-container-{{uniqid}}'); }); {{/js}} diff --git a/mod/forum/templates/forum_discussion_post.mustache b/mod/forum/templates/forum_discussion_post.mustache index aeb91bb180a..7387b73b6a3 100644 --- a/mod/forum/templates/forum_discussion_post.mustache +++ b/mod/forum/templates/forum_discussion_post.mustache @@ -34,15 +34,16 @@ class="relativelink mb-2" data-post-id="{{id}}" data-region="post" + data-target="{{id}}-target" tabindex="-1" aria-labelledby="post-header-{{id}}" aria-describedby="post-content-{{id}}" > -
{{#isfirstunread}}{{/isfirstunread}} {{^isdeleted}} @@ -69,8 +70,8 @@ {{/author}} {{/isdeleted}} -
-
+
+
{{#parentauthorname}} {{#str}} inreplyto, mod_forum, {{.}} {{/str}} {{/parentauthorname}} @@ -241,6 +242,9 @@ href="{{{urls.reply}}}" class="btn btn-link" role="menuitem" + data-post-id="{{id}}" + data-action="collapsible-link" + title="{{#str}} reply, mod_forum {{/str}}" > {{#str}} reply, mod_forum {{/str}} @@ -266,13 +270,15 @@
+
{{$replies}} +
{{#hasreplies}} -
- {{#replies}} - {{> mod_forum/forum_discussion_post }} - {{/replies}} -
+ {{#replies}} + {{> mod_forum/forum_discussion_post }} + {{/replies}} {{/hasreplies}} +
{{/replies}} +
diff --git a/mod/forum/templates/forum_discussion_threaded_post.mustache b/mod/forum/templates/forum_discussion_threaded_post.mustache index 82fa1893c0b..fd4c0346420 100644 --- a/mod/forum/templates/forum_discussion_threaded_post.mustache +++ b/mod/forum/templates/forum_discussion_threaded_post.mustache @@ -33,6 +33,7 @@ class="mb-2" data-post-id="{{id}}" data-region="post" + data-target="{{id}}-target" tabindex="-1" > {{subject}} @@ -42,9 +43,11 @@ {{/isdeleted}} -
- {{#replies}} - {{> mod_forum/forum_discussion_threaded_post }} - {{/replies}} +
+
+ {{#replies}} + {{> mod_forum/forum_discussion_threaded_post }} + {{/replies}} +
diff --git a/mod/forum/templates/forum_discussion_threaded_posts.mustache b/mod/forum/templates/forum_discussion_threaded_posts.mustache index 3a6b407a1e0..a9eee4cb6ec 100644 --- a/mod/forum/templates/forum_discussion_threaded_posts.mustache +++ b/mod/forum/templates/forum_discussion_threaded_posts.mustache @@ -34,7 +34,7 @@ {{< mod_forum/forum_discussion_post }} {{$replies}} -
+
{{#replies}} {{> mod_forum/forum_discussion_threaded_post }} {{/replies}} diff --git a/mod/forum/templates/inpage_reply.mustache b/mod/forum/templates/inpage_reply.mustache new file mode 100644 index 00000000000..8e103efbf60 --- /dev/null +++ b/mod/forum/templates/inpage_reply.mustache @@ -0,0 +1,56 @@ +{{! + 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_post_email_htmlemail + + Template which defines a forum post for sending in a single-post HTML email. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + } +}} + \ No newline at end of file diff --git a/mod/forum/tests/behat/behat_mod_forum.php b/mod/forum/tests/behat/behat_mod_forum.php index c5c244bbbbe..c6288a7c89e 100644 --- a/mod/forum/tests/behat/behat_mod_forum.php +++ b/mod/forum/tests/behat/behat_mod_forum.php @@ -74,6 +74,7 @@ class behat_mod_forum extends behat_base { $this->execute('behat_general::click_link', $this->escape($forumname)); $this->execute('behat_general::click_link', $this->escape($postsubject)); $this->execute('behat_general::click_link', get_string('reply', 'forum')); + $this->execute('behat_general::click_link', get_string('advanced', 'forum')); // Fill form and post. $this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table); @@ -82,6 +83,27 @@ class behat_mod_forum extends behat_base { $this->execute('behat_general::i_wait_to_be_redirected'); } + /** + * Inpage Reply - adds a reply to the specified post of the specified forum. The step begins from the forum's page or from the forum's course page. + * + * @Given /^I reply "(?P(?:[^"]|\\")*)" post from "(?P(?:[^"]|\\")*)" forum using an inpage reply with:$/ + * @param string $postsubject The subject of the post + * @param string $forumname The forum name + * @param TableNode $table + */ + public function i_reply_post_from_forum_using_an_inpage_reply_with($postsubject, $forumname, TableNode $table) { + + // Navigate to forum. + $this->execute('behat_general::click_link', $this->escape($forumname)); + $this->execute('behat_general::click_link', $this->escape($postsubject)); + $this->execute('behat_general::click_link', get_string('reply', 'forum')); + + // Fill form and post. + $this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table); + + $this->execute('behat_forms::press_button', get_string('submit', 'core')); + } + /** * Returns the steps list to add a new discussion to a forum. * diff --git a/mod/forum/tests/behat/discussion_display.feature b/mod/forum/tests/behat/discussion_display.feature index 63c60a0d58c..7ef29379e41 100644 --- a/mod/forum/tests/behat/discussion_display.feature +++ b/mod/forum/tests/behat/discussion_display.feature @@ -1,4 +1,4 @@ -@mod @mod_forum +@mod @mod_forum @javascript Feature: Students can choose from 4 discussion display options and their choice is remembered In order to read forum posts in a suitable view As a user diff --git a/mod/forum/tests/behat/inpage_reply.feature b/mod/forum/tests/behat/inpage_reply.feature new file mode 100644 index 00000000000..6fa68bba61b --- /dev/null +++ b/mod/forum/tests/behat/inpage_reply.feature @@ -0,0 +1,34 @@ +@mod @mod_forum @javascript +Feature: Students can reply to a discussion in page. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | student1@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + And I log in as "admin" + And I am on "Course 1" course homepage with editing mode on + And I add a "Forum" to section "1" and I fill the form with: + | Forum name | Test forum name | + | Description | Test forum description | + And I add a new discussion to "Test forum name" forum with: + | Subject | Discussion 1 | + | Message | Discussion contents 1, first message | + And I add a new discussion to "Test forum name" forum with: + | Subject | Discussion 2 | + | Message | Discussion contents 2, first message | + And I log out + And I log in as "student1" + And I am on "Course 1" course homepage + + Scenario: Confirm inpage replies work + Given I reply "Discussion 2" post from "Test forum name" forum using an inpage reply with: + | post | Discussion contents 1, third message | + Then I should see "Discussion contents 1, third message" + When I reload the page + Then I should see "Discussion contents 1, third message" \ No newline at end of file