diff --git a/src/discussions/in-context-topics/TopicPostsView.jsx b/src/discussions/in-context-topics/TopicPostsView.jsx index 56cbc837..b0daea1c 100644 --- a/src/discussions/in-context-topics/TopicPostsView.jsx +++ b/src/discussions/in-context-topics/TopicPostsView.jsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; -import first from 'lodash/first'; import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; @@ -13,7 +12,8 @@ import { selectTopicThreads } from '../posts/data/selectors'; import PostsList from '../posts/PostsList'; import { discussionsPath, handleKeyDown } from '../utils'; import { - selectLoadingStatus, selectNonCoursewareTopics, selectSubsectionUnits, selectUnits, + selectArchivedTopic, selectLoadingStatus, selectNonCoursewareTopics, + selectSubsection, selectSubsectionUnits, selectUnits, } from './data/selectors'; import { BackButton, NoResults } from './components'; import messages from './messages'; @@ -25,8 +25,10 @@ function TopicPostsView({ intl }) { const topicsLoadingStatus = useSelector(selectLoadingStatus); const posts = useSelector(selectTopicThreads([topicId])); const selectedSubsectionUnits = useSelector(selectSubsectionUnits(category)); + const selectedSubsection = useSelector(selectSubsection(category)); const selectedUnit = useSelector(selectUnits)?.find(unit => unit.id === topicId); const selectedNonCoursewareTopic = useSelector(selectNonCoursewareTopics)?.find(topic => topic.id === topicId); + const selectedArchivedTopic = useSelector(selectArchivedTopic(topicId)); const backButtonPath = () => { const path = selectedUnit ? Routes.TOPICS.CATEGORY : Routes.TOPICS.ALL; @@ -39,12 +41,13 @@ function TopicPostsView({ intl }) { {topicId ? ( ) : ( )}
diff --git a/src/discussions/in-context-topics/TopicsView.jsx b/src/discussions/in-context-topics/TopicsView.jsx index 4dea2256..1fdc5a1a 100644 --- a/src/discussions/in-context-topics/TopicsView.jsx +++ b/src/discussions/in-context-topics/TopicsView.jsx @@ -1,6 +1,7 @@ import React, { useContext, useEffect } from 'react'; import classNames from 'classnames'; +import isEmpty from 'lodash/isEmpty'; import { useDispatch, useSelector } from 'react-redux'; import { Spinner } from '@edx/paragon'; @@ -8,22 +9,23 @@ import { Spinner } from '@edx/paragon'; import SearchInfo from '../../components/SearchInfo'; import { RequestStatus } from '../../data/constants'; import { DiscussionContext } from '../common/context'; -import { selectDiscussionProvider } from '../data/selectors'; +import { selectAreThreadsFiltered, selectDiscussionProvider } from '../data/selectors'; +import { clearFilter, clearSort } from '../posts/data/slices'; import NoResults from '../posts/NoResults'; import { handleKeyDown } from '../utils'; import { - selectCoursewareTopics, - selectFilteredTopics, selectLoadingStatus, + selectArchivedTopics, selectCoursewareTopics, selectFilteredTopics, selectLoadingStatus, selectNonCoursewareTopics, selectTopicFilter, } from './data/selectors'; import { setFilter } from './data/slices'; import { fetchCourseTopicsV3 } from './data/thunks'; -import { SectionBaseGroup, Topic } from './topic'; +import { ArchivedBaseGroup, SectionBaseGroup, Topic } from './topic'; function TopicsList() { const loadingStatus = useSelector(selectLoadingStatus); const coursewareTopics = useSelector(selectCoursewareTopics); const nonCoursewareTopics = useSelector(selectNonCoursewareTopics); + const archivedTopics = useSelector(selectArchivedTopics); return ( <> @@ -43,6 +45,12 @@ function TopicsList() { showDivider={(coursewareTopics.length - 1) !== index} /> ))} + {!isEmpty(archivedTopics) && ( + + )} {loadingStatus === RequestStatus.IN_PROGRESS && (
@@ -59,6 +67,7 @@ function TopicsView() { const topicFilter = useSelector(selectTopicFilter); const filteredTopics = useSelector(selectFilteredTopics); const loadingStatus = useSelector(selectLoadingStatus); + const isPostsFiltered = useSelector(selectAreThreadsFiltered); useEffect(() => { if (provider) { @@ -66,6 +75,13 @@ function TopicsView() { } }, [provider]); + useEffect(() => { + if (isPostsFiltered) { + dispatch(clearFilter()); + dispatch(clearSort()); + } + }, [isPostsFiltered]); + return (
{topicFilter && ( diff --git a/src/discussions/in-context-topics/data/selectors.js b/src/discussions/in-context-topics/data/selectors.js index 38bf7f20..824d8fbd 100644 --- a/src/discussions/in-context-topics/data/selectors.js +++ b/src/discussions/in-context-topics/data/selectors.js @@ -18,6 +18,22 @@ export const selectSubsectionUnits = subsectionId => state => state.inContextTop unit => unit.parentId === subsectionId, ); +export const selectSubsection = category => createSelector( + selectCoursewareTopics, + (coursewareTopics) => ( + coursewareTopics?.map((topic) => topic?.children)?.flat()?.find((topic) => topic.id === category) + ), +); + +export const selectArchivedTopics = state => state.inContextTopics.archivedTopics; + +export const selectArchivedTopic = topic => createSelector( + selectArchivedTopics, + (archivedTopics) => ( + archivedTopics?.find((archivedTopic) => archivedTopic.id === topic) + ), +); + export const selectLoadingStatus = state => state.inContextTopics.status; export const selectFilteredTopics = createSelector( diff --git a/src/discussions/in-context-topics/data/slices.js b/src/discussions/in-context-topics/data/slices.js index ac43c945..95c76fa2 100644 --- a/src/discussions/in-context-topics/data/slices.js +++ b/src/discussions/in-context-topics/data/slices.js @@ -12,6 +12,7 @@ const topicsSlice = createSlice({ nonCoursewareTopics: [], nonCoursewareIds: [], units: [], + archivedTopics: [], filter: '', }, reducers: { @@ -25,6 +26,7 @@ const topicsSlice = createSlice({ state.nonCoursewareTopics = payload.nonCoursewareTopics; state.nonCoursewareIds = payload.nonCoursewareIds; state.units = payload.units; + state.archivedTopics = payload.archivedTopics; }, fetchCourseTopicsFailed: (state) => { state.status = RequestStatus.FAILED; diff --git a/src/discussions/in-context-topics/data/thunks.js b/src/discussions/in-context-topics/data/thunks.js index b47907c0..34c1ec42 100644 --- a/src/discussions/in-context-topics/data/thunks.js +++ b/src/discussions/in-context-topics/data/thunks.js @@ -29,8 +29,15 @@ function normalizeTopicsV3(topics) { return arrayOfUnits; }, []); + const archivedTopics = reduce(topics, (arrayOfArchivedTopics, topic) => { + if (topic.id === 'archived') { + return topic.children; + } + return arrayOfArchivedTopics; + }, []); + const coursewareTopics = topics.filter((topic) => topic.courseware); - const nonCoursewareTopics = topics.filter((topic) => !topic.courseware); + const nonCoursewareTopics = topics.filter((topic) => !topic.courseware && topic.enabledInContext); const nonCoursewareIds = nonCoursewareTopics?.map((topic) => topic.id); return { @@ -39,6 +46,7 @@ function normalizeTopicsV3(topics) { coursewareTopics, nonCoursewareTopics, nonCoursewareIds, + archivedTopics, }; } diff --git a/src/discussions/in-context-topics/messages.js b/src/discussions/in-context-topics/messages.js index 87886cf6..8ae99e6e 100644 --- a/src/discussions/in-context-topics/messages.js +++ b/src/discussions/in-context-topics/messages.js @@ -69,6 +69,11 @@ const messages = defineMessages({ defaultMessage: 'Nothing here yet', description: 'Helping Text to display if nothing here yet', }, + archivedTopics: { + id: 'discussions.topics.archived.label', + defaultMessage: 'Archived', + description: 'Heading for displaying topics that are archived.', + }, }); export default messages; diff --git a/src/discussions/in-context-topics/topic/ArchivedBaseGroup.jsx b/src/discussions/in-context-topics/topic/ArchivedBaseGroup.jsx new file mode 100644 index 00000000..eef9fcd0 --- /dev/null +++ b/src/discussions/in-context-topics/topic/ArchivedBaseGroup.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import messages from '../messages'; +import Topic, { topicShape } from './Topic'; + +function ArchivedBaseGroup({ + archivedTopics, + showDivider, + intl, +}) { + return ( + <> + {showDivider && ( + <> +
+
+ + )} +
+
{intl.formatMessage(messages.archivedTopics)}
+ {archivedTopics?.map((topic, index) => ( + + ))} +
+ + ); +} + +ArchivedBaseGroup.propTypes = { + archivedTopics: PropTypes.arrayOf(topicShape).isRequired, + showDivider: PropTypes.bool, + intl: intlShape.isRequired, +}; + +ArchivedBaseGroup.defaultProps = { + showDivider: false, +}; +export default injectIntl(ArchivedBaseGroup); diff --git a/src/discussions/in-context-topics/topic/index.js b/src/discussions/in-context-topics/topic/index.js index 28f8a040..ef5526f7 100644 --- a/src/discussions/in-context-topics/topic/index.js +++ b/src/discussions/in-context-topics/topic/index.js @@ -1,3 +1,4 @@ /* eslint-disable import/prefer-default-export */ +export { default as ArchivedBaseGroup } from './ArchivedBaseGroup'; export { default as SectionBaseGroup } from './SectionBaseGroup'; export { default as Topic } from './Topic'; diff --git a/src/discussions/posts/data/slices.js b/src/discussions/posts/data/slices.js index 6501f9ff..8d3c5a55 100644 --- a/src/discussions/posts/data/slices.js +++ b/src/discussions/posts/data/slices.js @@ -217,6 +217,19 @@ const threadsSlice = createSlice({ clearRedirect: (state) => { state.redirectToThread = null; }, + clearFilter: (state) => { + state.filters = { + status: PostsStatusFilter.ALL, + postType: ThreadType.ALL, + cohort: '', + search: '', + }; + state.pages = []; + }, + clearSort: (state) => { + state.sortedBy = ThreadOrdering.BY_LAST_ACTIVITY; + state.pages = []; + }, }, }); @@ -253,6 +266,8 @@ export const { hidePostEditor, clearRedirect, clearPostsPages, + clearFilter, + clearSort, } = threadsSlice.actions; export const threadsReducer = threadsSlice.reducer; diff --git a/src/discussions/posts/post-editor/PostEditor.jsx b/src/discussions/posts/post-editor/PostEditor.jsx index 91700979..adffc4fc 100644 --- a/src/discussions/posts/post-editor/PostEditor.jsx +++ b/src/discussions/posts/post-editor/PostEditor.jsx @@ -36,6 +36,7 @@ import { } from '../../data/selectors'; import { EmptyPage } from '../../empty-posts'; import { + selectArchivedTopics, selectCoursewareTopics as inContextCourseware, selectNonCoursewareIds as inContextCoursewareIds, selectNonCoursewareTopics as inContextNonCourseware, @@ -114,6 +115,7 @@ function PostEditor({ const { allowAnonymous, allowAnonymousToPeers } = useSelector(selectAnonymousPostingConfig); const { reasonCodesEnabled, editReasons } = useSelector(selectModerationSettings); const userIsStaff = useSelector(selectUserIsStaff); + const archivedTopics = useSelector(selectArchivedTopics); const canDisplayEditReason = (reasonCodesEnabled && editExisting && (userHasModerationPrivileges || userIsGroupTa || userIsStaff) @@ -317,7 +319,8 @@ function PostEditor({ ))} {enableInContext ? ( - coursewareTopics?.map(section => ( + <> + {coursewareTopics?.map(section => ( section?.children?.map(subsection => ( )) - )) + ))} + {(userIsStaff || userIsGroupTa || userHasModerationPrivileges) && ( + + {archivedTopics.map(topic => ( + + ))} + + )} + ) : ( coursewareTopics.map(categoryObj => (