From dab1e16ad7713a4e09d8957a9a7e2dd255278b6b Mon Sep 17 00:00:00 2001 From: Bogdanova Olga Date: Wed, 31 Mar 2021 23:37:24 +0300 Subject: [PATCH 1/6] Issues-479:Watch functionality changes --- .../controllers/class.watchingcontroller.php | 157 ++++++++++++++++++ Topcoder/design/topcoder.css | 19 +++ Topcoder/js/topcoder.js | 21 +++ Topcoder/views/watching/categories.php | 14 ++ Topcoder/views/watching/discussions.php | 11 ++ Topcoder/views/watching/index.php | 43 +++++ 6 files changed, 265 insertions(+) create mode 100644 Topcoder/controllers/class.watchingcontroller.php create mode 100644 Topcoder/js/topcoder.js create mode 100644 Topcoder/views/watching/categories.php create mode 100644 Topcoder/views/watching/discussions.php create mode 100644 Topcoder/views/watching/index.php diff --git a/Topcoder/controllers/class.watchingcontroller.php b/Topcoder/controllers/class.watchingcontroller.php new file mode 100644 index 0000000..d3cd725 --- /dev/null +++ b/Topcoder/controllers/class.watchingcontroller.php @@ -0,0 +1,157 @@ +Menu->highlightRoute('/watching'); + + /** + * The default Cache-Control header does not include no-store, which can cause issues (e.g. inaccurate unread + * status or new comment counts) when users visit the discussion list via the browser's back button. The same + * check is performed here as in Gdn_Controller before the Cache-Control header is added, but this value + * includes the no-store specifier. + */ + if (Gdn::session()->isValid()) { + $this->setHeader('Cache-Control', 'private, no-cache, no-store, max-age=0, must-revalidate'); + } + + $this->CountCommentsPerPage = c('Vanilla.Comments.PerPage', 30); + $this->fireEvent('AfterInitialize'); + } + + /** + * Display categorioes and discussions the user has watched + * + * @param string $cp Category page + * @param string $dp Discussion page + * @throws Exception + */ + public function index($cp = '', $dp = '') { + $this->addJsFile('jquery.gardenmorepager.js'); + $this->addJsFile('topcoder.js'); + $this->permission('Garden.SignIn.Allow'); + Gdn_Theme::section('CategoryList'); + + // Sort filter is used for categories and discussions + $sort = Gdn::request()->get('sort', null); + $saveSorting = $sort !== null && Gdn::request()->get('save') && Gdn::session()->validateTransientKey(Gdn::request()->get('TransientKey', '')); + if($saveSorting) { + Gdn::session()->setPreference('CategorySort', $sort); + } + $sort = Gdn::session()->getPreference('CategorySort', false); + $this->setData('CategorySort', $sort); + + $userMetaModel = new UserMetaModel(); + list($cp, $categoryLimit) = offsetLimit($cp, 30); + + // Validate Category Page + if (!is_numeric($cp) || $cp < 0) { + $cp = 0; + } + $categorySort = $sort == 'old'? 'asc': 'desc'; + $watchedCategoryIDs = $userMetaModel->getWatchedCategories(Gdn::session()->UserID, $categorySort, $categoryLimit, $cp); + $countOfWatchedCategories = $userMetaModel->userWatchedCategoriesCount(Gdn::session()->UserID); + + $categories = []; + $categoryModel = new CategoryModel(); + foreach ($watchedCategoryIDs as $item) { + $category = CategoryModel::categories(val('CategoryID', $item)); + // $category['Archived'] + // if (!$category['PermsDiscussionsView']) { + // continue; + // } + $categories[] = $category; + } + $categoryModel->joinRecent($categories); + $this->setData('WatchedCategories', $categories); + $this->setData('CountWatchedCategories', $countOfWatchedCategories); + + $pagerFactory = new Gdn_PagerFactory(); + $this->WatchedCategoriesPager = $pagerFactory->getPager('MorePager', $this); + $this->WatchedCategoriesPager->ClientID='WatchingCategories'; + $this->WatchedCategoriesPager->MoreCode = 'More Categories'; + $this->WatchedCategoriesPager->configure($cp, + $categoryLimit, + $countOfWatchedCategories, + 'watching?cp={Page}' + ); + + Gdn_Theme::section('DiscussionList'); + + list($dp, $discussionlimit) = offsetLimit($dp, 30); + if (!is_numeric($dp) || $dp < 0) { + $dp = 0; + } + + $discussionModel = new DiscussionModel(); + $discussionModel->setSort($sort); + $discussionModel->setFilters(Gdn::request()->get()); + $wheres = [ + 'w.Bookmarked' => '1', + 'w.UserID' => Gdn::session()->UserID + ]; + + $this->DiscussionData = $discussionModel->get($dp, $discussionlimit, $wheres); + $this->setData('Discussions', $this->DiscussionData); + $countDiscussions = $discussionModel->getCount($wheres); + $this->setData('CountDiscussions', $countDiscussions); + + $pagerFactory = new Gdn_PagerFactory(); + $this->DiscussionPager = $pagerFactory->getPager('MorePager', $this); + $this->DiscussionPager->ClientID='WatchingDiscussions'; + $this->DiscussionPager->MoreCode = 'More Discussions'; + $this->DiscussionPager->configure($dp, + $discussionlimit, + $countDiscussions, + 'watching?dp={Page}'); + + $this->allowJSONP(true); + + // Deliver JSON data if necessary + if ($this->deliveryType() != DELIVERY_TYPE_ALL) { + if ($dp > 0) { + //$this->setJson('LessRow', $this->DiscussionPager->toString('less')); + $this->setJson('MoreRow', $this->DiscussionPager->toString('more')); + $this->setJson('Loading', $dp.' to '.$discussionlimit); + $this->View = 'discussions'; + } else if($cp > 0) { + //$this->setJson('LessRow', $this->WatchedCategoriesPager->toString('less')); + $this->setJson('MoreRow', $this->WatchedCategoriesPager->toString('more')); + $this->setJson('Loading', $cp.' to '.$categoryLimit); + $this->View = 'categories'; + } + + } + + $this->canonicalUrl(url('/watching', true)); + + // Add modules + $this->addModule('DiscussionFilterModule'); + + // Render default view + $this->setData('Title', t('Watching')); + $this->setData('Breadcrumbs', [['Name' => t('Watching'), 'Url' => '/watching']]); + + $this->render(); + } +} diff --git a/Topcoder/design/topcoder.css b/Topcoder/design/topcoder.css index e91081c..01543c9 100644 --- a/Topcoder/design/topcoder.css +++ b/Topcoder/design/topcoder.css @@ -126,3 +126,22 @@ a:hover span.challengeRoles { padding: 0px 0px; text-transform: none; } + +.Topcoder h2.HomepageTitle { + font-family: Barlow_Condensed, Helvetica, Arial, sans-serif; + font-weight: 500; + color: #2a2a2a !important; + font-size: 24px !important; + font-weight: 500 !important; + line-height: 28px !important; + text-transform: uppercase !important; +} +.Topcoder h1.HomepageTitle { + font-family: Barlow_Condensed, Helvetica, Arial, sans-serif; + font-weight: 500; + color: #2a2a2a !important; + font-size: 34px !important; + font-weight: 500 !important; + line-height: 38px !important; + text-transform: uppercase !important; +} \ No newline at end of file diff --git a/Topcoder/js/topcoder.js b/Topcoder/js/topcoder.js new file mode 100644 index 0000000..9e3a02d --- /dev/null +++ b/Topcoder/js/topcoder.js @@ -0,0 +1,21 @@ +jQuery(document).ready(function($) { + + // Set up paging + if ($.morepager) { + + $('#WatchingDiscussionsMore').morepager({ + pageContainerSelector: 'ul.Discussions:last', + afterPageLoaded: function() { + $(document).trigger('DiscussionPagingComplete'); + } + }); + + // profile/discussions paging + $('#WatchingCategoriesMore').morepager({ + pageContainerSelector: 'ul.WatchedCategoryList:last', + afterPageLoaded: function() { + $(document).trigger('DiscussionPagingComplete'); + } + }); + } +}); diff --git a/Topcoder/views/watching/categories.php b/Topcoder/views/watching/categories.php new file mode 100644 index 0000000..ccc2e82 --- /dev/null +++ b/Topcoder/views/watching/categories.php @@ -0,0 +1,14 @@ +fetchViewLocation('helper_functions', 'categories', 'vanilla'); + +if($this->data('WatchedCategories')) { + $categories = $this->data('WatchedCategories'); + ?> + +fetchViewLocation('helper_functions', 'discussions', 'vanilla'); + +if ($this->data('Discussions')->numRows() > 0) { + ?> + + fetchViewLocation('discussions', 'Discussions', 'Vanilla')); ?> + +fetchViewLocation('helper_functions', 'discussions', 'vanilla'); +include_once $this->fetchViewLocation('helper_functions', 'categories', 'vanilla'); + +echo '

'. + adminCheck(NULL, ['', ' ']). + $this->data('Title'). + '

'; + +$this->fireEvent('AfterPageTitle'); +echo '
'; +echo categorySorts(); +echo '
'; + +if($this->data('WatchedCategories')) { + echo '

Categories

'; + $categories = $this->data('WatchedCategories'); + ?> + + WatchedCategoriesPager->toString('more'); +} + +if ($this->data('Discussions')->numRows() > 0) { + echo '

Discussions

'; + ?> + + DiscussionPager->toString('more'); +} + + + From c02d40d2760fad0ee7c0182fb670930d2ebde48a Mon Sep 17 00:00:00 2001 From: Bogdanova Olga Date: Thu, 1 Apr 2021 15:48:59 +0300 Subject: [PATCH 2/6] Fixed styles for Topcoder handles in notification popup --- Topcoder/design/topcoder.css | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Topcoder/design/topcoder.css b/Topcoder/design/topcoder.css index 01543c9..333e0f7 100644 --- a/Topcoder/design/topcoder.css +++ b/Topcoder/design/topcoder.css @@ -69,44 +69,59 @@ a.coderRatingRed:hover, a.coderRatingYellow:hover, a.coderRatingBlue:hover, a.co .MessageList .ItemDiscussion .Username.coderRatingRed, .MessageList .ItemDiscussion .Username.coderRatingRed:hover, .Content.MainContent .MessageList.DataList.Comments li.Item .Item-Header.CommentHeader .Username.coderRatingRed, .Flyout.Flyout a.coderRatingRed, .Flyout.Flyout a.coderRatingRed:link, .Flyout.Flyout a.coderRatingRed:hover, .Flyout.Flyout a.coderRatingRed:active, .Flyout.Flyout a.coderRatingRed:visited, -.userContent p a.coderRatingRed, .userContent p a.coderRatingRed:link, .userContent p a.coderRatingRed:hover, .userContent p a.coderRatingRed:active, .userContent p a.coderRatingRed:visited { color: #EE0000 !important; } +.userContent p a.coderRatingRed, .userContent p a.coderRatingRed:link, .userContent p a.coderRatingRed:hover, .userContent p a.coderRatingRed:active, .userContent p a.coderRatingRed:visited, +.InformMessages .InformMessage .coderRatingRed, .InformMessages .InformMessage .coderRatingRed:link, .InformMessages .InformMessage .coderRatingRed:hover, .InformMessages .InformMessage .coderRatingRed:active, .InformMessages .InformMessage .coderRatingRed:visited +{ color: #EE0000 !important; } /* Yellow */ .DataList .MItem a.coderRatingYellow, .DataList .MItem a.coderRatingYellow:link, .DataList .MItem a.coderRatingYellow:hover, .DataList .MItem a.coderRatingYellow:visited, .MessageList .ItemDiscussion .Username.coderRatingYellow, .MessageList .ItemDiscussion .Username.coderRatingYellow:hover, .Content.MainContent .MessageList.DataList.Comments li.Item .Item-Header.CommentHeader .Username.coderRatingYellow, .Flyout.Flyout a.coderRatingYellow, .Flyout.Flyout a.coderRatingYellow:link, .Flyout.Flyout a.coderRatingYellow:hover, .Flyout.Flyout a.coderRatingYellow:active, .Flyout.Flyout a.coderRatingYellow:visited, -.userContent p a.coderRatingYellow, .userContent p a.coderRatingYellow:link, .userContent p a.coderRatingYellow:hover, .userContent p a.coderRatingYellow:active, .userContent p a.coderRatingYellow:visited { color: #DDCC00 !important; } +.userContent p a.coderRatingYellow, .userContent p a.coderRatingYellow:link, .userContent p a.coderRatingYellow:hover, .userContent p a.coderRatingYellow:active, .userContent p a.coderRatingYellow:visited, +.InformMessages .InformMessage .coderRatingYellow, .InformMessages .InformMessage .coderRatingYellow:link, .InformMessages .InformMessage .coderRatingYellow:hover, .InformMessages .InformMessage .coderRatingYellow:active, .InformMessages .InformMessage .coderRatingYellow:visited +{ color: #DDCC00 !important; } /* Blue */ .DataList .MItem a.coderRatingBlue, .DataList .MItem a.coderRatingBlue:link, .DataList .MItem a.coderRatingBlue:hover, .DataList .MItem a.coderRatingBlue:visited, .MessageList .ItemDiscussion .Username.coderRatingBlue, .MessageList .ItemDiscussion .Username.coderRatingBlue:hover, .Content.MainContent .MessageList.DataList.Comments li.Item .Item-Header.CommentHeader .Username.coderRatingBlue, .Flyout.Flyout a.coderRatingBlue, .Flyout.Flyout a.coderRatingBlue:link, .Flyout.Flyout a.coderRatingBlue:active, .Flyout.Flyout a.coderRatingBlue:hover, .Flyout.Flyout a.coderRatingBlue:visited, -.userContent p a.coderRatingBlue, .userContent p a.coderRatingBlue:link, .userContent p a.coderRatingBlue:active, .userContent p a.coderRatingBlue:hover, .userContent p a.coderRatingBlue:visited{ color: #6666FF !important; } +.userContent p a.coderRatingBlue, .userContent p a.coderRatingBlue:link, .userContent p a.coderRatingBlue:active, .userContent p a.coderRatingBlue:hover, .userContent p a.coderRatingBlue:visited, +.InformMessages .InformMessage .coderRatingBlue, .InformMessages .InformMessage .coderRatingBlue:link, .InformMessages .InformMessage .coderRatingBlue:hover, .InformMessages .InformMessage .coderRatingBlue:active, .InformMessages .InformMessage .coderRatingBlue:visited +{ color: #6666FF !important; } + /* Green */ .DataList .MItem a.coderRatingGreen, .DataList .MItem a.coderRatingGreen:link, .DataList .MItem a.coderRatingGreen:hover, .DataList .MItem a.coderRatingGreen:visited, .MessageList .ItemDiscussion .Username.coderRatingGreen,.MessageList .ItemDiscussion .Username.coderRatingGreen:hover, .Content.MainContent .MessageList.DataList.Comments li.Item .Item-Header.CommentHeader .Username.coderRatingGreen, .Flyout.Flyout a.coderRatingGreen, .Flyout.Flyout a.coderRatingGreen:link, .Flyout.Flyout a.coderRatingGreen:active, .Flyout.Flyout a.coderRatingGreen:hover, .Flyout.Flyout a.coderRatingGreen:visited, -.userContent p a.coderRatingGreen, .userContent p a.coderRatingGreen:link, .userContent p a.coderRatingGreen:active, .userContent p a.coderRatingGreen:hover, .userContent p a.coderRatingGreen:visited { color: #00A900 !important; } +.userContent p a.coderRatingGreen, .userContent p a.coderRatingGreen:link, .userContent p a.coderRatingGreen:active, .userContent p a.coderRatingGreen:hover, .userContent p a.coderRatingGreen:visited, +.InformMessages .InformMessage .coderRatingGreen, .InformMessages .InformMessage .coderRatingGreen:link, .InformMessages .InformMessage .coderRatingGreen:hover, .InformMessages .InformMessage .coderRatingGreen:active, .InformMessages .InformMessage .coderRatingGreen:visited +{ color: #00A900 !important; } /* Gray */ .DataList .MItem a.coderRatingGrey, .DataList .MItem a.coderRatingGrey:link, .DataList .MItem a.coderRatingGrey:hover, .DataList .MItem a.coderRatingGrey:visited, .MessageList .ItemDiscussion .Username.coderRatingGrey,.MessageList .ItemDiscussion .Username.coderRatingGrey:hover, .Content.MainContent .MessageList.DataList.Comments li.Item .Item-Header.CommentHeader .Username.coderRatingGrey, .Flyout.Flyout a.coderRatingGrey, .Flyout.Flyout a.coderRatingGrey:link, .Flyout.Flyout a.coderRatingGrey:active, .Flyout.Flyout a.coderRatingGrey:hover, .Flyout.Flyout a.coderRatingGrey:visited, -.userContent p a.coderRatingGrey, .userContent p a.coderRatingGrey:link, .userContent p a.coderRatingGrey:active, .userContent p a.coderRatingGrey:hover, .userContent p a.coderRatingGrey:visited{ color: #999999 !important; } +.userContent p a.coderRatingGrey, .userContent p a.coderRatingGrey:link, .userContent p a.coderRatingGrey:active, .userContent p a.coderRatingGrey:hover, .userContent p a.coderRatingGrey:visited, +.InformMessages .InformMessage .coderRatingGrey, .InformMessages .InformMessage .coderRatingGrey:link, .InformMessages .InformMessage .coderRatingGrey:hover, .InformMessages .InformMessage .coderRatingGrey:active, .InformMessages .InformMessage .coderRatingGrey:visited +{ color: #999999 !important; } .DataList .MItem a.coderRatingNone, .DataList .MItem a.coderRatingNone:link, .DataList .MItem a.coderRatingNone:hover, .DataList .MItem a.coderRatingNone:visited, .MessageList .ItemDiscussion .Username.coderRatingNone, .MessageList .ItemDiscussion .Username.coderRatingNone:hover, .Content.MainContent .MessageList.DataList.Comments li.Item .Item-Header.CommentHeader .Username.coderRatingNone, .Flyout.Flyout a.coderRatingNone, .Flyout.Flyout a.coderRatingNone:link, .Flyout.Flyout a.coderRatingNone:active, .Flyout.Flyout a.coderRatingNone:hover, .Flyout.Flyout a.coderRatingNone:visited, -.userContent p a.coderRatingNone, .userContent p a.coderRatingNone:link, .userContent p a.coderRatingNone:active, .userContent p a.coderRatingNone:hover, .userContent p a.coderRatingNone:visited { color: #000000; !important;} +.userContent p a.coderRatingNone, .userContent p a.coderRatingNone:link, .userContent p a.coderRatingNone:active, .userContent p a.coderRatingNone:hover, .userContent p a.coderRatingNone:visited, +.InformMessages .InformMessage .coderRatingNone, .InformMessages .InformMessage .coderRatingNone:link, .InformMessages .InformMessage .coderRatingNone:hover, .InformMessages .InformMessage .coderRatingNone:active, .InformMessages .InformMessage .coderRatingNone:visited +{ color: #000000; !important;} /* Topcoder Admin */ .DataList .MItem a.topcoderAdmin, .DataList .MItem a.topcoderAdmin:link, .DataList .MItem a.topcoderAdmin:hover, .DataList .MItem a.topcoderAdmin:visited, .MessageList .ItemDiscussion .Username.topcoderAdmin, .MessageList .ItemDiscussion .Username.topcoderAdmin:hover, .Content.MainContent .MessageList.DataList.Comments li.Item .Item-Header.CommentHeader .Username.topcoderAdmin, .Flyout.Flyout a.topcoderAdmin, .Flyout.Flyout a.topcoderAdmin:link,.Flyout.Flyout a.topcoderAdmin:visited, .Flyout.Flyout a.topcoderAdmin:hover, .Flyout.Flyout a.topcoderAdmin:active, -.userContent p a.topcoderAdmin, .userContent p a.topcoderAdmin:link, .userContent p a.topcoderAdmin:visited, .userContent p a.topcoderAdmin:hover, .userContent p a.topcoderAdmin:active { color: #ff9900 !important; } +.userContent p a.topcoderAdmin, .userContent p a.topcoderAdmin:link, .userContent p a.topcoderAdmin:visited, .userContent p a.topcoderAdmin:hover, .userContent p a.topcoderAdmin:active, +.InformMessages .InformMessage .topcoderAdmin, .InformMessages .InformMessage .topcoderAdmin:link, .InformMessages .InformMessage .topcoderAdmin:hover, .InformMessages .InformMessage .topcoderAdmin:active, .InformMessages .InformMessage .topcoderAdmin:visited +{ color: #ff9900 !important; } a:hover span.topcoderHandle{ text-decoration: underline; From 10bb9e77999fc62baf04cb63c6b798103ad40e74 Mon Sep 17 00:00:00 2001 From: Bogdanova Olga Date: Sat, 3 Apr 2021 15:33:29 +0300 Subject: [PATCH 3/6] Issues-511: Reply placement --- ReplyTo/class.replyto.plugin.php | 126 +++++++++++++++++++++++++------ ReplyTo/js/replyto.js | 25 ------ 2 files changed, 105 insertions(+), 46 deletions(-) diff --git a/ReplyTo/class.replyto.plugin.php b/ReplyTo/class.replyto.plugin.php index d4d605d..9ac3064 100644 --- a/ReplyTo/class.replyto.plugin.php +++ b/ReplyTo/class.replyto.plugin.php @@ -10,6 +10,7 @@ class ReplyToPlugin extends Gdn_Plugin { const QUERY_PARAMETER_VIEW='view'; const VIEW_FLAT = 'flat'; const VIEW_THREADED = 'threaded'; + const VIEW_MODE = 'ReplyTo.ViewMode'; private $replyToModel; /** @@ -56,24 +57,102 @@ public function assetModel_styleCss_handler($sender) { // Set JS for this plugin. protected function prepareController(&$sender) { $sender->addJsFile('replyto.js', 'plugins/ReplyTo'); + } + + /** + * Set a view mode for Discussion Controller + * View Mode is calculated from request url. + * Flat mode is used - '/discussion/{DiscussionID}/p{Page} + * Threaded mode is used by default + * + * @param $sender + * @param $args + */ + public function discussionController_initialize_handler($sender, $args) { + $viewMode = self::getViewMode(); + $sender->setData(self::VIEW_MODE, $viewMode); + + } + + /** + * Set a view mode for Post Controller + * Replying to a comment and leaving a comment are processed by Post Controller. + * (the url 'post/comment/, 'post' method). + * Use HTTP_REFERER to get the current view mode + * @param $sender + * @param $args + */ + public function postController_initialize_handler($sender, $args) { + if(isset($_SERVER['HTTP_REFERER'])) { + $url = $_SERVER['HTTP_REFERER']; + } + parse_str( parse_url( $url, PHP_URL_QUERY), $array ); + $viewMode = $array[self::QUERY_PARAMETER_VIEW]; + if(!$viewMode) { + $viewMode = self::isPagingUrl($url)? self::VIEW_FLAT: self::VIEW_THREADED; + } + $sender->setData(self::VIEW_MODE, $viewMode); } public function discussionController_render_before(&$sender) { $this->prepareController($sender); } + /** + * After deleting a comment in a threaded view, the comment tree should be re-rendered + * because tree left/right might be changed if a parent comment has been deleted. + * deliveryType is VIEW in a threaded view + * deliveryType is BOOL in a flat view. Don't re-render a view. Deleted comment + * is hidden on the client. + * + * @param $sender + * @param $args + */ + public function discussionController_AfterCommentDeleted_handler($sender, $args) { + $viewMode = $sender->data('ReplyTo.ViewMode'); + if($sender->deliveryMethod() == DELIVERY_METHOD_JSON) { + $discussionID = $args['DiscussionID']; + $sender->json(self::VIEW_MODE, $viewMode); + if ($viewMode == self::VIEW_THREADED) { + // Show all comments + $commentModel = new CommentModel(); + $CountComments = $commentModel->getCountByDiscussion($discussionID); + $sender->setData('Comments', $commentModel->getByDiscussion($discussionID, $CountComments, 0)); + $sender->ClassName = 'DiscussionController'; + $sender->ControllerName = 'discussion'; + $sender->View = 'comments'; + } + } + } + public function postController_render_before($sender) { $this->prepareController($sender); } /** - * Add View Mode before rendering comments + * The 'beforeCommentRender' are fired by DiscussionController and PostController. + * Re-render a comment tree if new comment is added in threaded view. + * * @param $sender * @param $args */ - public function base_beforeCommentsRender_handler($sender, $args) { - $viewMode = self::getViewMode(); - $sender->setData('ViewMode', $viewMode); + public function base_beforeCommentRender_handler($sender, $args) { + // Editing existing comment or new comment added + if ($sender->deliveryType() != DELIVERY_TYPE_DATA) { + $sender->json('ReplyTo.ViewMode', $sender->data(self::VIEW_MODE)); + $isNewComment = $sender->data('NewComments'); + if($isNewComment) { + $discussionID = val('DiscussionID', $args['Discussion']); + $commentModel = new CommentModel(); + $countComments = $commentModel->getCountByDiscussion($discussionID); + // FIX: https://github.com/topcoder-platform/forums/issues/511 + // Render a full comment tree in threaded mode + if($sender->data(self::VIEW_MODE) == self::VIEW_THREADED) { + // Show all comments + $sender->setData('Comments', $commentModel->getByDiscussion($discussionID, $countComments, 0)); + } + } + } } /** @@ -81,7 +160,7 @@ public function base_beforeCommentsRender_handler($sender, $args) { * @param $sender * @param $args */ - public function base_InlineDiscussionOptionsLeft_handler($sender, $args){ + public function discussionController_InlineDiscussionOptionsLeft_handler($sender, $args){ $discussion = $sender->data('Discussion'); if (!$discussion) { return; @@ -92,7 +171,7 @@ public function base_InlineDiscussionOptionsLeft_handler($sender, $args){ } $discussionUrl = discussionUrl($discussion, '', '/'); - $viewMode = self::getViewMode(); + $viewMode = $sender->data(self::VIEW_MODE); echo ''; echo 'View: '; @@ -143,8 +222,16 @@ public function commentModel_deleteComment_handler(&$Sender) { $this->replyToModel->onDeleteComment($Comment); } + /** + * Set offset and limit depends on view mode. + * In the threaded mode, all comments are displayed. + * In the flat mode, comments are displayed with pagination. + * The hook is used when rendering a discussion page with comments + * @param $sender + * @param $args + */ public function discussionController_BeforeCalculatingOffsetLimit_handler($sender, $args) { - $viewMode = self::getViewMode(); + $viewMode = $sender->data(self::VIEW_MODE); // $offsetProvided = $args['OffsetProvided']; $discussion = $args['Discussion']; $offset = & $args['Offset']; @@ -179,7 +266,7 @@ public function discussionController_beforeDiscussionRender_handler($sender, $ar return; } - $viewMode = self::getViewMode(); + $viewMode = $sender->data(self::VIEW_MODE); if($viewMode == self::VIEW_FLAT) { return; } @@ -222,19 +309,16 @@ public function base_commentOptions_handler($sender, $args) { 'Class' => 'ReplyComment' ]; - $viewMode = self::getViewMode(); + $viewMode = $sender->data(self::VIEW_MODE); + $deliveryType = $viewMode == self::VIEW_THREADED? DELIVERY_TYPE_VIEW : DELIVERY_TYPE_BOOL; foreach ($options as $key => $value) { - $currentUrl = $options[$key]['Url']; - if (strpos($currentUrl, '?') !== false ) { - if (strpos($currentUrl, 'Target') !== false) { - $options[$key]['Url'] = $currentUrl.urlencode('?view='.$viewMode); - } else { - $options[$key]['Url'] = $currentUrl. '&view=' . $viewMode; - } - } else { - $options[$key]['Url'] = $currentUrl.'?view='.$viewMode; + $options[$key]['Url'] = strpos($options[$key]['Url'], '?') !== false ? $options[$key]['Url']: $options[$key]['Url'].'?'; + $options[$key]['Url'] .= '&view=' . $viewMode; + if($key == 'DeleteComment') { + $options[$key]['Url'] .='&deliveryType='.$deliveryType; } } + } /** @@ -276,7 +360,7 @@ public function base_inlineDiscussionOptions_handler($sender, $args) { * @param $args */ public function base_beforeCommentDisplay_handler($sender, $args) { - if($sender->deliveryType() != DELIVERY_TYPE_ALL) { + if($sender->deliveryType() != DELIVERY_TYPE_ALL) { // Editing a comment is processed by PostController // Ajax request to post new comments or update comments if(isset($_SERVER['HTTP_REFERER'])) { $previous = $_SERVER['HTTP_REFERER']; @@ -286,13 +370,13 @@ public function base_beforeCommentDisplay_handler($sender, $args) { if(!$viewMode) { $viewMode = self::isPagingUrl($previous) ? self::VIEW_FLAT : self::VIEW_THREADED; } - $sender->setData('ViewMode', $viewMode); + $sender->setData(self::VIEW_MODE, $viewMode); if($viewMode == self::VIEW_THREADED) { $this->buildCommentReplyToCssClasses($sender); } } } else { - $viewMode = self::getViewMode(); + $viewMode = $sender->data(self::VIEW_MODE); if($viewMode == self::VIEW_THREADED) { $this->buildCommentReplyToCssClasses($sender); } diff --git a/ReplyTo/js/replyto.js b/ReplyTo/js/replyto.js index 5ed35b1..01d6e8f 100644 --- a/ReplyTo/js/replyto.js +++ b/ReplyTo/js/replyto.js @@ -4,12 +4,6 @@ jQuery(document).ready(function($) { return (href.split(name + '=')[1] || '').split('&')[0]; } - //If view is not flat, reload a page to rebuild a tree - function reloadPage() { - var currentView = param(window.location.href, 'view'); - return currentView == 'threaded'; - } - $(document).on('click','a.ReplyComment', function(ev) { var btn = this; var parent = $(btn).parents('.MainContent'); @@ -61,25 +55,6 @@ jQuery(document).ready(function($) { return false; }); - - // Comment was added. - $(document).on('CommentAdded',function(ev) { - if (reloadPage() === true) { - window.location.reload(); - return false; - } - return false; - }); - - // Comment was deleted. - $(document).on('CommentDeleted',function(ev) { - if (reloadPage() === true) { - window.location.reload(); - return false; - } - return false; - }); - $(document).on('click','a.CancelReplyComment', function(ev) { clearReplyCommentForm(this); return false; From 8428acc29079510b41c1640ac754f597192f98d5 Mon Sep 17 00:00:00 2001 From: Bogdanova Olga Date: Sun, 4 Apr 2021 18:29:17 +0300 Subject: [PATCH 4/6] Issues-527: Made tcadmin handle unclickable --- Topcoder/class.topcoder.plugin.php | 15 +++++++++++++-- Topcoder/design/topcoder.css | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Topcoder/class.topcoder.plugin.php b/Topcoder/class.topcoder.plugin.php index 0d11db4..d3a648a 100644 --- a/Topcoder/class.topcoder.plugin.php +++ b/Topcoder/class.topcoder.plugin.php @@ -1892,6 +1892,10 @@ private static function topcoderUserTopcoderCache($userFields) { return $cached; } + public static function isUnclickableUser($userName) { + return strtolower($userName) == 'tcadmin'; + } + public static function log($message, $data = []) { if (c('Vanilla.SSO.Debug') || c('Debug')) { Logger::event( @@ -2046,7 +2050,8 @@ function userPhoto($user, $options = []) { $isTopcoderAdmin = val('IsAdmin', $topcoderProfile); $photoUrl = isset($photoUrl) && !empty(trim($photoUrl)) ? $photoUrl: UserModel::getDefaultAvatarUrl(); - $href = (val('NoLink', $options)) ? '' : ' href="'.url($userLink).'"'; + $isUnlickableUser = TopcoderPlugin::isUnclickableUser($name); + $href = (val('NoLink', $options)) || $isUnlickableUser ? '' : ' href="'.url($userLink).'"'; Gdn::controller()->EventArguments['User'] = $user; Gdn::controller()->EventArguments['Title'] =& $title; @@ -2136,7 +2141,8 @@ function userAnchor($user, $cssClass = null, $options = null) { } // Go to Topcoder user profile link instead of Vanilla profile link - $userUrl = topcoderUserUrl($user, $px); + $isUnlickableUser = TopcoderPlugin::isUnclickableUser($name); + $userUrl = $isUnlickableUser? '#' : topcoderUserUrl($user, $px); $topcoderProfile = TopcoderPlugin::getTopcoderUser($userID); $topcoderRating = val('Rating',$topcoderProfile, false); @@ -2150,6 +2156,11 @@ function userAnchor($user, $cssClass = null, $options = null) { $attributes['class'] = $attributes['class'].' '. 'topcoderAdmin' ; } + if($isUnlickableUser) { + $attributes['class'] = $attributes['class'].' '. 'disabledLink' ; + } + + Gdn::controller()->EventArguments['User'] = $user; Gdn::controller()->EventArguments['IsTopcoderAdmin'] =$isTopcoderAdmin; Gdn::controller()->EventArguments['Text'] =& $text; diff --git a/Topcoder/design/topcoder.css b/Topcoder/design/topcoder.css index 333e0f7..55d950b 100644 --- a/Topcoder/design/topcoder.css +++ b/Topcoder/design/topcoder.css @@ -159,4 +159,8 @@ a:hover span.challengeRoles { font-weight: 500 !important; line-height: 38px !important; text-transform: uppercase !important; +} + +.disabledLink { + pointer-events:none } \ No newline at end of file From 06932cc381da431e9d4f4c3aec5c7cafeabe9b18 Mon Sep 17 00:00:00 2001 From: Bogdanova Olga Date: Tue, 6 Apr 2021 10:00:36 +0300 Subject: [PATCH 5/6] Issues-523: support different query types --- DebugPlugin/controllers/api/SqlApiController.php | 12 +++++++----- DebugPlugin/openapi/sql.yml | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/DebugPlugin/controllers/api/SqlApiController.php b/DebugPlugin/controllers/api/SqlApiController.php index f10ad6e..7980c98 100644 --- a/DebugPlugin/controllers/api/SqlApiController.php +++ b/DebugPlugin/controllers/api/SqlApiController.php @@ -22,17 +22,19 @@ public function index(array $query) { $this->permission('Garden.Settings.Manage'); $in = $this->schema([ - 'sql:s' => 'Sql query' + 'sql:s' => 'Sql query', + 'type:s' => 'Type' ], 'in')->setDescription('Get a list of records.'); $query = $in->validate($query); $sql = $query['sql']; + $type = $query['type']; - if (strpos(strtolower($sql), 'select') !== 0) { - throw new ClientException('Unable to execute this query.'); - } + // if (strpos(strtolower($sql), 'select') !== 0) { + // throw new ClientException('Unable to execute this query.'); + // } - $data = Gdn::sql()->query($sql, 'select')->resultArray(); + $data = Gdn::sql()->query($sql, $type)->resultArray(); return $data; } diff --git a/DebugPlugin/openapi/sql.yml b/DebugPlugin/openapi/sql.yml index bde252d..ce532af 100644 --- a/DebugPlugin/openapi/sql.yml +++ b/DebugPlugin/openapi/sql.yml @@ -4,11 +4,16 @@ paths: /sql: get: parameters: - - description: SQL select query. + - description: SQL query. in: query name: sql schema: type: string + - description: SQL type query. + in: query + name: type + schema: + type: string responses: '200': content: From 7f7e852370ab6a91dabba019fb243642424ce03c Mon Sep 17 00:00:00 2001 From: Bogdanova Olga Date: Wed, 7 Apr 2021 13:41:58 +0300 Subject: [PATCH 6/6] Issues-519: read tideways logs --- .../controllers/api/ServiceApiController.php | 44 +++++++++++++++++++ DebugPlugin/openapi/service.yml | 15 +++++++ 2 files changed, 59 insertions(+) create mode 100644 DebugPlugin/controllers/api/ServiceApiController.php create mode 100644 DebugPlugin/openapi/service.yml diff --git a/DebugPlugin/controllers/api/ServiceApiController.php b/DebugPlugin/controllers/api/ServiceApiController.php new file mode 100644 index 0000000..7aa9c48 --- /dev/null +++ b/DebugPlugin/controllers/api/ServiceApiController.php @@ -0,0 +1,44 @@ +permission('Garden.Settings.Manage'); + + if (file_exists($path)) { + //Get file type and set it as Content Type + $finfo = finfo_open(FILEINFO_MIME_TYPE); + header('Content-Type: ' . finfo_file($finfo, $path)); + finfo_close($finfo); + + header('Content-Description: File Transfer'); + header('Content-Disposition: attachment; filename='.basename($path)); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Pragma: public'); + header('Content-Length: ' . filesize($path)); + ob_clean(); + flush(); + readfile($path); + exit; + } else { + throw notFoundException('File'); + } + } + +} \ No newline at end of file diff --git a/DebugPlugin/openapi/service.yml b/DebugPlugin/openapi/service.yml new file mode 100644 index 0000000..f4aff48 --- /dev/null +++ b/DebugPlugin/openapi/service.yml @@ -0,0 +1,15 @@ +openapi: 3.0.2 +info: Vanilla Service API +paths: + /service/tidewayslog: + get: + responses: + '200': + content: + 'text/plain': + schema: + type: string + description: Success + tags: + - Services + summary: File.