feat: added blackout dates implementation (#271)

* feat: added blackout dates implementation

Co-authored-by: Mehak Nasir <mehaknasir94@gmail.com>
This commit is contained in:
Ahtisham Shahid
2022-09-01 19:58:42 +05:00
committed by GitHub
parent 8a73f23cb0
commit b0737da689
7 changed files with 74 additions and 21 deletions

View File

@@ -10,7 +10,9 @@ import { Button, useToggle } from '@edx/paragon';
import HTMLLoader from '../../../components/HTMLLoader';
import { ContentActions } from '../../../data/constants';
import { AlertBanner, DeleteConfirmation, EndorsedAlertBanner } from '../../common';
import { selectBlackoutDate } from '../../data/selectors';
import { fetchThread } from '../../posts/data/thunks';
import { inBlackoutDateRange } from '../../utils';
import CommentIcons from '../comment-icons/CommentIcons';
import { selectCommentCurrentPage, selectCommentHasMorePages, selectCommentResponses } from '../data/selectors';
import { editComment, fetchCommentResponses, removeComment } from '../data/thunks';
@@ -36,6 +38,7 @@ function Comment({
const [isReplying, setReplying] = useState(false);
const hasMorePages = useSelector(selectCommentHasMorePages(comment.id));
const currentPage = useSelector(selectCommentCurrentPage(comment.id));
const blackoutDateRange = useSelector(selectBlackoutDate);
useEffect(() => {
// If the comment has a parent comment, it won't have any children, so don't fetch them.
@@ -124,13 +127,18 @@ function Comment({
/>
) : (
<>
{!isClosedPost
{(!isClosedPost && !inBlackoutDateRange(blackoutDateRange))
&& (
<Button className="d-flex flex-grow mt-4.5" variant="outline-primary" onClick={() => setReplying(true)}>
{intl.formatMessage(messages.addComment)}
</Button>
<Button
className="d-flex flex-grow mt-4.5"
variant="outline-primary"
onClick={() => setReplying(true)}
>
{intl.formatMessage(messages.addComment)}
</Button>
)}
</>
)
)}
</div>

View File

@@ -2,10 +2,13 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import { selectBlackoutDate } from '../../data/selectors';
import { inBlackoutDateRange } from '../../utils';
import messages from '../messages';
import CommentEditor from './CommentEditor';
@@ -20,6 +23,8 @@ function ResponseEditor({
setAddingResponse(false);
}, [postId]);
const blackoutDateRange = useSelector(selectBlackoutDate);
return addingResponse
? (
<div className={classNames({ 'bg-white p-4 mb-4 rounded': addWrappingDiv })}>
@@ -29,7 +34,8 @@ function ResponseEditor({
onCloseEditor={() => setAddingResponse(false)}
/>
</div>
) : (
)
: !inBlackoutDateRange(blackoutDateRange) && (
<div className={classNames({ 'mb-4': addWrappingDiv }, 'actions d-flex')}>
<Button variant="primary" className="px-2.5 py-2" onClick={() => setAddingResponse(true)}>
{intl.formatMessage(messages.addResponse)}

View File

@@ -1,6 +1,8 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import {
@@ -10,9 +12,10 @@ import { MoreHoriz } from '@edx/paragon/icons';
import { ContentActions } from '../../data/constants';
import { commentShape } from '../comments/comment/proptypes';
import { selectBlackoutDate } from '../data/selectors';
import messages from '../messages';
import { postShape } from '../posts/post/proptypes';
import { useActions } from '../utils';
import { inBlackoutDateRange, useActions } from '../utils';
function ActionsDropdown({
intl,
@@ -31,6 +34,11 @@ function ActionsDropdown({
logError(`Unknown or unimplemented action ${action}`);
}
};
const blackoutDateRange = useSelector(selectBlackoutDate);
// Find and remove edit action if in blackout date range.
if (inBlackoutDateRange(blackoutDateRange)) {
actions.splice(actions.findIndex(action => action.id === 'edit'), 1);
}
return (
<>
<IconButton

View File

@@ -9,6 +9,7 @@ import { camelCaseObject, initializeMockApp, snakeCaseObject } from '@edx/fronte
import { AppProvider } from '@edx/frontend-platform/react';
import { ContentActions } from '../../data/constants';
import { initializeStore } from '../../store';
import messages from '../messages';
import { ACTIONS_LIST } from '../utils';
import ActionsDropdown from './ActionsDropdown';
@@ -126,6 +127,7 @@ describe('ActionsDropdown', () => {
roles: [],
},
});
store = initializeStore();
});
it.each(buildTestContent())('can open drop down if enabled', async (commentOrPost) => {

View File

@@ -18,6 +18,8 @@ export const selectLearnersTabEnabled = state => state.config.learnersTabEnabled
export const selectDivisionSettings = state => state.config.settings;
export const selectBlackoutDate = state => state.config.blackouts;
export const selectModerationSettings = state => ({
postCloseReasons: state.config.postCloseReasons,
editReasons: state.config.editReasons,

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
@@ -10,7 +10,8 @@ import {
import { Close } from '@edx/paragon/icons';
import Search from '../../../components/Search';
import { postMessageToParent } from '../../utils';
import { selectBlackoutDate } from '../../data/selectors';
import { inBlackoutDateRange, postMessageToParent } from '../../utils';
import { showPostEditor } from '../data';
import messages from './messages';
@@ -24,6 +25,7 @@ function PostActionsBar({
const handleCloseInContext = () => {
postMessageToParent('learning.events.sidebar.close');
};
const blackoutDateRange = useSelector(selectBlackoutDate);
return (
<div className="d-flex justify-content-end py-1 flex-grow-1">
{!inContext && (
@@ -37,15 +39,18 @@ function PostActionsBar({
{intl.formatMessage(messages.title)}
</h4>
)}
<Button
variant={inContext ? 'plain' : 'brand'}
className="my-0"
onClick={() => dispatch(showPostEditor())}
size="sm"
>
{intl.formatMessage(messages.addAPost)}
</Button>
{
!inBlackoutDateRange(blackoutDateRange) && (
<Button
variant={inContext ? 'plain' : 'brand'}
className="my-0"
onClick={() => dispatch(showPostEditor())}
size="sm"
>
{intl.formatMessage(messages.addAPost)}
</Button>
)
}
{inContext && (
<>
<div className="border-right mr-3 ml-4" />

View File

@@ -229,10 +229,10 @@ export function postMessageToParent(type, payload = {}) {
export const isPostPreviewAvailable = (htmlNode) => {
const containsImage = htmlNode.match(/(<img((?:\\.|.)*)>)/);
const isLatex = htmlNode.match(/(\${1,2})((?:\\.|.)*)/)
|| htmlNode.match(/(\[mathjax](.+?))+/)
|| htmlNode.match(/(\[mathjaxinline](.+?))+/)
|| htmlNode.match(/(\\\[(.+?))+/)
|| htmlNode.match(/(\\\((.+?))+/);
|| htmlNode.match(/(\[mathjax](.+?))+/)
|| htmlNode.match(/(\[mathjaxinline](.+?))+/)
|| htmlNode.match(/(\\\[(.+?))+/)
|| htmlNode.match(/(\\\((.+?))+/);
if (containsImage || isLatex || htmlNode === '') {
return false;
@@ -253,3 +253,25 @@ export const filterPosts = (posts, filterBy, sortBy = 'createdAt', order = 'desc
post => (filterBy.startsWith('un') ? !post[filterBy.slice(2)] : post[filterBy]),
), [sortBy], [order],
);
/**
* Helper function to make a check if date is in given range
* @param {Date} date this date to be checked in range
* @param {Date} start start date
* @param {Date} end end date
*/
export function dateInDateRange(date, start, end) {
return date >= start && date <= end;
}
/**
* Helper function to make a check if date is in given range
* @param {array} blackoutDateRanges start date
* @return Boolean
*/
export function inBlackoutDateRange(blackoutDateRanges) {
const now = new Date();
return blackoutDateRanges.some(
(blackoutDateRange) => dateInDateRange(now, new Date(blackoutDateRange.start), new Date(blackoutDateRange.end)),
);
}