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
This commit is contained in:
Peter 2019-03-11 09:22:57 +08:00
parent 9bcfd21bcd
commit 9b4f09bad2
21 changed files with 428 additions and 59 deletions

View File

@ -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)}}});

1
mod/forum/amd/build/posts_list.min.js vendored Normal file
View File

@ -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)}}});

View File

@ -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}});
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}});

View File

@ -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"]'}}});
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']"}}});

View File

@ -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 <http://www.gnu.org/licenses/>.
/**
* 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);
}
};
});

View File

@ -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 <http://www.gnu.org/licenses/>.
/**
* 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);
}
};
});

View File

@ -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
};
});

View File

@ -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']",
}
};
});

View File

@ -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)
),

View File

@ -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()
)
);
}

View File

@ -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';

View File

@ -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;

View File

@ -58,7 +58,6 @@
{{$discussion_list_header}}
<thead>
<tr>
<th scope="col">&nbsp;</th>
<th scope="col">&nbsp;</th>
<th scope="col" class="p-l-0">{{#str}}discussion, mod_forum{{/str}}</th>
<th scope="col" class="author">{{#str}}startedby, mod_forum{{/str}}</th>
@ -75,6 +74,7 @@
{{/forum.userstate.tracked}}
{{/forum.capabilities.viewdiscussions}}
<th scope="col" class="lastpost">{{#str}}lastpost, mod_forum{{/str}}</th>
<th scope="col">&nbsp;</th>
{{#forum.capabilities.subscribe}}
<th scope="col" class="discussionsubscription"></th>
{{/forum.capabilities.subscribe}}
@ -91,27 +91,6 @@
{{#pix}}i/pinned, mod_forum, {{#str}}discussionpinned, mod_forum{{/str}}{{/pix}}
{{/discussion.pinned}}
</td>
<td scope="col" class="timed p-0 text-center align-middle">
{{#discussion.timed.istimed}}
<div class="timedpost">
{{#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}}
</div>
{{/discussion.timed.istimed}}
</td>
<td scope="col" class="topic p-0 align-middle">
<a class="p-3 p-l-0 w-100 h-100 d-block" href="{{discussion.urls.view}}">{{{discussion.name}}}</a>
</td>
@ -208,6 +187,27 @@
</div>
{{/latestpostid}}
</td>
<td scope="col" class="timed p-0 text-center align-middle">
{{#discussion.timed.istimed}}
<div class="timedpost">
{{#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}}
</div>
{{/discussion.timed.istimed}}
</td>
<td scope="col" class="p-0 align-middle">
{{#discussion}}
{{> mod_forum/discussion_subscription_toggle}}

View File

@ -30,7 +30,7 @@
}
}}
<div id="discussion-container-{{uniqid}}">
<div id="discussion-container-{{uniqid}}" data-content="forum-discussion">
{{#html}}
{{{subscribe}}}
{{{neighbourlinks}}}
@ -52,8 +52,10 @@
{{#html.neighbourlinks}}{{{.}}}{{/html.neighbourlinks}}
</div>
{{#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}}

View File

@ -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}}"
>
<!-- The firstpost and starter classes below aren't used for anything other than to identify the first post in behat -->
<div
class="d-flex border p-2 mb-2 forumpost {{#unread}}unread{{/unread}} {{#firstpost}}firstpost starter{{/firstpost}}"
aria-label='{{#str}} postbyuser, mod_forum, {"post": "{{subject}}", "user": "{{author.fullname}}"} {{/str}}'
data-post-id="{{id}}" data-content="forum-post"
>
{{#isfirstunread}}<a id="unread" aria-hidden="true"></a>{{/isfirstunread}}
{{^isdeleted}}
@ -69,8 +70,8 @@
{{/author}}
{{/isdeleted}}
<div class="d-flex flex-column ml-2 w-100">
<header id="post-header-{{id}}" class="mb-2 header row">
<div class="d-flex flex-column ml-2 w-100" data-region-content="forum-post-core">
<header class="mb-2 header row">
{{#parentauthorname}}
<span class="sr-only">{{#str}} inreplyto, mod_forum, {{.}} {{/str}}</span>
{{/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}}
</a>
@ -266,13 +270,15 @@
</div>
</div>
<div data-region="replies-container">
{{$replies}}
<div>
{{#hasreplies}}
<div>
{{#replies}}
{{> mod_forum/forum_discussion_post }}
{{/replies}}
</div>
{{#replies}}
{{> mod_forum/forum_discussion_post }}
{{/replies}}
{{/hasreplies}}
</div>
{{/replies}}
</div>
</article>

View File

@ -33,6 +33,7 @@
class="mb-2"
data-post-id="{{id}}"
data-region="post"
data-target="{{id}}-target"
tabindex="-1"
>
<a href="{{{urls.viewisolated}}}">{{subject}}</a>
@ -42,9 +43,11 @@
</address>
{{/isdeleted}}
<div class="indent">
{{#replies}}
{{> mod_forum/forum_discussion_threaded_post }}
{{/replies}}
<div data-region="replies-container">
<div class="indent">
{{#replies}}
{{> mod_forum/forum_discussion_threaded_post }}
{{/replies}}
</div>
</div>
</div>

View File

@ -34,7 +34,7 @@
{{< mod_forum/forum_discussion_post }}
{{$replies}}
<!-- The forumthread class is only added for behat -->
<div class="indent forumthread">
<div class="indent forumthread post-replies">
{{#replies}}
{{> mod_forum/forum_discussion_threaded_post }}
{{/replies}}

View File

@ -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 <http://www.gnu.org/licenses/>.
}}
{{!
@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):
{
}
}}
<div class="row" data-content="inpage-reply-content" style="display: none;">
<div class="card card-body">
<form data-post-id="{{postid}}" id="inpage-reply-{{postid}}" data-content="inpage-reply-form" action="{{{reply_url}}}">
<div class="row pb-1">
<span>
<textarea rows="5" name="post" title="post" class="w-100" placeholder="{{#str}} replyplaceholder, forum {{/str}}"></textarea>
</span>
<input type="hidden" name="subject" value="Re: Post reply"/>
<input type="hidden" name="reply" value="{{postid}}"/>
<input type="hidden" name="sesskey" value="{{sesskey}}"/>
</div>
<div class="row">
<button class="btn btn-primary" title="{{#str}} submit, core {{/str}}" data-action="forum-inpage-submit">
{{#str}} submit, core {{/str}}
</button>
<button class="btn btn-secondary" title="{{#str}} cancel, core {{/str}}" data-action="collapsible-link">
{{#str}} cancel, core {{/str}}
</button>
<button title="{{#str}} advanced, forum {{/str}}" data-action="forum-advanced-reply" class="btn btn-link float-right" type="submit">
{{#str}} advanced, forum {{/str}}
</button>
</div>
</form>
</div>
</div>

View File

@ -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_subject_string>(?:[^"]|\\")*)" post from "(?P<forum_name_string>(?:[^"]|\\")*)" 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.
*

View File

@ -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

View File

@ -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"