@@ -1,16 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { _extends } from './common';
|
||||
|
||||
export default function Help(props) {
|
||||
return /* #__PURE__ */React.createElement('svg', _extends({
|
||||
width: 24,
|
||||
height: 24,
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
}, props), /* #__PURE__ */React.createElement('path', {
|
||||
d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z',
|
||||
fill: '#2D494E',
|
||||
}));
|
||||
}
|
||||
18
src/components/icons/People.jsx
Normal file
18
src/components/icons/People.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function People() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
fill="#000"
|
||||
d="M11.072 7.332a1.992 1.992 0 001.993-2 1.997 1.997 0 10-3.993 0c0 1.107.893 2 2 2zm-5.334 0a1.992 1.992 0 001.994-2 1.997 1.997 0 10-3.993 0c0 1.107.893 2 2 2zm0 1.333c-1.553 0-4.666.78-4.666 2.334v1.666h9.333V11c0-1.554-3.113-2.334-4.667-2.334zm5.334 0c-.194 0-.414.014-.647.034.773.56 1.313 1.313 1.313 2.3v1.666h4V11c0-1.554-3.113-2.334-4.666-2.334z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
35
src/components/icons/Question.jsx
Normal file
35
src/components/icons/Question.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Question() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="29"
|
||||
height="28"
|
||||
fill="none"
|
||||
viewBox="0 0 29 28"
|
||||
>
|
||||
<g clipPath="url(#clip0_5714_503)">
|
||||
<path
|
||||
fill="#2D494E"
|
||||
stroke="#fff"
|
||||
strokeWidth="2"
|
||||
d="M14.404 1.333C7.412 1.333 1.737 7.008 1.737 14s5.675 12.667 12.667 12.667S27.07 20.992 27.07 14 21.396 1.333 14.404 1.333z"
|
||||
/>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M13.237 22.167h2.333v-2.334h-2.333v2.334zM16.936 14.198l1.05-1.073A3.712 3.712 0 0019.07 10.5a4.665 4.665 0 00-4.666-4.667A4.665 4.665 0 009.737 10.5h2.333a2.34 2.34 0 012.334-2.333 2.34 2.34 0 012.333 2.333c0 .642-.256 1.225-.688 1.645l-1.447 1.47a4.696 4.696 0 00-1.365 3.302v.583h2.333c0-1.75.526-2.45 1.366-3.302z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5714_503">
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M0 0H28V28H0z"
|
||||
transform="translate(.404)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { _extends } from './common';
|
||||
|
||||
export default function QuestionAnswer(props) {
|
||||
return /* #__PURE__ */React.createElement('svg', _extends({
|
||||
width: 20,
|
||||
height: 20,
|
||||
viewBox: '0 0 20 20',
|
||||
fill: 'none',
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
}, props), /* #__PURE__ */React.createElement('path', {
|
||||
d: 'M16.7371 4.00002H14.2371V11.5H3.4038V14H13.4038L16.7371 17.3334V4.00002ZM12.5705 9.83335V0.666687H0.0704651V13.1667L3.4038 9.83335H12.5705Z',
|
||||
fill: 'currentColor',
|
||||
}));
|
||||
}
|
||||
18
src/components/icons/QuestionAnswer.jsx
Normal file
18
src/components/icons/QuestionAnswer.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function QuestionAnswer() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="20"
|
||||
fill="none"
|
||||
viewBox="0 0 21 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18.737 5h-2.5v7.5H5.404V15h10l3.333 3.333V5zm-4.166 5.833V1.667H2.07v12.5l3.333-3.334h9.166z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { _extends } from './common';
|
||||
|
||||
export default function QuestionAnswerOutline(props) {
|
||||
return /* #__PURE__ */React.createElement('svg', _extends({
|
||||
width: 20,
|
||||
height: 20,
|
||||
viewBox: '0 0 20 20',
|
||||
fill: 'none',
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
}, props), /* #__PURE__ */React.createElement('path', {
|
||||
d: 'M 16.7371 4 H 14.2371 V 11.5 H 3.4038 V 14 H 13.4038 L 16.7371 17.3334 V 4 Z M 12.5705 9.8333 V 0.6667 H 0.0705 V 13.1667 L 3.4038 9.8333 H 12.5705 Z M 11.465 8.618 H 1.038 V 1.683 H 11.465Z',
|
||||
fill: 'currentColor',
|
||||
}));
|
||||
}
|
||||
18
src/components/icons/QuestionAnswerOutline.jsx
Normal file
18
src/components/icons/QuestionAnswerOutline.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function QuestionAnswerOutline() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.6 3.267v6.067H4.08l-.512.512-.502.502v-7.08H12.6zm.867-1.733H2.198a.87.87 0 00-.867.867v12.134L4.8 11.068h8.668a.87.87 0 00.866-.867v-7.8a.87.87 0 00-.867-.867zM17.8 5h-1.733v7.8H4.799v1.734c0 .476.39.867.867.867H15.2l3.467 3.466v-13A.87.87 0 0017.8 5z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { _extends } from './common';
|
||||
|
||||
export default function StarFilled(props) {
|
||||
return /* #__PURE__ */React.createElement('svg', _extends({
|
||||
width: 20,
|
||||
height: 20,
|
||||
viewBox: '0 0 20 20',
|
||||
fill: 'none',
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
}, props), /* #__PURE__ */React.createElement('path', {
|
||||
d: 'M8.4038 13.3917L13.5538 16.5L12.1871 10.6417L16.7371 6.70002L10.7455 6.19169L8.4038 0.666687L6.06213 6.19169L0.0704651 6.70002L4.62047 10.6417L3.2538 16.5L8.4038 13.3917Z',
|
||||
fill: 'currentColor',
|
||||
}));
|
||||
}
|
||||
18
src/components/icons/StarFilled.jsx
Normal file
18
src/components/icons/StarFilled.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function StarFilled() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="20"
|
||||
fill="none"
|
||||
viewBox="0 0 21 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.404 14.392l5.15 3.108-1.367-5.858 4.55-3.942-5.991-.508-2.342-5.525-2.342 5.525L2.07 7.7l4.55 3.942L5.254 17.5l5.15-3.108z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { _extends } from './common';
|
||||
|
||||
export default function StarOutline(props) {
|
||||
return /* #__PURE__ */React.createElement('svg', _extends({
|
||||
width: 20,
|
||||
height: 20,
|
||||
viewBox: '0 0 20 20',
|
||||
fill: 'none',
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
}, props), /* #__PURE__ */React.createElement('path', {
|
||||
d: 'M16.7371 6.69999L10.7455 6.18332L8.4038 0.666656L6.06213 6.19166L0.0704651 6.69999L4.62047 10.6417L3.2538 16.5L8.4038 13.3917L13.5538 16.5L12.1955 10.6417L16.7371 6.69999ZM8.4038 11.8333L5.27047 13.725L6.1038 10.1583L3.33713 7.75832L6.98713 7.44166L8.4038 4.08332L9.8288 7.44999L13.4788 7.76666L10.7121 10.1667L11.5455 13.7333L8.4038 11.8333Z',
|
||||
fill: 'currentColor',
|
||||
}));
|
||||
}
|
||||
18
src/components/icons/StarOutline.jsx
Normal file
18
src/components/icons/StarOutline.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function StarOutline() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18.737 7.7l-5.991-.517-2.342-5.516-2.342 5.525L2.07 7.7l4.55 3.942L5.254 17.5l5.15-3.108 5.15 3.108-1.359-5.858L18.737 7.7zm-8.333 5.133L7.27 14.725l.834-3.567-2.767-2.4 3.65-.316 1.417-3.359 1.425 3.367 3.65.317-2.767 2.4.834 3.566-3.142-1.9z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { _extends } from './common';
|
||||
|
||||
export default function ThumbUpFilled(props) {
|
||||
return /* #__PURE__ */React.createElement('svg', _extends({
|
||||
width: 20,
|
||||
height: 20,
|
||||
viewBox: '0 0 20 20',
|
||||
fill: 'none',
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
}, props), /* #__PURE__ */React.createElement('path', {
|
||||
d: 'M11.2122 0.833344L5.23715 6.81668V17.5H15.4955L18.5705 10.3333V6.66668H11.6455L12.5788 2.18334L11.2122 0.833344ZM0.237152 7.50001H3.57049V17.5H0.237152V7.50001Z',
|
||||
fill: 'currentColor',
|
||||
}));
|
||||
}
|
||||
18
src/components/icons/ThumbUpFilled.jsx
Normal file
18
src/components/icons/ThumbUpFilled.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function ThumbUpFilled() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="20"
|
||||
fill="none"
|
||||
viewBox="0 0 21 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.212.833L6.237 6.817V17.5h10.258l3.075-7.167V6.667h-6.925l.934-4.484-1.367-1.35zM1.237 7.5H4.57v10H1.237v-10z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { _extends } from './common';
|
||||
|
||||
export default function ThumbUpOutline(props) {
|
||||
return /* #__PURE__ */React.createElement('svg', _extends({
|
||||
width: 20,
|
||||
height: 20,
|
||||
viewBox: '0 0 20 20',
|
||||
fill: 'none',
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
}, props), /* #__PURE__ */React.createElement('path', {
|
||||
d: 'M18.5705 6.66668V10.3333L15.4955 17.5L5.23715 17.5L5.23715 6.81668L11.2122 0.833344L12.5788 2.18334L11.6455 6.66668L18.5705 6.66668ZM6.90382 7.50834L6.90382 15.8333L14.3955 15.8333L16.9038 9.99168V8.33334L9.59548 8.33334L10.5205 3.88334L6.90382 7.50834Z M3.57049 17.5H0.237152L0.237152 7.50001H3.57049L3.57049 17.5Z',
|
||||
fill: 'currentColor',
|
||||
}));
|
||||
}
|
||||
21
src/components/icons/ThumbUpOutline.jsx
Normal file
21
src/components/icons/ThumbUpOutline.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function ThumbUpOutline() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
d="M19.57 6.667v3.666L16.495 17.5H6.238V6.817L12.212.833l1.367 1.35-.934 4.484h6.925zm-11.666.841v8.325h7.492l2.508-5.841V8.333h-7.309l.925-4.45-3.616 3.625z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path fill="currentColor" d="M4.57 17.5H1.237v-10H4.57v10z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* eslint-disable no-func-assign */
|
||||
/* eslint-disable prefer-object-spread */
|
||||
/* eslint-disable prefer-rest-params */
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function _extends() {
|
||||
_extends = Object.assign || function (target) {
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
const source = arguments[i];
|
||||
for (const key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
return _extends.apply(this, arguments);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as Help } from './Help';
|
||||
export { default as People } from './People';
|
||||
export { default as Question } from './Question';
|
||||
export { default as QuestionAnswer } from './QuestionAnswer';
|
||||
export { default as QuestionAnswerOutline } from './QuestionAnswerOutline';
|
||||
export { default as StarFilled } from './StarFilled';
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Icon } from '@edx/paragon';
|
||||
@@ -18,6 +19,7 @@ function AuthorLabel({
|
||||
}) {
|
||||
let icon = null;
|
||||
let authorLabelMessage = null;
|
||||
|
||||
if (authorLabel === 'Staff') {
|
||||
icon = Institution;
|
||||
authorLabelMessage = intl.formatMessage(messages.authorLabelStaff);
|
||||
@@ -26,9 +28,15 @@ function AuthorLabel({
|
||||
icon = School;
|
||||
authorLabelMessage = intl.formatMessage(messages.authorLabelTA);
|
||||
}
|
||||
|
||||
const fontWeight = authorLabelMessage ? 'font-weight-500' : 'font-weight-normal text-primary-500';
|
||||
const className = classNames('d-flex align-items-center', labelColor);
|
||||
|
||||
const labelContents = (
|
||||
<>
|
||||
<span className="mr-1 font-weight-normal font-size-14 font-style-normal font-family-inter">{author}</span>
|
||||
<span className={`mr-1 font-size-14 font-style-normal font-family-inter ${fontWeight}`}>
|
||||
{capitalize(author)}
|
||||
</span>
|
||||
{icon && (
|
||||
<Icon
|
||||
style={{
|
||||
@@ -39,13 +47,16 @@ function AuthorLabel({
|
||||
/>
|
||||
)}
|
||||
{authorLabelMessage && (
|
||||
<span className="mr-3 ml-1 font-weight-normal font-size-14 font-style-normal font-family-inter">
|
||||
<span
|
||||
className={`mr-3 font-size-14 font-style-normal font-family-inter ${fontWeight}`}
|
||||
style={{ marginLeft: '2px' }}
|
||||
>
|
||||
{authorLabelMessage}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const className = classNames('d-flex align-items-center', labelColor);
|
||||
|
||||
return linkToProfile
|
||||
? React.createElement('a', { href: '#nowhere', className }, labelContents)
|
||||
: React.createElement('div', { className }, labelContents);
|
||||
|
||||
@@ -11,8 +11,8 @@ export default function timeLocale(number, index, totalSec) {
|
||||
['%sd', 'in %s days'],
|
||||
['1w', 'in 1 week'],
|
||||
['%sw', 'in %s weeks'],
|
||||
['1M', 'in 1 month'],
|
||||
['%sM', 'in %s months'],
|
||||
['1m', 'in 1 month'],
|
||||
['%sm', 'in %s months'],
|
||||
['1y', 'in 1 year'],
|
||||
['%sy', 'in %s years'],
|
||||
][index];
|
||||
|
||||
@@ -114,18 +114,21 @@ describe('PostsView', () => {
|
||||
});
|
||||
expect(screen.getAllByText(/this is thread-\d+/i)).toHaveLength(threadCount);
|
||||
});
|
||||
|
||||
test('displays a list of user posts', async () => {
|
||||
await act(async () => {
|
||||
await renderComponent({ myPosts: true });
|
||||
});
|
||||
expect(screen.getAllByText('abc123')).toHaveLength(threadCount);
|
||||
expect(screen.getAllByText('Abc123')).toHaveLength(threadCount);
|
||||
});
|
||||
|
||||
test('displays a list of posts in a topic', async () => {
|
||||
await act(async () => {
|
||||
await renderComponent({ topicId: 'some-topic-1' });
|
||||
});
|
||||
expect(screen.getAllByText(/this is thread-\d+ in topic some-topic-1/i)).toHaveLength(Math.ceil(threadCount / 3));
|
||||
});
|
||||
|
||||
test('displays a list of posts in a category', async () => {
|
||||
await act(async () => {
|
||||
await renderComponent({ category: 'test-usage-key' });
|
||||
|
||||
@@ -2,9 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Icon, IconButton, OverlayTrigger, Tooltip,
|
||||
} from '@edx/paragon';
|
||||
import { Icon, IconButtonWithTooltip } from '@edx/paragon';
|
||||
|
||||
import { ThumbUpFilled, ThumbUpOutline } from '../../../components/icons';
|
||||
import messages from './messages';
|
||||
@@ -24,23 +22,19 @@ function LikeButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center align-content-center mr-4">
|
||||
<OverlayTrigger
|
||||
overlay={(
|
||||
<Tooltip id={`like-${count}-tooltip`}>
|
||||
{intl.formatMessage(voted ? messages.removeLike : messages.like)}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleClick}
|
||||
className="p-3 mr-1.5"
|
||||
alt="Like"
|
||||
iconAs={Icon}
|
||||
size="inline"
|
||||
src={voted ? ThumbUpFilled : ThumbUpOutline}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
<div className="d-flex align-items-center mr-4">
|
||||
<IconButtonWithTooltip
|
||||
id={`like-${count}-tooltip`}
|
||||
tooltipPlacement="top"
|
||||
tooltipContent={intl.formatMessage(voted ? messages.removeLike : messages.like)}
|
||||
src={voted ? ThumbUpFilled : ThumbUpOutline}
|
||||
iconAs={Icon}
|
||||
alt="Like"
|
||||
onClick={handleClick}
|
||||
size="inline"
|
||||
className="p-3 mr-0.5"
|
||||
iconClassNames="icon-size"
|
||||
/>
|
||||
{(count && count > 0) ? count : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,13 +6,14 @@ import * as timeago from 'timeago.js';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Badge, Icon, IconButton, OverlayTrigger, Tooltip,
|
||||
Badge, Icon, IconButtonWithTooltip, OverlayTrigger, Tooltip,
|
||||
} from '@edx/paragon';
|
||||
import {
|
||||
Locked, People,
|
||||
Locked,
|
||||
} from '@edx/paragon/icons';
|
||||
|
||||
import {
|
||||
People,
|
||||
QuestionAnswer,
|
||||
QuestionAnswerOutline,
|
||||
StarFilled,
|
||||
@@ -31,6 +32,7 @@ function PostFooter({
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
timeago.register('time-locale', timeLocale);
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<LikeButton
|
||||
@@ -38,71 +40,61 @@ function PostFooter({
|
||||
onClick={() => dispatch(updateExistingThread(post.id, { voted: !post.voted }))}
|
||||
voted={post.voted}
|
||||
/>
|
||||
<OverlayTrigger
|
||||
overlay={(
|
||||
<Tooltip id={`follow-${post.id}-tooltip`}>
|
||||
{intl.formatMessage(post.following ? messages.unfollow : messages.follow)}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
dispatch(updateExistingThread(post.id, { following: !post.following }));
|
||||
return true;
|
||||
}}
|
||||
alt="Follow"
|
||||
iconAs={Icon}
|
||||
size="inline"
|
||||
src={post.following ? StarFilled : StarOutline}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
{preview && post.commentCount > 1
|
||||
&& (
|
||||
<IconButtonWithTooltip
|
||||
id={`follow-${post.id}-tooltip`}
|
||||
tooltipPlacement="top"
|
||||
tooltipContent={intl.formatMessage(post.following ? messages.unFollow : messages.follow)}
|
||||
src={post.following ? StarFilled : StarOutline}
|
||||
iconAs={Icon}
|
||||
alt="Follow"
|
||||
onClick={() => {
|
||||
dispatch(updateExistingThread(post.id, { following: !post.following }));
|
||||
return true;
|
||||
}}
|
||||
size="inline"
|
||||
className="p-3"
|
||||
iconClassNames="icon-size"
|
||||
/>
|
||||
{preview && post.commentCount > 1 && (
|
||||
<div className="d-flex align-items-center ml-4">
|
||||
<IconButtonWithTooltip
|
||||
tooltipPlacement="top"
|
||||
tooltipContent={intl.formatMessage(messages.viewActivity)}
|
||||
src={post.unreadCommentCount ? QuestionAnswer : QuestionAnswerOutline}
|
||||
iconAs={Icon}
|
||||
alt="Comment Count"
|
||||
size="inline"
|
||||
className="p-3 mr-0.5"
|
||||
iconClassNames="icon-size"
|
||||
/>
|
||||
{post.commentCount}
|
||||
</div>
|
||||
)}
|
||||
{preview && post?.unreadCommentCount > 0 && post.commentCount > 1 && (
|
||||
<Badge variant="light" className="ml-2">
|
||||
{intl.formatMessage(messages.newLabel, { count: post.unreadCommentCount })}
|
||||
</Badge>
|
||||
)}
|
||||
<div className="d-flex flex-fill justify-content-end align-items-center">
|
||||
{post.groupId && (
|
||||
<>
|
||||
<Icon src={post.unreadCommentCount ? QuestionAnswer : QuestionAnswerOutline} className="ml-4 mr-2 my-0 mt-1.5" />
|
||||
<span style={{ minWidth: '1rem' }}>
|
||||
{post.commentCount}
|
||||
<OverlayTrigger
|
||||
overlay={(
|
||||
<Tooltip id={`visibility-${post.id}-tooltip`}>{post.groupName}</Tooltip>
|
||||
)}
|
||||
>
|
||||
<span data-testid="cohort-icon">
|
||||
<People />
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
<span
|
||||
className="text-light-700 mx-1.5 font-weight-500"
|
||||
style={{ fontSize: '16px' }}
|
||||
>
|
||||
·
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{post.unreadCommentCount && post.unreadCommentCount > 0 && post.commentCount > 1 ? (
|
||||
<Badge variant="light">{intl.formatMessage(messages.newLabel, { count: post.unreadCommentCount })}</Badge>
|
||||
) : null}
|
||||
<div className="d-flex flex-fill justify-content-end align-items-center">
|
||||
{
|
||||
post.groupId
|
||||
? (
|
||||
<>
|
||||
<OverlayTrigger
|
||||
overlay={(
|
||||
<Tooltip id={`visibility-${post.id}-tooltip`}>
|
||||
{post.groupName || intl.formatMessage(messages.visibleToAll)}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
data-testid="cohort-icon"
|
||||
src={People}
|
||||
style={{
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
color: 'black',
|
||||
}}
|
||||
/>
|
||||
</OverlayTrigger>
|
||||
<span
|
||||
className="text-light-700 mx-1.5 ml-1.5 font-weight-500"
|
||||
style={{
|
||||
height: '1.5rem',
|
||||
width: '0.31rem',
|
||||
fontSize: '16px',
|
||||
}}
|
||||
>
|
||||
·
|
||||
</span>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
<span title={post.createdAt} className="text-gray-500">
|
||||
{timeago.format(post.createdAt, 'time-locale')}
|
||||
</span>
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('PostFooter', () => {
|
||||
});
|
||||
|
||||
it("shows 'x new' badge for new comments", () => {
|
||||
renderComponent(mockPost);
|
||||
renderComponent(mockPost, true);
|
||||
expect(screen.getByText('2 New')).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -84,6 +84,7 @@ describe('PostFooter', () => {
|
||||
renderComponent({ ...mockPost, groupId: 5, groupName: 'Test Cohort' });
|
||||
expect(screen.getByTestId('cohort-icon')).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([[true, /unfollow/i], [false, /follow/i]])('test follow button when following=%s', async (following, message) => {
|
||||
renderComponent({ ...mockPost, following });
|
||||
const followButton = screen.getByRole('button', { name: /follow/i });
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSelector } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Avatar, Badge, Icon } from '@edx/paragon';
|
||||
|
||||
import { Help } from '../../../components/icons';
|
||||
import { Question } from '../../../components/icons';
|
||||
import { AvatarBorderAndLabelColors, ThreadType } from '../../../data/constants';
|
||||
import { ActionsDropdown, AuthorLabel } from '../../common';
|
||||
import { selectAuthorAvatars } from '../data/selectors';
|
||||
@@ -16,31 +16,37 @@ import { postShape } from './proptypes';
|
||||
export function PostAvatar({ post, authorLabel, fromPostLink }) {
|
||||
const authorAvatars = useSelector(selectAuthorAvatars(post.author));
|
||||
const borderColor = AvatarBorderAndLabelColors[authorLabel];
|
||||
|
||||
return (
|
||||
<div style={{ width: '3.75rem' }} className="d-flex pr-2.5">
|
||||
<div className="ml-auto mr-auto">
|
||||
{post.type === ThreadType.QUESTION && (
|
||||
<div className={`mr-3 ${post.type !== ThreadType.QUESTION && 'pt-1.5'}`}>
|
||||
{post.type === ThreadType.QUESTION && (
|
||||
<Icon
|
||||
src={Help}
|
||||
src={Question}
|
||||
className="position-absolute bg-white rounded-circle"
|
||||
style={{
|
||||
width: '1.5rem',
|
||||
height: '1.5rem',
|
||||
width: '1.75rem',
|
||||
height: '1.75rem',
|
||||
top: fromPostLink ? '10px' : '',
|
||||
left: fromPostLink ? '14px' : '',
|
||||
marginTop: !fromPostLink ? '5px' : '',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Avatar
|
||||
size={fromPostLink ? 'sm' : 'md'}
|
||||
className={`${borderColor && `border-${borderColor}`} ${post.type === ThreadType.QUESTION ? 'mt-2.5 ml-2.5' : ''}`}
|
||||
style={{
|
||||
borderWidth: '2px',
|
||||
height: post.type === ThreadType.QUESTION ? '1.5rem' : '2rem',
|
||||
width: post.type === ThreadType.QUESTION ? '1.5rem' : '2rem',
|
||||
}}
|
||||
alt={post.author}
|
||||
src={authorAvatars?.imageUrlSmall}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Avatar
|
||||
size={fromPostLink ? 'sm' : 'md'}
|
||||
className={`${borderColor && `border-${borderColor}`}
|
||||
${post.type === ThreadType.QUESTION && fromPostLink ? 'mt-3 ml-2' : ''}
|
||||
`}
|
||||
style={{
|
||||
borderWidth: '2px',
|
||||
height: post.type === ThreadType.QUESTION ? '1.5rem' : '2rem',
|
||||
width: post.type === ThreadType.QUESTION ? '1.5rem' : '2rem',
|
||||
marginTop: post.type === ThreadType.QUESTION ? '22px' : '',
|
||||
marginLeft: post.type === ThreadType.QUESTION ? '18px' : '',
|
||||
}}
|
||||
alt={post.author}
|
||||
src={authorAvatars?.imageUrlSmall}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@ function PostLink({
|
||||
const authorLabelColor = AvatarBorderAndLabelColors[post.authorLabel];
|
||||
return (
|
||||
<Link
|
||||
className="discussion-post list-group-item list-group-item-action p-0 text-decoration-none text-gray-900 mw-100"
|
||||
className="discussion-post list-group-item list-group-item-action p-0 text-decoration-none text-gray-900"
|
||||
to={linkUrl}
|
||||
style={{ lineHeight: '21px', height: '7.5rem' }}
|
||||
aria-current={isSelected(post.id) ? 'page' : undefined}
|
||||
onClick={() => isSelected(post.id)}
|
||||
style={{ lineHeight: '21px' }}
|
||||
>
|
||||
{post.pinned && (
|
||||
<div className="d-flex flex-fill justify-content-end mr-4 text-light-500 p-0">
|
||||
@@ -52,44 +52,53 @@ function PostLink({
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classNames('d-flex flex-row flex-fill mw-100 p-2.5 pr-4 border-primary-500', { 'bg-light-300': post.read })}
|
||||
className={
|
||||
classNames('d-flex flex-row py-2.5 px-4 border-primary-500',
|
||||
{ 'bg-light-300': post.read })
|
||||
}
|
||||
style={post.id === postId ? {
|
||||
borderRightWidth: '4px',
|
||||
borderRightStyle: 'solid',
|
||||
} : null}
|
||||
>
|
||||
<PostAvatar post={post} authorLabel={post.authorLabel} fromPostLink />
|
||||
<div className="d-flex flex-column" style={{ width: 'calc(100% - 4rem)' }}>
|
||||
<div className="align-items-center d-flex flex-row flex-fill">
|
||||
<div className="d-flex flex-column justify-content-start mw-100 flex-fill">
|
||||
<div className="d-flex align-items-center pb-0 mb-0 flex-fill font-weight-500">
|
||||
<div className="flex-fill text-truncate text-primary-500 font-weight-500 font-size-14 font-style-normal font-family-inter">
|
||||
{post.title}
|
||||
</div>
|
||||
{showAnsweredBadge
|
||||
&& (
|
||||
<div className="ml-auto">
|
||||
<Badge variant="success">{intl.formatMessage(messages.answered)}</Badge>
|
||||
<span className="sr-only">{' '}answered</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(post.abuseFlagged || post.abuseFlaggedCount)
|
||||
&& (
|
||||
<div className={showAnsweredBadge ? 'ml-2' : 'ml-auto'}>
|
||||
<Badge variant="danger" data-testid="reported-post">{intl.formatMessage(messages.contentReported)}</Badge>
|
||||
<span className="sr-only">{' '}reported</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="d-flex flex-column flex-fill" style={{ minWidth: 0 }}>
|
||||
<div className="d-flex flex-column justify-content-start mw-100 flex-fill">
|
||||
<div className="d-flex align-items-center pb-0 mb-0 flex-fill font-weight-500">
|
||||
<div
|
||||
className="text-truncate font-weight-500 font-size-14 text-primary-500 font-style-normal font-family-inter"
|
||||
>
|
||||
{post.title}
|
||||
</div>
|
||||
<AuthorLabel
|
||||
author={post.author || intl.formatMessage(messages.anonymous)}
|
||||
authorLabel={post.authorLabel}
|
||||
labelColor={authorLabelColor && `text-${authorLabelColor}`}
|
||||
/>
|
||||
|
||||
{showAnsweredBadge && (
|
||||
<Badge variant="success" className="font-weight-500 ml-auto badge-padding">
|
||||
{intl.formatMessage(messages.answered)}
|
||||
<span className="sr-only">{' '}answered</span>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{(post.abuseFlagged || post.abuseFlaggedCount) && (
|
||||
<Badge
|
||||
variant="danger"
|
||||
data-testid="reported-post"
|
||||
className={`font-weight-500 badge-padding ${showAnsweredBadge ? 'ml-2' : 'ml-auto'}`}
|
||||
>
|
||||
{intl.formatMessage(messages.contentReported)}
|
||||
<span className="sr-only">{' '}reported</span>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-truncate text-primary-500 font-weight-normal font-size-14 font-style-normal font-family-inter" style={{ maxHeight: '1.6em' }}>
|
||||
<AuthorLabel
|
||||
author={post.author || intl.formatMessage(messages.anonymous)}
|
||||
authorLabel={post.authorLabel}
|
||||
labelColor={authorLabelColor && `text-${authorLabelColor}`}
|
||||
/>
|
||||
<div
|
||||
className="text-truncate text-primary-500 font-weight-normal font-size-14 font-style-normal font-family-inter"
|
||||
style={{ maxHeight: '1.5rem' }}
|
||||
>
|
||||
{isPostPreviewAvailable(post.previewBody)
|
||||
? post.previewBody
|
||||
: intl.formatMessage(messages.postWithoutPreview)}
|
||||
|
||||
@@ -33,8 +33,8 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Answered',
|
||||
description: 'Tooltip/alttext for button to unfollow a discussion post',
|
||||
},
|
||||
unfollow: {
|
||||
id: 'discussions.post.unfollow',
|
||||
unFollow: {
|
||||
id: 'discussions.post.unFollow',
|
||||
defaultMessage: 'Unfollow',
|
||||
description: 'Tooltip/alttext for button to unfollow a discussion post',
|
||||
},
|
||||
@@ -45,9 +45,14 @@ const messages = defineMessages({
|
||||
},
|
||||
removeLike: {
|
||||
id: 'discussions.post.removeLike',
|
||||
defaultMessage: 'Remove like',
|
||||
defaultMessage: 'Unlike',
|
||||
description: 'Tooltip/alttext for button to remove the like applied to a discussion post',
|
||||
},
|
||||
viewActivity: {
|
||||
id: 'discussions.post.viewActivity',
|
||||
defaultMessage: 'View Activity',
|
||||
description: 'Tooltip/alttext for button to view the activity of a discussion post',
|
||||
},
|
||||
postClosed: {
|
||||
id: 'discussions.post.closed',
|
||||
defaultMessage: 'Post closed for responses and comments',
|
||||
@@ -58,11 +63,6 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Related to',
|
||||
description: 'Message followed the category and topic of post linking to in-course context',
|
||||
},
|
||||
visibleToAll: {
|
||||
id: 'discussions.post.cohort.everyone',
|
||||
defaultMessage: 'Everyone',
|
||||
description: 'Cohort visibility indicator for all people',
|
||||
},
|
||||
deletePostTitle: {
|
||||
id: 'discussions.editor.delete.post.title',
|
||||
defaultMessage: 'Delete post',
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
$fa-font-path: "~font-awesome/fonts";
|
||||
@import "~font-awesome/scss/font-awesome";
|
||||
|
||||
|
||||
#post, #comment, #reply {
|
||||
#post,
|
||||
#comment,
|
||||
#reply {
|
||||
img {
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
@@ -22,6 +23,10 @@ $fa-font-path: "~font-awesome/fonts";
|
||||
border-color: #998200;
|
||||
}
|
||||
|
||||
.border-anonymous {
|
||||
border-color: #F2F0EF;
|
||||
}
|
||||
|
||||
.font-size-14 {
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -35,5 +40,23 @@ $fa-font-path: "~font-awesome/fonts";
|
||||
}
|
||||
|
||||
.font-family-inter {
|
||||
font-family: 'Inter';
|
||||
}
|
||||
font-family: "Inter";
|
||||
}
|
||||
|
||||
.icon-size {
|
||||
height: 20px !important;
|
||||
width: 20px !important;
|
||||
}
|
||||
|
||||
.mr-0\.5 {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.badge-padding {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px
|
||||
}
|
||||
|
||||
.discussion-post:hover {
|
||||
background-color: unset !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user