MDL-64956 mod_forum: Inpage reply
authorPeter <peter@moodle.com>
Mon, 11 Mar 2019 01:22:57 +0000 (09:22 +0800)
committerPeter <peter@moodle.com>
Wed, 17 Apr 2019 02:13:41 +0000 (10:13 +0800)
* 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

21 files changed:
mod/forum/amd/build/inpage_reply.min.js [new file with mode: 0644]
mod/forum/amd/build/posts_list.min.js [new file with mode: 0644]
mod/forum/amd/build/repository.min.js
mod/forum/amd/build/selectors.min.js
mod/forum/amd/src/inpage_reply.js [new file with mode: 0644]
mod/forum/amd/src/posts_list.js [new file with mode: 0644]
mod/forum/amd/src/repository.js
mod/forum/amd/src/selectors.js
mod/forum/db/services.php
mod/forum/externallib.php
mod/forum/lang/en/forum.php
mod/forum/post.php
mod/forum/templates/discussion_list.mustache
mod/forum/templates/forum_discussion.mustache
mod/forum/templates/forum_discussion_post.mustache
mod/forum/templates/forum_discussion_threaded_post.mustache
mod/forum/templates/forum_discussion_threaded_posts.mustache
mod/forum/templates/inpage_reply.mustache [new file with mode: 0644]
mod/forum/tests/behat/behat_mod_forum.php
mod/forum/tests/behat/discussion_display.feature
mod/forum/tests/behat/inpage_reply.feature [new file with mode: 0644]

diff --git a/mod/forum/amd/build/inpage_reply.min.js b/mod/forum/amd/build/inpage_reply.min.js
new file mode 100644 (file)
index 0000000..fa33552
Binary files /dev/null and b/mod/forum/amd/build/inpage_reply.min.js differ
diff --git a/mod/forum/amd/build/posts_list.min.js b/mod/forum/amd/build/posts_list.min.js
new file mode 100644 (file)
index 0000000..0908103
Binary files /dev/null and b/mod/forum/amd/build/posts_list.min.js differ
index 2b3fcee..3c7817f 100644 (file)
Binary files a/mod/forum/amd/build/repository.min.js and b/mod/forum/amd/build/repository.min.js differ
index bb3a8f9..ffe5084 100644 (file)
Binary files a/mod/forum/amd/build/selectors.min.js and b/mod/forum/amd/build/selectors.min.js differ
diff --git a/mod/forum/amd/src/inpage_reply.js b/mod/forum/amd/src/inpage_reply.js
new file mode 100644 (file)
index 0000000..d67da7b
--- /dev/null
@@ -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);
+
+        }
+    };
+});
diff --git a/mod/forum/amd/src/posts_list.js b/mod/forum/amd/src/posts_list.js
new file mode 100644 (file)
index 0000000..4575e46
--- /dev/null
@@ -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);
+        }
+    };
+});
index c09a6d8..d8a3758 100644 (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
     };
 });
index b8d935a..ebc63a9 100644 (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']",
         }
     };
 });
index 3907220..e2bafac 100644 (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)
     ),
index 72bc014..cfc5b4b 100644 (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()
             )
         );
     }
index 0d40c17..b176540 100644 (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';
index 4daa039..4aa76e7 100644 (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;
index bbc9162..58b0cc3 100644 (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}}
                                     {{#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>
                                     </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}}
index 9ba956c..816b492 100644 (file)
@@ -30,7 +30,7 @@
     }
 }}
 
-<div id="discussion-container-{{uniqid}}">
+<div id="discussion-container-{{uniqid}}" data-content="forum-discussion">
 {{#html}}
     {{{subscribe}}}
     {{{neighbourlinks}}}
 {{#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}}
index aeb91bb..7387b73 100644 (file)
     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}}
                                             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>
         </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>
index 82fa189..fd4c034 100644 (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>
         </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>
index 3a6b407..a9eee4c 100644 (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}}
diff --git a/mod/forum/templates/inpage_reply.mustache b/mod/forum/templates/inpage_reply.mustache
new file mode 100644 (file)
index 0000000..8e103ef
--- /dev/null
@@ -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>
\ No newline at end of file
index c5c244b..c6288a7 100644 (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.
      *
index 63c60a0..7ef2937 100644 (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
diff --git a/mod/forum/tests/behat/inpage_reply.feature b/mod/forum/tests/behat/inpage_reply.feature
new file mode 100644 (file)
index 0000000..6fa68bb
--- /dev/null
@@ -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