diff --git a/mod/forum/amd/build/favourite_toggle.min.js b/mod/forum/amd/build/favourite_toggle.min.js new file mode 100644 index 00000000000..ffde663e8ab --- /dev/null +++ b/mod/forum/amd/build/favourite_toggle.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=function(f){f.on("click",e.favourite.toggle,function(e){var f=a(this),g=f.data("forumid"),h=f.data("discussionid"),i=f.data("targetstate");d.toggleFavouriteDiscussionState(g,h,i).then(function(a){return b.render("mod_forum/discussion_favourite_toggle",a)}).then(function(a,c){return b.replaceNode(f,a,c)})["catch"](c.exception),e.preventDefault()})};return{init:function(a){f(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 f8098717b81..561f8bf6622 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]},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]},d=function(b,c,d){var e={methodname:"mod_forum_set_lock_state",args:{forumid:b,discussionid:c,targetstate:d}};return a.call([e])[0]};return{setDiscussionSubscriptionState:b,addDiscussionPost:c,setDiscussionLockState:d}}); \ 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]},d=function(b,c,d){var e={methodname:"mod_forum_toggle_favourite_state",args:{forumid:b,discussionid:c,targetstate:d}};return a.call([e])[0]},e=function(b,c,d){var e={methodname:"mod_forum_set_lock_state",args:{forumid:b,discussionid:c,targetstate:d}};return a.call([e])[0]};return{setDiscussionSubscriptionState:b,addDiscussionPost:c,setDiscussionLockState:e,toggleFavouriteDiscussionState:d}}); \ 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 d3dda425712..d0f40b6eeea 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']"},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']",forumSubject:"[data-region-content='forum-post-core-subject']",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']"},lock:{toggle:"[data-action='toggle'][data-type='lock-toggle']"}}}); \ 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']",forumSubject:"[data-region-content='forum-post-core-subject']",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']"},lock:{toggle:"[data-action='toggle'][data-type='lock-toggle']"},favourite:{toggle:"[data-type='favorite-toggle'][data-action='toggle']"}}}); \ No newline at end of file diff --git a/mod/forum/amd/src/favourite_toggle.js b/mod/forum/amd/src/favourite_toggle.js new file mode 100644 index 00000000000..4fe7bab8a57 --- /dev/null +++ b/mod/forum/amd/src/favourite_toggle.js @@ -0,0 +1,69 @@ +// 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 . + +/** + * Handle discussion subscription toggling on a discussion list in + * the forum view. + * + * @module mod_forum/favourite_toggle + * @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 + ) { + + /** + * Register event listeners for the subscription toggle. + * + * @param {object} root The discussion list root element + */ + var registerEventListeners = function(root) { + root.on('click', Selectors.favourite.toggle, function(e) { + var toggleElement = $(this); + var forumId = toggleElement.data('forumid'); + var discussionId = toggleElement.data('discussionid'); + var subscriptionState = toggleElement.data('targetstate'); + + Repository.toggleFavouriteDiscussionState(forumId, discussionId, subscriptionState) + .then(function(context) { + return Templates.render('mod_forum/discussion_favourite_toggle', context); + }) + .then(function(html, js) { + return Templates.replaceNode(toggleElement, html, js); + }) + .catch(Notification.exception); + + e.preventDefault(); + }); + }; + + return { + init: function(root) { + registerEventListeners(root); + } + }; +}); diff --git a/mod/forum/amd/src/repository.js b/mod/forum/amd/src/repository.js index fcd10afa3d6..3022e5d925f 100644 --- a/mod/forum/amd/src/repository.js +++ b/mod/forum/amd/src/repository.js @@ -52,7 +52,26 @@ define(['core/ajax'], function(Ajax) { subject: subject } }; + return Ajax.call([request])[0]; + }; + /** + * Set the favourite state for a discussion in a forum. + * + * @param {number} forumId ID of the forum the discussion belongs to + * @param {number} discussionId ID of the discussion with the subscription state + * @param {boolean} targetState Set the favourite state. True == favourited; false == unfavourited. + * @return {object} jQuery promise + */ + var toggleFavouriteDiscussionState = function(forumId, discussionId, targetState) { + var request = { + methodname: 'mod_forum_toggle_favourite_state', + args: { + forumid: forumId, + discussionid: discussionId, + targetstate: targetState + } + }; return Ajax.call([request])[0]; }; @@ -71,6 +90,7 @@ define(['core/ajax'], function(Ajax) { return { setDiscussionSubscriptionState: setDiscussionSubscriptionState, addDiscussionPost: addDiscussionPost, - setDiscussionLockState: setDiscussionLockState + setDiscussionLockState: setDiscussionLockState, + toggleFavouriteDiscussionState: toggleFavouriteDiscussionState }; }); diff --git a/mod/forum/amd/src/selectors.js b/mod/forum/amd/src/selectors.js index 605c138fb14..521f9694bed 100644 --- a/mod/forum/amd/src/selectors.js +++ b/mod/forum/amd/src/selectors.js @@ -45,6 +45,9 @@ define([], function() { }, lock: { toggle: "[data-action='toggle'][data-type='lock-toggle']", + }, + favourite: { + toggle: "[data-type='favorite-toggle'][data-action='toggle']", } }; }); diff --git a/mod/forum/classes/local/entities/discussion.php b/mod/forum/classes/local/entities/discussion.php index b43340135f2..7cc0b970285 100644 --- a/mod/forum/classes/local/entities/discussion.php +++ b/mod/forum/classes/local/entities/discussion.php @@ -320,4 +320,17 @@ class discussion { public function is_timed_discussion_visible() : bool { return !$this->is_timed_discussion() || ($this->has_started() && !$this->has_ended()); } + + /** + * Check whether the provided discussion has been favourited by the user. + * + * @param discussion $discussion The discussion record + * @param context $forumcontext Forum context + * @param \stdClass $user The user to check the favourite against + */ + public static function is_favourited(discussion $discussion, \context_module $forumcontext, \stdClass $user) { + $usercontext = \context_user::instance($user->id); + $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); + return $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext); + } } diff --git a/mod/forum/classes/local/exporters/discussion.php b/mod/forum/classes/local/exporters/discussion.php index e7d2e4e8055..a607550c231 100644 --- a/mod/forum/classes/local/exporters/discussion.php +++ b/mod/forum/classes/local/exporters/discussion.php @@ -96,6 +96,7 @@ class discussion extends exporter { 'userstate' => [ 'type' => [ 'subscribed' => ['type' => PARAM_BOOL], + 'favourited' => ['type' => PARAM_BOOL], ], ], 'capabilities' => [ @@ -105,6 +106,7 @@ class discussion extends exporter { 'pin' => ['type' => PARAM_BOOL], 'post' => ['type' => PARAM_BOOL], 'manage' => ['type' => PARAM_BOOL], + 'favourite' => ['type' => PARAM_BOOL] ] ], 'urls' => [ @@ -195,14 +197,16 @@ class discussion extends exporter { 'locked' => $discussion->get_locked() ], 'userstate' => [ - 'subscribed' => \mod_forum\subscriptions::is_subscribed($user->id, $forumrecord, $discussion->get_id()) + 'subscribed' => \mod_forum\subscriptions::is_subscribed($user->id, $forumrecord, $discussion->get_id()), + 'favourited' => discussion_entity::is_favourited($discussion, $forum->get_context(), $user), ], 'capabilities' => [ 'subscribe' => $capabilitymanager->can_subscribe_to_discussion($user, $discussion), 'move' => $capabilitymanager->can_move_discussion($user, $discussion), 'pin' => $capabilitymanager->can_pin_discussion($user, $discussion), 'post' => $capabilitymanager->can_post_in_discussion($user, $discussion), - 'manage' => $capabilitymanager->can_manage_forum($user) + 'manage' => $capabilitymanager->can_manage_forum($user), + 'favourite' => $capabilitymanager->can_favourite_discussion($user, $discussion) // Defaulting to true until we get capabilities sorted ], 'urls' => [ 'view' => $urlfactory->get_discussion_view_url_from_discussion($discussion)->out(false), diff --git a/mod/forum/classes/local/managers/capability.php b/mod/forum/classes/local/managers/capability.php index 8bc2046c7f1..3d83718e2ea 100644 --- a/mod/forum/classes/local/managers/capability.php +++ b/mod/forum/classes/local/managers/capability.php @@ -321,6 +321,18 @@ class capability { return forum_user_can_post($forumrecord, $discussionrecord, $user, $coursemodule, $course, $context); } + /** + * Can the user favourite the discussion + * + * @param stdClass $user The user to check + * @param discussion_entity $discussion The discussion to check + * @return bool + */ + public function can_favourite_discussion(stdClass $user, discussion_entity $discussion) : bool { + $context = $this->get_context(); + return has_capability('mod/forum::cantogglefavourite', $context, $user); + } + /** * Can the user view the content of the post in this discussion? * diff --git a/mod/forum/classes/local/renderers/discussion.php b/mod/forum/classes/local/renderers/discussion.php index 500bc75e8f8..a5fbd14be39 100644 --- a/mod/forum/classes/local/renderers/discussion.php +++ b/mod/forum/classes/local/renderers/discussion.php @@ -201,6 +201,10 @@ class discussion { $exporteddiscussion['html']['pindiscussion'] = $this->get_pin_discussion_html(); } + if ($capabilities['favourite']) { + $exporteddiscussion['html']['favouritediscussion'] = $this->get_favourite_discussion_html($exporteddiscussion); + } + return $this->renderer->render_from_template('mod_forum/forum_discussion', $exporteddiscussion); } @@ -335,18 +339,24 @@ class discussion { if ($discussion->is_pinned()) { $pinlink = FORUM_DISCUSSION_UNPINNED; - $pintext = get_string('discussionunpin', 'forum'); + $pintext = get_string('unpindiscussion', 'forum'); } else { $pinlink = FORUM_DISCUSSION_PINNED; - $pintext = get_string('discussionpin', 'forum'); + $pintext = get_string('pindiscussion', 'forum'); } - $button = new single_button( - new moodle_url('discuss.php', ['pin' => $pinlink, 'd' => $discussion->get_id()]), - $pintext, - 'post' - ); - return $this->renderer->render($button); + $params = [ + 'pin' => $pinlink, + 'd' => $discussion->get_id(), + 'sesskey' => sesskey(), + ]; + $url = new moodle_url('discuss.php', $params); + $link = new \action_link($url, $pintext, null, ['class' => 'btn btn-link']); + return $this->renderer->render($link); + } + + private function get_favourite_discussion_html($exporteddiscussion) : string { + return $this->renderer->render_from_template('mod_forum/discussion_favourite_toggle', $exporteddiscussion); } /** diff --git a/mod/forum/classes/local/vaults/discussion_list.php b/mod/forum/classes/local/vaults/discussion_list.php index 4047f41e8e2..6633bd7c4dd 100644 --- a/mod/forum/classes/local/vaults/discussion_list.php +++ b/mod/forum/classes/local/vaults/discussion_list.php @@ -74,6 +74,10 @@ class discussion_list extends db_table_vault { return 'd'; } + protected function get_favourite_alias() : string { + return 'favalias'; + } + /** * Build the SQL to be used in get_records_sql. * @@ -81,7 +85,7 @@ class discussion_list extends db_table_vault { * @param string|null $sortsql Order by conditions for the SQL * @return string */ - protected function generate_get_records_sql(string $wheresql = null, ?string $sortsql = null) : string { + protected function generate_get_records_sql(string $wheresql = null, ?string $sortsql = null, ?string $joinsql = null) : string { $alias = $this->get_table_alias(); $db = $this->get_db(); @@ -106,6 +110,7 @@ class discussion_list extends db_table_vault { $tables .= ' JOIN {user} fa ON fa.id = ' . $alias . '.userid'; $tables .= ' JOIN {user} la ON la.id = ' . $alias . '.usermodified'; $tables .= ' JOIN ' . $posttable->get_from_sql() . ' ON fp.id = ' . $alias . '.firstpost'; + $tables .= $joinsql ? $joinsql : ''; $selectsql = 'SELECT ' . $fields . ' FROM ' . $tables; $selectsql .= $wheresql ? ' WHERE ' . $wheresql : ''; @@ -183,24 +188,18 @@ class discussion_list extends db_table_vault { $alias = $this->get_table_alias(); - if ($sortmethod == self::SORTORDER_CREATED_DESC) { - $keyfield = "fp.created"; - $direction = "DESC"; - } else { - // TODO consider user favourites... - $keyfield = "{$alias}.timemodified"; - $direction = "DESC"; + $keyfield = "{$alias}.timemodified"; + $direction = "DESC"; - if ($sortmethod == self::SORTORDER_OLDEST_FIRST) { - $direction = "ASC"; - } - - if (!empty($CFG->forum_enabletimedposts)) { - $keyfield = "CASE WHEN {$keyfield} < {$alias}.timestart THEN {$alias}.timestart ELSE {$keyfield} END"; - } + if ($sortmethod == self::SORTORDER_OLDEST_FIRST) { + $direction = "ASC"; } - return "{$alias}.pinned DESC, {$keyfield} {$direction}, {$alias}.id DESC"; + if (!empty($CFG->forum_enabletimedposts)) { + $keyfield = "CASE WHEN {$keyfield} < {$alias}.timestart THEN {$alias}.timestart ELSE {$keyfield} END"; + } + + return "{$alias}.pinned DESC, {$this->get_favourite_alias()}.id DESC, {$keyfield} {$direction}"; } /** @@ -263,7 +262,10 @@ class discussion_list extends db_table_vault { 'forumid' => $forumid, ]); - $sql = $this->generate_get_records_sql($wheresql, $this->get_sort_order($sortorder)); + list($favsql, $favparams) = $this->get_favourite_sql(); + $params += $favparams; + + $sql = $this->generate_get_records_sql($wheresql, $this->get_sort_order($sortorder), $favsql); $records = $this->get_db()->get_records_sql($sql, $params, $offset, $limit); return $this->transform_db_records_to_entities($records); @@ -390,4 +392,17 @@ class discussion_list extends db_table_vault { return $this->get_db()->count_records_sql($this->generate_count_records_sql($wheresql), $params); } + + /** + * Get the standard favouriting sql. + */ + private function get_favourite_sql(): array { + global $USER; + $usercontext = \context_user::instance($USER->id); + $alias = $this->get_table_alias(); + $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); + list($favsql, $favparams) = $ufservice->get_join_sql_by_type('mod_forum', 'discussions', $this->get_favourite_alias(), "$alias.id"); + + return [$favsql, $favparams]; + } } diff --git a/mod/forum/db/access.php b/mod/forum/db/access.php index b9d201542cd..270e975d1bf 100644 --- a/mod/forum/db/access.php +++ b/mod/forum/db/access.php @@ -405,5 +405,13 @@ $capabilities = array( ), 'clonepermissionsfrom' => 'mod/forum:canoverridediscussionlock' ), + 'mod/forum::cantogglefavourite' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => array( + 'user' => CAP_ALLOW + ), + 'clonepermissionsfrom' => 'moodle/user:manageownfiles' + ) ); diff --git a/mod/forum/db/services.php b/mod/forum/db/services.php index 3506d12081c..725c932381c 100644 --- a/mod/forum/db/services.php +++ b/mod/forum/db/services.php @@ -145,4 +145,14 @@ $functions = array( 'capabilities' => 'moodle/course:manageactivities', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + + 'mod_forum_toggle_favourite_state' => array( + 'classname' => 'mod_forum_external', + 'methodname' => 'toggle_favourite_state', + 'classpath' => 'mod/forum/externallib.php', + 'description' => 'Toggle the favourite state', + 'type' => 'write', + 'ajax' => true, + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), + ), ); diff --git a/mod/forum/externallib.php b/mod/forum/externallib.php index c7175d4b746..38789786668 100644 --- a/mod/forum/externallib.php +++ b/mod/forum/externallib.php @@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die; require_once("$CFG->libdir/externallib.php"); use mod_forum\local\exporters\post as post_exporter; +use mod_forum\local\exporters\discussion as discussion_exporter; class mod_forum_external extends external_api { @@ -1096,6 +1097,68 @@ class mod_forum_external extends external_api { ); } + public static function toggle_favourite_state($forumid, $discussionid, $targetstate) { + global $DB, $PAGE, $USER; + + $params = self::validate_parameters(self::toggle_favourite_state_parameters(), [ + 'forumid' => $forumid, + 'discussionid' => $discussionid, + 'targetstate' => $targetstate + ]); + + $vaultfactory = mod_forum\local\container::get_vault_factory(); + $forumvault = $vaultfactory->get_forum_vault(); + $forum = $forumvault->get_from_id($params['forumid']); + $forumcontext = $forum->get_context(); + $usercontext = context_user::instance($USER->id); + + self::validate_context($forumcontext); + + $managerfactory = mod_forum\local\container::get_manager_factory(); + $capabilitymanager = $managerfactory->get_capability_manager($forum); + + // Get the discussion vault and the corresponding discussion entity. + $discussionvault = $vaultfactory->get_discussion_vault(); + $discussion = $discussionvault->get_from_id($params['discussionid']); + + // Does the user have the ability to favourite the discussion? + if (!$capabilitymanager->can_favourite_discussion($USER, $discussion)) { + throw new moodle_exception('cannotfavourite', 'forum'); + } + + $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); + $isfavourited = $ufservice->favourite_exists('mod_forum', 'discussions', $discussion->get_id(), $forumcontext); + + $favouritefunction = $targetstate ? 'create_favourite' : 'delete_favourite'; + if ($isfavourited != (bool) $params['targetstate']) { + $ufservice->{$favouritefunction}('mod_forum', 'discussions', $discussion->get_id(), $forumcontext); + } + + $exporterfactory = mod_forum\local\container::get_exporter_factory(); + $exporter = $exporterfactory->get_discussion_exporter($USER, $forum, $discussion); + return $exporter->export($PAGE->get_renderer('mod_forum')); + } + + /** + * Returns description of method result value + * + * @return external_description + * @since Moodle 3.0 + */ + public static function toggle_favourite_state_returns() { + return discussion_exporter::get_read_structure(); + } + + public function toggle_favourite_state_parameters() { + return new external_function_parameters( + [ + 'forumid' => new external_value(PARAM_INT, 'Forum that the discussion is in'), + 'discussionid' => new external_value(PARAM_INT, 'The discussion to subscribe or unsubscribe'), + 'targetstate' => new external_value(PARAM_BOOL, 'The target state') + ] + ); + } + /** * Returns description of method parameters * diff --git a/mod/forum/lang/en/forum.php b/mod/forum/lang/en/forum.php index 15d9a1190fc..4b1fe2e277d 100644 --- a/mod/forum/lang/en/forum.php +++ b/mod/forum/lang/en/forum.php @@ -27,6 +27,7 @@ $string['activityoverview'] = 'There are new forum posts'; $string['addanewdiscussion'] = 'Add a new discussion topic'; $string['addanewquestion'] = 'Add a new question'; $string['addanewtopic'] = 'Add a new topic'; +$string['addtofavourites'] = 'Star this discussion'; $string['advancedsearch'] = 'Advanced search'; $string['allforums'] = 'All forums'; $string['allowdiscussions'] = 'Can a {$a} post to this forum?'; @@ -79,6 +80,7 @@ $string['cannotremovesubscriber'] = 'Could not remove subscriber with id {$a} fr $string['cannotreply'] = 'You cannot reply to this post'; $string['cannotsplit'] = 'Discussions from this forum cannot be split'; $string['cannotsubscribe'] = 'Sorry, but you must be a group member to subscribe.'; +$string['cannotfavourite'] = 'Sorry, but you do not have the permission to favourite.'; $string['cannottrack'] = 'Could not stop tracking that forum'; $string['cannotunsubscribe'] = 'Could not unsubscribe you from that forum'; $string['cannotupdatepost'] = 'You can not update this post'; @@ -89,6 +91,8 @@ $string['clicktolockdiscussion'] = 'Click to lock this discussion'; $string['clicktounlockdiscussion'] = 'Click to unlock this discussion'; $string['clicktounsubscribe'] = 'You are subscribed to this discussion. Click to unsubscribe.'; $string['clicktosubscribe'] = 'You are not subscribed to this discussion. Click to subscribe.'; +$string['clicktounfavourite'] = 'You have starred this discussion. Click to unstar.'; +$string['clicktofavourite'] = 'You have not starred this discussion. Click to star.'; $string['completiondiscussions'] = 'Student must create discussions:'; $string['completiondiscussionsdesc'] = 'Student must create at least {$a} discussion(s)'; $string['completiondiscussionsgroup'] = 'Require discussions'; @@ -233,6 +237,7 @@ $string['existingsubscribers'] = 'Existing subscribers'; $string['exportdiscussion'] = 'Export whole discussion to portfolio'; $string['exportattachmentname'] = 'Export attachment {$a} to portfolio'; $string['firstpost'] = 'First post'; +$string['favourites'] = 'Starred'; $string['forcedreadtracking'] = 'Allow forced read tracking'; $string['forcedreadtracking_desc'] = 'Allows forums to be set to forced read tracking. Will result in decreased performance for some users, particularly on courses with many forums and posts. When off, any forums previously set to Forced are treated as optional.'; $string['forcesubscribed_help'] = 'This forum has been configured so that you cannot unsubscribe from discussions.'; @@ -433,6 +438,7 @@ $string['permalink'] = 'Permalink'; $string['permanentlinktopost'] = 'Permanent link to this post'; $string['permanentlinktoparentpost'] = 'Permanent link to the parent of this post'; $string['postisprivatereply'] = 'This post was made privately and is not visible to all users.'; +$string['pindiscussion'] = 'Pin this discussion'; $string['posttomygroups'] = 'Post a copy to all groups'; $string['posttomygroups_help'] = 'Posts a copy of this message to all groups you have access to. Participants in groups you do not have access to will not see this post'; $string['prevdiscussiona'] = 'Previous discussion: {$a}'; @@ -527,6 +533,7 @@ $string['qandanotify'] = 'This is a question and answer forum. In order to see o $string['re'] = 'Re:'; $string['readtherest'] = 'Read the rest of this topic'; $string['removeallforumtags'] = 'Remove all forum tags'; +$string['removefromfavourites'] = 'Unstar this discussion'; $string['replies'] = 'Replies'; $string['repliesmany'] = '{$a} replies so far'; $string['repliesone'] = '{$a} reply so far'; @@ -568,6 +575,7 @@ $string['searchuserid'] = 'The Moodle ID of the author'; $string['searchwhichforums'] = 'Choose which forums to search'; $string['searchwords'] = 'These words can appear anywhere in the post'; $string['seeallposts'] = 'See all posts made by this user'; +$string['settings'] = 'Settings'; $string['shortpost'] = 'Short post'; $string['showsubscribers'] = 'Show/edit current subscribers'; $string['singleforum'] = 'A single simple discussion'; @@ -638,6 +646,7 @@ $string['unsubscribeallempty'] = 'You are not subscribed to any forums. To disab $string['unsubscribed'] = 'Unsubscribed'; $string['unsubscribeshort'] = 'Unsubscribe'; $string['usermarksread'] = 'Manual message read marking'; +$string['unpindiscussion'] = 'Unpin this discussion'; $string['viewalldiscussions'] = 'View all discussions'; $string['viewthediscussion'] = 'View the discussion'; $string['warnafter'] = 'Post threshold for warning'; diff --git a/mod/forum/pix/t/star.png b/mod/forum/pix/t/star.png new file mode 100644 index 00000000000..3ba7acbbbba Binary files /dev/null and b/mod/forum/pix/t/star.png differ diff --git a/mod/forum/pix/t/star.svg b/mod/forum/pix/t/star.svg new file mode 100644 index 00000000000..7ed29a2be8e --- /dev/null +++ b/mod/forum/pix/t/star.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/mod/forum/styles.css b/mod/forum/styles.css index 48cf44876eb..a58e70ed0ca 100644 --- a/mod/forum/styles.css +++ b/mod/forum/styles.css @@ -302,14 +302,18 @@ span.unread { } .path-mod-forum .discussionsubscription, -.path-mod-forum .discussionlock { +.path-mod-forum .discussionlock, +.path-mod-forum .discussion-settings-menu, +.path-mod-forum .discussionsubscription { margin-top: -10px; text-align: right; margin-bottom: 10px; } .path-mod-forum .discussionsubscription > a > img, -.path-mod-forum .discussionlock > a > img { +.path-mod-forum .discussionlock > a > img, +.path-mod-forum .favourite-discussion > a > img, +.path-mod-forum .discussion-settings-menu > a > img { width: 12px; padding: 0 4px; } diff --git a/mod/forum/templates/discussion_favourite_toggle.mustache b/mod/forum/templates/discussion_favourite_toggle.mustache new file mode 100644 index 00000000000..3cd26bc894a --- /dev/null +++ b/mod/forum/templates/discussion_favourite_toggle.mustache @@ -0,0 +1,57 @@ +{{! + 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/discussion_subscription_toggle + + Template to display the discussion subscription toggle. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * TODO + + Example context (json): + { + } +}} +{{#capabilities.favourite}} + + {{#userstate.favourited}} + {{#str}}removefromfavourites, mod_forum{{/str}} + {{/userstate.favourited}} + {{^userstate.favourited}} + {{#str}}addtofavourites, mod_forum{{/str}} + {{/userstate.favourited}} + +{{/capabilities.favourite}} \ No newline at end of file diff --git a/mod/forum/templates/discussion_list.mustache b/mod/forum/templates/discussion_list.mustache index 1f5303bcdbb..cad9d2c18eb 100644 --- a/mod/forum/templates/discussion_list.mustache +++ b/mod/forum/templates/discussion_list.mustache @@ -93,6 +93,11 @@ {{#discussion.pinned}} {{#pix}}i/pinned, mod_forum, {{#str}}discussionpinned, mod_forum{{/str}}{{/pix}} {{/discussion.pinned}} + {{^discussion.pinned}} + {{#discussion.userstate.favourited}} + {{#pix}}i/pinned, mod_forum, {{#str}}discussionpinned, mod_forum{{/str}}{{/pix}} + {{/discussion.userstate.favourited}} + {{/discussion.pinned}} {{{discussion.name}}} diff --git a/mod/forum/templates/forum_action_menu.mustache b/mod/forum/templates/forum_action_menu.mustache new file mode 100644 index 00000000000..2347bc6ebe6 --- /dev/null +++ b/mod/forum/templates/forum_action_menu.mustache @@ -0,0 +1,43 @@ +{{! + 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-action-menu + + This template renders action menu for each course. + + Example context (json): + { + + } +}} + \ No newline at end of file diff --git a/mod/forum/templates/forum_discussion.mustache b/mod/forum/templates/forum_discussion.mustache index e660461d60b..6fa8ea5e131 100644 --- a/mod/forum/templates/forum_discussion.mustache +++ b/mod/forum/templates/forum_discussion.mustache @@ -40,6 +40,11 @@ {{/istimelocked}} {{/capabilities.manage}} +
+
+ {{> mod_forum/forum_action_menu}} +
+
{{{subscribe}}}
{{{neighbourlinks}}} @@ -61,11 +66,13 @@ {{#html.neighbourlinks}}{{{.}}}{{/html.neighbourlinks}} {{#js}} -require(['jquery', 'mod_forum/discussion', 'mod_forum/posts_list', 'mod_forum/lock_toggle'], function($, Discussion, PostsList, LockToggle) { - var root = $("[data-content='forum-discussion']"); - Discussion.init(root); - PostsList.init(root); - var root = $('[data-container="discussion-tools"]'); - LockToggle.init(root); +require(['jquery', 'mod_forum/discussion', 'mod_forum/posts_list', 'mod_forum/lock_toggle', 'mod_forum/favourite_toggle'], + function($, Discussion, PostsList, LockToggle, FavouriteToggle) { + var root = $("[data-content='forum-discussion']"); + Discussion.init(root); + PostsList.init(root); + var root = $('[data-container="discussion-tools"]'); + LockToggle.init(root); + FavouriteToggle.init(root); }); -{{/js}} +{{/js}} \ No newline at end of file