Compare commits

..

1 Commits

Author SHA1 Message Date
adeel.tajamul
ddb7a2c13b fix: switch to use PUBLIC_PATH for routes 2023-05-23 12:10:48 +05:00
92 changed files with 28914 additions and 7054 deletions

View File

@@ -1,10 +1,9 @@
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig(
'eslint',
{
plugins: ['simple-import-sort'],
rules: {
module.exports = createConfig('eslint',
{
"plugins": ["simple-import-sort"],
"rules": {
'import/no-extraneous-dependencies': 'off',
'react-hooks/exhaustive-deps': 'off',
'jsx-a11y/no-noninteractive-element-interactions': 'off',
@@ -26,6 +25,7 @@ module.exports = createConfig(
},
],
'simple-import-sort/exports': 'error',
},
},
}
}
);

View File

@@ -9,17 +9,18 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- name: Setup Nodejs
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VER }}
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm ci
- name: Validate package-lock.json changes

View File

@@ -10,4 +10,4 @@ on:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master

2
.nvmrc
View File

@@ -1 +1 @@
18
16

View File

@@ -1,8 +1,7 @@
export TRANSIFEX_RESOURCE = frontend-app-discussions
transifex_resource = frontend-app-discussions
transifex_langs = "ar,fr,es_419,zh_CN,tr_TR,pl,fr_CA,fr_FR,de_DE,it_IT,pt_PT,uk,ru,hi,cs,es_AR,es_ES,fa_IR"
transifex_langs = "ar,fr,es_419,zh_CN,tr_TR,pl,fr_CA,fr_FR,de_DE,it_IT"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
@@ -56,24 +55,9 @@ push_translations:
# Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex.
pull_translations:
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull --filter=$(transifex_langs) \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-app-discussions/src/i18n/messages:frontend-app-discussions
$(intl_imports) frontend-component-header frontend-component-footer paragon frontend-app-discussions
endif
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:

View File

@@ -1,15 +1,10 @@
|Codecov| |license|
.. |Codecov| image:: https://codecov.io/gh/openedx/frontend-app-discussions/branch/master/graph/badge.svg?token=3z7XvuzTq3
:target: https://codecov.io/gh/openedx/frontend-app-discussions
.. |license| image:: https://img.shields.io/badge/license-AGPL-informational
:target: https://github.com/openedx/frontend-app-discussions/blob/master/LICENSE
frontend-app-discussions
========================
Introduction
------------
|Build Status| |Codecov| |license|
Purpose
-------
This repository is a React-based micro frontend for the Open edX discussion forums.
@@ -84,3 +79,10 @@ Internationalization
--------------------
Please see `edx/frontend-platform's i18n module <https://edx.github.io/frontend-platform/module-Internationalization.html>`_ for documentation on internationalization. The documentation explains how to use it, and the `How To <https://github.com/openedx/frontend-i18n/blob/master/docs/how_tos/i18n.rst>`_ has more detail.
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-app-discussions.svg?branch=master
:target: https://travis-ci.org/edx/frontend-app-discussions
.. |Codecov| image:: https://codecov.io/gh/edx/frontend-app-discussions/branch/master/graph/badge.svg
:target: https://codecov.io/gh/edx/frontend-app-discussions
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-discussions.svg
:target: @edx/frontend-app-discussions

32754
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,10 +34,10 @@
},
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "12.1.0",
"@edx/frontend-component-header": "4.3.0",
"@edx/frontend-platform": "4.6.0",
"@edx/paragon": "20.44.0",
"@edx/frontend-component-footer": "11.2.0",
"@edx/frontend-component-header": "3.2.0",
"@edx/frontend-platform": "2.6.1",
"@edx/paragon": "20.15.0",
"@reduxjs/toolkit": "1.8.0",
"@tinymce/tinymce-react": "3.13.1",
"babel-polyfill": "6.26.0",
@@ -48,9 +48,9 @@
"lodash.snakecase": "4.1.1",
"prop-types": "15.8.1",
"raw-loader": "4.0.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-redux": "7.2.9",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.6",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"redux": "4.1.2",
@@ -61,10 +61,10 @@
},
"devDependencies": {
"@edx/browserslist-config": "1.1.0",
"@edx/frontend-build": "12.8.27",
"@edx/frontend-build": "11.0.1",
"@edx/reactifex": "1.0.3",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.5",
"@testing-library/react": "12.1.4",
"@testing-library/user-event": "13.5.0",
"axios-mock-adapter": "1.20.0",
"babel-plugin-react-intl": "8.2.25",

View File

@@ -1,33 +1,9 @@
{
"extends": [
"config:base",
"schedule:weekly",
":automergeLinters",
":automergeMinor",
":automergeTesters",
":enableVulnerabilityAlerts",
":rebaseStalePrs",
":semanticCommits",
":updateNotScheduled"
"config:base"
],
"packageRules": [
{
"matchDepTypes": [
"devDependencies"
],
"matchUpdateTypes": [
"lockFileMaintenance",
"minor",
"patch",
"pin"
],
"automerge": true
},
{
"matchPackagePatterns": ["@edx", "@openedx"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
],
"timezone": "America/New_York"
"patch": {
"automerge": true
},
"rebaseStalePrs": true
}

View File

@@ -19,21 +19,19 @@ import { selectCourseCohorts } from '../discussions/cohorts/data/selectors';
import messages from '../discussions/posts/post-filter-bar/messages';
import { ActionItem } from '../discussions/posts/post-filter-bar/PostFilterBar';
const FilterBar = ({
function FilterBar({
intl,
filters,
selectedFilters,
onFilterChange,
showCohortsFilter,
}) => {
}) {
const [isOpen, setOpen] = useState(false);
const cohorts = useSelector(selectCourseCohorts);
const { status } = useSelector(state => state.cohorts);
const selectedCohort = useMemo(
() => cohorts.find(cohort => (
toString(cohort.id) === selectedFilters.cohort)),
[selectedFilters.cohort],
);
const selectedCohort = useMemo(() => cohorts.find(cohort => (
toString(cohort.id) === selectedFilters.cohort)),
[selectedFilters.cohort]);
const allFilters = [
{
@@ -185,7 +183,7 @@ const FilterBar = ({
</Collapsible.Body>
</Collapsible.Advanced>
);
};
}
FilterBar.propTypes = {
intl: intlShape.isRequired,

View File

@@ -24,23 +24,25 @@ const CourseTabsNavigation = ({
}, [courseId]);
return (
<div id="courseTabsNavigation" className={classNames('course-tabs-navigation px-4', className)}>
{!!tabs.length && (
<Tabs
className="nav-underline-tabs"
aria-label={intl.formatMessage(messages.courseMaterial)}
>
{tabs.map(({ url, title, slug }) => (
<a
key={slug}
className={classNames('nav-item flex-shrink-0 nav-link', { active: slug === activeTab })}
href={url}
>
{title}
</a>
))}
</Tabs>
)}
<div id="courseTabsNavigation" className={classNames('course-tabs-navigation', className)}>
<div className="container-xl">
{!!tabs.length && (
<Tabs
className="nav-underline-tabs"
aria-label={intl.formatMessage(messages.courseMaterial)}
>
{tabs.map(({ url, title, slug }) => (
<a
key={slug}
className={classNames('nav-item flex-shrink-0 nav-link', { active: slug === activeTab })}
href={url}
>
{title}
</a>
))}
</Tabs>
)}
</div>
</div>
);
};

View File

@@ -56,10 +56,8 @@ describe('Navigation bar api tests', () => {
});
it('Denied to get navigation bar when user has no access on course', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(
200,
(Factory.build('navigationBar', 1, { hasCourseAccess: false })),
);
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(200,
(Factory.build('navigationBar', 1, { hasCourseAccess: false })));
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.courseStatus).toEqual('denied');

View File

@@ -8,7 +8,7 @@ import { Dropdown } from '@edx/paragon';
import useIndexOfLastVisibleChild from './useIndexOfLastVisibleChild';
const Tabs = ({ children, className, ...attrs }) => {
export default function Tabs({ children, className, ...attrs }) {
const [
indexOfLastVisibleChild,
containerElementRef,
@@ -31,28 +31,25 @@ const Tabs = ({ children, className, ...attrs }) => {
// Insert the overflow menu at the cut off index (even if it will be hidden
// it so it can be part of measurements)
wrappedChildren.splice(
indexOfOverflowStart,
0, (
<div
className="nav-item flex-shrink-0"
style={indexOfOverflowStart >= React.Children.count(children) ? invisibleStyle : null}
ref={overflowElementRef}
key="overflow"
>
<Dropdown className="h-100">
<Dropdown.Toggle variant="link" className="nav-link h-100" id="learn.course.tabs.navigation.overflow.menu">
<FormattedMessage
id="learn.course.tabs.navigation.overflow.menu"
description="The title of the overflow menu for course tabs"
defaultMessage="More..."
/>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">{overflowChildren}</Dropdown.Menu>
</Dropdown>
</div>
),
);
wrappedChildren.splice(indexOfOverflowStart, 0, (
<div
className="nav-item flex-shrink-0"
style={indexOfOverflowStart >= React.Children.count(children) ? invisibleStyle : null}
ref={overflowElementRef}
key="overflow"
>
<Dropdown className="h-100">
<Dropdown.Toggle variant="link" className="nav-link h-100" id="learn.course.tabs.navigation.overflow.menu">
<FormattedMessage
id="learn.course.tabs.navigation.overflow.menu"
description="The title of the overflow menu for course tabs"
defaultMessage="More..."
/>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">{overflowChildren}</Dropdown.Menu>
</Dropdown>
</div>
));
return wrappedChildren;
}, [children, indexOfLastVisibleChild]);
@@ -65,7 +62,7 @@ const Tabs = ({ children, className, ...attrs }) => {
{tabChildren}
</nav>
);
};
}
Tabs.propTypes = {
children: PropTypes.node,
@@ -76,5 +73,3 @@ Tabs.defaultProps = {
children: null,
className: undefined,
};
export default Tabs;

View File

@@ -1,5 +1,5 @@
import React, {
useCallback, useContext, useEffect, useRef, useState,
useCallback, useContext, useEffect, useState,
} from 'react';
import camelCase from 'lodash/camelCase';
@@ -25,7 +25,6 @@ const Search = () => {
const isPostSearch = ['posts', 'my-posts'].includes(page);
const isTopicSearch = 'topics'.includes(page);
const [searchValue, setSearchValue] = useState('');
const previousSearchValueRef = useRef('');
let currentValue = '';
if (isPostSearch) {
@@ -40,15 +39,14 @@ const Search = () => {
dispatch(setSearchQuery(''));
dispatch(setTopicFilter(''));
dispatch(setUsernameSearch(''));
previousSearchValueRef.current = '';
}, [previousSearchValueRef]);
}, []);
const onChange = useCallback((query) => {
setSearchValue(query);
}, []);
const onSubmit = useCallback((query) => {
if (query === '' || query === previousSearchValueRef.current) {
if (query === '') {
return;
}
@@ -59,8 +57,7 @@ const Search = () => {
} else if (page === 'learners') {
dispatch(setUsernameSearch(query));
}
previousSearchValueRef.current = query;
}, [page, searchValue, previousSearchValueRef]);
}, [page, searchValue]);
const handleIconClick = useCallback((e) => {
e.preventDefault();

View File

@@ -42,7 +42,7 @@ import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.c
import contentUiCss from '!!raw-loader!tinymce/skins/ui/oxide/content.min.css';
/* istanbul ignore next */
const TinyMCEEditor = (props) => {
function TinyMCEEditor(props) {
// note that skin and content_css is disabled to avoid the normal
// loading process and is instead loaded as a string via content_style
@@ -152,6 +152,6 @@ const TinyMCEEditor = (props) => {
</AlertModal>
</>
);
};
}
export default React.memo(TinyMCEEditor);

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function InsertLink() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function Issue() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function People() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function PushPin() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function Question() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function QuestionAnswer() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function QuestionAnswerOutline() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function StarFilled() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function StarOutline() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function ThumbUpFilled() {
return (
<svg

View File

@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line react/function-component-definition
export default function ThumbUpOutline() {
return (
<svg

View File

@@ -13,23 +13,23 @@ import {
import { MoreHoriz } from '@edx/paragon/icons';
import { ContentActions } from '../../data/constants';
import { selectIsPostingEnabled } from '../data/selectors';
import { selectBlackoutDate } from '../data/selectors';
import messages from '../messages';
import { useActions } from '../utils';
import { inBlackoutDateRange, useActions } from '../utils';
const ActionsDropdown = ({
function ActionsDropdown({
actionHandlers,
contentType,
disabled,
dropDownIconSize,
iconSize,
id,
}) => {
}) {
const buttonRef = useRef();
const intl = useIntl();
const [isOpen, open, close] = useToggle(false);
const [target, setTarget] = useState(null);
const isPostingEnabled = useSelector(selectIsPostingEnabled);
const blackoutDateRange = useSelector(selectBlackoutDate);
const actions = useActions(contentType, id);
const handleActions = useCallback((action) => {
@@ -41,12 +41,12 @@ const ActionsDropdown = ({
}
}, [actionHandlers]);
// Find and remove edit action if in Posting is disabled.
// Find and remove edit action if in blackout date range.
useMemo(() => {
if (!isPostingEnabled) {
if (inBlackoutDateRange(blackoutDateRange)) {
actions.splice(actions.findIndex(action => action.id === 'edit'), 1);
}
}, [actions, isPostingEnabled]);
}, [actions, blackoutDateRange]);
const onClickButton = useCallback(() => {
setTarget(buttonRef.current);
@@ -109,7 +109,7 @@ const ActionsDropdown = ({
</div>
</>
);
};
}
ActionsDropdown.propTypes = {
id: PropTypes.string.isRequired,

View File

@@ -13,8 +13,6 @@ import { AppProvider } from '@edx/frontend-platform/react';
import { ContentActions } from '../../data/constants';
import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import { getCourseConfigApiUrl } from '../data/api';
import { fetchCourseConfig } from '../data/thunks';
import messages from '../messages';
import { getCommentsApiUrl } from '../post-comments/data/api';
import { addComment, fetchThreadComments } from '../post-comments/data/thunks';
@@ -31,7 +29,6 @@ let store;
let axiosMock;
const commentsApiUrl = getCommentsApiUrl();
const threadsApiUrl = getThreadsApiUrl();
const courseId = 'course-v1:edX+TestX+Test_Course';
const discussionThreadId = 'thread-1';
const questionThreadId = 'thread-2';
const commentContent = 'This is a comment for thread-1';
@@ -173,7 +170,7 @@ const findOpenActionsDropdownButton = async () => (
);
describe('ActionsDropdown', () => {
beforeEach(async () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
@@ -185,11 +182,6 @@ describe('ActionsDropdown', () => {
store = initializeStore();
Factory.resetAll();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`)
.reply(200, { isPostingEnabled: true });
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
});
it.each(Object.values(buildTestContent()))('can open drop down if enabled', async (commentOrPost) => {

View File

@@ -66,20 +66,19 @@ describe('Author label', () => {
['retired__user', null, false, ''],
['staff_user', 'Staff', true, 'text-staff-color'],
['learner_user', null, false, ''],
])('for %s', (author, authorLabel, linkToProfile, labelColor) => {
it(
'it has author name text',
])('for %s', (
author, authorLabel, linkToProfile, labelColor,
) => {
it('it has author name text',
async () => {
renderComponent(author, authorLabel, linkToProfile, labelColor);
const authorElement = container.querySelector('[role=heading]');
const authorName = author.startsWith('retired__user') ? '[Deactivated]' : author;
expect(authorElement).toHaveTextContent(authorName);
},
);
});
it(
`it is "${!linkToProfile && 'not'}" clickable when linkToProfile is ${!!linkToProfile}`,
it(`it is "${!linkToProfile && 'not'}" clickable when linkToProfile is ${!!linkToProfile}`,
async () => {
renderComponent(author, authorLabel, linkToProfile, labelColor);
@@ -88,11 +87,9 @@ describe('Author label', () => {
} else {
expect(screen.queryByTestId('learner-posts-link')).not.toBeInTheDocument();
}
},
);
});
it(
`it has "${!linkToProfile && 'not'}" label text and label color when linkToProfile is ${!!linkToProfile}`,
it(`it has "${!linkToProfile && 'not'}" label text and label color when linkToProfile is ${!!linkToProfile}`,
async () => {
renderComponent(author, authorLabel, linkToProfile, labelColor);
const authorElement = container.querySelector('[role=heading]');
@@ -107,7 +104,6 @@ describe('Author label', () => {
expect(authorElement.parentNode.lastChild).not.toHaveTextContent(label, { exact: true });
expect(authorElement.parentNode).not.toHaveClass(labelColor, { exact: true });
}
},
);
});
});
});

View File

@@ -6,7 +6,7 @@ import { ActionRow, Button, ModalDialog } from '@edx/paragon';
import messages from '../messages';
const Confirmation = ({
function Confirmation({
isOpen,
title,
description,
@@ -15,7 +15,7 @@ const Confirmation = ({
closeButtonVaraint,
confirmButtonVariant,
confirmButtonText,
}) => {
}) {
const intl = useIntl();
return (
@@ -40,7 +40,7 @@ const Confirmation = ({
</ModalDialog.Footer>
</ModalDialog>
);
};
}
Confirmation.propTypes = {
isOpen: PropTypes.bool.isRequired,

View File

@@ -13,12 +13,12 @@ import { PostCommentsContext } from '../post-comments/postCommentsContext';
import AuthorLabel from './AuthorLabel';
import timeLocale from './time-locale';
const EndorsedAlertBanner = ({
function EndorsedAlertBanner({
endorsed,
endorsedAt,
endorsedBy,
endorsedByLabel,
}) => {
}) {
timeago.register('time-locale', timeLocale);
const intl = useIntl();
@@ -62,7 +62,7 @@ const EndorsedAlertBanner = ({
</Alert>
)
);
};
}
EndorsedAlertBanner.propTypes = {
endorsed: PropTypes.bool.isRequired,

View File

@@ -22,7 +22,9 @@ function buildTestContent(type, buildParams) {
return camelCaseObject(Factory.build(type, { ...buildParamsSnakeCase }, null));
}
const renderComponent = (content, postType) => {
function renderComponent(
content, postType,
) {
render(
<IntlProvider locale="en">
<AppProvider store={store}>
@@ -45,7 +47,7 @@ const renderComponent = (content, postType) => {
</AppProvider>
</IntlProvider>,
);
};
}
describe.each([
{

View File

@@ -11,7 +11,7 @@ import {
import {
StarFilled, StarOutline, ThumbUpFilled, ThumbUpOutline,
} from '../../components/icons';
import { useUserPostingEnabled } from '../data/hooks';
import { useUserCanAddThreadInBlackoutDate } from '../data/hooks';
import { PostCommentsContext } from '../post-comments/postCommentsContext';
import ActionsDropdown from './ActionsDropdown';
import { DiscussionContext } from './context';
@@ -31,7 +31,7 @@ const HoverCard = ({
const intl = useIntl();
const { enableInContextSidebar } = useContext(DiscussionContext);
const { isClosed } = useContext(PostCommentsContext);
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
return (
<div
@@ -39,14 +39,12 @@ const HoverCard = ({
data-testid={`hover-card-${id}`}
id={`hover-card-${id}`}
>
{isUserPrivilagedInPostingRestriction && (
{userCanAddThreadInBlackoutDate && (
<div className="d-flex">
<Button
variant="tertiary"
className={classNames(
'px-2.5 py-2 border-0 font-style text-gray-700 font-size-12',
{ 'w-100': enableInContextSidebar },
)}
className={classNames('px-2.5 py-2 border-0 font-style text-gray-700 font-size-12',
{ 'w-100': enableInContextSidebar })}
onClick={() => handleResponseCommentButton()}
disabled={isClosed}
style={{ lineHeight: '20px' }}
@@ -127,7 +125,6 @@ HoverCard.propTypes = {
addResponseCommentButtonMessage: PropTypes.string.isRequired,
onLike: PropTypes.func.isRequired,
voted: PropTypes.bool.isRequired,
// eslint-disable-next-line react/forbid-prop-types
endorseIcons: PropTypes.objectOf(PropTypes.any),
onFollow: PropTypes.func,
following: PropTypes.bool,

View File

@@ -6,17 +6,15 @@ import { IntlProvider } from 'react-intl';
import { MemoryRouter, Route } from 'react-router';
import { Factory } from 'rosie';
import { initializeMockApp } from '@edx/frontend-platform';
import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import { getCourseConfigApiUrl } from '../data/api';
import { fetchCourseConfig } from '../data/thunks';
import DiscussionContent from '../discussions-home/DiscussionContent';
import { getCommentsApiUrl } from '../post-comments/data/api';
import { fetchCommentResponses } from '../post-comments/data/thunks';
import { fetchCommentResponses, fetchThreadComments } from '../post-comments/data/thunks';
import { getThreadsApiUrl } from '../posts/data/api';
import { fetchThreads } from '../posts/data/thunks';
import { DiscussionContext } from './context';
@@ -27,11 +25,45 @@ import '../post-comments/data/__factories__';
const commentsApiUrl = getCommentsApiUrl();
const threadsApiUrl = getThreadsApiUrl();
const discussionPostId = 'thread-1';
const questionPostId = 'thread-2';
const courseId = 'course-v1:edX+TestX+Test_Course';
const reverseOrder = true;
const enableInContextSidebar = false;
let store;
let axiosMock;
let container;
async function mockAxiosReturnPagedComments() {
const endorsedArray = [null, false, true];
const pageArray = [1, 2];
endorsedArray.forEach(async (endorsed) => {
const postId = endorsed === null ? discussionPostId : questionPostId;
pageArray.forEach(async (page) => {
const params = {
thread_id: postId,
page,
page_size: undefined,
requested_fields: 'profile_image',
endorsed,
reverse_order: reverseOrder,
enable_in_context_sidebar: enableInContextSidebar,
signal: {},
};
axiosMock.onGet(commentsApiUrl, { ...params }).reply(200, Factory.build('commentsResult', { can_delete: true }, {
threadId: postId,
page,
pageSize: 1,
count: 2,
endorsed,
childCount: page === 1 ? 2 : 0,
}));
await executeThunk(fetchThreadComments(postId, { ...params }), store.dispatch, store.getState);
});
});
}
async function mockAxiosReturnPagedCommentsResponses() {
const parentId = 'comment-1';
const commentsResponsesApiUrl = `${commentsApiUrl}${parentId}/`;
@@ -43,15 +75,13 @@ async function mockAxiosReturnPagedCommentsResponses() {
};
[1, 2].forEach(async (page) => {
axiosMock.onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } }).reply(
200,
axiosMock.onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } }).reply(200,
Factory.build('commentsResult', null, {
parentId,
page,
pageSize: 1,
count: 2,
}),
);
}));
await executeThunk(fetchCommentResponses(parentId), store.dispatch, store.getState);
});
@@ -86,7 +116,6 @@ describe('HoverCard', () => {
username: 'abc123',
administrator: true,
roles: [],
isPostingEnabled: true,
},
});
@@ -94,17 +123,26 @@ describe('HoverCard', () => {
Factory.resetAll();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(threadsApiUrl).reply(200, Factory.build('threadsResult'));
axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`).reply(200, { isPostingEnabled: true });
axiosMock.onGet(commentsApiUrl).reply(200, Factory.build('commentsResult', { can_delete: true }, {
threadId: discussionPostId,
endorsed: false,
pageSize: 1,
count: 2,
childCount: 2,
}));
axiosMock.onPatch(new RegExp(`${commentsApiUrl}*`)).reply(({ url, data }) => {
const commentId = url.match(/comments\/(?<id>[a-z1-9-]+)\//).groups.id;
const { rawBody } = camelCaseObject(JSON.parse(data));
return [200, Factory.build('comment', {
id: commentId,
rendered_body: rawBody,
raw_body: rawBody,
})];
});
axiosMock.onPost(commentsApiUrl).reply(({ data }) => {
const { rawBody, threadId } = camelCaseObject(JSON.parse(data));
return [200, Factory.build('comment', {
rendered_body: rawBody,
raw_body: rawBody,
thread_id: threadId,
})];
});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
await executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
await mockAxiosReturnPagedComments();
await mockAxiosReturnPagedCommentsResponses();
});

View File

@@ -22,13 +22,13 @@ import tourCheckpoints from '../tours/constants';
import { selectTours } from '../tours/data/selectors';
import { updateTourShowStatus } from '../tours/data/thunks';
import messages from '../tours/messages';
import { discussionsPath } from '../utils';
import { discussionsPath, inBlackoutDateRange } from '../utils';
import {
selectAreThreadsFiltered,
selectBlackoutDate,
selectEnableInContext,
selectIsCourseAdmin,
selectIsCourseStaff,
selectIsPostingEnabled,
selectLearnersTabEnabled,
selectModerationSettings,
selectPostThreadCount,
@@ -40,14 +40,12 @@ import { fetchCourseConfig } from './thunks';
export function useTotalTopicThreadCount() {
const topics = useSelector(selectTopics);
const count = useMemo(
() => (
Object.keys(topics)?.reduce((total, topicId) => {
const topic = topics[topicId];
return total + topic.threadCounts.discussion + topic.threadCounts.question;
}, 0)),
[],
);
const count = useMemo(() => (
Object.keys(topics)?.reduce((total, topicId) => {
const topic = topics[topicId];
return total + topic.threadCounts.discussion + topic.threadCounts.question;
}, 0)),
[]);
return count;
}
@@ -192,16 +190,17 @@ export const useCurrentDiscussionTopic = () => {
return null;
};
export const useUserPostingEnabled = () => {
const isPostingEnabled = useSelector(selectIsPostingEnabled);
export const useUserCanAddThreadInBlackoutDate = () => {
const blackoutDateRange = useSelector(selectBlackoutDate);
const isUserAdmin = useSelector(selectUserIsStaff);
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const isUserGroupTA = useSelector(selectUserIsGroupTa);
const isCourseAdmin = useSelector(selectIsCourseAdmin);
const isCourseStaff = useSelector(selectIsCourseStaff);
const isPrivileged = isUserAdmin || userHasModerationPrivileges || isUserGroupTA || isCourseAdmin || isCourseStaff;
const isInBlackoutDateRange = useMemo(() => inBlackoutDateRange(blackoutDateRange), [blackoutDateRange]);
return (isPostingEnabled || isPrivileged);
return (!(isInBlackoutDateRange) || (isPrivileged));
};
function camelToConstant(string) {

View File

@@ -11,7 +11,7 @@ import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import { DiscussionContext } from '../common/context';
import { getCourseConfigApiUrl } from './api';
import { useCurrentDiscussionTopic, useUserPostingEnabled } from './hooks';
import { useCurrentDiscussionTopic, useUserCanAddThreadInBlackoutDate } from './hooks';
import { fetchCourseConfig } from './thunks';
const courseId = 'course-v1:edX+TestX+Test_Course';
@@ -19,8 +19,8 @@ const courseConfigApiUrl = getCourseConfigApiUrl();
let store;
let axiosMock;
const generateApiResponse = (isPostingEnabled, isCourseAdmin = false) => ({
isPostingEnabled,
const generateApiResponse = (blackouts = [], isCourseAdmin = false) => ({
blackouts,
hasModerationPrivileges: false,
isGroupTa: false,
isCourseAdmin,
@@ -30,14 +30,14 @@ const generateApiResponse = (isPostingEnabled, isCourseAdmin = false) => ({
describe('Hooks', () => {
describe('useCurrentDiscussionTopic', () => {
const ComponentWithHook = () => {
function ComponentWithHook() {
const topic = useCurrentDiscussionTopic();
return (
<div>
{String(topic)}
</div>
);
};
}
function renderComponent({ topicId, category }) {
return render(
@@ -102,15 +102,15 @@ describe('Hooks', () => {
});
});
describe('useUserPostingEnabled', () => {
const ComponentWithHook = () => {
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
describe('useUserCanAddThreadInBlackoutDate', () => {
function ComponentWithHook() {
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
return (
<div>
{String(isUserPrivilagedInPostingRestriction)}
{String(userCanAddThreadInBlackoutDate)}
</div>
);
};
}
function renderComponent() {
return render(
@@ -121,7 +121,7 @@ describe('Hooks', () => {
</IntlProvider>,
);
}
describe('User can add Thread in Posting Restrictions ', () => {
describe('User can add Thread in blackoutdates ', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
@@ -136,34 +136,37 @@ describe('Hooks', () => {
store = initializeStore();
});
test('when posting is not disabled and Role is Learner return true', async () => {
test('when blackoutdates are not active and Role is Learner return true', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse(true, false));
.reply(200, generateApiResponse([], false));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
const { queryByText } = renderComponent();
expect(queryByText('true')).toBeInTheDocument();
});
test('when posting is not disabled and Role is not Learner return true', async () => {
test('when blackoutdates are not active and Role is not Learner return true', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse(true, true));
.reply(200, generateApiResponse([], true));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
const { queryByText } = renderComponent();
expect(queryByText('true')).toBeInTheDocument();
});
test('when posting is disabled and Role is Learner return false', async () => {
test('when blackoutdates are active and Role is Learner return false', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse(false, false));
.reply(200, generateApiResponse([{
start: '2022-11-25T00:00:00Z',
end: '2050-11-25T23:59:00Z',
}], false));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
const { queryByText } = renderComponent();
expect(queryByText('false')).toBeInTheDocument();
});
test('when posting is not disabled and Role is not Learner return true', async () => {
test('when blackoutdates are active and Role is not Learner return true', async () => {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`)
.reply(200, generateApiResponse(false, true));
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
.reply(200, generateApiResponse([
{ start: '2022-11-25T00:00:00Z', end: '2050-11-25T23:59:00Z' }], true));
const { queryByText } = renderComponent();
expect(queryByText('true')).toBeInTheDocument();
});

View File

@@ -20,6 +20,8 @@ export const selectUserRoles = state => state.config.userRoles;
export const selectDivisionSettings = state => state.config.settings;
export const selectBlackoutDate = state => state.config.blackouts;
export const selectGroupAtSubsection = state => state.config.groupAtSubsection;
export const selectIsCourseAdmin = state => state.config.isCourseAdmin;
@@ -28,8 +30,6 @@ export const selectIsCourseStaff = state => state.config.isCourseStaff;
export const selectEnableInContext = state => state.config.enableInContext;
export const selectIsPostingEnabled = state => state.config.isPostingEnabled;
export const selectModerationSettings = state => ({
postCloseReasons: state.config.postCloseReasons,
editReasons: state.config.editReasons,

View File

@@ -7,6 +7,7 @@ const configSlice = createSlice({
name: 'config',
initialState: {
status: RequestStatus.IN_PROGRESS,
blackouts: [],
allowAnonymous: false,
allowAnonymousToPeers: false,
userRoles: [],
@@ -17,7 +18,6 @@ const configSlice = createSlice({
isCourseStaff: false,
isUserAdmin: false,
learnersTabEnabled: false,
isPostingEnabled: false,
settings: {
divisionScheme: 'none',
alwaysDivideInlineDiscussions: false,

View File

@@ -1,20 +1,23 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { PageBanner } from '@edx/paragon';
import { RequestStatus } from '../../data/constants';
import { selectconfigLoadingStatus, selectIsPostingEnabled } from '../data/selectors';
import { selectBlackoutDate } from '../data/selectors';
import messages from '../messages';
import { inBlackoutDateRange } from '../utils';
const DiscussionsRestrictionBanner = () => {
const BlackoutInformationBanner = () => {
const intl = useIntl();
const isPostingEnabled = useSelector(selectIsPostingEnabled);
const configLoadingStatus = useSelector(selectconfigLoadingStatus);
const blackoutDate = useSelector(selectBlackoutDate);
const [showBanner, setShowBanner] = useState(true);
const isDiscussionsBlackout = useMemo(() => (
inBlackoutDateRange(blackoutDate)
), [blackoutDate]);
const handleDismiss = useCallback(() => {
setShowBanner(false);
}, []);
@@ -22,7 +25,7 @@ const DiscussionsRestrictionBanner = () => {
return (
<PageBanner
variant="accentB"
show={!isPostingEnabled && showBanner && configLoadingStatus === RequestStatus.SUCCESSFUL}
show={isDiscussionsBlackout && showBanner}
dismissible
onDismiss={handleDismiss}
>
@@ -33,4 +36,4 @@ const DiscussionsRestrictionBanner = () => {
);
};
export default DiscussionsRestrictionBanner;
export default BlackoutInformationBanner;

View File

@@ -8,7 +8,7 @@ import { initializeStore } from '../../store';
import { DiscussionContext } from '../common/context';
import { fetchConfigSuccess } from '../data/slices';
import messages from '../messages';
import DiscussionsRestrictionBanner from './DiscussionsRestrictionBanner';
import BlackoutInformationBanner from './BlackoutInformationBanner';
let store;
let container;
@@ -20,13 +20,13 @@ activeEndDate.setDate(activeEndDate.getDate() + 2);
activeStartDate = activeStartDate.toISOString();
activeEndDate = activeEndDate.toISOString();
const getConfigData = (isPostingEnabled) => ({
const getConfigData = (blackouts = []) => ({
id: 'course-v1:edX+DemoX+Demo_Course',
userRoles: ['Admin', 'Student'],
hasModerationPrivileges: false,
isGroupTa: false,
isUserAdmin: false,
isPostingEnabled,
blackouts,
});
function renderComponent() {
@@ -34,7 +34,7 @@ function renderComponent() {
<IntlProvider locale="en">
<AppProvider store={store}>
<DiscussionContext.Provider value={{ courseId }}>
<DiscussionsRestrictionBanner />
<BlackoutInformationBanner />
</DiscussionContext.Provider>
</AppProvider>
</IntlProvider>,
@@ -43,7 +43,7 @@ function renderComponent() {
return container;
}
describe('Discussions Restriction Banner', () => {
describe('Blackout Information Banner', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
@@ -56,11 +56,13 @@ describe('Discussions Restriction Banner', () => {
});
test.each([
{ isPostingEnabled: false, visibility: true },
{ isPostingEnabled: true, visibility: false },
])('Test Discussions Restriction is visible on app load if posting is disabled', async ({ isPostingEnabled, visibility }) => {
{ blackouts: [], visibility: false },
{ blackouts: ['2021-12-31T10:15', '2021-12-31T10:20'], visibility: false },
{ blackouts: [{ start: activeStartDate, end: activeEndDate }], visibility: true },
{ blackouts: [{ start: activeEndDate, end: activeEndDate }], visibility: false },
])('Test Blackout Banner is visible on app load if blackout date is active', async ({ blackouts, visibility }) => {
store = initializeStore();
await store.dispatch(fetchConfigSuccess(getConfigData(isPostingEnabled)));
await store.dispatch(fetchConfigSuccess(getConfigData(blackouts)));
renderComponent();
if (visibility) {
const element = await screen.findByRole('alert');

View File

@@ -1,4 +1,3 @@
/* eslint-disable react/jsx-no-constructed-context-values */
import React, { lazy, Suspense, useRef } from 'react';
import classNames from 'classnames';
@@ -30,7 +29,7 @@ const CourseTabsNavigation = lazy(() => import('../../components/NavigationBar/C
const LegacyBreadcrumbMenu = lazy(() => import('../navigation/breadcrumb-menu/LegacyBreadcrumbMenu'));
const NavigationBar = lazy(() => import('../navigation/navigation-bar/NavigationBar'));
const DiscussionsProductTour = lazy(() => import('../tours/DiscussionsProductTour'));
const DiscussionsRestrictionBanner = lazy(() => import('./DiscussionsRestrictionBanner'));
const BlackoutInformationBanner = lazy(() => import('./BlackoutInformationBanner'));
const DiscussionContent = lazy(() => import('./DiscussionContent'));
const DiscussionSidebar = lazy(() => import('./DiscussionSidebar'));
const InformationBanner = lazy(() => import('./InformationBanner'));
@@ -96,7 +95,7 @@ const DiscussionsHome = () => {
<PostActionsBar />
</div>
{isFeedbackBannerVisible && <InformationBanner />}
<DiscussionsRestrictionBanner />
<BlackoutInformationBanner />
</div>
{provider === DiscussionProvider.LEGACY && (
<Suspense fallback={(<Spinner />)}>

View File

@@ -127,7 +127,7 @@ describe('DiscussionsHome', () => {
test('header, course navigation bar and footer are only visible in Discussions MFE', async () => {
renderComponent();
waitFor(() => expect(screen.queryByRole('banner')).toBeInTheDocument());
expect(screen.queryByRole('banner')).toBeInTheDocument();
expect(document.getElementById('courseTabsNavigation')).toBeInTheDocument();
expect(screen.queryByRole('contentinfo')).toBeInTheDocument();
});
@@ -172,8 +172,7 @@ describe('DiscussionsHome', () => {
it.each([
{ searchByEndPoint: 'category/section-topic-1' },
{ searchByEndPoint: 'topics' },
])(
'should display No Topic selected message on inContext topic pages when user has yet to select a topic %s',
])('should display No Topic selected message on inContext topic pages when user has yet to select a topic %s',
async ({ searchByEndPoint }) => {
axiosMock.onGet(getDiscussionsConfigUrl(courseId)).reply(200, {
enableInContext: true, provider: 'openedx', hasModerationPrivileges: true,
@@ -194,8 +193,7 @@ describe('DiscussionsHome', () => {
await renderComponent(`/${courseId}/${searchByEndPoint}`);
expect(screen.queryByText('No topic selected')).toBeInTheDocument();
},
);
});
it('should display empty page message for empty learners list', async () => {
axiosMock.onGet(getDiscussionsConfigUrl(courseId)).reply(200, {

View File

@@ -8,7 +8,6 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { useIsOnDesktop } from '../data/hooks';
import { selectAreThreadsFiltered, selectPostThreadCount } from '../data/selectors';
import messages from '../messages';
// eslint-disable-next-line import/no-cycle
import { messages as postMessages, showPostEditor } from '../posts';
import EmptyPage from './EmptyPage';

View File

@@ -9,7 +9,6 @@ import { ALL_ROUTES } from '../../data/constants';
import { useIsOnDesktop, useTotalTopicThreadCount } from '../data/hooks';
import { selectTopicThreadCount } from '../data/selectors';
import messages from '../messages';
// eslint-disable-next-line import/no-cycle
import { messages as postMessages, showPostEditor } from '../posts';
import EmptyPage from './EmptyPage';

View File

@@ -1,5 +1,4 @@
export { default as EmptyLearners } from './EmptyLearners';
export { default as EmptyPage } from './EmptyPage';
// eslint-disable-next-line import/no-cycle
export { default as EmptyPosts } from './EmptyPosts';
export { default as EmptyTopics } from './EmptyTopics';

View File

@@ -92,6 +92,7 @@ describe('InContext Topic Posts View', () => {
enableInContext: true,
provider: 'openedx',
hasModerationPrivileges: true,
blackouts: [],
},
});
Factory.resetAll();
@@ -205,8 +206,7 @@ describe('InContext Topic Posts View', () => {
test.each([
{ searchText: 'hello world', output: 'Showing 0 results for', resultCount: 0 },
{ searchText: 'introduction', output: 'Showing 8 results for', resultCount: 8 },
])(
'It should have a search bar with a clear button and \'$output\' results found text.',
])('It should have a search bar with a clear button and \'$output\' results found text.',
async ({ searchText, output, resultCount }) => {
await setupTopicsMockResponse();
await renderComponent();
@@ -226,8 +226,7 @@ describe('InContext Topic Posts View', () => {
expect(clearButton).toBeInTheDocument();
expect(units).toHaveLength(resultCount);
});
},
);
});
it('When click on the clear button it should move to main topics pages.', async () => {
await setupTopicsMockResponse();
@@ -254,8 +253,7 @@ describe('InContext Topic Posts View', () => {
});
});
it(
'should display Nothing here yet and No topic exists message when topics and selectedSubsectionUnits are empty',
it('should display Nothing here yet and No topic exists message when topics and selectedSubsectionUnits are empty',
async () => {
await setupTopicsMockResponse(0, 0, 0);
await renderComponent({ topicId: 'test-topic', category: 'test-category' });
@@ -263,8 +261,7 @@ describe('InContext Topic Posts View', () => {
await waitFor(() => expect(within(container).queryByText('Nothing here yet')).toBeInTheDocument());
expect(within(container).queryByText('No topic exists')).toBeInTheDocument();
expect(within(container).queryByText('Unnamed Topic')).toBeInTheDocument();
},
);
});
it('should display all topics when search by an empty search string', async () => {
await setupTopicsMockResponse();

View File

@@ -9,9 +9,9 @@ import { ArrowBack } from '@edx/paragon/icons';
import messages from '../messages';
const BackButton = ({
function BackButton({
intl, path, title, loading,
}) => {
}) {
const history = useHistory();
return (
@@ -32,7 +32,7 @@ const BackButton = ({
<div className="border-bottom border-light-400" />
</>
);
};
}
BackButton.propTypes = {
intl: intlShape.isRequired,

View File

@@ -20,21 +20,12 @@ Factory.define('sub-section')
.sequence('id', ['topicPrefix'], (idx, topicPrefix) => `${topicPrefix}-topic-${idx}`)
.sequence('display-name', ['sectionPrefix'], (idx, sectionPrefix) => `Introduction ${sectionPrefix + idx}`)
.option('courseId', null, 'course-v1:edX+DemoX+Demo_Course')
.sequence(
'legacy_web_url',
['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/block-v1:${id}?experience=legacy`,
)
.sequence(
'lms_web_url',
['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/block-v1:${id}`,
)
.sequence(
'student_view_url',
['id', 'courseId'],
(idx, id) => `${getApiBaseUrl}/xblock/block-v1:${id}`,
)
.sequence('legacy_web_url', ['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/block-v1:${id}?experience=legacy`)
.sequence('lms_web_url', ['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/block-v1:${id}`)
.sequence('student_view_url', ['id', 'courseId'],
(idx, id) => `${getApiBaseUrl}/xblock/block-v1:${id}`)
.attr('type', null, 'sequential')
.attr('activeFlags', null, true)
.attr('thread_counts', ['discussionCount', 'questionCount'], (discCount, questCount) => {
@@ -60,21 +51,12 @@ Factory.define('section')
.attr('courseware', null, true)
.sequence('display-name', (idx) => `Introduction ${idx}`)
.option('courseId', null, 'course-v1:edX+DemoX+Demo_Course')
.sequence(
'legacy_web_url',
['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/${courseId.replace('course-v1:', 'block-v1:')}+type@chapter+block@${id}?experience=legacy`,
)
.sequence(
'lms_web_url',
['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/${courseId.replace('course-v1:', 'block-v1:')}+type@chapter+block@${id}`,
)
.sequence(
'student_view_url',
['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/xblock/${courseId.replace('course-v1:', 'block-v1:')}+type@chapter+block@${id}`,
)
.sequence('legacy_web_url', ['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/${courseId.replace('course-v1:', 'block-v1:')}+type@chapter+block@${id}?experience=legacy`)
.sequence('lms_web_url', ['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/courses/${courseId}/jump_to/${courseId.replace('course-v1:', 'block-v1:')}+type@chapter+block@${id}`)
.sequence('student_view_url', ['id', 'courseId'],
(idx, id, courseId) => `${getApiBaseUrl}/xblock/${courseId.replace('course-v1:', 'block-v1:')}+type@chapter+block@${id}`)
.attr('type', null, 'chapter')
.attr('children', ['id', 'display-name'], (id, name) => {
Factory.reset('sub-section');

View File

@@ -8,7 +8,7 @@ import { SearchField } from '@edx/paragon';
import { setFilter } from '../data';
import messages from '../messages';
const TopicSearchResultBar = ({ intl }) => {
function TopicSearchResultBar({ intl }) {
const dispatch = useDispatch();
return (
@@ -21,7 +21,7 @@ const TopicSearchResultBar = ({ intl }) => {
/>
</div>
);
};
}
TopicSearchResultBar.propTypes = {
intl: intlShape.isRequired,

View File

@@ -1,4 +1,3 @@
/* eslint-disable react/prop-types */
/* eslint-disable no-unused-vars, react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';

View File

@@ -110,8 +110,7 @@ describe('Learner Posts View', () => {
expect(backButton).toBeInTheDocument();
});
test(
'Learner title bar should redirect to the learners list when clicking on the back button',
test('Learner title bar should redirect to the learners list when clicking on the back button',
async () => {
await renderComponent();
@@ -123,8 +122,7 @@ describe('Learner Posts View', () => {
await waitFor(() => {
expect(lastLocation.pathname.endsWith('/learners')).toBeTruthy();
});
},
);
});
it('should display a post-filter bar and All posts sorted by recent activity text.', async () => {
await renderComponent();

View File

@@ -66,7 +66,6 @@ const LearnersView = () => {
courseConfigLoadingStatus === RequestStatus.SUCCESSFUL && learnersTabEnabled && learners.map((learner) => (
<LearnerCard learner={learner} key={learner.username} />
))
// eslint-disable-next-line react/jsx-no-useless-fragment
) || <></>
), [courseConfigLoadingStatus, learnersTabEnabled, learners]);

View File

@@ -1,4 +1,3 @@
/* eslint-disable default-param-last */
import React from 'react';
import {
@@ -202,8 +201,7 @@ describe('LearnersView', () => {
username:
['learner-1', 'learner-2'],
},
])(
'should have a search bar with a clear button and \'$output\' results found text.',
])('should have a search bar with a clear button and \'$output\' results found text.',
async ({
searchText, output, learnersCount, username,
}) => {
@@ -229,8 +227,7 @@ describe('LearnersView', () => {
expect(clearButton).toBeInTheDocument();
expect(leaners).toHaveLength(learnersCount);
});
},
);
});
test('When click on the clear button it should move to a list of all learners.', async () => {
await setUpLearnerMockResponse();
@@ -260,8 +257,7 @@ describe('LearnersView', () => {
expect(learners).toHaveLength(3);
});
it(
'should display reported and previously reported message by passing activeFlags or inactiveFlags',
it('should display reported and previously reported message by passing activeFlags or inactiveFlags',
async () => {
await setUpLearnerMockResponse(2, 2, 1, ['learner-1', 'learner-2'], '', 1, 1);
await assignPrivilages(true);
@@ -278,8 +274,7 @@ describe('LearnersView', () => {
expect(reportedIcon).toBeInTheDocument();
expect(reported).toBeInTheDocument();
expect(previouslyReported).toBeInTheDocument();
},
);
});
it('should display load more button and display more learners by clicking on button.', async () => {
await setUpLearnerMockResponse();

View File

@@ -29,8 +29,7 @@ describe('Learner api test cases', () => {
axiosMock.reset();
});
it(
'Successfully get and store API response for the learner\'s list and learners posts in redux',
it('Successfully get and store API response for the learner\'s list and learners posts in redux',
async () => {
const learners = await setupLearnerMockResponse();
const threads = await setupPostsMockResponse();
@@ -39,23 +38,20 @@ describe('Learner api test cases', () => {
expect(Object.values(learners.learnerProfiles)).toHaveLength(3);
expect(threads.status).toEqual('successful');
expect(Object.values(threads.threadsById)).toHaveLength(2);
},
);
});
it.each([
{ status: 'statusUnread', search: 'Title', cohort: 'post' },
{ status: 'statusUnanswered', search: 'Title', cohort: 'post' },
{ status: 'statusReported', search: 'Title', cohort: 'post' },
{ status: 'statusUnresponded', search: 'Title', cohort: 'post' },
])(
'Successfully fetch user posts based on %s filters',
])('Successfully fetch user posts based on %s filters',
async ({ status, search, cohort }) => {
const threads = await setupPostsMockResponse({ filters: { status, search, cohort } });
expect(threads.status).toEqual('successful');
expect(Object.values(threads.threadsById)).toHaveLength(2);
},
);
});
it('Failed to fetch learners', async () => {
const learners = await setupLearnerMockResponse({ learnerCourseId: courseId2 });

View File

@@ -37,8 +37,7 @@ describe('Learner redux test cases', () => {
test('Successfully load initial states in redux', async () => {
executeThunk(
fetchLearners('course-v1:edX+DemoX+Demo_Course', { usernameSearch: 'learner-1' }),
store.dispatch,
store.getState,
store.dispatch, store.getState,
);
const { learners } = store.getState();
@@ -56,8 +55,7 @@ describe('Learner redux test cases', () => {
expect(learners.postFilter.cohort).toEqual('');
});
test(
'Successfully store a learner posts stats data as pages object in redux',
test('Successfully store a learner posts stats data as pages object in redux',
async () => {
const learners = await setupLearnerMockResponse();
const page = learners.pages[0];
@@ -67,11 +65,9 @@ describe('Learner redux test cases', () => {
expect(statsObject.responses).toEqual(3);
expect(statsObject.threads).toEqual(1);
expect(statsObject.replies).toEqual(0);
},
);
});
test(
'Successfully store the nextPage, totalPages, totalLearners, and sortedBy data in redux',
test('Successfully store the nextPage, totalPages, totalLearners, and sortedBy data in redux',
async () => {
const learners = await setupLearnerMockResponse();
@@ -79,8 +75,7 @@ describe('Learner redux test cases', () => {
expect(learners.totalPages).toEqual(2);
expect(learners.totalLearners).toEqual(3);
expect(learners.sortedBy).toEqual('activity');
},
);
});
test('Successfully updated the learner\'s sort data in redux', async () => {
const learners = await setupLearnerMockResponse();
@@ -111,8 +106,7 @@ describe('Learner redux test cases', () => {
expect(updatedLearners.pages).toHaveLength(0);
});
test(
'Successfully update the learner\'s search query in redux when searching for a learner',
test('Successfully update the learner\'s search query in redux when searching for a learner',
async () => {
const learners = await setupLearnerMockResponse();
@@ -122,6 +116,5 @@ describe('Learner redux test cases', () => {
const updatedLearners = store.getState().learners;
expect(updatedLearners.usernameSearch).toEqual('learner-2');
},
);
});
});

View File

@@ -12,7 +12,7 @@ import { fetchCourseCohorts } from '../../cohorts/data/thunks';
import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
import { setPostFilter } from '../data/slices';
const LearnerPostFilterBar = () => {
function LearnerPostFilterBar() {
const dispatch = useDispatch();
const { courseId } = useParams();
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
@@ -98,6 +98,6 @@ const LearnerPostFilterBar = () => {
showCohortsFilter={userHasModerationPrivileges || userIsGroupTa}
/>
);
};
}
export default LearnerPostFilterBar;

View File

@@ -185,7 +185,7 @@ const messages = defineMessages({
},
blackoutDiscussionInformation: {
id: 'discussion.blackoutBanner.information',
defaultMessage: 'Posting in discussions is disabled by the course team',
defaultMessage: 'Posting in discussions is temporarily disabled by the course team',
description: 'Informative text when discussion posting is disabled',
},
imageWarningMessage: {

View File

@@ -74,7 +74,6 @@ const PostCommentsView = () => {
}
return (
// eslint-disable-next-line react/jsx-no-constructed-context-values
<PostCommentsContext.Provider value={{
isClosed: closed,
postType: type,

View File

@@ -10,7 +10,6 @@ import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { getApiBaseUrl } from '../../data/constants';
import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import { getCohortsApiUrl } from '../cohorts/data/api';
@@ -21,13 +20,12 @@ import { fetchCourseConfig } from '../data/thunks';
import DiscussionContent from '../discussions-home/DiscussionContent';
import { getThreadsApiUrl } from '../posts/data/api';
import { fetchThread, fetchThreads } from '../posts/data/thunks';
import { fetchCourseTopics } from '../topics/data/thunks';
import { getDiscussionTourUrl } from '../tours/data/api';
import { selectTours } from '../tours/data/selectors';
import { fetchDiscussionTours } from '../tours/data/thunks';
import discussionTourFactory from '../tours/data/tours.factory';
import { getCommentsApiUrl } from './data/api';
import { fetchCommentResponses, removeComment } from './data/thunks';
import { fetchCommentResponses, fetchThreadComments, removeComment } from './data/thunks';
import '../posts/data/__factories__';
import './data/__factories__';
@@ -41,21 +39,44 @@ const discussionPostId = 'thread-1';
const questionPostId = 'thread-2';
const closedPostId = 'thread-2';
const courseId = 'course-v1:edX+TestX+Test_Course';
const topicsApiUrl = `${getApiBaseUrl()}/api/discussion/v1/course_topics/${courseId}`;
const reverseOrder = true;
const enableInContextSidebar = false;
let store;
let axiosMock;
let testLocation;
let container;
let unmount;
async function mockAxiosReturnPagedComments(threadId, endorsed = false, page = 1, count = 2) {
axiosMock.onGet(commentsApiUrl).reply(200, Factory.build('commentsResult', { can_delete: true }, {
threadId,
endorsed,
pageSize: 1,
count,
childCount: page === 1 ? 2 : 0,
}));
async function mockAxiosReturnPagedComments() {
const endorsedArray = [null, false, true];
const pageArray = [1, 2];
endorsedArray.forEach(async (endorsed) => {
const postId = endorsed === null ? discussionPostId : questionPostId;
pageArray.forEach(async (page) => {
const params = {
thread_id: postId,
page,
page_size: undefined,
requested_fields: 'profile_image',
endorsed,
reverse_order: reverseOrder,
enable_in_context_sidebar: enableInContextSidebar,
signal: {},
};
axiosMock.onGet(commentsApiUrl, { ...params }).reply(200, Factory.build('commentsResult', { can_delete: true }, {
threadId: postId,
page,
pageSize: 1,
count: 2,
endorsed,
childCount: page === 1 ? 2 : 0,
}));
await executeThunk(fetchThreadComments(postId, { ...params }), store.dispatch, store.getState);
});
});
}
async function mockAxiosReturnPagedCommentsResponses() {
@@ -69,16 +90,13 @@ async function mockAxiosReturnPagedCommentsResponses() {
};
[1, 2].forEach(async (page) => {
axiosMock.onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } }).reply(
200,
axiosMock.onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } }).reply(200,
Factory.build('commentsResult', null, {
threadId: discussionPostId,
parentId,
page,
pageSize: 1,
count: 2,
}),
);
}));
await executeThunk(fetchCommentResponses(parentId), store.dispatch, store.getState);
});
@@ -89,30 +107,12 @@ async function getThreadAPIResponse(attr = null) {
await executeThunk(fetchThread(discussionPostId), store.dispatch, store.getState);
}
async function setupCourseConfig(reasonCodesEnabled = true) {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
has_moderation_privileges: true,
isPostingEnabled: true,
reason_codes_enabled: reasonCodesEnabled,
editReasons: [
{ code: 'reason-1', label: 'reason 1' },
{ code: 'reason-2', label: 'reason 2' },
],
postCloseReasons: [
{ code: 'reason-1', label: 'reason 1' },
{ code: 'reason-2', label: 'reason 2' },
],
});
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
}
function renderComponent(postId, isClosed = false) {
function renderComponent(postId) {
const wrapper = render(
<IntlProvider locale="en">
<AppProvider store={store}>
<DiscussionContext.Provider
value={{ courseId, postId, isClosed }}
value={{ courseId, postId }}
>
<MemoryRouter initialEntries={[`/${courseId}/posts/${postId}`]}>
<DiscussionContent />
@@ -132,45 +132,6 @@ function renderComponent(postId, isClosed = false) {
unmount = wrapper.unmount;
}
describe('PostView', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
Factory.resetAll();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(topicsApiUrl)
.reply(200, {
non_courseware_topics: Factory.buildList('topic', 1, {}, { topicPrefix: 'non-courseware-' }),
courseware_topics: Factory.buildList('category', 1, {}, { name: 'courseware' }),
});
executeThunk(fetchCourseTopics(courseId), store.dispatch, store.getState);
});
it('should show Topic Info for non-courseware topics', async () => {
await getThreadAPIResponse({ id: 'thread-1', topic_id: 'non-courseware-topic-1' });
await waitFor(() => renderComponent(discussionPostId));
expect(await screen.findByText('Related to')).toBeInTheDocument();
expect(await screen.findByText('non-courseware-topic 1')).toBeInTheDocument();
});
it('should show Topic Info for courseware topics with category', async () => {
await getThreadAPIResponse({ id: 'thread-2', topic_id: 'courseware-topic-2' });
await waitFor(() => renderComponent('thread-2'));
expect(await screen.findByText('Related to')).toBeInTheDocument();
expect(await screen.findByText('category-1 / courseware-topic 2')).toBeInTheDocument();
});
});
describe('ThreadView', () => {
beforeEach(async () => {
initializeMockApp({
@@ -204,12 +165,10 @@ describe('ThreadView', () => {
thread_id: threadId,
})];
});
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, { isPostingEnabled: true });
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
await executeThunk(fetchCourseCohorts(courseId), store.dispatch, store.getState);
await mockAxiosReturnPagedComments(discussionPostId);
await executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
await mockAxiosReturnPagedComments();
await mockAxiosReturnPagedCommentsResponses();
});
@@ -222,16 +181,6 @@ describe('ThreadView', () => {
expect(JSON.parse(axiosMock.history.patch[axiosMock.history.patch.length - 1].data)).toMatchObject(data);
}
it('should not allow posting a comment on a closed post', async () => {
axiosMock.reset();
await mockAxiosReturnPagedComments(closedPostId, true);
await waitFor(() => renderComponent(closedPostId, true));
const comments = await waitFor(() => screen.findAllByTestId('comment-comment-4'));
const hoverCard = within(comments[0]).getByTestId('hover-card-comment-4');
expect(within(hoverCard).getByRole('button', { name: /Add comment/i })).toBeDisabled();
});
it('should display post content', async () => {
await waitFor(() => renderComponent(discussionPostId));
const post = await waitFor(() => screen.getByTestId('post-thread-1'));
@@ -312,7 +261,7 @@ describe('ThreadView', () => {
await act(async () => { fireEvent.click(screen.getByText(/submit/i)); });
expect(screen.queryByTestId('tinymce-editor')).not.toBeInTheDocument();
await waitFor(async () => expect(await screen.findByTestId('reply-comment-2')).toBeInTheDocument());
await waitFor(async () => expect(await screen.findByTestId('reply-comment-7')).toBeInTheDocument());
});
it('should allow editing an existing comment', async () => {
@@ -330,6 +279,23 @@ describe('ThreadView', () => {
});
});
async function setupCourseConfig(reasonCodesEnabled = true) {
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
has_moderation_privileges: true,
reason_codes_enabled: reasonCodesEnabled,
editReasons: [
{ code: 'reason-1', label: 'reason 1' },
{ code: 'reason-2', label: 'reason 2' },
],
postCloseReasons: [
{ code: 'reason-1', label: 'reason 1' },
{ code: 'reason-2', label: 'reason 2' },
],
});
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
}
it('should show reason codes when closing a post', async () => {
await setupCourseConfig();
await waitFor(() => renderComponent(discussionPostId));
@@ -359,39 +325,6 @@ describe('ThreadView', () => {
assertLastUpdateData({ closed: true, close_reason_code: 'reason-1' });
});
it('should show reason codes when editing an existing comment', async () => {
setupCourseConfig();
renderComponent(discussionPostId);
const comment = await waitFor(() => screen.findByTestId('comment-comment-1'));
const hoverCard = within(comment).getByTestId('hover-card-comment-1');
await act(async () => {
fireEvent.click(
within(hoverCard).getByRole('button', { name: /actions menu/i }),
);
});
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
});
expect(screen.queryByRole('combobox', { name: /reason for editing/i })).toBeInTheDocument();
expect(screen.getAllByRole('option', { name: /reason \d/i })).toHaveLength(2);
await act(async () => {
fireEvent.change(
screen.queryByRole('combobox', { name: /reason for editing/i }),
{ target: { value: null } },
);
});
await act(async () => {
fireEvent.change(screen.queryByRole('combobox', { name: /reason for editing/i }), { target: { value: 'reason-1' } });
});
await act(async () => {
fireEvent.change(screen.getByTestId('tinymce-editor'), { target: { value: 'testing123' } });
});
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
});
assertLastUpdateData({ edit_reason_code: 'reason-1' });
});
it('should close the post directly if reason codes are not enabled', async () => {
await setupCourseConfig(false);
await waitFor(() => renderComponent(discussionPostId));
@@ -550,10 +483,8 @@ describe('ThreadView', () => {
});
describe('for discussion thread', () => {
const findLoadMoreCommentsButton = () => screen.findByTestId('load-more-comments');
it('shown post not found when post id does not belong to course', async () => {
await waitFor(() => renderComponent('unloaded-id'));
renderComponent('unloaded-id');
expect(await screen.findByText('Thread not found', { exact: true }))
.toBeInTheDocument();
});
@@ -566,70 +497,6 @@ describe('ThreadView', () => {
.not
.toBeInTheDocument();
});
it('pressing load more button will load next page of comments', async () => {
await waitFor(() => renderComponent(discussionPostId));
await mockAxiosReturnPagedComments(discussionPostId, false, 2);
const loadMoreButton = await findLoadMoreCommentsButton();
await act(async () => {
fireEvent.click(loadMoreButton);
});
await screen.findByTestId('comment-comment-1');
await screen.findByTestId('comment-comment-4');
});
it('newly loaded comments are appended to the old ones', async () => {
await waitFor(() => renderComponent(discussionPostId));
await mockAxiosReturnPagedComments(discussionPostId, false, 2);
const loadMoreButton = await findLoadMoreCommentsButton();
await act(async () => {
fireEvent.click(loadMoreButton);
});
await screen.findByTestId('comment-comment-1');
// check that comments from the first page are also displayed
expect(screen.queryByTestId('comment-comment-4'))
.toBeInTheDocument();
});
});
describe('for question thread', () => {
const findLoadMoreCommentsButtons = () => screen.findByTestId('load-more-comments');
it('initially loads only the first page', async () => {
await mockAxiosReturnPagedComments(questionPostId);
act(() => renderComponent(questionPostId));
expect(await screen.findByTestId('comment-comment-4'))
.toBeInTheDocument();
expect(screen.queryByTestId('comment-comment-5'))
.not
.toBeInTheDocument();
});
it('pressing load more button will load next page of comments', async () => {
await mockAxiosReturnPagedComments(questionPostId);
await waitFor(() => renderComponent(questionPostId));
const loadMoreButton = await findLoadMoreCommentsButtons();
expect(await screen.findByTestId('comment-comment-4'))
.toBeInTheDocument();
// Comments from next page should not be loaded yet.
expect(await screen.queryByTestId('comment-comment-5'))
.not
.toBeInTheDocument();
await mockAxiosReturnPagedComments(questionPostId, false, 2, 1);
await act(async () => {
fireEvent.click(loadMoreButton);
});
// Endorsed comment from next page should be loaded now.
await waitFor(() => expect(screen.queryByTestId('comment-comment-5'))
.toBeInTheDocument());
});
});
describe('for comments replies', () => {
@@ -638,8 +505,8 @@ describe('ThreadView', () => {
it('initially loads only the first page', async () => {
await waitFor(() => renderComponent(discussionPostId));
await waitFor(() => screen.findByTestId('reply-comment-2'));
expect(screen.queryByTestId('reply-comment-3')).not.toBeInTheDocument();
await waitFor(() => screen.findByTestId('reply-comment-7'));
expect(screen.queryByTestId('reply-comment-8')).not.toBeInTheDocument();
});
it('pressing load more button will load next page of responses', async () => {
@@ -649,7 +516,8 @@ describe('ThreadView', () => {
await act(async () => {
fireEvent.click(loadMoreButton);
});
await screen.findByTestId('reply-comment-3');
await screen.findByTestId('reply-comment-8');
});
it('newly loaded responses are appended to the old ones', async () => {
@@ -660,9 +528,9 @@ describe('ThreadView', () => {
fireEvent.click(loadMoreButton);
});
await screen.findByTestId('reply-comment-3');
await screen.findByTestId('reply-comment-8');
// check that comments from the first page are also displayed
expect(screen.queryByTestId('reply-comment-2')).toBeInTheDocument();
expect(screen.queryByTestId('reply-comment-7')).toBeInTheDocument();
});
it('load more button is hidden when no more responses pages to load', async () => {
@@ -673,7 +541,8 @@ describe('ThreadView', () => {
fireEvent.click(loadMoreButton);
});
await screen.findByTestId('reply-comment-3');
await screen.findByTestId('reply-comment-8');
await expect(findLoadMoreCommentsResponsesButton()).rejects.toThrow();
});
});
@@ -707,21 +576,21 @@ describe('ThreadView', () => {
it('shows action dropdown for replies', async () => {
await waitFor(() => renderComponent(discussionPostId));
const reply = await waitFor(() => screen.findByTestId('reply-comment-2'));
const reply = await waitFor(() => screen.findByTestId('reply-comment-7'));
expect(within(reply).getByRole('button', { name: /actions menu/i })).toBeInTheDocument();
});
it('should display reply content', async () => {
await waitFor(() => renderComponent(discussionPostId));
const reply = await waitFor(() => screen.findByTestId('reply-comment-2'));
expect(within(reply).queryByTestId('comment-2')).toBeInTheDocument();
const reply = await waitFor(() => screen.findByTestId('reply-comment-7'));
expect(within(reply).queryByTestId('comment-7')).toBeInTheDocument();
});
it('shows delete confirmation modal', async () => {
await waitFor(() => renderComponent(discussionPostId));
const reply = await waitFor(() => screen.findByTestId('reply-comment-2'));
const reply = await waitFor(() => screen.findByTestId('reply-comment-7'));
await act(async () => { fireEvent.click(within(reply).getByRole('button', { name: /actions menu/i })); });
await act(async () => { fireEvent.click(screen.queryByRole('button', { name: /Delete/i })); });

View File

@@ -5,7 +5,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
import { Button, Spinner } from '@edx/paragon';
import { EndorsementStatus } from '../../../data/constants';
import { useUserPostingEnabled } from '../../data/hooks';
import { useUserCanAddThreadInBlackoutDate } from '../../data/hooks';
import { isLastElementOfList } from '../../utils';
import { usePostComments } from '../data/hooks';
import messages from '../messages';
@@ -16,8 +16,7 @@ const CommentsView = ({ endorsed }) => {
const intl = useIntl();
const [addingResponse, setAddingResponse] = useState(false);
const { isClosed } = useContext(PostCommentsContext);
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
const {
endorsedCommentsIds,
unEndorsedCommentsIds,
@@ -73,7 +72,6 @@ const CommentsView = ({ endorsed }) => {
), [hasMorePages, isLoading, handleLoadMoreResponses]);
return (
// eslint-disable-next-line react/jsx-no-useless-fragment
<>
{((hasMorePages && isLoading) || !isLoading) && (
<>
@@ -90,7 +88,7 @@ const CommentsView = ({ endorsed }) => {
{handleDefinition(messages.responseCount, unEndorsedCommentsIds.length)}
{unEndorsedCommentsIds.length === 0 && <br />}
{handleComments(unEndorsedCommentsIds, false)}
{(isUserPrivilagedInPostingRestriction && !!unEndorsedCommentsIds.length && !isClosed) && (
{(userCanAddThreadInBlackoutDate && !!unEndorsedCommentsIds.length && !isClosed) && (
<div className="mx-4">
{!addingResponse && (
<Button

View File

@@ -15,7 +15,7 @@ import { AlertBanner, Confirmation, EndorsedAlertBanner } from '../../../common'
import { DiscussionContext } from '../../../common/context';
import HoverCard from '../../../common/HoverCard';
import { ContentTypes } from '../../../data/constants';
import { useUserPostingEnabled } from '../../../data/hooks';
import { useUserCanAddThreadInBlackoutDate } from '../../../data/hooks';
import { fetchThread } from '../../../posts/data/thunks';
import LikeButton from '../../../posts/post/LikeButton';
import { useActions } from '../../../utils';
@@ -61,7 +61,7 @@ const Comment = ({
const currentPage = useSelector(selectCommentCurrentPage(id));
const sortedOrder = useSelector(selectCommentSortOrder);
const actions = useActions(ContentTypes.COMMENT, id);
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
useEffect(() => {
// If the comment has a parent comment, it won't have any children, so don't fetch them.
@@ -119,10 +119,10 @@ const Comment = ({
), [id, currentPage, sortedOrder]);
const handleAddCommentButton = useCallback(() => {
if (isUserPrivilagedInPostingRestriction) {
if (userCanAddThreadInBlackoutDate) {
setReplying(true);
}
}, [isUserPrivilagedInPostingRestriction]);
}, [userCanAddThreadInBlackoutDate]);
const handleCommentLike = useCallback(async () => {
await dispatch(editComment(id, { voted: !voted }));
@@ -265,7 +265,7 @@ const Comment = ({
/>
</div>
) : (
!isClosed && isUserPrivilagedInPostingRestriction && (inlineReplies.length >= 5) && (
!isClosed && userCanAddThreadInBlackoutDate && (inlineReplies.length >= 5) && (
<Button
className="d-flex flex-grow mt-2 font-size-14 font-style font-weight-500 text-primary-500"
variant="plain"

View File

@@ -24,12 +24,12 @@ import { formikCompatibleHandler, isFormikFieldInvalid } from '../../../utils';
import { addComment, editComment } from '../../data/thunks';
import messages from '../../messages';
const CommentEditor = ({
function CommentEditor({
comment,
edit,
formClasses,
onCloseEditor,
}) => {
}) {
const {
id, threadId, parentId, rawBody, author, lastEdit,
} = comment;
@@ -60,8 +60,7 @@ const CommentEditor = ({
const initialValues = {
comment: rawBody,
// eslint-disable-next-line react/prop-types
editReasonCode: lastEdit?.reasonCode || (userIsStaff && canDisplayEditReason ? 'violates-guidelines' : undefined),
editReasonCode: lastEdit?.reasonCode || (userIsStaff ? 'violates-guidelines' : ''),
};
const handleCloseEditor = useCallback((resetForm) => {
@@ -177,13 +176,13 @@ const CommentEditor = ({
)}
</Formik>
);
};
}
CommentEditor.propTypes = {
comment: PropTypes.shape({
author: PropTypes.string,
id: PropTypes.string,
lastEdit: PropTypes.shape({}),
lastEdit: PropTypes.object,
parentId: PropTypes.string,
rawBody: PropTypes.string,
threadId: PropTypes.string.isRequired,

View File

@@ -59,7 +59,6 @@ Factory.define('commentsResult')
return Factory.buildList('comment', len, {
thread_id: threadId,
parent_id: parentId,
endorsed,
}, {
endorsedBy: endorsed ? 'staff' : null,
childCount,

View File

@@ -20,14 +20,16 @@ export const getCommentsApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussi
* @param enableInContextSidebar
* @returns {Promise<{}>}
*/
export const getThreadComments = async (threadId, {
endorsed,
page,
pageSize,
reverseOrder,
enableInContextSidebar = false,
signal,
} = {}) => {
export async function getThreadComments(
threadId, {
endorsed,
page,
pageSize,
reverseOrder,
enableInContextSidebar = false,
signal,
} = {},
) {
const params = snakeCaseObject({
threadId,
endorsed: EndorsementValue[endorsed],
@@ -40,7 +42,7 @@ export const getThreadComments = async (threadId, {
const { data } = await getAuthenticatedHttpClient().get(getCommentsApiUrl(), { params: { ...params, signal } });
return data;
};
}
/**
* Fetches a responses to a comment.
@@ -49,11 +51,13 @@ export const getThreadComments = async (threadId, {
* @param {number=} pageSize
* @returns {Promise<{}>}
*/
export const getCommentResponses = async (commentId, {
page,
pageSize,
reverseOrder,
} = {}) => {
export async function getCommentResponses(
commentId, {
page,
pageSize,
reverseOrder,
} = {},
) {
const url = `${getCommentsApiUrl()}${commentId}/`;
const params = snakeCaseObject({
page,
@@ -64,7 +68,7 @@ export const getCommentResponses = async (commentId, {
const { data } = await getAuthenticatedHttpClient()
.get(url, { params });
return data;
};
}
/**
* Posts a comment.
@@ -74,13 +78,13 @@ export const getCommentResponses = async (commentId, {
* @param {boolean} enableInContextSidebar
* @returns {Promise<{}>}
*/
export const postComment = async (comment, threadId, parentId = null, enableInContextSidebar = false) => {
export async function postComment(comment, threadId, parentId = null, enableInContextSidebar = false) {
const { data } = await getAuthenticatedHttpClient()
.post(getCommentsApiUrl(), snakeCaseObject({
threadId, raw_body: comment, parentId, enableInContextSidebar,
}));
return data;
};
}
/**
* Updates existing comment.
@@ -92,13 +96,13 @@ export const postComment = async (comment, threadId, parentId = null, enableInCo
* @param {string=} editReasonCode The moderation reason code for editing.
* @returns {Promise<{}>}
*/
export const updateComment = async (commentId, {
export async function updateComment(commentId, {
comment,
voted,
flagged,
endorsed,
editReasonCode,
}) => {
}) {
const url = `${getCommentsApiUrl()}${commentId}/`;
const postData = snakeCaseObject({
raw_body: comment,
@@ -111,14 +115,14 @@ export const updateComment = async (commentId, {
const { data } = await getAuthenticatedHttpClient()
.patch(url, postData, { headers: { 'Content-Type': 'application/merge-patch+json' } });
return data;
};
}
/**
* Deletes existing comment.
* @param {string} commentId ID of comment to delete
*/
export const deleteComment = async (commentId) => {
export async function deleteComment(commentId) {
const url = `${getCommentsApiUrl()}${commentId}/`;
await getAuthenticatedHttpClient()
.delete(url);
};
}

View File

@@ -141,7 +141,7 @@ const commentsSlice = createSlice({
const commentRemoveListType = !endorsed ? EndorsementStatus.ENDORSED : EndorsementStatus.UNENDORSED;
state.commentsInThreads[threadId][commentRemoveListType] = (
state.commentsInThreads[threadId]?.[commentRemoveListType]?.filter(item => item !== commentId)
state.commentsInThreads[threadId]?.[commentRemoveListType]?.filter(item => item !== commentId)
);
state.commentsInThreads[threadId][commentAddListtype] = [
...state.commentsInThreads[threadId][commentAddListtype], payload.id,

View File

@@ -28,20 +28,22 @@ export const getCoursesApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussio
* @param {number} cohort
* @returns {Promise<{}>}
*/
export const getThreads = async (courseId, {
topicIds,
page,
pageSize,
textSearch,
orderBy,
following,
view,
author,
flagged,
threadType,
countFlagged,
cohort,
} = {}) => {
export async function getThreads(
courseId, {
topicIds,
page,
pageSize,
textSearch,
orderBy,
following,
view,
author,
flagged,
threadType,
countFlagged,
cohort,
} = {},
) {
const params = snakeCaseObject({
courseId,
page,
@@ -60,19 +62,19 @@ export const getThreads = async (courseId, {
});
const { data } = await getAuthenticatedHttpClient().get(getThreadsApiUrl(), { params });
return data;
};
}
/**
* Fetches a single thread.
* @param {string} threadId
* @returns {Promise<{}>}
*/
export const getThread = async (threadId, courseId) => {
export async function getThread(threadId, courseId) {
const params = { requested_fields: 'profile_image', course_id: courseId };
const url = `${getThreadsApiUrl()}${threadId}/`;
const { data } = await getAuthenticatedHttpClient().get(url, { params });
return data;
};
}
/**
* Posts a new thread.
@@ -88,7 +90,7 @@ export const getThread = async (threadId, courseId) => {
* @param {boolean} enableInContextSidebar
* @returns {Promise<{}>}
*/
export const postThread = async (
export async function postThread(
courseId,
topicId,
type,
@@ -101,7 +103,7 @@ export const postThread = async (
anonymousToPeers,
} = {},
enableInContextSidebar = false,
) => {
) {
const postData = snakeCaseObject({
courseId,
topicId,
@@ -117,7 +119,7 @@ export const postThread = async (
const { data } = await getAuthenticatedHttpClient()
.post(getThreadsApiUrl(), postData);
return data;
};
}
/**
* Updates an existing thread.
@@ -136,7 +138,7 @@ export const postThread = async (
* @param {string} closeReasonCode
* @returns {Promise<{}>}
*/
export const updateThread = async (threadId, {
export async function updateThread(threadId, {
flagged,
voted,
read,
@@ -149,7 +151,7 @@ export const updateThread = async (threadId, {
pinned,
editReasonCode,
closeReasonCode,
} = {}) => {
} = {}) {
const url = `${getThreadsApiUrl()}${threadId}/`;
const patchData = snakeCaseObject({
topicId,
@@ -168,17 +170,17 @@ export const updateThread = async (threadId, {
const { data } = await getAuthenticatedHttpClient()
.patch(url, patchData, { headers: { 'Content-Type': 'application/merge-patch+json' } });
return data;
};
}
/**
* Deletes a thread.
* @param {string} threadId
*/
export const deleteThread = async (threadId) => {
export async function deleteThread(threadId) {
const url = `${getThreadsApiUrl()}${threadId}/`;
await getAuthenticatedHttpClient()
.delete(url);
};
}
/**
* Upload a file.
@@ -188,7 +190,7 @@ export const deleteThread = async (threadId) => {
* @param {string} threadKey
* @returns {Promise<{ location: string }>}
*/
export const uploadFile = async (blob, filename, courseId, threadKey) => {
export async function uploadFile(blob, filename, courseId, threadKey) {
const uploadUrl = `${getCoursesApiUrl()}${courseId}/upload`;
const formData = new FormData();
formData.append('thread_key', threadKey);
@@ -198,4 +200,4 @@ export const uploadFile = async (blob, filename, courseId, threadKey) => {
throw new Error(data.developer_message);
}
return data;
};
}

View File

@@ -2,6 +2,5 @@
export { showPostEditor } from './data';
export { default as Post } from './post/Post';
export { default as messages } from './post-actions-bar/messages';
// eslint-disable-next-line import/no-cycle
export { default as PostEditor } from './post-editor/PostEditor';
export { default as PostsView } from './PostsView';

View File

@@ -12,7 +12,7 @@ import { Close } from '@edx/paragon/icons';
import Search from '../../../components/Search';
import { RequestStatus } from '../../../data/constants';
import { DiscussionContext } from '../../common/context';
import { useUserPostingEnabled } from '../../data/hooks';
import { useUserCanAddThreadInBlackoutDate } from '../../data/hooks';
import { selectconfigLoadingStatus, selectEnableInContext } from '../../data/selectors';
import { TopicSearchBar as IncontextSearch } from '../../in-context-topics/topic-search';
import { postMessageToParent } from '../../utils';
@@ -26,7 +26,7 @@ const PostActionsBar = () => {
const dispatch = useDispatch();
const loadingStatus = useSelector(selectconfigLoadingStatus);
const enableInContext = useSelector(selectEnableInContext);
const isUserPrivilagedInPostingRestriction = useUserPostingEnabled();
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
const { enableInContextSidebar, page } = useContext(DiscussionContext);
const handleCloseInContext = useCallback(() => {
@@ -49,15 +49,13 @@ const PostActionsBar = () => {
{intl.formatMessage(messages.title)}
</h4>
)}
{loadingStatus === RequestStatus.SUCCESSFUL && isUserPrivilagedInPostingRestriction && (
{loadingStatus === RequestStatus.SUCCESSFUL && userCanAddThreadInBlackoutDate && (
<>
{!enableInContextSidebar && <div className="border-right border-light-400 mx-3" />}
<Button
variant={enableInContextSidebar ? 'plain' : 'brand'}
className={classNames(
'my-0 font-style border-0 line-height-24',
{ 'px-3 py-10px border-0': enableInContextSidebar },
)}
className={classNames('my-0 font-style border-0 line-height-24',
{ 'px-3 py-10px border-0': enableInContextSidebar })}
onClick={handleAddPost}
size={enableInContextSidebar ? 'md' : 'sm'}
>

View File

@@ -33,7 +33,6 @@ import {
selectUserIsGroupTa,
selectUserIsStaff,
} from '../../data/selectors';
// eslint-disable-next-line import/no-cycle
import { EmptyPage } from '../../empty-posts';
import {
selectArchivedTopics,
@@ -132,11 +131,9 @@ const PostEditor = ({
}, [postId, topicId, post?.author, category, editExisting, commentsPagePath, location]);
// null stands for no cohort restriction ("All learners" option)
const selectedCohort = useCallback(
(cohort) => (
cohort === 'default' ? null : cohort),
[],
);
const selectedCohort = useCallback((cohort) => (
cohort === 'default' ? null : cohort),
[]);
const submitForm = useCallback(async (values, { resetForm }) => {
if (editExisting) {
@@ -291,18 +288,18 @@ const PostEditor = ({
{enableInContext ? (
<>
{coursewareTopics?.map(section => (
section?.children?.map(subsection => (
<optgroup
label={handleInContextSelectLabel(section, subsection)}
key={subsection.id}
>
{subsection?.children?.map(unit => (
<option key={unit.id} value={unit.id}>
{unit.name || intl.formatMessage(messages.unnamedSubTopics)}
</option>
))}
</optgroup>
))
section?.children?.map(subsection => (
<optgroup
label={handleInContextSelectLabel(section, subsection)}
key={subsection.id}
>
{subsection?.children?.map(unit => (
<option key={unit.id} value={unit.id}>
{unit.name || intl.formatMessage(messages.unnamedSubTopics)}
</option>
))}
</optgroup>
))
))}
{(userIsStaff || userIsGroupTa || userHasModerationPrivileges) && (
<optgroup label={intl.formatMessage(messages.archivedTopics)}>

View File

@@ -77,14 +77,13 @@ const Post = ({ handleAddResponseButton }) => {
}
}, [closed, postId, reasonCodesEnabled, showClosePostModal]);
const handlePostCopyLink = useCallback(() => {
const postURL = new URL(`${getConfig().PUBLIC_PATH}${courseId}/posts/${postId}`, window.location.origin);
navigator.clipboard.writeText(postURL.href);
}, [window.location.origin, postId, courseId]);
const handlePostCopyLink = useCallback(() => navigator.clipboard.writeText(
`${window.location.origin}/${courseId}/posts/${postId}`,
), [window.location.origin, postId, courseId]);
const handlePostPin = useCallback(() => dispatch(
updateExistingThread(postId, { pinned: !pinned }),
), [postId, pinned]);
const handlePostPin = useCallback(() => dispatch(updateExistingThread(
postId, { pinned: !pinned },
)), [postId, pinned]);
const handlePostReport = useCallback(() => {
if (abuseFlagged) {
@@ -189,10 +188,8 @@ const Post = ({ handleAddResponseButton }) => {
</div>
{(topicContext || topic) && (
<div
className={classNames(
'mt-14px font-style font-size-12',
{ 'w-100': enableInContextSidebar, 'mb-1': !displayPostFooter },
)}
className={classNames('mt-14px font-style font-size-12',
{ 'w-100': enableInContextSidebar, 'mb-1': !displayPostFooter })}
style={{ lineHeight: '20px' }}
>
<span className="text-gray-500" style={{ lineHeight: '20px' }}>

View File

@@ -51,109 +51,105 @@ const PostLink = ({
const canSeeReportedBadge = abuseFlagged || abuseFlaggedCount;
const isPostRead = read || (!read && commentCount !== unreadCommentCount);
const checkIsSelected = useMemo(
() => (
window.location.pathname.includes(postId)),
[window.location.pathname],
);
const checkIsSelected = useMemo(() => (
window.location.pathname.includes(postId)),
[window.location.pathname]);
return (
<Link
className={
<>
<Link
className={
classNames('discussion-post p-0 text-decoration-none text-gray-900', {
'border-bottom border-light-400': showDivider,
})
}
to={linkUrl}
aria-current={checkIsSelected ? 'page' : undefined}
role="option"
tabIndex={(checkIsSelected || idx === 0) ? 0 : -1}
>
<div
className={
classNames(
'd-flex flex-row pt-2 pb-2 px-4 border-primary-500 position-relative',
{ 'bg-light-300': isPostRead },
{ 'post-summary-card-selected': id === selectedPostId },
)
}
to={linkUrl}
aria-current={checkIsSelected ? 'page' : undefined}
role="option"
tabIndex={(checkIsSelected || idx === 0) ? 0 : -1}
>
<PostAvatar
postType={type}
author={author}
authorLabel={authorLabel}
fromPostLink
read={isPostRead}
/>
<div className="d-flex flex-column flex-fill" style={{ minWidth: 0 }}>
<div className="d-flex flex-column justify-content-start mw-100 flex-fill" style={{ marginBottom: '-3px' }}>
<div className="d-flex align-items-center pb-0 mb-0 flex-fill font-weight-500">
<Truncate lines={1} className="mr-1.5" whiteSpace>
<span
class={
classNames(
'font-weight-500 font-size-14 text-primary-500 font-style align-bottom',
{ 'font-weight-bolder': !read },
)
}
>
{title}
</span>
<span class="align-bottom"> </span>
<span
class="text-gray-700 font-weight-normal font-size-14 font-style align-bottom"
>
{isPostPreviewAvailable(previewBody)
? previewBody
: intl.formatMessage(messages.postWithoutPreview)}
</span>
</Truncate>
{showAnsweredBadge && (
<Icon src={CheckCircle} className="text-success font-weight-500 ml-auto badge-padding" data-testid="check-icon">
<span className="sr-only">{' '}answered</span>
</Icon>
)}
{canSeeReportedBadge && (
<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>
)}
{pinned && (
<Icon
src={PushPin}
className={`post-summary-icons-dimensions text-gray-700
${canSeeReportedBadge || showAnsweredBadge ? 'ml-2' : 'ml-auto'}`}
/>
)}
</div>
</div>
<AuthorLabel
author={author || intl.formatMessage(messages.anonymous)}
<div
className={
classNames('d-flex flex-row pt-2 pb-2 px-4 border-primary-500 position-relative',
{ 'bg-light-300': isPostRead },
{ 'post-summary-card-selected': id === selectedPostId })
}
>
<PostAvatar
postType={type}
author={author}
authorLabel={authorLabel}
labelColor={authorLabelColor && `text-${authorLabelColor}`}
/>
<PostSummaryFooter
postId={id}
voted={voted}
voteCount={voteCount}
following={following}
commentCount={commentCount}
unreadCommentCount={unreadCommentCount}
groupId={groupId}
groupName={groupName}
createdAt={createdAt}
preview
showNewCountLabel={isPostRead}
fromPostLink
read={isPostRead}
/>
<div className="d-flex flex-column flex-fill" style={{ minWidth: 0 }}>
<div className="d-flex flex-column justify-content-start mw-100 flex-fill" style={{ marginBottom: '-3px' }}>
<div className="d-flex align-items-center pb-0 mb-0 flex-fill font-weight-500">
<Truncate lines={1} className="mr-1.5" whiteSpace>
<span
class={
classNames('font-weight-500 font-size-14 text-primary-500 font-style align-bottom',
{ 'font-weight-bolder': !read })
}
>
{title}
</span>
<span class="align-bottom"> </span>
<span
class="text-gray-700 font-weight-normal font-size-14 font-style align-bottom"
>
{isPostPreviewAvailable(previewBody)
? previewBody
: intl.formatMessage(messages.postWithoutPreview)}
</span>
</Truncate>
{showAnsweredBadge && (
<Icon src={CheckCircle} className="text-success font-weight-500 ml-auto badge-padding" data-testid="check-icon">
<span className="sr-only">{' '}answered</span>
</Icon>
)}
{canSeeReportedBadge && (
<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>
)}
{pinned && (
<Icon
src={PushPin}
className={`post-summary-icons-dimensions text-gray-700
${canSeeReportedBadge || showAnsweredBadge ? 'ml-2' : 'ml-auto'}`}
/>
)}
</div>
</div>
<AuthorLabel
author={author || intl.formatMessage(messages.anonymous)}
authorLabel={authorLabel}
labelColor={authorLabelColor && `text-${authorLabelColor}`}
/>
<PostSummaryFooter
postId={id}
voted={voted}
voteCount={voteCount}
following={following}
commentCount={commentCount}
unreadCommentCount={unreadCommentCount}
groupId={groupId}
groupName={groupName}
createdAt={createdAt}
preview
showNewCountLabel={isPostRead}
/>
</div>
</div>
</div>
{!showDivider && pinned && <div className="pt-1 bg-light-500 border-top border-light-700" />}
</Link>
{!showDivider && pinned && <div className="pt-1 bg-light-500 border-top border-light-700" />}
</Link>
</>
);
};

View File

@@ -39,7 +39,6 @@ const TopicGroupBase = ({
const renderFilteredTopics = useMemo(() => {
if (!hasFilteredSubtopics) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable react/prop-types */
/* eslint-disable no-unused-vars, react/forbid-prop-types */
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';

View File

@@ -10,7 +10,7 @@ export default function countFilteredTopics(topicsSelector, provider) {
? item.name.toLowerCase().includes(query)
: true
));
count += nonCoursewareTopicsList?.length ?? 0;
count += nonCoursewareTopicsList?.length;
// Counting legacy topics
if (provider === DiscussionProvider.LEGACY) {
const categories = topicsSelector?.categoryIds;

View File

@@ -17,7 +17,6 @@ const DiscussionsProductTour = () => {
}, []);
return (
// eslint-disable-next-line react/jsx-no-useless-fragment
<>
{!isEmpty(config) && (
<ProductTour

View File

@@ -267,6 +267,28 @@ export const filterPosts = (posts, filterBy) => uniqBy(posts, 'id').filter(
post => (filterBy.startsWith('un') ? !post[filterBy.slice(2)] : post[filterBy]),
);
/**
* 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)),
);
}
export function handleKeyDown(event) {
const { key } = event;
if (key !== 'ArrowDown' && key !== 'ArrowUp') { return; }

View File

@@ -1,51 +0,0 @@
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as headerMessages } from '@edx/frontend-component-header';
import { messages as paragonMessages } from '@edx/paragon';
import arMessages from './messages/ar.json';
// no need to import en messages-- they are in the defaultMessage field
import csMessages from './messages/cs.json';
import deMessages from './messages/de_DE.json';
import es419Messages from './messages/es_419.json';
import esARMessages from './messages/es_AR.json';
import esESMessages from './messages/es_ES.json';
import faIRMessages from './messages/fa_IR.json';
import frMessages from './messages/fr.json';
import frCAMessages from './messages/fr_CA.json';
import frFRMessages from './messages/fr_FR.json';
import hiMessages from './messages/hi.json';
import itITMessages from './messages/it_IT.json';
import plMessages from './messages/pl.json';
import ptPTMessages from './messages/pt_PT.json';
import ruMessages from './messages/ru.json';
import trTRMessages from './messages/tr_TR.json';
import ukMessages from './messages/uk.json';
import zhcnMessages from './messages/zh_CN.json';
const appMessages = {
ar: arMessages,
de: deMessages,
'es-419': es419Messages,
fr: frMessages,
'fr-ca': frCAMessages,
'fr-fr': frFRMessages,
'it-it': itITMessages,
pl: plMessages,
'tr-tr': trTRMessages,
'zh-cn': zhcnMessages,
'pt-pt': ptPTMessages,
uk: ukMessages,
ru: ruMessages,
hi: hiMessages,
cs: csMessages,
'es-AR': esARMessages,
'es-ES': esESMessages,
'fa-IR': faIRMessages,
};
export default [
headerMessages,
footerMessages,
paragonMessages,
appMessages,
];

26
src/i18n/index.jsx Normal file
View File

@@ -0,0 +1,26 @@
import arMessages from './messages/ar.json';
// no need to import en messages-- they are in the defaultMessage field
import deMessages from './messages/de_DE.json';
import es419Messages from './messages/es_419.json';
import frMessages from './messages/fr.json';
import frCAMessages from './messages/fr_CA.json';
import frFRMessages from './messages/fr_FR.json';
import itITMessages from './messages/it_IT.json';
import plMessages from './messages/pl.json';
import trTRMessages from './messages/tr_TR.json';
import zhcnMessages from './messages/zh_CN.json';
const messages = {
ar: arMessages,
de: deMessages,
'es-419': es419Messages,
fr: frMessages,
'fr-ca': frCAMessages,
'fr-fr': frFRMessages,
'it-it': itITMessages,
pl: plMessages,
'tr-tr': trTRMessages,
'zh-cn': zhcnMessages,
};
export default messages;

View File

@@ -1,18 +1,18 @@
{
"navigation.course.tabs.label": "مواد المساق",
"learn.course.tabs.navigation.overflow.menu": "المزيد...",
"discussions.topics.backAlt": "العودة إلى قائمة المواضيع",
"discussions.topics.backAlt": "Back to topics list",
"discussions.topics.discussions": "{count، plural, =0 {لا مناقشات} one {مناقشة واحدة} two {مناقشتان} few {# مناقشات} many {# مناقشة} other {# مناقشات}",
"discussions.topics.questions": "{count، plural, =0 {لا مناقشات} one {سؤال واحد} two {سؤالان} few {# اسئلة} many {# سؤالًا} other {# أسئلة}",
"discussions.topics.reported": "تم الإبلاغ عن {reported}",
"discussions.topics.previouslyReported": "تم الإبلاغ عن {previouslyReported} من قبل",
"discussions.topics.find.label": "البحث في المواضيع",
"discussions.topics.unnamed.section.label": "قسم بدون اسم",
"discussions.topics.unnamed.subsection.label": "قسم فرعي بدون اسم",
"discussions.subtopics.unnamed.topic.label": "موضوع لم يذكر اسمه",
"discussions.topics.title": "لا يوجد موضوع",
"discussions.topics.unnamed.section.label": "Unnamed Section",
"discussions.topics.unnamed.subsection.label": "Unnamed Subsection",
"discussions.subtopics.unnamed.topic.label": "Unnamed Topic",
"discussions.topics.title": "No topic exists",
"discussions.topics.createTopic": "Please contact you admin to create a topic",
"discussions.topics.nothing": "لا شيء هنا حتى الان",
"discussions.topics.nothing": "Nothing here yet",
"discussions.topics.archived.label": "مؤرشف",
"discussions.learner.reported": "تم الإبلاغ عن {reported}",
"discussions.learner.previouslyReported": "تم الإبلاغ عن {previouslyReported} من قبل",
@@ -142,7 +142,7 @@
"discussions.post.editor.anonymousPost": "النشر كمجهول",
"discussions.post.editor.anonymousToPeersPost": "انشر ﻷقرانك كمجهول",
"discussions.editor.posts.editReasonCode": "سبب التعديل",
"discussions.editor.posts.showPreview.button": "عرض المعاينة",
"discussions.editor.posts.showPreview.button": "Show preview",
"discussions.topic.noName.label": "تصنيف دون اسم",
"discussions.subtopic.noName.label": "تصنيف فرعي دون اسم",
"discussions.posts.filter.showALl": "عرض الكل",
@@ -164,22 +164,22 @@
"discussions.posts.sort.voteCount": "الأكثر إعجابًا",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {جميع}\n true {ما كتبته من}\n other {{own}}\n } \n {type, select,\ndiscussion {المناقشات}\nquestion {الأسئلة}\nall {المنشورات}\nother {{type}}\n}\n {status, select,\n statusAll {}\n statusUnread {غير المقروءة}\n statusFollowing {التي تتابعها}\n statusReported {المُبلّغ عنها}\n statusUnanswered {دون إجابة}\n statusUnresponded {دون رد}\n other {{status}}\n } و المنشورة {cohortType, select,\n all {}\n group {ضمن {cohort}}\n other {{cohortType}}\n }، مرتبة حسب {sort, select,\n lastActivityAt {أحدث نشاط}\n commentCount {أكثر نشاط}\n voteCount {أكثر إعجاب}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "مجهول",
"discussions.post.addResponse": "أضف الرد",
"discussions.post.addResponse": "Add response",
"discussions.post.lastResponse": "آخر رد {time}",
"discussions.post.postedOn": "منشور في {time} من طرف {author} {authorLabel}",
"discussions.post.contentReported": "تم الإبلاغ",
"discussions.post.following": "جاري المتابعة",
"discussions.post.follow": "متابعة",
"discussions.post.followed": "تابع",
"discussions.post.notFollowed": "ليس متابع",
"discussions.post.followed": "Followed",
"discussions.post.notFollowed": "Not Followed",
"discussions.post.answered": "تمّت الإجابة",
"discussions.post.unFollow": "إلغاء المتابعة",
"discussions.post.like": "أعجبني",
"discussions.post.removeLike": "إلغاء الإغجاب",
"discussions.post.liked": "اعحبني",
"discussions.post.likes": "الإعجابات",
"discussions.post.liked": "liked",
"discussions.post.likes": "likes",
"discussions.post.viewActivity": "عرض النشاط",
"discussions.post.activity": "النشاط",
"discussions.post.activity": "Activity",
"discussions.post.closed": "منشور مقفل أمام الردود والتعليقات",
"discussions.post.relatedTo": "متعلق بـ",
"discussions.editor.delete.post.title": "حذف المنشور",
@@ -196,7 +196,7 @@
"discussions.post.editedBy": "عدّله",
"discussions.post.editReason": "السبب",
"discussions.post.postWithoutPreview": "المعاينة غير متاحة",
"discussions.post.follow.description": "أنت تتابع هذا المنشور/الرد",
"discussions.post.follow.description": "you are following this post",
"discussions.post.unfollow.description": "you are not following this post",
"discussions.topics.sort.message": "مرتبة حسب {sortBy}",
"discussions.topics.sort.lastActivity": "الأحدث نشاطًا",
@@ -204,11 +204,11 @@
"discussions.topics.sort.courseStructure": "هيكل المساق",
"discussions.topics.unnamed.label": "فئة بدون اسم",
"discussions.subtopics.unnamed.label": "فئة فرعية بدون اسم",
"tour.action.advance": "التالي",
"tour.action.dismiss": "تجاهل",
"tour.action.end": "حسنًا",
"tour.body.notRespondedFilter": "يمكنك الآن تصفية المناقشات للعثور على مشاركات بدون رد.",
"tour.title.notRespondedFilter": "خيار تصفية جديد!",
"tour.action.advance": "Next",
"tour.action.dismiss": "Dismiss",
"tour.action.end": "Okay",
"tour.body.notRespondedFilter": "Now you can filter discussions to find posts with no response.",
"tour.title.notRespondedFilter": "New filtering option!",
"tour.body.responseSortTour": "Responses and comments are now sorted by newest first. Please use this option to change the sort order",
"tour.title.responseSortTour": "فرز الردود!"
"tour.title.responseSortTour": "Sort Responses!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "Materiály ke kurzu",
"learn.course.tabs.navigation.overflow.menu": "Více...",
"discussions.topics.backAlt": "Zpět na seznam témat",
"discussions.topics.discussions": "{count, plural,\n =0 {Diskuzí}\n one {# Diskuze}\n other {# Diskuzí}\n }",
"discussions.topics.questions": "{count, plural,\n =0 {Otázek}\n one {# Otázka}\n other {# Otázky}\n }",
"discussions.topics.reported": "{reported} nahlášeno",
"discussions.topics.previouslyReported": "{previouslyReported} minule nahlášeno",
"discussions.topics.find.label": "Hledat témata",
"discussions.topics.unnamed.section.label": "Nejmenovaná sekce",
"discussions.topics.unnamed.subsection.label": "Nepojmenovaná podsekce",
"discussions.subtopics.unnamed.topic.label": "Nepojmenované téma",
"discussions.topics.title": "Žádné téma neexistuje",
"discussions.topics.createTopic": "Chcete-li vytvořit téma, kontaktujte svého správce",
"discussions.topics.nothing": "Ještě tu nic není",
"discussions.topics.archived.label": "Archivováno",
"discussions.learner.reported": "{reported} nahlášeno",
"discussions.learner.previouslyReported": "{previouslyReported} minule nahlášeno",
"discussions.learner.lastLogin": "Poslední aktivita {lastActiveTime}",
"discussions.learner.loadMostLearners": "Načíst více",
"discussions.learner.back": "Zpět",
"discussions.learner.activityForLearner": "Aktivita pro {username}",
"discussions.learner.mostActivity": "Nejvíce aktivity",
"discussions.learner.reportedActivity": "Nahlášené aktivity",
"discussions.learner.recentActivity": "Nedávné aktivity",
"discussions.learner.sortFilterStatus": "Všichni studenti seřazení podle {sort, select,\n flagged {nahlášené aktivity}\n activity {nejvíce aktivity}\n other {{sort}}\n }",
"discussion.learner.allActivity": "Všechna aktivita",
"discussion.learner.posts": "Příspěvky",
"discussions.actions.button.alt": "Nabídka akcí",
"discussions.actions.copylink": "Kopírovat odkaz",
"discussions.actions.edit": "Editovat",
"discussions.actions.pin": "Připnout",
"discussions.actions.unpin": "Odepnout",
"discussions.actions.delete": "Smazat",
"discussions.confirmation.button.confirm": "Potvrdit",
"discussions.actions.close": "Zavřít",
"discussions.actions.reopen": "Znovu otevřít",
"discussions.actions.report": "Nahlásit",
"discussions.actions.unreport": "Zrušit nahlášení",
"discussions.actions.endorse": "Schválit",
"discussions.actions.unendorse": "Nepodporovat",
"discussions.actions.markAnswered": "Označit jako zodpovězené",
"discussions.actions.unMarkAnswered": "Označit jako nezodpovězené",
"discussions.modal.confirmation.button.cancel": "Zrušit",
"discussions.empty.allTopics": "Veškerá aktivita diskuze pro tato témata se zobrazí zde.",
"discussions.empty.allPosts": "Veškerá aktivita diskuze pro vaše kurzy se zobrazí zde.",
"discussions.empty.myPosts": "Příspěvky, se kterými jste interagovali, se zobrazí zde.",
"discussions.empty.topic": "Veškerá aktivita diskuze pro toto téma se zobrazí zde.",
"discussions.empty.title": "Ještě tu nic není",
"discussions.empty.noPostSelected": "Nevybrány žádné příspěvky",
"discussions.empty.noTopicSelected": "Nevybrána žádná témata",
"discussions.sidebar.noResultsFound": "Nebyly nalezeny žádné výsledky",
"discussions.sidebar.differentKeywords": "Zkuste vyhledávat různá klíčová slova",
"discussions.sidebar.removeKeywords": "Zkuste vyhledávat klíčová slova nebo odstranit některé filtry",
"discussions.sidebar.removeKeywordsOnly": "Zkuste vyhledávat různá klíčová slova",
"discussions.sidebar.removeFilters": "Zkuste odstranit některé filtry",
"discussions.empty.iconAlt": "Prázdný",
"discussions.authors.label.staff": "Učitelé",
"discussions.authors.label.ta": "TA",
"discussions.learner.loadMostPosts": "Načíst další příspěvky",
"discussions.post.anonymous.author": "anonymní",
"discussion.banner.welcomeMessage": "🎉 Vítejte v novém a vylepšeném prostředí diskuzí!",
"discussion.banner.learnMore": "Zjistěte více",
"discussion.banner.shareFeedback": "Sdílejte zpětnou vazbu",
"discussion.blackoutBanner.information": "Zasílání příspěvků v diskuzích je dočasně zakázáno týmem kurzu",
"discussions.editor.image.warning.message": "Obrázky mající šířku nebo výšku větší než 999px nebudou viditelné, když příspěvek, odpověď nebo komentář zobrazíte pomocí diskuzí v rámci kurzu",
"discussions.editor.image.warning.title": "Varování!",
"discussions.editor.image.warning.dismiss": "Ok",
"discussions.navigation.breadcrumbMenu.allTopics": "Témata",
"discussions.navigation.breadcrumbMenu.showAll": "Zobrazit vše",
"discussions.navigation.navigationBar.allPosts": "Všechny příspěvky",
"discussions.navigation.navigationBar.allTopics": "Témata",
"discussions.navigation.navigationBar.myPosts": "Moje příspěvky",
"discussions.navigation.navigationBar.learners": "Studenti",
"discussions.comments.comment.addComment": "Přidat komentář",
"discussions.comments.comment.addResponse": "Přidat odpověď",
"discussions.comments.comment.abuseFlaggedMessage": "Obsah byl nahlášen učiteli ke kontrole",
"discussions.actions.back.alt": "Zpět na seznam",
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {Žádné odpovědi}\n one {Zobrazení # odpovědi}\n other {Zobrazení # odpovědí}\n }",
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {Žádné schválené odpovědi}\n one {Zobrazení # schválené odpovědi}\n other {Zobrazení # schválených odpovědí}\n }",
"discussions.comments.comment.loadMoreComments": "Načíst další komentáře",
"discussions.comments.comment.loadMoreResponses": "Načíst další odpovědi",
"discussions.comments.comment.visibility": "Tento příspěvek je viditelný pro {group, select,\n null {Všichni}\n other {{group}}\n }.",
"discussions.comments.comment.postedTime": "{postType, select,\n discussion {Discussion}\n question {Question}\n other {{postType}}\n } odesláno {relativeTime} uživatelem",
"discussions.comments.comment.commentTime": "Odesláno {relativeTime}",
"discussions.comments.comment.answer": "Odpověď",
"discussions.comments.comment.answeredlabel": "Označit jako zodpovězené",
"discussions.comments.comment.endorsed": "Schválené",
"discussions.comments.comment.endorsedlabel": "Schváleno uživatelem",
"discussions.actions.label": "Nabídka akcí",
"discussions.editor.submit": "Odeslat",
"discussions.editor.submitting": "Odesílám",
"discussions.editor.cancel": "Zrušit",
"discussions.editor.error.empty": "Obsah příspěvku nemůže být prázdný.",
"discussions.editor.delete.response.title": "Smazat obsah",
"discussions.editor.delete.response.description": "Opravdu chcete trvale smazat tento obsah?",
"discussions.editor.delete.comment.title": "Smazat komentář",
"discussions.editor.delete.comment.description": "Opravdu chcete smazat tento komentář?",
"discussions.delete.confirmation.button.delete": "Smazat",
"discussions.editor.response.response.title": "Nahlásit nevhodný obsah?",
"discussions.editor.response.description": "Tým pro moderování diskuze zkontroluje tento obsah a podnikne příslušné kroky.",
"discussions.editor.report.comment.title": "Nahlásit nevhodný obsah?",
"discussions.editor.report.comment.description": "Tým pro moderování diskuze zkontroluje tento obsah a podnikne příslušné kroky.",
"discussions.editor.comments.editReasonCode": "Důvod úpravy",
"discussions.editor.posts.editReasonCode.error": "Zvolit důvod úpravy",
"discussions.comment.comments.editedBy": "Úprava od",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "Důvod",
"discussions.post.closedBy": "Příspěvek zavřen uživatelem",
"discussion.comment.time": "Před {time}",
"discussion.thread.notFound": "Vlákno nebylo nelezeno",
"discussions.comment.sortFilterStatus": "{sort, select,\n false {Nejstarší první}\n true {Nejnovější první}\n other {{sort}}\n }",
"discussions.app.title": "Diskuze",
"discussions.posts.actionBar.searchAllPosts": "Prohledejte příspěvky",
"discussions.posts.actionBar.search": "{page, select,\n topics {Hledat témata}\n posts {Hledat všechny příspěvky}\n learners {Hledat studenty}\n myPosts {Hledat všechny příspěvky}\n other {{page}}\n }",
"discussions.actionBar.searchInfo": "Zobrazuji {count} výsledků pro \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "Nenylezeny žádné výsledky pro \"{searchString}\". Zobrazuji {count} výsledků pro \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "Vyhledávám...",
"discussions.actionBar.clearSearch": "Vymazat vyhledávání",
"discussion.posts.actionBar.add": "Přidat příspěvek",
"discussion.posts.actionBar.close": "Zavřít",
"discussions.post.editor.type": "Typ příspěvku",
"discussions.post.editor.addPostHeading": "Přidat příspěvek",
"discussions.post.editor.editPostHeading": "Upravit příspěvek",
"discussions.post.editor.typeDescription": "Otázky vyvolávají problémy, které vyžadují odpovědi. Diskuse sdílejí nápady a zahajují konverzace.",
"discussions.post.editor.required": "Povinný",
"discussions.post.editor.questionType": "Otázka",
"discussions.post.editor.questionDescription": "Upozorňovat na problémy, které vyžadují odpovědi",
"discussions.post.editor.discussionType": "Diskuze",
"discussions.post.editor.discussionDescription": "Sdílet nápady a začít konverzaci",
"discussions.post.editor.topicArea": "Tematická oblast",
"discussions.post.editor.topicAreaDescription": "Přidejte svůj příspěvek k relevantnímu tématu, aby ho ostatní mohli najít.",
"discussions.post.editor.cohortVisibility": "Viditelnost kohorty",
"discussions.post.editor.cohortVisibilityAllLearners": "Všichni studenti",
"discussions.post.editor.title": "Název příspěvku",
"discussions.post.editor.titleDescription": "Přidejte jasný a popisný název, abyste podpořili účast.",
"discussions.post.editor.title.error": "Název příspěvku nemůže být prázdný.",
"discussions.post.editor.content.error": "Obsah příspěvku nemůže být prázdný.",
"discussions.post.editor.questionText": "Vaše otázka či nápad (povinné)",
"discussions.post.editor.preview": "Náhled",
"discussions.post.editor.followPost": "Sledovat tento příspěvek",
"discussions.post.editor.anonymousPost": "Odeslat anonymně",
"discussions.post.editor.anonymousToPeersPost": "Anonymně přispívat spolužákům",
"discussions.editor.posts.editReasonCode": "Důvod úpravy",
"discussions.editor.posts.showPreview.button": "Zobrazit náhled",
"discussions.topic.noName.label": "Nepojmenovaná kategorie",
"discussions.subtopic.noName.label": "Nepojmenovaná podkategorie",
"discussions.posts.filter.showALl": "Zobrazit vše",
"discussions.posts.filter.discussions": "Diskuze",
"discussions.posts.filter.questions": "Otázky",
"discussions.posts.filter.message": "Stav: {filterBy}",
"discussions.posts.status.filter.anyStatus": "Jakýkoli stav",
"discussions.posts.status.filter.unread": "Nepřečtený",
"discussions.posts.status.filter.following": "Sledováno",
"discussions.posts.status.filter.reported": "Nahlášeno",
"discussions.posts.status.filter.unanswered": "Nezodpovězené",
"discussions.posts.status.filter.unresponded": "Nereagované",
"discussions.posts.filter.myPosts": "Moje příspěvky",
"discussions.posts.filter.myDiscussions": "Moje diskuze",
"discussions.posts.filter.myQuestions": "Moje otázky",
"discussions.posts.sort.message": "Seřazeno podle {sortBy}",
"discussions.posts.sort.lastActivity": "Nedávné aktivity",
"discussions.posts.sort.commentCount": "Největší aktivity",
"discussions.posts.sort.voteCount": "Nejvíce lajků",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {Vše}\n true {Vlastní}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {nepřečtené}\n statusFollowing {sledované}\n statusReported {nahlášené}\n statusUnanswered {nezodpovězené}\n statusUnresponded {nereagované}\n other {{status}}\n } {type, select,\n discussion {diskuze}\n question {otázky}\n all {příspěvky}\n other {{type}}\n } {cohortType, select,\n all {}\n group {v {cohort}}\n other {{cohortType}}\n } seřazeno podle {sort, select,\n lastActivityAt {nedávné aktivity}\n commentCount {nejvíce aktivity}\n voteCount {nejvíce lajků}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "anonymní",
"discussions.post.addResponse": "Přidat odpověď",
"discussions.post.lastResponse": "Poslední odpověď {time}",
"discussions.post.postedOn": "Odesláno {time} uživatelem {author} {authorLabel}",
"discussions.post.contentReported": "Nahlášeno",
"discussions.post.following": "Sledováno",
"discussions.post.follow": "Sledovat",
"discussions.post.followed": "Sledoval",
"discussions.post.notFollowed": "Nesledoval",
"discussions.post.answered": "Zodpovězeno",
"discussions.post.unFollow": "Zrušit sledování",
"discussions.post.like": "Lajk",
"discussions.post.removeLike": "Zrušit lajk",
"discussions.post.liked": "Lajknul",
"discussions.post.likes": "Lajky",
"discussions.post.viewActivity": "Zobrazit aktivitu",
"discussions.post.activity": "Aktivita",
"discussions.post.closed": "Příspěvek uzavřen pro odpovědi a komentáře",
"discussions.post.relatedTo": "Související s",
"discussions.editor.delete.post.title": "Odstranit příspěvek",
"discussions.editor.delete.post.description": "Opravdu chcete smazat tento příspěvek?",
"discussions.post.delete.confirmation.button.delete": "Smazat",
"discussions.editor.report.post.title": "Nahlásit nevhodný obsah?",
"discussions.editor.report.post.description": "Tým pro moderování diskuze zkontroluje tento obsah a podnikne příslušné kroky.",
"discussions.post.closePostModal.title": "Zavřít příspěvek",
"discussions.post.closePostModal.text": "Zadejte důvod pro uzavření tohoto příspěvku. Toto se zobrazí pouze ostatním moderátorům.",
"discussions.post.closePostModal.reasonCodeInput": "Důvod",
"discussions.post.closePostModal.cancel": "Zrušit",
"discussions.post.closePostModal.confirm": "Zavřít příspěvek",
"discussions.post.label.new": "{count} nových",
"discussions.post.editedBy": "Úprava od",
"discussions.post.editReason": "Důvod",
"discussions.post.postWithoutPreview": "Není k dispozici žádný náhled",
"discussions.post.follow.description": "sledujete tento příspěvek",
"discussions.post.unfollow.description": "nesledujete tento příspěvek",
"discussions.topics.sort.message": "Seřazeno podle {sortBy}",
"discussions.topics.sort.lastActivity": "Nedávná aktivita",
"discussions.topics.sort.commentCount": "Nejvíce aktivity",
"discussions.topics.sort.courseStructure": "Struktura kurzu",
"discussions.topics.unnamed.label": "Nepojmenovaná kategorie",
"discussions.subtopics.unnamed.label": "Nepojmenovaná podkategorie",
"tour.action.advance": "Další",
"tour.action.dismiss": "Zamítnout",
"tour.action.end": "Dobře",
"tour.body.notRespondedFilter": "Nyní můžete filtrovat diskuse a najít příspěvky bez odpovědi.",
"tour.title.notRespondedFilter": "Nová možnost filtrování!",
"tour.body.responseSortTour": "Odpovědi a komentáře jsou nyní seřazeny podle nejnovějších. Tuto možnost použijte ke změně pořadí řazení",
"tour.title.responseSortTour": "Seřadit odpovědi!"
}

View File

@@ -209,6 +209,6 @@
"tour.action.end": "Okey",
"tour.body.notRespondedFilter": "Ahora puede filtrar debates para encontrar publicaciones sin respuesta.",
"tour.title.notRespondedFilter": "¡Nueva opción de filtrado!",
"tour.body.responseSortTour": "Las respuestas y los comentarios ahora se ordenan por los más recientes primero. Utilice esta opción para cambiar el orden de clasificación",
"tour.title.responseSortTour": "¡Clasificar respuestas!"
"tour.body.responseSortTour": "Responses and comments are now sorted by newest first. Please use this option to change the sort order",
"tour.title.responseSortTour": "Sort Responses!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "Material del curso",
"learn.course.tabs.navigation.overflow.menu": "Más...",
"discussions.topics.backAlt": "Volver a la lista de temas",
"discussions.topics.discussions": "{count, plural, =0 {Discusión} one {# Discusión} other {# Discusiones} }",
"discussions.topics.questions": "{count, plural, =0 {Pregunta} one {# Pregunta} other {# Preguntas} }",
"discussions.topics.reported": "{reported} denunciado",
"discussions.topics.previouslyReported": "{previouslyReported} informado anteriormente",
"discussions.topics.find.label": "Buscar temas",
"discussions.topics.unnamed.section.label": "Sección sin nombre",
"discussions.topics.unnamed.subsection.label": "Subsección sin nombre",
"discussions.subtopics.unnamed.topic.label": "Tema sin nombre",
"discussions.topics.title": "No existe ningún tema",
"discussions.topics.createTopic": "Póngase en contacto con su administrador para crear un tema",
"discussions.topics.nothing": "Nada aquí todavía",
"discussions.topics.archived.label": "Archivado",
"discussions.learner.reported": "{reported} reportado",
"discussions.learner.previouslyReported": "{previouslyReported} informado anteriormente",
"discussions.learner.lastLogin": "Último activo {lastActiveTime}",
"discussions.learner.loadMostLearners": "Cargar más",
"discussions.learner.back": "atrás",
"discussions.learner.activityForLearner": "Actividad para {username}",
"discussions.learner.mostActivity": "La mayoría de la actividad",
"discussions.learner.reportedActivity": "Actividad reportada",
"discussions.learner.recentActivity": "Actividad reciente",
"discussions.learner.sortFilterStatus": "Todos los alumnos ordenados por {sort, select, flagged {actividad notificada} activity {mayor actividad} other {{sort}} }",
"discussion.learner.allActivity": "Toda la actividad",
"discussion.learner.posts": "Publicaciones",
"discussions.actions.button.alt": "Menú de acciones",
"discussions.actions.copylink": "Copiar link",
"discussions.actions.edit": "Editar",
"discussions.actions.pin": "Alfiler",
"discussions.actions.unpin": "Desprender",
"discussions.actions.delete": "Borrar",
"discussions.confirmation.button.confirm": "Confirmar",
"discussions.actions.close": "Cerrar",
"discussions.actions.reopen": "Reabrir",
"discussions.actions.report": "Informar",
"discussions.actions.unreport": "No informar",
"discussions.actions.endorse": "Validar",
"discussions.actions.unendorse": "Invalidar",
"discussions.actions.markAnswered": "Marcar como respondida",
"discussions.actions.unMarkAnswered": "Desmarcar como respondida",
"discussions.modal.confirmation.button.cancel": "Cancelar",
"discussions.empty.allTopics": "Toda la actividad de debate de estos temas se mostrará aquí.",
"discussions.empty.allPosts": "Toda la actividad de debate de su curso se mostrará aquí.",
"discussions.empty.myPosts": "Las publicaciones con las que has interactuado se mostrarán aquí.",
"discussions.empty.topic": "Toda la actividad de debate sobre este tema se mostrará aquí.",
"discussions.empty.title": "Nada aquí todavía",
"discussions.empty.noPostSelected": "Ninguna publicación seleccionada",
"discussions.empty.noTopicSelected": "Ningún tema seleccionado",
"discussions.sidebar.noResultsFound": "No se han encontrado resultados",
"discussions.sidebar.differentKeywords": "Intenta buscar diferentes palabras clave",
"discussions.sidebar.removeKeywords": "Intente buscar diferentes palabras clave o elimine algunos filtros.",
"discussions.sidebar.removeKeywordsOnly": "Intenta buscar diferentes palabras clave",
"discussions.sidebar.removeFilters": "Prueba a eliminar algunos filtros.",
"discussions.empty.iconAlt": "Vacío",
"discussions.authors.label.staff": "Personal",
"discussions.authors.label.ta": "ejército de reserva",
"discussions.learner.loadMostPosts": "Cargar más entradas",
"discussions.post.anonymous.author": "anónimo",
"discussion.banner.welcomeMessage": "🎉 ¡Bienvenido a la nueva y mejorada experiencia de debates!",
"discussion.banner.learnMore": "Aprende más",
"discussion.banner.shareFeedback": "Compartir comentarios",
"discussion.blackoutBanner.information": "El equipo del curso deshabilita temporalmente la publicación en discusiones",
"discussions.editor.image.warning.message": "Las imágenes que tengan un ancho o alto superior a 999 px no serán visibles cuando la publicación, la respuesta o el comentario se vean mediante debates en línea del curso.",
"discussions.editor.image.warning.title": "¡Advertencia!",
"discussions.editor.image.warning.dismiss": "Ok",
"discussions.navigation.breadcrumbMenu.allTopics": "Temas",
"discussions.navigation.breadcrumbMenu.showAll": "Mostrar todo",
"discussions.navigation.navigationBar.allPosts": "Todos los mensajes",
"discussions.navigation.navigationBar.allTopics": "Temas",
"discussions.navigation.navigationBar.myPosts": "Mis publicaciones",
"discussions.navigation.navigationBar.learners": "Estudiantes",
"discussions.comments.comment.addComment": "Agregar comentario",
"discussions.comments.comment.addResponse": "Agregar una respuesta",
"discussions.comments.comment.abuseFlaggedMessage": "Contenido informado para que el personal lo revise",
"discussions.actions.back.alt": "Volver a la lista",
"discussions.comments.comment.responseCount": "{num, plural, =0 {Sin respuestas} one {Mostrando # respuestas} other {Mostrando # respuestas} }",
"discussions.comments.comment.endorsedResponseCount": "{num, plural, =0 {Sin respuestas respaldadas} one {Mostrando # respuesta respaldada} other {Mostrando # respuestas respaldadas} }",
"discussions.comments.comment.loadMoreComments": "Cargar más comentarios",
"discussions.comments.comment.loadMoreResponses": "Cargar más respuestas",
"discussions.comments.comment.visibility": "Esta publicación es visible para {group, select, null {Todos} other {{group}} }.",
"discussions.comments.comment.postedTime": "{postType, select, discussion {Discusión} question {Pregunta} other {{postType}} } publicado {a0917e9bee}14c5z0",
"discussions.comments.comment.commentTime": "Publicado {relativeTime}",
"discussions.comments.comment.answer": "Respuesta",
"discussions.comments.comment.answeredlabel": "Marcado como respondido por",
"discussions.comments.comment.endorsed": "Respaldado",
"discussions.comments.comment.endorsedlabel": "Avalado por",
"discussions.actions.label": "Menú de acciones",
"discussions.editor.submit": "Enviar",
"discussions.editor.submitting": "Enviar",
"discussions.editor.cancel": "Cancelar",
"discussions.editor.error.empty": "El contenido de la publicación no puede estar vacío.",
"discussions.editor.delete.response.title": "Eliminar respuesta",
"discussions.editor.delete.response.description": "¿Está seguro de que desea eliminar esta respuesta de forma permanente?",
"discussions.editor.delete.comment.title": "Eliminar comentario",
"discussions.editor.delete.comment.description": "¿Estás seguro de que quieres eliminar este comentario de forma permanente?",
"discussions.delete.confirmation.button.delete": "Borrar",
"discussions.editor.response.response.title": "¿Denunciar contenido inapropiado?",
"discussions.editor.response.description": "El equipo de moderación de debates revisará este contenido y tomará las medidas adecuadas.",
"discussions.editor.report.comment.title": "¿Denunciar contenido inapropiado?",
"discussions.editor.report.comment.description": "El equipo de moderación de debates revisará este contenido y tomará las medidas adecuadas.",
"discussions.editor.comments.editReasonCode": "Motivo de la edición",
"discussions.editor.posts.editReasonCode.error": "Seleccione el motivo de la edición",
"discussions.comment.comments.editedBy": "Editado por",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "Motivo",
"discussions.post.closedBy": "Publicación cerrada por",
"discussion.comment.time": "{time} hace",
"discussion.thread.notFound": "Hilo no encontrado",
"discussions.comment.sortFilterStatus": "{sort, select,\n false {Oldest first}\n true {Newest first}\n other {{sort}}\n }",
"discussions.app.title": "Debates",
"discussions.posts.actionBar.searchAllPosts": "Buscar todas las publicaciones",
"discussions.posts.actionBar.search": "{page, select, topics {Search topics} posts {Search all posts} learners {Search learners} myPosts {Search all posts} other {{page}} }",
"discussions.actionBar.searchInfo": "Mostrando resultados {count} para \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "No results found for \"{searchString}\". Showing {count} results for \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "Buscando...",
"discussions.actionBar.clearSearch": "Borrar resultados",
"discussion.posts.actionBar.add": "Agregar una publicación",
"discussion.posts.actionBar.close": "Cerrar",
"discussions.post.editor.type": "Tipo de publicación",
"discussions.post.editor.addPostHeading": "Agregar una publicación",
"discussions.post.editor.editPostHeading": "Editar publicación",
"discussions.post.editor.typeDescription": "Las preguntas plantean problemas que necesitan respuestas. Las discusiones comparten ideas y comienzan conversaciones.",
"discussions.post.editor.required": "Requerido",
"discussions.post.editor.questionType": "Pregunta",
"discussions.post.editor.questionDescription": "Plantear problemas que necesitan respuestas",
"discussions.post.editor.discussionType": "Debate",
"discussions.post.editor.discussionDescription": "Comparte ideas e inicia conversaciones",
"discussions.post.editor.topicArea": "Área de temas",
"discussions.post.editor.topicAreaDescription": "Agregar la publicación a un tema relevante para ayudar a otros a encontrarla.",
"discussions.post.editor.cohortVisibility": "Visibilidad de la cohorte",
"discussions.post.editor.cohortVisibilityAllLearners": "Todos los estudiantes",
"discussions.post.editor.title": "Título de la entrada",
"discussions.post.editor.titleDescription": "Agregue un título claro y descriptivo para fomentar la participación.",
"discussions.post.editor.title.error": "El título de la publicación no puede estar vacío.",
"discussions.post.editor.content.error": "El contenido de la publicación no puede estar vacío.",
"discussions.post.editor.questionText": "Su pregunta o idea (obligatorio)",
"discussions.post.editor.preview": "Vista previa",
"discussions.post.editor.followPost": "Seguir esta publicación",
"discussions.post.editor.anonymousPost": "Publicar de forma anónima",
"discussions.post.editor.anonymousToPeersPost": "Publicar de forma anónima a los compañeros",
"discussions.editor.posts.editReasonCode": "Motivo de la edición",
"discussions.editor.posts.showPreview.button": "Mostrar vista previa",
"discussions.topic.noName.label": "Categoría sin nombre",
"discussions.subtopic.noName.label": "Subcategoría sin nombre",
"discussions.posts.filter.showALl": "Mostrar todo",
"discussions.posts.filter.discussions": "Debates",
"discussions.posts.filter.questions": "Preguntas",
"discussions.posts.filter.message": "Estado: {filterBy}",
"discussions.posts.status.filter.anyStatus": "Cualquier estatus",
"discussions.posts.status.filter.unread": "No leído",
"discussions.posts.status.filter.following": "Siguiendo",
"discussions.posts.status.filter.reported": "Reportado",
"discussions.posts.status.filter.unanswered": "Sin respuesta",
"discussions.posts.status.filter.unresponded": "Sin respuesta",
"discussions.posts.filter.myPosts": "Mis publicaciones",
"discussions.posts.filter.myDiscussions": "Mis debates",
"discussions.posts.filter.myQuestions": "Mis preguntas",
"discussions.posts.sort.message": "Ordenado por {sortBy}",
"discussions.posts.sort.lastActivity": "Actividad reciente",
"discussions.posts.sort.commentCount": "La mayoría de la actividad",
"discussions.posts.sort.voteCount": "La mayoría me gusta",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "anónimo",
"discussions.post.addResponse": "Añadir respuesta",
"discussions.post.lastResponse": "Última respuesta {time}",
"discussions.post.postedOn": "Publicado {time} por {author} {authorLabel}",
"discussions.post.contentReported": "Reportado",
"discussions.post.following": "Siguiendo",
"discussions.post.follow": "Seguir",
"discussions.post.followed": "Seguido",
"discussions.post.notFollowed": "No seguido",
"discussions.post.answered": "Una vez respondido",
"discussions.post.unFollow": "Dejar de seguir",
"discussions.post.like": "Como",
"discussions.post.removeLike": "A diferencia de",
"discussions.post.liked": "apreciado",
"discussions.post.likes": "gustos",
"discussions.post.viewActivity": "Ver actividad",
"discussions.post.activity": "Actividad ",
"discussions.post.closed": "Publicación cerrada por respuestas y comentarios.",
"discussions.post.relatedTo": "Relacionado con",
"discussions.editor.delete.post.title": "Eliminar mensaje",
"discussions.editor.delete.post.description": "¿Seguro que quieres eliminar esta publicación de forma permanente?",
"discussions.post.delete.confirmation.button.delete": "Borrar",
"discussions.editor.report.post.title": "¿Denunciar contenido inapropiado?",
"discussions.editor.report.post.description": "El equipo de moderación de debates revisará este contenido y tomará las medidas adecuadas.",
"discussions.post.closePostModal.title": "Cerrar publicación",
"discussions.post.closePostModal.text": "Introduce un motivo para cerrar esta publicación. Esto solo se mostrará a otros moderadores.",
"discussions.post.closePostModal.reasonCodeInput": "Razón",
"discussions.post.closePostModal.cancel": "Cancelar",
"discussions.post.closePostModal.confirm": "Cerrar publicación",
"discussions.post.label.new": "{count} Nuevo",
"discussions.post.editedBy": "Editado por",
"discussions.post.editReason": "Razón",
"discussions.post.postWithoutPreview": "No hay vista previa disponible",
"discussions.post.follow.description": "estás siguiendo esta publicación",
"discussions.post.unfollow.description": "no estas siguiendo esta publicación",
"discussions.topics.sort.message": "Ordenado por {sortBy}",
"discussions.topics.sort.lastActivity": "Actividad reciente",
"discussions.topics.sort.commentCount": "La mayoría de la actividad",
"discussions.topics.sort.courseStructure": "Estructura del curso",
"discussions.topics.unnamed.label": "Categoría sin nombre",
"discussions.subtopics.unnamed.label": "Subcategoría sin nombre",
"tour.action.advance": "Próximo",
"tour.action.dismiss": "Descartar",
"tour.action.end": "Ok",
"tour.body.notRespondedFilter": "Ahora puede filtrar debates para encontrar publicaciones sin respuesta.",
"tour.title.notRespondedFilter": "¡Nueva opción de filtrado!",
"tour.body.responseSortTour": "Las respuestas y los comentarios ahora se ordenan por los más recientes primero. Utilice esta opción para cambiar el orden de clasificación",
"tour.title.responseSortTour": "¡Ordenar respuestas!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "Material del curso",
"learn.course.tabs.navigation.overflow.menu": "Más...",
"discussions.topics.backAlt": "Volver a la lista de temas",
"discussions.topics.discussions": "{count, plural,\n =0 {Discussion}\n one {# Discussion}\n other {# Discussions}\n }",
"discussions.topics.questions": "{count, plural,\n =0 {Question}\n one {# Question}\n other {# Questions}\n }",
"discussions.topics.reported": "{reported} informado",
"discussions.topics.previouslyReported": "{previouslyReported} informado anteriormente",
"discussions.topics.find.label": "Buscar temas",
"discussions.topics.unnamed.section.label": "Sección sin nombre",
"discussions.topics.unnamed.subsection.label": "Subsección sin nombre",
"discussions.subtopics.unnamed.topic.label": "Tema sin nombre",
"discussions.topics.title": "No existe ningún tema",
"discussions.topics.createTopic": "Póngase en contacto con su administrador para crear un tema",
"discussions.topics.nothing": "Nada aquí todavía",
"discussions.topics.archived.label": "Archivado",
"discussions.learner.reported": "{reported} informado",
"discussions.learner.previouslyReported": "{previouslyReported} informado anteriormente",
"discussions.learner.lastLogin": "Último vez activo {lastActiveTime}",
"discussions.learner.loadMostLearners": "Cargar más",
"discussions.learner.back": "Volver",
"discussions.learner.activityForLearner": "Actividad para {username}",
"discussions.learner.mostActivity": "La mayoría de la actividad",
"discussions.learner.reportedActivity": "Actividad reportada",
"discussions.learner.recentActivity": "Actividad reciente",
"discussions.learner.sortFilterStatus": "Todos los alumnos por {sort, select, flagged {actividad notificada} activity {mayor actividad} other {{sort}} }",
"discussion.learner.allActivity": "Toda la actividad",
"discussion.learner.posts": "Publicación",
"discussions.actions.button.alt": "Menú de acciones",
"discussions.actions.copylink": "Copiar link",
"discussions.actions.edit": "Editar",
"discussions.actions.pin": "Marcar",
"discussions.actions.unpin": "Desmarcar",
"discussions.actions.delete": "Eliminar",
"discussions.confirmation.button.confirm": "Confirmar",
"discussions.actions.close": "Cerrar",
"discussions.actions.reopen": "Reabrir",
"discussions.actions.report": "Informar",
"discussions.actions.unreport": "No informar",
"discussions.actions.endorse": "Apoyar",
"discussions.actions.unendorse": "No apoyar",
"discussions.actions.markAnswered": "Marcar como respondida",
"discussions.actions.unMarkAnswered": "Desmarcar como respondida",
"discussions.modal.confirmation.button.cancel": "Cancelar",
"discussions.empty.allTopics": "Toda la actividad de debate de estos temas se mostrará aquí.",
"discussions.empty.allPosts": "Toda la actividad de debate sobre este tema se mostrará aquí.",
"discussions.empty.myPosts": "Las publicaciones con las que has interactuado se mostrarán aquí.",
"discussions.empty.topic": "Toda la actividad de debate sobre este tema se mostrará aquí.",
"discussions.empty.title": "Nada aquí todavía",
"discussions.empty.noPostSelected": "Ninguna publicación seleccionada",
"discussions.empty.noTopicSelected": "Ningún tema seleccionado",
"discussions.sidebar.noResultsFound": "No se han encontrado resultados",
"discussions.sidebar.differentKeywords": "Intente buscar diferentes palabras clave",
"discussions.sidebar.removeKeywords": "Intente buscar diferentes palabras clave o elimine algunos filtros.",
"discussions.sidebar.removeKeywordsOnly": "Intenta buscar diferentes palabras clave",
"discussions.sidebar.removeFilters": "Pruebe eliminar algunos filtros.",
"discussions.empty.iconAlt": "Vacío",
"discussions.authors.label.staff": "Equipo docente",
"discussions.authors.label.ta": "TA",
"discussions.learner.loadMostPosts": "Cargar más entradas",
"discussions.post.anonymous.author": "anónimo",
"discussion.banner.welcomeMessage": "🎉 ¡Bienvenido a la nueva y mejorada experiencia de debates!",
"discussion.banner.learnMore": "Aprende más",
"discussion.banner.shareFeedback": "Compartir comentarios",
"discussion.blackoutBanner.information": "El equipo del curso deshabilita temporalmente la publicación en discusiones",
"discussions.editor.image.warning.message": "Las imágenes que tengan un ancho o alto superior a 999 px no serán visibles cuando la publicación, la respuesta o el comentario se vean mediante debates en línea del curso.",
"discussions.editor.image.warning.title": "¡Advertencia!",
"discussions.editor.image.warning.dismiss": "Aceptar",
"discussions.navigation.breadcrumbMenu.allTopics": "Temas",
"discussions.navigation.breadcrumbMenu.showAll": "Mostrar todo",
"discussions.navigation.navigationBar.allPosts": "Todos los mensajes",
"discussions.navigation.navigationBar.allTopics": "Temas",
"discussions.navigation.navigationBar.myPosts": "Mis publicaciones",
"discussions.navigation.navigationBar.learners": "Estudiantes",
"discussions.comments.comment.addComment": "Añadir comentario",
"discussions.comments.comment.addResponse": "Agregar una respuesta",
"discussions.comments.comment.abuseFlaggedMessage": "Contenido informado para que el personal lo revise",
"discussions.actions.back.alt": "Volver a la lista",
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
"discussions.comments.comment.loadMoreComments": "Cargar más comentarios",
"discussions.comments.comment.loadMoreResponses": "Cargar más respuestas",
"discussions.comments.comment.visibility": "Esta publicación es visible para {group, select, null {Todos} other {{group}} }.",
"discussions.comments.comment.postedTime": "{postType, select,\n discussion {Discussion}\n question {Question}\n other {{postType}}\n } posted {relativeTime} by",
"discussions.comments.comment.commentTime": "Publicado {relativeTime}",
"discussions.comments.comment.answer": "Respuesta",
"discussions.comments.comment.answeredlabel": "Marcado como respondido por",
"discussions.comments.comment.endorsed": "respaldado",
"discussions.comments.comment.endorsedlabel": "Avalado por",
"discussions.actions.label": "Menú de acciones",
"discussions.editor.submit": "Enviar",
"discussions.editor.submitting": "Enviando",
"discussions.editor.cancel": "Cancelar",
"discussions.editor.error.empty": "El contenido de la publicación no puede estar vacío.",
"discussions.editor.delete.response.title": "Eliminar respuesta",
"discussions.editor.delete.response.description": "¿Está seguro de que desea eliminar esta respuesta de forma permanente?",
"discussions.editor.delete.comment.title": "Eliminar comentario",
"discussions.editor.delete.comment.description": "¿Estás seguro de que desea eliminar este comentario de forma permanente?",
"discussions.delete.confirmation.button.delete": "Eliminar",
"discussions.editor.response.response.title": "¿Denunciar contenido inapropiado?",
"discussions.editor.response.description": "El equipo de moderación de debates revisará este contenido y tomará las medidas adecuadas.",
"discussions.editor.report.comment.title": "¿Denunciar contenido inapropiado?",
"discussions.editor.report.comment.description": "El equipo de moderación de debates revisará este contenido y tomará las medidas adecuadas.",
"discussions.editor.comments.editReasonCode": "Motivo de la edición",
"discussions.editor.posts.editReasonCode.error": "Seleccione el motivo de la edición",
"discussions.comment.comments.editedBy": "Editado por",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "Motivo",
"discussions.post.closedBy": "Publicación cerrada por",
"discussion.comment.time": "hace {time}",
"discussion.thread.notFound": "Tema no encontrado",
"discussions.comment.sortFilterStatus": "{sort, select, false {Primero los más antiguos} true {Primero los más nuevos} other {{sort}} }",
"discussions.app.title": "Foros",
"discussions.posts.actionBar.searchAllPosts": "Buscar todas las entradas",
"discussions.posts.actionBar.search": "{page, select,\n topics {Search topics}\n posts {Search all posts}\n learners {Search learners}\n myPosts {Search all posts}\n other {{page}}\n }",
"discussions.actionBar.searchInfo": "Mostrando resultados {count} para \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "No se han encontrado resultados para \"{searchString}\". Mostrando resultados de {count} para \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "Buscando...",
"discussions.actionBar.clearSearch": "Borrar resultados",
"discussion.posts.actionBar.add": "Agregar una publicación",
"discussion.posts.actionBar.close": "Cerrar",
"discussions.post.editor.type": "Tipo de publicación",
"discussions.post.editor.addPostHeading": "Agregar una publicación",
"discussions.post.editor.editPostHeading": "Editar publicación",
"discussions.post.editor.typeDescription": "Las preguntas suscitan cuestiones que necesitan respuestas. Los debates permiten compartir ideas e iniciar conversaciones.",
"discussions.post.editor.required": "Obligatorio",
"discussions.post.editor.questionType": "Pregunta",
"discussions.post.editor.questionDescription": "Plantear problemas que necesitan respuestas",
"discussions.post.editor.discussionType": "Foro",
"discussions.post.editor.discussionDescription": "Comparte ideas e inicia conversaciones",
"discussions.post.editor.topicArea": "Tema",
"discussions.post.editor.topicAreaDescription": "Añadir tu comentario a un tema relevante ayuda a los demás a encontrarlo.",
"discussions.post.editor.cohortVisibility": "Visibilidad de la cohorte",
"discussions.post.editor.cohortVisibilityAllLearners": "Todos los estudiantes",
"discussions.post.editor.title": "Título de la publicación",
"discussions.post.editor.titleDescription": "Añadir un título claro y descriptivo para animar a la participación.",
"discussions.post.editor.title.error": "El título de la publicación no puede estar vacío.",
"discussions.post.editor.content.error": "El contenido de la publicación no puede estar vacío.",
"discussions.post.editor.questionText": "Su pregunta o idea (obligatorio)",
"discussions.post.editor.preview": "Vista previa",
"discussions.post.editor.followPost": "Seguir este post",
"discussions.post.editor.anonymousPost": "Publicar de forma anónima",
"discussions.post.editor.anonymousToPeersPost": "Publicar de forma anónima a los compañeros",
"discussions.editor.posts.editReasonCode": "Motivo de la edición",
"discussions.editor.posts.showPreview.button": "Mostrar vista previa",
"discussions.topic.noName.label": "Categoría sin nombre",
"discussions.subtopic.noName.label": "Subcategoría sin nombre",
"discussions.posts.filter.showALl": "Mostrar todo",
"discussions.posts.filter.discussions": "Foros",
"discussions.posts.filter.questions": "Preguntas",
"discussions.posts.filter.message": "Estado: {filterBy}",
"discussions.posts.status.filter.anyStatus": "Cualquier estado",
"discussions.posts.status.filter.unread": "No leído",
"discussions.posts.status.filter.following": "Siguiendo",
"discussions.posts.status.filter.reported": "Informado",
"discussions.posts.status.filter.unanswered": "Sin responder",
"discussions.posts.status.filter.unresponded": "Sin respuesta",
"discussions.posts.filter.myPosts": "Mis publicaciones",
"discussions.posts.filter.myDiscussions": "Mis debates",
"discussions.posts.filter.myQuestions": "Mis preguntas",
"discussions.posts.sort.message": "Ordenado por {sortBy}",
"discussions.posts.sort.lastActivity": "Actividad reciente",
"discussions.posts.sort.commentCount": "La mayoría de la actividad",
"discussions.posts.sort.voteCount": "La mayoría me gusta",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "anónimo",
"discussions.post.addResponse": "Agregar respuesta",
"discussions.post.lastResponse": "Última respuesta {time}",
"discussions.post.postedOn": "Publicado {time} por {author} {authorLabel}",
"discussions.post.contentReported": "Informado",
"discussions.post.following": "Siguiendo",
"discussions.post.follow": "Seguir",
"discussions.post.followed": "Seguido",
"discussions.post.notFollowed": "No seguido",
"discussions.post.answered": "Respondido",
"discussions.post.unFollow": "Dejar de seguir",
"discussions.post.like": "Me gusta",
"discussions.post.removeLike": "No me gusta",
"discussions.post.liked": "Me gustó",
"discussions.post.likes": "gustos",
"discussions.post.viewActivity": "Ver actividad",
"discussions.post.activity": "Actividad",
"discussions.post.closed": "Publicación cerrada por respuestas y comentarios.",
"discussions.post.relatedTo": "Relacionado con",
"discussions.editor.delete.post.title": "Eliminar publicación",
"discussions.editor.delete.post.description": "¿Seguro que quiere eliminar esta publicación de forma permanente?",
"discussions.post.delete.confirmation.button.delete": "Eliminar",
"discussions.editor.report.post.title": "¿Denunciar contenido inapropiado?",
"discussions.editor.report.post.description": "El equipo de moderación de debates revisará este contenido y tomará las medidas adecuadas.",
"discussions.post.closePostModal.title": "Cerrar publicación",
"discussions.post.closePostModal.text": "Introduce un motivo para cerrar esta publicación. Esto solo se mostrará a otros moderadores.",
"discussions.post.closePostModal.reasonCodeInput": "Motivo",
"discussions.post.closePostModal.cancel": "Cancelar",
"discussions.post.closePostModal.confirm": "Cerrar publicación",
"discussions.post.label.new": "{count} Nuevo",
"discussions.post.editedBy": "Editado por",
"discussions.post.editReason": "Motivo",
"discussions.post.postWithoutPreview": "No hay vista previa disponible",
"discussions.post.follow.description": "Está siguiendo esta publicación",
"discussions.post.unfollow.description": "No está siguiendo esta publicación",
"discussions.topics.sort.message": "Ordenado por {sortBy}",
"discussions.topics.sort.lastActivity": "Actividad reciente",
"discussions.topics.sort.commentCount": "La mayoría de la actividad",
"discussions.topics.sort.courseStructure": "Estructura del curso",
"discussions.topics.unnamed.label": "Categoría sin nombre",
"discussions.subtopics.unnamed.label": "Subcategoría sin nombre",
"tour.action.advance": "Siguiente",
"tour.action.dismiss": "Descartar",
"tour.action.end": "De acuerdo",
"tour.body.notRespondedFilter": "Ahora puede filtrar debates para encontrar publicaciones sin respuesta.",
"tour.title.notRespondedFilter": "¡Nueva opción de filtrado!",
"tour.body.responseSortTour": "Las respuestas y los comentarios ahora se ordenan por los más recientes primero. Utilice esta opción para cambiar el orden de clasificación",
"tour.title.responseSortTour": "¡Ordenar respuestas!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "منابع دوره آموزشی",
"learn.course.tabs.navigation.overflow.menu": "بیشتر...",
"discussions.topics.backAlt": "Back to topics list",
"discussions.topics.discussions": "{count, plural,\n =0 {Discussion}\n one {# Discussion}\n other {# Discussions}\n }",
"discussions.topics.questions": "{count, plural,\n =0 {Question}\n one {# Question}\n other {# Questions}\n }",
"discussions.topics.reported": "{reported} گزارش شده",
"discussions.topics.previouslyReported": "{previouslyReported} قبلا گزارش شده‌است",
"discussions.topics.find.label": "جستجوی موضوعات",
"discussions.topics.unnamed.section.label": "Unnamed Section",
"discussions.topics.unnamed.subsection.label": "Unnamed Subsection",
"discussions.subtopics.unnamed.topic.label": "Unnamed Topic",
"discussions.topics.title": "No topic exists",
"discussions.topics.createTopic": "Please contact you admin to create a topic",
"discussions.topics.nothing": "Nothing here yet",
"discussions.topics.archived.label": "آرشیو شده",
"discussions.learner.reported": "{reported} گزارش شده",
"discussions.learner.previouslyReported": "{previouslyReported} قبلا گزارش شده‌است",
"discussions.learner.lastLogin": "آخرین فعال {lastActiveTime}",
"discussions.learner.loadMostLearners": "بارگیری بیشتر",
"discussions.learner.back": "بازگشت",
"discussions.learner.activityForLearner": "فعالیت برای {username}",
"discussions.learner.mostActivity": "بیشترین فعالیت",
"discussions.learner.reportedActivity": "فعالیت گزارش‌شده",
"discussions.learner.recentActivity": "فعالیت اخیر",
"discussions.learner.sortFilterStatus": "All learners sorted by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
"discussion.learner.allActivity": "تمام فعالیت",
"discussion.learner.posts": "نوشته ها",
"discussions.actions.button.alt": "منوی فعالیت‌ها",
"discussions.actions.copylink": "کپی پیوند",
"discussions.actions.edit": " ویرایش",
"discussions.actions.pin": "نشانه",
"discussions.actions.unpin": "حذف نشانه",
"discussions.actions.delete": "حذف",
"discussions.confirmation.button.confirm": "Confirm",
"discussions.actions.close": "بستن",
"discussions.actions.reopen": "گشایش مجدد",
"discussions.actions.report": "گزارش‌",
"discussions.actions.unreport": "لغو گزارش",
"discussions.actions.endorse": "تأیید",
"discussions.actions.unendorse": "عدم تأیید",
"discussions.actions.markAnswered": "Mark as answered",
"discussions.actions.unMarkAnswered": "علامت را به‌عنوان پاسخ بردارید",
"discussions.modal.confirmation.button.cancel": "Cancel",
"discussions.empty.allTopics": "همه فعالیت‌های گفتگو برای این موضوعات در اینجا نشان داده می‌شود.",
"discussions.empty.allPosts": "همه فعالیت‌های گفتگو برای دوره آموزشی شما در اینجا نمایش داده می‌شود.",
"discussions.empty.myPosts": "مطالبی که با آنها تعامل داشته‌اید در اینجا نشان داده می‌شوند.",
"discussions.empty.topic": "همه فعالیت‌های بحث برای این موضوع در این قسمت نشان داده می‌شود.",
"discussions.empty.title": "اینجا هنوز چیزی نیست",
"discussions.empty.noPostSelected": "هیچ مطلبی انتخاب نشده‌است",
"discussions.empty.noTopicSelected": "هیچ موضوعی انتخاب نشده‌است",
"discussions.sidebar.noResultsFound": "هیچ نتیجه‌ای پیدا نشد",
"discussions.sidebar.differentKeywords": "کلیدواژه‌های دیگری استفاده کرده و دوباره جستجو کنید",
"discussions.sidebar.removeKeywords": "سعی کنید کلمات کلیدی مختلف را جستجو کنید یا برخی از فیلترها را حذف کنید",
"discussions.sidebar.removeKeywordsOnly": "کلیدواژه‌های دیگری استفاده کرده و دوباره جستجو کنید",
"discussions.sidebar.removeFilters": "سعی کنید برخی از فیلترها را حذف کنید",
"discussions.empty.iconAlt": "خالی",
"discussions.authors.label.staff": "کارکنان",
"discussions.authors.label.ta": "کمک مربی",
"discussions.learner.loadMostPosts": "بارگیری مطالب بیشتر",
"discussions.post.anonymous.author": "بی‌نام",
"discussion.banner.welcomeMessage": "🎉 به نسخه جدید و بهبود یافته discussions خوش اومدید!",
"discussion.banner.learnMore": "اطلاعات بیشتر",
"discussion.banner.shareFeedback": "اشتراک‌گذاری بازخورد",
"discussion.blackoutBanner.information": "Posting in discussions is temporarily disabled by the course team",
"discussions.editor.image.warning.message": "هنگامی که مطلب یا پاسخ یا نظری در گفتگوهای دوره به صورت درون‌خطی مشاهده می شود، تصاویری که عرض یا ارتفاع آنها بیشتر از 999 پیکسل باشد، قابل مشاهده نخواهند بود.",
"discussions.editor.image.warning.title": "هشدار!",
"discussions.editor.image.warning.dismiss": "بسیارخوب",
"discussions.navigation.breadcrumbMenu.allTopics": "عناوین ",
"discussions.navigation.breadcrumbMenu.showAll": "نمایش همه",
"discussions.navigation.navigationBar.allPosts": "همه مطالب",
"discussions.navigation.navigationBar.allTopics": "عناوین ",
"discussions.navigation.navigationBar.myPosts": "مطالب ارسالی من",
"discussions.navigation.navigationBar.learners": "یادگیرندگان",
"discussions.comments.comment.addComment": "Add comment",
"discussions.comments.comment.addResponse": "افزودن پاسخ",
"discussions.comments.comment.abuseFlaggedMessage": "محتوا برای بررسی کارکنان گزارش شده است",
"discussions.actions.back.alt": "Back to list",
"discussions.comments.comment.responseCount": "{num, plural,\n=0 {No responses}\none {Showing # response}\nother {Showing # responses}\n}",
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n=0 {No endorsed responses}\none {Showing # endorsed response}\nother {Showing # endorsed responses}\n}",
"discussions.comments.comment.loadMoreComments": "بارگیری نظرات بیشتر",
"discussions.comments.comment.loadMoreResponses": "بارگیری پاسخ‌های بیشتر",
"discussions.comments.comment.visibility": "این مطلب برای {group, select,\nnull {Everyone}\nother {{group}}\n}. قابل مشاهده است.",
"discussions.comments.comment.postedTime": "{postType, select,\ndiscussion {Discussion}\nquestion {Question}\nother {{postType}}\n} ارسال شده {relativeTime} توسط ",
"discussions.comments.comment.commentTime": "ارسال شده {relativeTime}",
"discussions.comments.comment.answer": "پاسخ",
"discussions.comments.comment.answeredlabel": "علامت‌گذاری شده به عنوان پاسخ داده‌شده توسط ",
"discussions.comments.comment.endorsed": "تاییدشده ",
"discussions.comments.comment.endorsedlabel": "تاییدشده توسط",
"discussions.actions.label": "منوی فعالیت‌ها",
"discussions.editor.submit": "ارسال",
"discussions.editor.submitting": "در حال ارسال",
"discussions.editor.cancel": "لغو",
"discussions.editor.error.empty": "محتوای مطلب نمی‌تواند خالی باشد.",
"discussions.editor.delete.response.title": "حذف پاسخ",
"discussions.editor.delete.response.description": "آیا از حذف این پاسخ برای همیشه اطمینان دارید؟",
"discussions.editor.delete.comment.title": "حذف نظر",
"discussions.editor.delete.comment.description": "آیا از حذف این نظر برای همیشه اطمینان دارید؟",
"discussions.delete.confirmation.button.delete": "حذف",
"discussions.editor.response.response.title": "Report inappropriate content?",
"discussions.editor.response.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.report.comment.title": "Report inappropriate content?",
"discussions.editor.report.comment.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.comments.editReasonCode": "علت ویرایش",
"discussions.editor.posts.editReasonCode.error": "انتخاب دلیل برای ویرایش",
"discussions.comment.comments.editedBy": "ویرایش‌شده به‌دست",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "علت",
"discussions.post.closedBy": "مطلب توسط این فرد بسته شده",
"discussion.comment.time": "{time} قبل",
"discussion.thread.notFound": "Thread not found",
"discussions.comment.sortFilterStatus": "{sort, select,\n false {Oldest first}\n true {Newest first}\n other {{sort}}\n }",
"discussions.app.title": "گفتگوها",
"discussions.posts.actionBar.searchAllPosts": "جستجوی همۀ مطالب ارسالی",
"discussions.posts.actionBar.search": "{page, select,\ntopics {Search topics}\nposts {Search all posts}\nlearners {Search learners}\nmyPosts {Search all posts}\nother {{page}}\n}",
"discussions.actionBar.searchInfo": "نمایش {count} نتایج برای \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "هیچ نتیجه ای برای \"{searchString}\" یافت نشد. نمایش نتایج {count} برای \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "در حال جستجو...",
"discussions.actionBar.clearSearch": "پاک‌کردن نتایج",
"discussion.posts.actionBar.add": "افزودن مطلب",
"discussion.posts.actionBar.close": "بستن",
"discussions.post.editor.type": "نوع مطلب ارسالی",
"discussions.post.editor.addPostHeading": "افزودن مطلب",
"discussions.post.editor.editPostHeading": "ویرایش مطلب",
"discussions.post.editor.typeDescription": "پرسش‌ها مسائلی را مطرح می‌کنند که نیاز به پاسخ دارند. گفتگوها ایده‌ها را به اشتراک می‌گذارند و مکالمه را شروع می‌کنند.",
"discussions.post.editor.required": "ضروری",
"discussions.post.editor.questionType": "سوال",
"discussions.post.editor.questionDescription": "مسائلی را مطرح کنید که نیاز به پاسخ دارند",
"discussions.post.editor.discussionType": "گفتگو",
"discussions.post.editor.discussionDescription": "ایده‌ها را به اشتراک بگذارید و گفتگو را شروع کنید",
"discussions.post.editor.topicArea": "محل موضوع",
"discussions.post.editor.topicAreaDescription": "مطلب ارسالی خود را به یک موضوع مرتبط اضافه کنید تا به دیگران در یافتن آن کمک کنید.",
"discussions.post.editor.cohortVisibility": "رؤیت‌پذیری انجمن",
"discussions.post.editor.cohortVisibilityAllLearners": "همه يادگيرندگان",
"discussions.post.editor.title": "ارسال عنوان",
"discussions.post.editor.titleDescription": "برای تشویق مشارکت سایرین، عنوانی واضح و توصیفی بیفزایید.",
"discussions.post.editor.title.error": "عنوان مطلب نمی‌تواند خالی باشد.",
"discussions.post.editor.content.error": "محتوای مطلب نمی‌تواند خالی باشد.",
"discussions.post.editor.questionText": "پرسش یا ایده شما (الزامی)",
"discussions.post.editor.preview": "پیش‌نمایش",
"discussions.post.editor.followPost": "این مطلب را دنبال می‌کنم",
"discussions.post.editor.anonymousPost": "به‎‌صورت ناشناس ارسال کنید",
"discussions.post.editor.anonymousToPeersPost": "به‌صورت ناشناس برای همتایان ارسال کنید",
"discussions.editor.posts.editReasonCode": "علت ویرایش",
"discussions.editor.posts.showPreview.button": "Show preview",
"discussions.topic.noName.label": "Unnamed category",
"discussions.subtopic.noName.label": "Unnamed subcategory",
"discussions.posts.filter.showALl": "نمایش همه",
"discussions.posts.filter.discussions": "گفتگوها",
"discussions.posts.filter.questions": "سوالات ",
"discussions.posts.filter.message": "وضعیت: {filterBy}",
"discussions.posts.status.filter.anyStatus": "هر وضعیتی",
"discussions.posts.status.filter.unread": "نخوانده",
"discussions.posts.status.filter.following": "دنبال می‌کنم",
"discussions.posts.status.filter.reported": "گزارش‌شده",
"discussions.posts.status.filter.unanswered": "پاسخ داده‌نشده",
"discussions.posts.status.filter.unresponded": "Not responded",
"discussions.posts.filter.myPosts": "مطالب ارسالی من",
"discussions.posts.filter.myDiscussions": "گفتگوهای من",
"discussions.posts.filter.myQuestions": "سوالات من",
"discussions.posts.sort.message": "منظم شده برحسب {sortBy}",
"discussions.posts.sort.lastActivity": "فعالیت اخیر",
"discussions.posts.sort.commentCount": "بیشترین فعالیت",
"discussions.posts.sort.voteCount": "بیشترین لایک",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "بی‌نام",
"discussions.post.addResponse": "Add response",
"discussions.post.lastResponse": "پاسخ اخیر {time}",
"discussions.post.postedOn": "ارسال شده {time}  به دست {author} {authorLabel}",
"discussions.post.contentReported": "گزارش‌شده",
"discussions.post.following": "دنبال می‌کنم",
"discussions.post.follow": "دنبال‌ می‌کنم",
"discussions.post.followed": "Followed",
"discussions.post.notFollowed": "Not Followed",
"discussions.post.answered": "پاسخ داده‌شده",
"discussions.post.unFollow": "دنبال ‌نمی‌کنم",
"discussions.post.like": "می‌پسندم",
"discussions.post.removeLike": "غیرمشابه",
"discussions.post.liked": "liked",
"discussions.post.likes": "likes",
"discussions.post.viewActivity": "مشاهده فعالیت",
"discussions.post.activity": "Activity",
"discussions.post.closed": "این مطلب برای پاسخ و نظر بسته است",
"discussions.post.relatedTo": "Related to",
"discussions.editor.delete.post.title": "حذف مطلب",
"discussions.editor.delete.post.description": "از حذف دایمی این مطلب اطمینان دارید؟",
"discussions.post.delete.confirmation.button.delete": "Delete",
"discussions.editor.report.post.title": "Report inappropriate content?",
"discussions.editor.report.post.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.post.closePostModal.title": "بستن مطلب",
"discussions.post.closePostModal.text": "دلیل بستن این مطلب را وارد کنید. این دلیل فقط برای مدیران دیگر نمایش داده می‌شود.",
"discussions.post.closePostModal.reasonCodeInput": "دلیل",
"discussions.post.closePostModal.cancel": "لغو",
"discussions.post.closePostModal.confirm": "بستن مطلب",
"discussions.post.label.new": "{count} جدید",
"discussions.post.editedBy": "ویرایش‌شده به دست",
"discussions.post.editReason": "علت",
"discussions.post.postWithoutPreview": "پیش‌نمایش در دسترس نیست",
"discussions.post.follow.description": "you are following this post",
"discussions.post.unfollow.description": "you are not following this post",
"discussions.topics.sort.message": "منظم برحسب {sortBy}",
"discussions.topics.sort.lastActivity": "فعالیت اخیر",
"discussions.topics.sort.commentCount": "بیشترین فعالیت",
"discussions.topics.sort.courseStructure": "ساختار دوره آموزشی",
"discussions.topics.unnamed.label": "Unnamed category",
"discussions.subtopics.unnamed.label": "Unnamed subcategory",
"tour.action.advance": "Next",
"tour.action.dismiss": "Dismiss",
"tour.action.end": "Okay",
"tour.body.notRespondedFilter": "Now you can filter discussions to find posts with no response.",
"tour.title.notRespondedFilter": "New filtering option!",
"tour.body.responseSortTour": "Responses and comments are now sorted by newest first. Please use this option to change the sort order",
"tour.title.responseSortTour": "Sort Responses!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "Course Material",
"learn.course.tabs.navigation.overflow.menu": "More...",
"discussions.topics.backAlt": "Back to topics list",
"discussions.topics.discussions": "{count, plural,\n =0 {Discussion}\n one {# Discussion}\n other {# Discussions}\n }",
"discussions.topics.questions": "{count, plural,\n =0 {Question}\n one {# Question}\n other {# Questions}\n }",
"discussions.topics.reported": "{reported} reported",
"discussions.topics.previouslyReported": "{previouslyReported} previously reported",
"discussions.topics.find.label": "Search topics",
"discussions.topics.unnamed.section.label": "Unnamed Section",
"discussions.topics.unnamed.subsection.label": "Unnamed Subsection",
"discussions.subtopics.unnamed.topic.label": "Unnamed Topic",
"discussions.topics.title": "No topic exists",
"discussions.topics.createTopic": "Please contact you admin to create a topic",
"discussions.topics.nothing": "Nothing here yet",
"discussions.topics.archived.label": "Archived",
"discussions.learner.reported": "{reported} reported",
"discussions.learner.previouslyReported": "{previouslyReported} previously reported",
"discussions.learner.lastLogin": "Last active {lastActiveTime}",
"discussions.learner.loadMostLearners": "Load more",
"discussions.learner.back": "Back",
"discussions.learner.activityForLearner": "Activity for {username}",
"discussions.learner.mostActivity": "Most activity",
"discussions.learner.reportedActivity": "Reported activity",
"discussions.learner.recentActivity": "Recent activity",
"discussions.learner.sortFilterStatus": "All learners sorted by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
"discussion.learner.allActivity": "All activity",
"discussion.learner.posts": "Posts",
"discussions.actions.button.alt": "Actions menu",
"discussions.actions.copylink": "Copy link",
"discussions.actions.edit": "Edit",
"discussions.actions.pin": "Pin",
"discussions.actions.unpin": "Unpin",
"discussions.actions.delete": "Delete",
"discussions.confirmation.button.confirm": "Confirm",
"discussions.actions.close": "Close",
"discussions.actions.reopen": "Reopen",
"discussions.actions.report": "Report",
"discussions.actions.unreport": "Unreport",
"discussions.actions.endorse": "Endorse",
"discussions.actions.unendorse": "Unendorse",
"discussions.actions.markAnswered": "Mark as answered",
"discussions.actions.unMarkAnswered": "Unmark as answered",
"discussions.modal.confirmation.button.cancel": "Cancel",
"discussions.empty.allTopics": "All discussion activity for these topics will show up here.",
"discussions.empty.allPosts": "All discussion activity for your course will show up here.",
"discussions.empty.myPosts": "Posts you've interacted with will show up here.",
"discussions.empty.topic": "All discussion activity for this topic will show up here.",
"discussions.empty.title": "Nothing here yet",
"discussions.empty.noPostSelected": "No post selected",
"discussions.empty.noTopicSelected": "No topic selected",
"discussions.sidebar.noResultsFound": "No results found",
"discussions.sidebar.differentKeywords": "Try searching different keywords",
"discussions.sidebar.removeKeywords": "Try searching different keywords or removing some filters",
"discussions.sidebar.removeKeywordsOnly": "Try searching different keywords",
"discussions.sidebar.removeFilters": "Try removing some filters",
"discussions.empty.iconAlt": "Empty",
"discussions.authors.label.staff": "Staff",
"discussions.authors.label.ta": "TA",
"discussions.learner.loadMostPosts": "Load more posts",
"discussions.post.anonymous.author": "anonymous",
"discussion.banner.welcomeMessage": "🎉 Welcome to the new and improved discussions experience!",
"discussion.banner.learnMore": "Learn more",
"discussion.banner.shareFeedback": "Share feedback",
"discussion.blackoutBanner.information": "Posting in discussions is temporarily disabled by the course team",
"discussions.editor.image.warning.message": "Images having width or height greater than 999px will not be visible when the post, response or comment is viewed using in-line course discussions",
"discussions.editor.image.warning.title": "Warning!",
"discussions.editor.image.warning.dismiss": "Ok",
"discussions.navigation.breadcrumbMenu.allTopics": "Topics",
"discussions.navigation.breadcrumbMenu.showAll": "Show all",
"discussions.navigation.navigationBar.allPosts": "All posts",
"discussions.navigation.navigationBar.allTopics": "Topics",
"discussions.navigation.navigationBar.myPosts": "My posts",
"discussions.navigation.navigationBar.learners": "Learners",
"discussions.comments.comment.addComment": "Add comment",
"discussions.comments.comment.addResponse": "Add a response",
"discussions.comments.comment.abuseFlaggedMessage": "Content reported for staff to review",
"discussions.actions.back.alt": "Back to list",
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
"discussions.comments.comment.loadMoreComments": "Load more comments",
"discussions.comments.comment.loadMoreResponses": "Load more responses",
"discussions.comments.comment.visibility": "This post is visible to {group, select,\n null {Everyone}\n other {{group}}\n }.",
"discussions.comments.comment.postedTime": "{postType, select,\n discussion {Discussion}\n question {Question}\n other {{postType}}\n } posted {relativeTime} by",
"discussions.comments.comment.commentTime": "Posted {relativeTime}",
"discussions.comments.comment.answer": "Answer",
"discussions.comments.comment.answeredlabel": "Marked as answered by",
"discussions.comments.comment.endorsed": "Endorsed",
"discussions.comments.comment.endorsedlabel": "Endorsed by",
"discussions.actions.label": "Actions menu",
"discussions.editor.submit": "Submit",
"discussions.editor.submitting": "Submitting",
"discussions.editor.cancel": "Cancel",
"discussions.editor.error.empty": "Post content cannot be empty.",
"discussions.editor.delete.response.title": "Delete response",
"discussions.editor.delete.response.description": "Are you sure you want to permanently delete this response?",
"discussions.editor.delete.comment.title": "Delete comment",
"discussions.editor.delete.comment.description": "Are you sure you want to permanently delete this comment?",
"discussions.delete.confirmation.button.delete": "Delete",
"discussions.editor.response.response.title": "Report inappropriate content?",
"discussions.editor.response.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.report.comment.title": "Report inappropriate content?",
"discussions.editor.report.comment.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.comments.editReasonCode": "Reason for editing",
"discussions.editor.posts.editReasonCode.error": "Select reason for editing",
"discussions.comment.comments.editedBy": "Edited by",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "Reason",
"discussions.post.closedBy": "Post closed by",
"discussion.comment.time": "{time} ago",
"discussion.thread.notFound": "Thread not found",
"discussions.comment.sortFilterStatus": "{sort, select,\n false {Oldest first}\n true {Newest first}\n other {{sort}}\n }",
"discussions.app.title": "Discussions",
"discussions.posts.actionBar.searchAllPosts": "Search all posts",
"discussions.posts.actionBar.search": "{page, select,\n topics {Search topics}\n posts {Search all posts}\n learners {Search learners}\n myPosts {Search all posts}\n other {{page}}\n }",
"discussions.actionBar.searchInfo": "Showing {count} results for \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "No results found for \"{searchString}\". Showing {count} results for \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "Searching...",
"discussions.actionBar.clearSearch": "Clear results",
"discussion.posts.actionBar.add": "Add a post",
"discussion.posts.actionBar.close": "Close",
"discussions.post.editor.type": "Post type",
"discussions.post.editor.addPostHeading": "Add a post",
"discussions.post.editor.editPostHeading": "Edit post",
"discussions.post.editor.typeDescription": "Questions raise issues that need answers. Discussions share ideas and start conversations.",
"discussions.post.editor.required": "Required",
"discussions.post.editor.questionType": "Question",
"discussions.post.editor.questionDescription": "Raise issues that need answers",
"discussions.post.editor.discussionType": "Discussion",
"discussions.post.editor.discussionDescription": "Share ideas and start conversations",
"discussions.post.editor.topicArea": "Topic area",
"discussions.post.editor.topicAreaDescription": "Add your post to a relevant topic to help others find it.",
"discussions.post.editor.cohortVisibility": "Cohort visibility",
"discussions.post.editor.cohortVisibilityAllLearners": "All learners",
"discussions.post.editor.title": "Post title",
"discussions.post.editor.titleDescription": "Add a clear and descriptive title to encourage participation.",
"discussions.post.editor.title.error": "Post title cannot be empty.",
"discussions.post.editor.content.error": "Post content cannot be empty.",
"discussions.post.editor.questionText": "Your question or idea (required)",
"discussions.post.editor.preview": "Preview",
"discussions.post.editor.followPost": "Follow this post",
"discussions.post.editor.anonymousPost": "Post anonymously",
"discussions.post.editor.anonymousToPeersPost": "Post anonymously to peers",
"discussions.editor.posts.editReasonCode": "Reason for editing",
"discussions.editor.posts.showPreview.button": "Show preview",
"discussions.topic.noName.label": "Unnamed category",
"discussions.subtopic.noName.label": "Unnamed subcategory",
"discussions.posts.filter.showALl": "Show all",
"discussions.posts.filter.discussions": "Discussions",
"discussions.posts.filter.questions": "Questions",
"discussions.posts.filter.message": "Status: {filterBy}",
"discussions.posts.status.filter.anyStatus": "Any status",
"discussions.posts.status.filter.unread": "Unread",
"discussions.posts.status.filter.following": "Following",
"discussions.posts.status.filter.reported": "Reported",
"discussions.posts.status.filter.unanswered": "Unanswered",
"discussions.posts.status.filter.unresponded": "Not responded",
"discussions.posts.filter.myPosts": "My posts",
"discussions.posts.filter.myDiscussions": "My discussions",
"discussions.posts.filter.myQuestions": "My questions",
"discussions.posts.sort.message": "Sorted by {sortBy}",
"discussions.posts.sort.lastActivity": "Recent activity",
"discussions.posts.sort.commentCount": "Most activity",
"discussions.posts.sort.voteCount": "Most likes",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "anonymous",
"discussions.post.addResponse": "Add response",
"discussions.post.lastResponse": "Last response {time}",
"discussions.post.postedOn": "Posted {time} by {author} {authorLabel}",
"discussions.post.contentReported": "Reported",
"discussions.post.following": "Following",
"discussions.post.follow": "Follow",
"discussions.post.followed": "Followed",
"discussions.post.notFollowed": "Not Followed",
"discussions.post.answered": "Answered",
"discussions.post.unFollow": "Unfollow",
"discussions.post.like": "Like",
"discussions.post.removeLike": "Unlike",
"discussions.post.liked": "liked",
"discussions.post.likes": "likes",
"discussions.post.viewActivity": "View activity",
"discussions.post.activity": "Activity",
"discussions.post.closed": "Post closed for responses and comments",
"discussions.post.relatedTo": "Related to",
"discussions.editor.delete.post.title": "Delete post",
"discussions.editor.delete.post.description": "Are you sure you want to permanently delete this post?",
"discussions.post.delete.confirmation.button.delete": "Delete",
"discussions.editor.report.post.title": "Report inappropriate content?",
"discussions.editor.report.post.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.post.closePostModal.title": "Close post",
"discussions.post.closePostModal.text": "Enter a reason for closing this post. This will only be displayed to other moderators.",
"discussions.post.closePostModal.reasonCodeInput": "Reason",
"discussions.post.closePostModal.cancel": "Cancel",
"discussions.post.closePostModal.confirm": "Close post",
"discussions.post.label.new": "{count} New",
"discussions.post.editedBy": "Edited by",
"discussions.post.editReason": "Reason",
"discussions.post.postWithoutPreview": "No preview available",
"discussions.post.follow.description": "you are following this post",
"discussions.post.unfollow.description": "you are not following this post",
"discussions.topics.sort.message": "Sorted by {sortBy}",
"discussions.topics.sort.lastActivity": "Recent activity",
"discussions.topics.sort.commentCount": "Most activity",
"discussions.topics.sort.courseStructure": "Course Structure",
"discussions.topics.unnamed.label": "Unnamed category",
"discussions.subtopics.unnamed.label": "Unnamed subcategory",
"tour.action.advance": "Next",
"tour.action.dismiss": "Dismiss",
"tour.action.end": "Okay",
"tour.body.notRespondedFilter": "Now you can filter discussions to find posts with no response.",
"tour.title.notRespondedFilter": "New filtering option!",
"tour.body.responseSortTour": "Responses and comments are now sorted by newest first. Please use this option to change the sort order",
"tour.title.responseSortTour": "Sort Responses!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "Material do Curso",
"learn.course.tabs.navigation.overflow.menu": "Mais...",
"discussions.topics.backAlt": "Voltar à lista de tópicos",
"discussions.topics.discussions": "{count, plural,\n =0 {Discussão}\n one {# Discussão}\n other {# Discussões}\n}",
"discussions.topics.questions": "{count, plural,\n =0 {Questão}\n one {# Questão}\n other {# Questões}\n}",
"discussions.topics.reported": "{reported} reportado",
"discussions.topics.previouslyReported": "{previouslyReported} anteriormente reportado",
"discussions.topics.find.label": "Pesquisar tópicos",
"discussions.topics.unnamed.section.label": "Secção sem nome",
"discussions.topics.unnamed.subsection.label": "Subsecção sem nome",
"discussions.subtopics.unnamed.topic.label": "Tópico sem nome",
"discussions.topics.title": "Não existe nenhum tópico",
"discussions.topics.createTopic": "Contacte o seu administrador para criar um tópico",
"discussions.topics.nothing": "Ainda não há aqui nada",
"discussions.topics.archived.label": "Arquivado",
"discussions.learner.reported": "{reported} reportado",
"discussions.learner.previouslyReported": "{previouslyReported} anteriormente reportado",
"discussions.learner.lastLogin": "Último activo {lastActiveTime}",
"discussions.learner.loadMostLearners": "Carregar mais",
"discussions.learner.back": "Voltar",
"discussions.learner.activityForLearner": "Actividade para {username}",
"discussions.learner.mostActivity": "Maior actividade",
"discussions.learner.reportedActivity": "Actividade comunicada",
"discussions.learner.recentActivity": "Actividade recente",
"discussions.learner.sortFilterStatus": "Todos os alunos ordenados por {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
"discussion.learner.allActivity": "Toda a actividade",
"discussion.learner.posts": "Publicações",
"discussions.actions.button.alt": "Menu de acções",
"discussions.actions.copylink": "Copiar ligação",
"discussions.actions.edit": "Editar",
"discussions.actions.pin": "Fixar",
"discussions.actions.unpin": "Desafixar",
"discussions.actions.delete": "Eliminar",
"discussions.confirmation.button.confirm": "Confirmar",
"discussions.actions.close": "Fechar",
"discussions.actions.reopen": "Reabrir",
"discussions.actions.report": "Relatório",
"discussions.actions.unreport": "Retirar denuncia",
"discussions.actions.endorse": "Apoiar",
"discussions.actions.unendorse": "Não apoiar",
"discussions.actions.markAnswered": "Marcar como respondido",
"discussions.actions.unMarkAnswered": "Desmarcar como respondido",
"discussions.modal.confirmation.button.cancel": "Cancelar",
"discussions.empty.allTopics": "Toda a actividade de fórum destes tópicos aparecerá aqui.",
"discussions.empty.allPosts": "Todas as actividades de debate do fórum do seu curso serão apresentadas aqui.",
"discussions.empty.myPosts": "As mensagens com as quais interagiu aparecerão aqui.",
"discussions.empty.topic": "Toda a actividade de debate do fórum deste tópico aparecerá aqui.",
"discussions.empty.title": "Ainda não há aqui nada",
"discussions.empty.noPostSelected": "Nenhuma mensagem seleccionada",
"discussions.empty.noTopicSelected": "Nenhum tópico seleccionado",
"discussions.sidebar.noResultsFound": "Sem resultados",
"discussions.sidebar.differentKeywords": "Tente pesquisar palavras-chave diferentes",
"discussions.sidebar.removeKeywords": "Tente pesquisar palavras-chave diferentes ou remover alguns filtros",
"discussions.sidebar.removeKeywordsOnly": "Tente pesquisar palavras-chave diferentes",
"discussions.sidebar.removeFilters": "Tente remover alguns filtros",
"discussions.empty.iconAlt": "Vazio",
"discussions.authors.label.staff": "Equipa",
"discussions.authors.label.ta": "TA",
"discussions.learner.loadMostPosts": "Carregar mais mensagens",
"discussions.post.anonymous.author": "anónimo",
"discussion.banner.welcomeMessage": "🎉 Bem-vindo à nova e melhorada experiência de debate!",
"discussion.banner.learnMore": "Saber mais",
"discussion.banner.shareFeedback": "Partilhar comentários",
"discussion.blackoutBanner.information": "O envio de mensagens para os debates está temporariamente desactivado pela equipa de curso",
"discussions.editor.image.warning.message": "As imagens com largura ou altura superior a 999px não serão visíveis quando a mensagem, resposta ou comentário for visualizado utilizando os debates em linha da disciplina",
"discussions.editor.image.warning.title": "Aviso!",
"discussions.editor.image.warning.dismiss": "Ok",
"discussions.navigation.breadcrumbMenu.allTopics": "Tópicos",
"discussions.navigation.breadcrumbMenu.showAll": "Mostrar tudo",
"discussions.navigation.navigationBar.allPosts": "Todas as mensagens",
"discussions.navigation.navigationBar.allTopics": "Tópicos",
"discussions.navigation.navigationBar.myPosts": "As minhas mensagens",
"discussions.navigation.navigationBar.learners": "Estudantes",
"discussions.comments.comment.addComment": "Adicionar comentário",
"discussions.comments.comment.addResponse": "Adicionar uma resposta",
"discussions.comments.comment.abuseFlaggedMessage": "Conteúdo comunicado para análise pelo pessoal",
"discussions.actions.back.alt": "Voltar à lista",
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {Sem respostas}\n one {Mostrar # resposta}\n other {Mostrar # respostas}\n}",
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {Nenhuma resposta aprovada}\n one {Mostrar # resposta aprovada}\n other {Mostrar # respostas aprovadas}\n}",
"discussions.comments.comment.loadMoreComments": "Carregar mais comentários",
"discussions.comments.comment.loadMoreResponses": "Carregar mais respostas",
"discussions.comments.comment.visibility": "Esta publicação é visível para {group, select,\n null {Todos}\n other {{group}}\n}.",
"discussions.comments.comment.postedTime": "{postType, select,\n discussion {Discussão}\n question {Questão}\n other {{postType}}\n} registada {relativeTime} by",
"discussions.comments.comment.commentTime": "Publicado {relativeTime}",
"discussions.comments.comment.answer": "Resposta",
"discussions.comments.comment.answeredlabel": "Marcado como respondido por",
"discussions.comments.comment.endorsed": "Aprovado",
"discussions.comments.comment.endorsedlabel": "Aprovado por",
"discussions.actions.label": "Menu de acções",
"discussions.editor.submit": "Submeter",
"discussions.editor.submitting": "A submeter",
"discussions.editor.cancel": "Cancelar",
"discussions.editor.error.empty": "O conteúdo do post não pode estar vazio.",
"discussions.editor.delete.response.title": "Eliminar resposta",
"discussions.editor.delete.response.description": "Tem a certeza de que pretende apagar permanentemente esta resposta?",
"discussions.editor.delete.comment.title": "Eliminar comentário",
"discussions.editor.delete.comment.description": "Tem a certeza de que pretende apagar permanentemente este comentário?",
"discussions.delete.confirmation.button.delete": "Eliminar",
"discussions.editor.response.response.title": "Comunicar conteúdo inadequado?",
"discussions.editor.response.description": "A equipa de moderação do debate analisará este conteúdo e tomará as medidas adequadas.",
"discussions.editor.report.comment.title": "Comunicar conteúdo inadequado?",
"discussions.editor.report.comment.description": "A equipa de moderação do debate analisará este conteúdo e tomará as medidas adequadas.",
"discussions.editor.comments.editReasonCode": "Motivo da edição",
"discussions.editor.posts.editReasonCode.error": "Seleccionar o motivo da edição",
"discussions.comment.comments.editedBy": "Editado por",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "Razão",
"discussions.post.closedBy": "Mensagem fechada por",
"discussion.comment.time": "{time} atrás",
"discussion.thread.notFound": "Thread não encontrado",
"discussions.comment.sortFilterStatus": "{sort, select,\n false {O mais velho primeiro}\n true {Mais recente primeiro}\n other {{sort}}\n}",
"discussions.app.title": "Debates",
"discussions.posts.actionBar.searchAllPosts": "Pesquisar publicações",
"discussions.posts.actionBar.search": "{page, select,\n topics {Tópicos de pesquisa}\n posts {Pesquisar todas as mensagens}\n learners {Pesquisar estudantes}\n myPosts {Pesquisar todas as minhas mensagens}\n other {{page}}\n}",
"discussions.actionBar.searchInfo": "Exibindo {count} resultados para \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "Não foram encontrados resultados para \"{searchString}\". A mostrar {count} resultados para \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "A procurar...",
"discussions.actionBar.clearSearch": "Limpar resultados",
"discussion.posts.actionBar.add": "Adicionar uma mensagem",
"discussion.posts.actionBar.close": "Fechar",
"discussions.post.editor.type": "Tipo de publicação",
"discussions.post.editor.addPostHeading": "Adicionar uma mensagem",
"discussions.post.editor.editPostHeading": "Editar mensagem",
"discussions.post.editor.typeDescription": "As perguntas levantam questões que precisam de uma resposta. Os debates partilham ideias e iniciam conversas.",
"discussions.post.editor.required": "Obrigatório",
"discussions.post.editor.questionType": "Questão",
"discussions.post.editor.questionDescription": "Levantar questões que precisam de resposta",
"discussions.post.editor.discussionType": "Fórum",
"discussions.post.editor.discussionDescription": "Partilhar ideias e iniciar conversas",
"discussions.post.editor.topicArea": "Área do tópico",
"discussions.post.editor.topicAreaDescription": "Adicione a sua mensagem a um tópico relevante para ajudar os outros a encontrá-la.",
"discussions.post.editor.cohortVisibility": "Visibilidade da coorte",
"discussions.post.editor.cohortVisibilityAllLearners": "Todos os estudantes",
"discussions.post.editor.title": "Título da mensagem",
"discussions.post.editor.titleDescription": "Adicionar um título claro e descritivo para incentivar a participação.",
"discussions.post.editor.title.error": "O título da mensagem não pode estar vazio.",
"discussions.post.editor.content.error": "O conteúdo do post não pode estar vazio.",
"discussions.post.editor.questionText": "A sua pergunta ou ideia (obrigatório)",
"discussions.post.editor.preview": "Pré-visualizar",
"discussions.post.editor.followPost": "Siga esta mensagem",
"discussions.post.editor.anonymousPost": "Publicar anonimamente",
"discussions.post.editor.anonymousToPeersPost": "Enviar mensagens anónimas aos seus pares",
"discussions.editor.posts.editReasonCode": "Motivo da edição",
"discussions.editor.posts.showPreview.button": "Mostrar pré-visualização",
"discussions.topic.noName.label": "Categoria sem nome",
"discussions.subtopic.noName.label": "Subcategoria sem nome",
"discussions.posts.filter.showALl": "Mostrar tudo",
"discussions.posts.filter.discussions": "Debates",
"discussions.posts.filter.questions": "Perguntas",
"discussions.posts.filter.message": "Estado: {filterBy}",
"discussions.posts.status.filter.anyStatus": "Qualquer estado",
"discussions.posts.status.filter.unread": "Não lido",
"discussions.posts.status.filter.following": "Seguir",
"discussions.posts.status.filter.reported": "Denunciado",
"discussions.posts.status.filter.unanswered": "Sem resposta",
"discussions.posts.status.filter.unresponded": "Não respondeu",
"discussions.posts.filter.myPosts": "As minhas mensagens",
"discussions.posts.filter.myDiscussions": "Os meus debates",
"discussions.posts.filter.myQuestions": "As minhas perguntas",
"discussions.posts.sort.message": "Ordenado por {sortBy}",
"discussions.posts.sort.lastActivity": "Actividade recente",
"discussions.posts.sort.commentCount": "Maior actividade",
"discussions.posts.sort.voteCount": "Mais gostos",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {Todos}\n true {Próprio}\n other {{own}}\n} {status, select,\n statusAll {}\n statusUnread {não lido}\n statusFollowing {seguindo}\n statusReported {comunicado}\n statusUnanswered {sem resposta}\n statusUnresponded {não respondido}\n other {{status}}\n} {type, select,\n discussion {debates}\n question {perguntas}\n all {todas as mensagens}\n other {{type}}\n} {cohortType, select,\n all {}\n group {em {cohort}}\n other {{cohortType}}\n} sorted by {sort, select,\n lastActivityAt {actividade recente}\n commentCount {maior actividade}\n voteCount {mais gostos}\n other {{sort}}\n}",
"discussions.post.author.anonymous": "anónimo",
"discussions.post.addResponse": "Adicionar resposta",
"discussions.post.lastResponse": "Última resposta {time}",
"discussions.post.postedOn": "Publicado {time} por {author} {authorLabel}",
"discussions.post.contentReported": "Denunciado",
"discussions.post.following": "Seguir",
"discussions.post.follow": "Seguir",
"discussions.post.followed": "Seguido",
"discussions.post.notFollowed": "Não seguido",
"discussions.post.answered": "Respondido",
"discussions.post.unFollow": "Deixar de Seguir",
"discussions.post.like": "Gosto da mensagem",
"discussions.post.removeLike": "Não gostei da mensagem",
"discussions.post.liked": "gostei",
"discussions.post.likes": "gostos",
"discussions.post.viewActivity": "Ver actividade",
"discussions.post.activity": "Atividade",
"discussions.post.closed": "Post fechado para respostas e comentários",
"discussions.post.relatedTo": "Relacionado com",
"discussions.editor.delete.post.title": "Eliminar mensagem",
"discussions.editor.delete.post.description": "Tem a certeza de que pretende apagar permanentemente esta mensagem?",
"discussions.post.delete.confirmation.button.delete": "Eliminar",
"discussions.editor.report.post.title": "Comunicar conteúdo inadequado?",
"discussions.editor.report.post.description": "A equipa de moderação do debate analisará este conteúdo e tomará as medidas adequadas.",
"discussions.post.closePostModal.title": "Fechar mensagem",
"discussions.post.closePostModal.text": "Introduza um motivo para encerrar esta mensagem. Isto só será exibido para outros moderadores.",
"discussions.post.closePostModal.reasonCodeInput": "Razão",
"discussions.post.closePostModal.cancel": "Cancelar",
"discussions.post.closePostModal.confirm": "Fechar mensagem",
"discussions.post.label.new": "{count} Novo",
"discussions.post.editedBy": "Editado por",
"discussions.post.editReason": "Razão",
"discussions.post.postWithoutPreview": "Não há pré-visualização disponível",
"discussions.post.follow.description": "está a seguir esta mensagem",
"discussions.post.unfollow.description": "não está a seguir este post",
"discussions.topics.sort.message": "Ordenado por {sortBy}",
"discussions.topics.sort.lastActivity": "Actividade recente",
"discussions.topics.sort.commentCount": "Maior actividade",
"discussions.topics.sort.courseStructure": "Estrutura do Curso",
"discussions.topics.unnamed.label": "Categoria sem nome",
"discussions.subtopics.unnamed.label": "Subcategoria sem nome",
"tour.action.advance": "Seguinte",
"tour.action.dismiss": "Ignorar",
"tour.action.end": "OK",
"tour.body.notRespondedFilter": "Agora pode filtrar discussões para encontrar mensagens sem resposta.",
"tour.title.notRespondedFilter": "Nova opção de filtragem!",
"tour.body.responseSortTour": "As respostas e os comentários estão agora ordenados pelo mais recente primeiro. Utilize esta opção para alterar a ordem de ordenação",
"tour.title.responseSortTour": "Ordenar respostas!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "Course Material",
"learn.course.tabs.navigation.overflow.menu": "More...",
"discussions.topics.backAlt": "Back to topics list",
"discussions.topics.discussions": "{count, plural,\n =0 {Discussion}\n one {# Discussion}\n other {# Discussions}\n }",
"discussions.topics.questions": "{count, plural,\n =0 {Question}\n one {# Question}\n other {# Questions}\n }",
"discussions.topics.reported": "{reported} reported",
"discussions.topics.previouslyReported": "{previouslyReported} previously reported",
"discussions.topics.find.label": "Search topics",
"discussions.topics.unnamed.section.label": "Unnamed Section",
"discussions.topics.unnamed.subsection.label": "Unnamed Subsection",
"discussions.subtopics.unnamed.topic.label": "Unnamed Topic",
"discussions.topics.title": "No topic exists",
"discussions.topics.createTopic": "Please contact you admin to create a topic",
"discussions.topics.nothing": "Nothing here yet",
"discussions.topics.archived.label": "Archived",
"discussions.learner.reported": "{reported} reported",
"discussions.learner.previouslyReported": "{previouslyReported} previously reported",
"discussions.learner.lastLogin": "Last active {lastActiveTime}",
"discussions.learner.loadMostLearners": "Load more",
"discussions.learner.back": "Back",
"discussions.learner.activityForLearner": "Activity for {username}",
"discussions.learner.mostActivity": "Most activity",
"discussions.learner.reportedActivity": "Reported activity",
"discussions.learner.recentActivity": "Recent activity",
"discussions.learner.sortFilterStatus": "All learners sorted by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
"discussion.learner.allActivity": "All activity",
"discussion.learner.posts": "Posts",
"discussions.actions.button.alt": "Actions menu",
"discussions.actions.copylink": "Copy link",
"discussions.actions.edit": "Edit",
"discussions.actions.pin": "Pin",
"discussions.actions.unpin": "Unpin",
"discussions.actions.delete": "Delete",
"discussions.confirmation.button.confirm": "Confirm",
"discussions.actions.close": "Close",
"discussions.actions.reopen": "Reopen",
"discussions.actions.report": "Report",
"discussions.actions.unreport": "Unreport",
"discussions.actions.endorse": "Endorse",
"discussions.actions.unendorse": "Unendorse",
"discussions.actions.markAnswered": "Mark as answered",
"discussions.actions.unMarkAnswered": "Unmark as answered",
"discussions.modal.confirmation.button.cancel": "Cancel",
"discussions.empty.allTopics": "All discussion activity for these topics will show up here.",
"discussions.empty.allPosts": "All discussion activity for your course will show up here.",
"discussions.empty.myPosts": "Posts you've interacted with will show up here.",
"discussions.empty.topic": "All discussion activity for this topic will show up here.",
"discussions.empty.title": "Nothing here yet",
"discussions.empty.noPostSelected": "No post selected",
"discussions.empty.noTopicSelected": "No topic selected",
"discussions.sidebar.noResultsFound": "No results found",
"discussions.sidebar.differentKeywords": "Try searching different keywords",
"discussions.sidebar.removeKeywords": "Try searching different keywords or removing some filters",
"discussions.sidebar.removeKeywordsOnly": "Try searching different keywords",
"discussions.sidebar.removeFilters": "Try removing some filters",
"discussions.empty.iconAlt": "Empty",
"discussions.authors.label.staff": "Staff",
"discussions.authors.label.ta": "TA",
"discussions.learner.loadMostPosts": "Load more posts",
"discussions.post.anonymous.author": "anonymous",
"discussion.banner.welcomeMessage": "🎉 Welcome to the new and improved discussions experience!",
"discussion.banner.learnMore": "Learn more",
"discussion.banner.shareFeedback": "Share feedback",
"discussion.blackoutBanner.information": "Posting in discussions is temporarily disabled by the course team",
"discussions.editor.image.warning.message": "Images having width or height greater than 999px will not be visible when the post, response or comment is viewed using in-line course discussions",
"discussions.editor.image.warning.title": "Warning!",
"discussions.editor.image.warning.dismiss": "Ok",
"discussions.navigation.breadcrumbMenu.allTopics": "Topics",
"discussions.navigation.breadcrumbMenu.showAll": "Show all",
"discussions.navigation.navigationBar.allPosts": "All posts",
"discussions.navigation.navigationBar.allTopics": "Topics",
"discussions.navigation.navigationBar.myPosts": "My posts",
"discussions.navigation.navigationBar.learners": "Learners",
"discussions.comments.comment.addComment": "Add comment",
"discussions.comments.comment.addResponse": "Add a response",
"discussions.comments.comment.abuseFlaggedMessage": "Content reported for staff to review",
"discussions.actions.back.alt": "Back to list",
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
"discussions.comments.comment.loadMoreComments": "Load more comments",
"discussions.comments.comment.loadMoreResponses": "Load more responses",
"discussions.comments.comment.visibility": "This post is visible to {group, select,\n null {Everyone}\n other {{group}}\n }.",
"discussions.comments.comment.postedTime": "{postType, select,\n discussion {Discussion}\n question {Question}\n other {{postType}}\n } posted {relativeTime} by",
"discussions.comments.comment.commentTime": "Posted {relativeTime}",
"discussions.comments.comment.answer": "Answer",
"discussions.comments.comment.answeredlabel": "Marked as answered by",
"discussions.comments.comment.endorsed": "Endorsed",
"discussions.comments.comment.endorsedlabel": "Endorsed by",
"discussions.actions.label": "Actions menu",
"discussions.editor.submit": "Submit",
"discussions.editor.submitting": "Submitting",
"discussions.editor.cancel": "Cancel",
"discussions.editor.error.empty": "Post content cannot be empty.",
"discussions.editor.delete.response.title": "Delete response",
"discussions.editor.delete.response.description": "Are you sure you want to permanently delete this response?",
"discussions.editor.delete.comment.title": "Delete comment",
"discussions.editor.delete.comment.description": "Are you sure you want to permanently delete this comment?",
"discussions.delete.confirmation.button.delete": "Delete",
"discussions.editor.response.response.title": "Report inappropriate content?",
"discussions.editor.response.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.report.comment.title": "Report inappropriate content?",
"discussions.editor.report.comment.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.comments.editReasonCode": "Reason for editing",
"discussions.editor.posts.editReasonCode.error": "Select reason for editing",
"discussions.comment.comments.editedBy": "Edited by",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "Reason",
"discussions.post.closedBy": "Post closed by",
"discussion.comment.time": "{time} ago",
"discussion.thread.notFound": "Thread not found",
"discussions.comment.sortFilterStatus": "{sort, select,\n false {Oldest first}\n true {Newest first}\n other {{sort}}\n }",
"discussions.app.title": "Discussions",
"discussions.posts.actionBar.searchAllPosts": "Search all posts",
"discussions.posts.actionBar.search": "{page, select,\n topics {Search topics}\n posts {Search all posts}\n learners {Search learners}\n myPosts {Search all posts}\n other {{page}}\n }",
"discussions.actionBar.searchInfo": "Showing {count} results for \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "No results found for \"{searchString}\". Showing {count} results for \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "Searching...",
"discussions.actionBar.clearSearch": "Clear results",
"discussion.posts.actionBar.add": "Add a post",
"discussion.posts.actionBar.close": "Close",
"discussions.post.editor.type": "Post type",
"discussions.post.editor.addPostHeading": "Add a post",
"discussions.post.editor.editPostHeading": "Edit post",
"discussions.post.editor.typeDescription": "Questions raise issues that need answers. Discussions share ideas and start conversations.",
"discussions.post.editor.required": "Required",
"discussions.post.editor.questionType": "Question",
"discussions.post.editor.questionDescription": "Raise issues that need answers",
"discussions.post.editor.discussionType": "Discussion",
"discussions.post.editor.discussionDescription": "Share ideas and start conversations",
"discussions.post.editor.topicArea": "Topic area",
"discussions.post.editor.topicAreaDescription": "Add your post to a relevant topic to help others find it.",
"discussions.post.editor.cohortVisibility": "Cohort visibility",
"discussions.post.editor.cohortVisibilityAllLearners": "All learners",
"discussions.post.editor.title": "Post title",
"discussions.post.editor.titleDescription": "Add a clear and descriptive title to encourage participation.",
"discussions.post.editor.title.error": "Post title cannot be empty.",
"discussions.post.editor.content.error": "Post content cannot be empty.",
"discussions.post.editor.questionText": "Your question or idea (required)",
"discussions.post.editor.preview": "Preview",
"discussions.post.editor.followPost": "Follow this post",
"discussions.post.editor.anonymousPost": "Post anonymously",
"discussions.post.editor.anonymousToPeersPost": "Post anonymously to peers",
"discussions.editor.posts.editReasonCode": "Reason for editing",
"discussions.editor.posts.showPreview.button": "Show preview",
"discussions.topic.noName.label": "Unnamed category",
"discussions.subtopic.noName.label": "Unnamed subcategory",
"discussions.posts.filter.showALl": "Show all",
"discussions.posts.filter.discussions": "Discussions",
"discussions.posts.filter.questions": "Questions",
"discussions.posts.filter.message": "Status: {filterBy}",
"discussions.posts.status.filter.anyStatus": "Any status",
"discussions.posts.status.filter.unread": "Unread",
"discussions.posts.status.filter.following": "Following",
"discussions.posts.status.filter.reported": "Reported",
"discussions.posts.status.filter.unanswered": "Unanswered",
"discussions.posts.status.filter.unresponded": "Not responded",
"discussions.posts.filter.myPosts": "My posts",
"discussions.posts.filter.myDiscussions": "My discussions",
"discussions.posts.filter.myQuestions": "My questions",
"discussions.posts.sort.message": "Sorted by {sortBy}",
"discussions.posts.sort.lastActivity": "Recent activity",
"discussions.posts.sort.commentCount": "Most activity",
"discussions.posts.sort.voteCount": "Most likes",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "anonymous",
"discussions.post.addResponse": "Add response",
"discussions.post.lastResponse": "Last response {time}",
"discussions.post.postedOn": "Posted {time} by {author} {authorLabel}",
"discussions.post.contentReported": "Reported",
"discussions.post.following": "Following",
"discussions.post.follow": "Follow",
"discussions.post.followed": "Followed",
"discussions.post.notFollowed": "Not Followed",
"discussions.post.answered": "Answered",
"discussions.post.unFollow": "Unfollow",
"discussions.post.like": "Like",
"discussions.post.removeLike": "Unlike",
"discussions.post.liked": "liked",
"discussions.post.likes": "likes",
"discussions.post.viewActivity": "View activity",
"discussions.post.activity": "Activity",
"discussions.post.closed": "Post closed for responses and comments",
"discussions.post.relatedTo": "Related to",
"discussions.editor.delete.post.title": "Delete post",
"discussions.editor.delete.post.description": "Are you sure you want to permanently delete this post?",
"discussions.post.delete.confirmation.button.delete": "Delete",
"discussions.editor.report.post.title": "Report inappropriate content?",
"discussions.editor.report.post.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.post.closePostModal.title": "Close post",
"discussions.post.closePostModal.text": "Enter a reason for closing this post. This will only be displayed to other moderators.",
"discussions.post.closePostModal.reasonCodeInput": "Reason",
"discussions.post.closePostModal.cancel": "Cancel",
"discussions.post.closePostModal.confirm": "Close post",
"discussions.post.label.new": "{count} New",
"discussions.post.editedBy": "Edited by",
"discussions.post.editReason": "Reason",
"discussions.post.postWithoutPreview": "No preview available",
"discussions.post.follow.description": "you are following this post",
"discussions.post.unfollow.description": "you are not following this post",
"discussions.topics.sort.message": "Sorted by {sortBy}",
"discussions.topics.sort.lastActivity": "Recent activity",
"discussions.topics.sort.commentCount": "Most activity",
"discussions.topics.sort.courseStructure": "Course Structure",
"discussions.topics.unnamed.label": "Unnamed category",
"discussions.subtopics.unnamed.label": "Unnamed subcategory",
"tour.action.advance": "Next",
"tour.action.dismiss": "Dismiss",
"tour.action.end": "Okay",
"tour.body.notRespondedFilter": "Now you can filter discussions to find posts with no response.",
"tour.title.notRespondedFilter": "New filtering option!",
"tour.body.responseSortTour": "Responses and comments are now sorted by newest first. Please use this option to change the sort order",
"tour.title.responseSortTour": "Sort Responses!"
}

View File

@@ -209,6 +209,6 @@
"tour.action.end": "Tamam",
"tour.body.notRespondedFilter": "Artık yanıt vermeyen iletileri bulmak için tartışmaları filtreleyebilirsiniz.",
"tour.title.notRespondedFilter": "Yeni filtreleme seçeneği!",
"tour.body.responseSortTour": "Yanıtlar ve yorumlar artık önce en yeniye göre sıralanıyor. Lütfen sıralama düzenini değiştirmek için bu seçeneği kullanın",
"tour.title.responseSortTour": "Yanıtları Sırala!"
"tour.body.responseSortTour": "Responses and comments are now sorted by newest first. Please use this option to change the sort order",
"tour.title.responseSortTour": "Sort Responses!"
}

View File

@@ -1,214 +0,0 @@
{
"navigation.course.tabs.label": "Course Material",
"learn.course.tabs.navigation.overflow.menu": "More...",
"discussions.topics.backAlt": "Back to topics list",
"discussions.topics.discussions": "{count, plural,\n =0 {Discussion}\n one {# Discussion}\n other {# Discussions}\n }",
"discussions.topics.questions": "{count, plural,\n =0 {Question}\n one {# Question}\n other {# Questions}\n }",
"discussions.topics.reported": "{reported} reported",
"discussions.topics.previouslyReported": "{previouslyReported} previously reported",
"discussions.topics.find.label": "Search topics",
"discussions.topics.unnamed.section.label": "Unnamed Section",
"discussions.topics.unnamed.subsection.label": "Unnamed Subsection",
"discussions.subtopics.unnamed.topic.label": "Unnamed Topic",
"discussions.topics.title": "No topic exists",
"discussions.topics.createTopic": "Please contact you admin to create a topic",
"discussions.topics.nothing": "Nothing here yet",
"discussions.topics.archived.label": "Archived",
"discussions.learner.reported": "{reported} reported",
"discussions.learner.previouslyReported": "{previouslyReported} previously reported",
"discussions.learner.lastLogin": "Last active {lastActiveTime}",
"discussions.learner.loadMostLearners": "Load more",
"discussions.learner.back": "Back",
"discussions.learner.activityForLearner": "Activity for {username}",
"discussions.learner.mostActivity": "Most activity",
"discussions.learner.reportedActivity": "Reported activity",
"discussions.learner.recentActivity": "Recent activity",
"discussions.learner.sortFilterStatus": "All learners sorted by {sort, select,\n flagged {reported activity}\n activity {most activity}\n other {{sort}}\n }",
"discussion.learner.allActivity": "All activity",
"discussion.learner.posts": "Posts",
"discussions.actions.button.alt": "Actions menu",
"discussions.actions.copylink": "Copy link",
"discussions.actions.edit": "Edit",
"discussions.actions.pin": "Pin",
"discussions.actions.unpin": "Unpin",
"discussions.actions.delete": "Delete",
"discussions.confirmation.button.confirm": "Confirm",
"discussions.actions.close": "Close",
"discussions.actions.reopen": "Reopen",
"discussions.actions.report": "Report",
"discussions.actions.unreport": "Unreport",
"discussions.actions.endorse": "Endorse",
"discussions.actions.unendorse": "Unendorse",
"discussions.actions.markAnswered": "Mark as answered",
"discussions.actions.unMarkAnswered": "Unmark as answered",
"discussions.modal.confirmation.button.cancel": "Cancel",
"discussions.empty.allTopics": "All discussion activity for these topics will show up here.",
"discussions.empty.allPosts": "All discussion activity for your course will show up here.",
"discussions.empty.myPosts": "Posts you've interacted with will show up here.",
"discussions.empty.topic": "All discussion activity for this topic will show up here.",
"discussions.empty.title": "Nothing here yet",
"discussions.empty.noPostSelected": "No post selected",
"discussions.empty.noTopicSelected": "No topic selected",
"discussions.sidebar.noResultsFound": "No results found",
"discussions.sidebar.differentKeywords": "Try searching different keywords",
"discussions.sidebar.removeKeywords": "Try searching different keywords or removing some filters",
"discussions.sidebar.removeKeywordsOnly": "Try searching different keywords",
"discussions.sidebar.removeFilters": "Try removing some filters",
"discussions.empty.iconAlt": "Empty",
"discussions.authors.label.staff": "Staff",
"discussions.authors.label.ta": "TA",
"discussions.learner.loadMostPosts": "Load more posts",
"discussions.post.anonymous.author": "anonymous",
"discussion.banner.welcomeMessage": "🎉 Welcome to the new and improved discussions experience!",
"discussion.banner.learnMore": "Learn more",
"discussion.banner.shareFeedback": "Share feedback",
"discussion.blackoutBanner.information": "Posting in discussions is temporarily disabled by the course team",
"discussions.editor.image.warning.message": "Images having width or height greater than 999px will not be visible when the post, response or comment is viewed using in-line course discussions",
"discussions.editor.image.warning.title": "Warning!",
"discussions.editor.image.warning.dismiss": "Ok",
"discussions.navigation.breadcrumbMenu.allTopics": "Topics",
"discussions.navigation.breadcrumbMenu.showAll": "Show all",
"discussions.navigation.navigationBar.allPosts": "All posts",
"discussions.navigation.navigationBar.allTopics": "Topics",
"discussions.navigation.navigationBar.myPosts": "My posts",
"discussions.navigation.navigationBar.learners": "Learners",
"discussions.comments.comment.addComment": "Add comment",
"discussions.comments.comment.addResponse": "Add a response",
"discussions.comments.comment.abuseFlaggedMessage": "Content reported for staff to review",
"discussions.actions.back.alt": "Back to list",
"discussions.comments.comment.responseCount": "{num, plural,\n =0 {No responses}\n one {Showing # response}\n other {Showing # responses}\n }",
"discussions.comments.comment.endorsedResponseCount": "{num, plural,\n =0 {No endorsed responses}\n one {Showing # endorsed response}\n other {Showing # endorsed responses}\n }",
"discussions.comments.comment.loadMoreComments": "Load more comments",
"discussions.comments.comment.loadMoreResponses": "Load more responses",
"discussions.comments.comment.visibility": "This post is visible to {group, select,\n null {Everyone}\n other {{group}}\n }.",
"discussions.comments.comment.postedTime": "{postType, select,\n discussion {Discussion}\n question {Question}\n other {{postType}}\n } posted {relativeTime} by",
"discussions.comments.comment.commentTime": "Posted {relativeTime}",
"discussions.comments.comment.answer": "Answer",
"discussions.comments.comment.answeredlabel": "Marked as answered by",
"discussions.comments.comment.endorsed": "Endorsed",
"discussions.comments.comment.endorsedlabel": "Endorsed by",
"discussions.actions.label": "Actions menu",
"discussions.editor.submit": "Submit",
"discussions.editor.submitting": "Submitting",
"discussions.editor.cancel": "Cancel",
"discussions.editor.error.empty": "Post content cannot be empty.",
"discussions.editor.delete.response.title": "Delete response",
"discussions.editor.delete.response.description": "Are you sure you want to permanently delete this response?",
"discussions.editor.delete.comment.title": "Delete comment",
"discussions.editor.delete.comment.description": "Are you sure you want to permanently delete this comment?",
"discussions.delete.confirmation.button.delete": "Delete",
"discussions.editor.response.response.title": "Report inappropriate content?",
"discussions.editor.response.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.report.comment.title": "Report inappropriate content?",
"discussions.editor.report.comment.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.editor.comments.editReasonCode": "Reason for editing",
"discussions.editor.posts.editReasonCode.error": "Select reason for editing",
"discussions.comment.comments.editedBy": "Edited by",
"discussions.comment.comments.fullStop": "•",
"discussions.comment.comments.reason": "Reason",
"discussions.post.closedBy": "Post closed by",
"discussion.comment.time": "{time} ago",
"discussion.thread.notFound": "Thread not found",
"discussions.comment.sortFilterStatus": "{sort, select,\n false {Oldest first}\n true {Newest first}\n other {{sort}}\n }",
"discussions.app.title": "Discussions",
"discussions.posts.actionBar.searchAllPosts": "Search all posts",
"discussions.posts.actionBar.search": "{page, select,\n topics {Search topics}\n posts {Search all posts}\n learners {Search learners}\n myPosts {Search all posts}\n other {{page}}\n }",
"discussions.actionBar.searchInfo": "Showing {count} results for \"{text}\"",
"discussions.actionBar.searchRewriteInfo": "No results found for \"{searchString}\". Showing {count} results for \"{textSearchRewrite}\".",
"discussions.actionBar.searchInfoSearching": "Searching...",
"discussions.actionBar.clearSearch": "Clear results",
"discussion.posts.actionBar.add": "Add a post",
"discussion.posts.actionBar.close": "Close",
"discussions.post.editor.type": "Post type",
"discussions.post.editor.addPostHeading": "Add a post",
"discussions.post.editor.editPostHeading": "Edit post",
"discussions.post.editor.typeDescription": "Questions raise issues that need answers. Discussions share ideas and start conversations.",
"discussions.post.editor.required": "Required",
"discussions.post.editor.questionType": "Question",
"discussions.post.editor.questionDescription": "Raise issues that need answers",
"discussions.post.editor.discussionType": "Discussion",
"discussions.post.editor.discussionDescription": "Share ideas and start conversations",
"discussions.post.editor.topicArea": "Topic area",
"discussions.post.editor.topicAreaDescription": "Add your post to a relevant topic to help others find it.",
"discussions.post.editor.cohortVisibility": "Cohort visibility",
"discussions.post.editor.cohortVisibilityAllLearners": "All learners",
"discussions.post.editor.title": "Post title",
"discussions.post.editor.titleDescription": "Add a clear and descriptive title to encourage participation.",
"discussions.post.editor.title.error": "Post title cannot be empty.",
"discussions.post.editor.content.error": "Post content cannot be empty.",
"discussions.post.editor.questionText": "Your question or idea (required)",
"discussions.post.editor.preview": "Preview",
"discussions.post.editor.followPost": "Follow this post",
"discussions.post.editor.anonymousPost": "Post anonymously",
"discussions.post.editor.anonymousToPeersPost": "Post anonymously to peers",
"discussions.editor.posts.editReasonCode": "Reason for editing",
"discussions.editor.posts.showPreview.button": "Show preview",
"discussions.topic.noName.label": "Unnamed category",
"discussions.subtopic.noName.label": "Unnamed subcategory",
"discussions.posts.filter.showALl": "Show all",
"discussions.posts.filter.discussions": "Discussions",
"discussions.posts.filter.questions": "Questions",
"discussions.posts.filter.message": "Status: {filterBy}",
"discussions.posts.status.filter.anyStatus": "Any status",
"discussions.posts.status.filter.unread": "Unread",
"discussions.posts.status.filter.following": "Following",
"discussions.posts.status.filter.reported": "Reported",
"discussions.posts.status.filter.unanswered": "Unanswered",
"discussions.posts.status.filter.unresponded": "Not responded",
"discussions.posts.filter.myPosts": "My posts",
"discussions.posts.filter.myDiscussions": "My discussions",
"discussions.posts.filter.myQuestions": "My questions",
"discussions.posts.sort.message": "Sorted by {sortBy}",
"discussions.posts.sort.lastActivity": "Recent activity",
"discussions.posts.sort.commentCount": "Most activity",
"discussions.posts.sort.voteCount": "Most likes",
"discussions.posts.sort-filter.sortFilterStatus": "{own, select,\n false {All}\n true {Own}\n other {{own}}\n } {status, select,\n statusAll {}\n statusUnread {unread}\n statusFollowing {followed}\n statusReported {reported}\n statusUnanswered {unanswered}\n statusUnresponded {unresponded}\n other {{status}}\n } {type, select,\n discussion {discussions}\n question {questions}\n all {posts}\n other {{type}}\n } {cohortType, select,\n all {}\n group {in {cohort}}\n other {{cohortType}}\n } sorted by {sort, select,\n lastActivityAt {recent activity}\n commentCount {most activity}\n voteCount {most likes}\n other {{sort}}\n }",
"discussions.post.author.anonymous": "anonymous",
"discussions.post.addResponse": "Add response",
"discussions.post.lastResponse": "Last response {time}",
"discussions.post.postedOn": "Posted {time} by {author} {authorLabel}",
"discussions.post.contentReported": "Reported",
"discussions.post.following": "Following",
"discussions.post.follow": "Follow",
"discussions.post.followed": "Followed",
"discussions.post.notFollowed": "Not Followed",
"discussions.post.answered": "Answered",
"discussions.post.unFollow": "Unfollow",
"discussions.post.like": "Like",
"discussions.post.removeLike": "Unlike",
"discussions.post.liked": "liked",
"discussions.post.likes": "likes",
"discussions.post.viewActivity": "View activity",
"discussions.post.activity": "Activity",
"discussions.post.closed": "Post closed for responses and comments",
"discussions.post.relatedTo": "Related to",
"discussions.editor.delete.post.title": "Delete post",
"discussions.editor.delete.post.description": "Are you sure you want to permanently delete this post?",
"discussions.post.delete.confirmation.button.delete": "Delete",
"discussions.editor.report.post.title": "Report inappropriate content?",
"discussions.editor.report.post.description": "The discussion moderation team will review this content and take appropriate action.",
"discussions.post.closePostModal.title": "Close post",
"discussions.post.closePostModal.text": "Enter a reason for closing this post. This will only be displayed to other moderators.",
"discussions.post.closePostModal.reasonCodeInput": "Reason",
"discussions.post.closePostModal.cancel": "Cancel",
"discussions.post.closePostModal.confirm": "Close post",
"discussions.post.label.new": "{count} New",
"discussions.post.editedBy": "Edited by",
"discussions.post.editReason": "Reason",
"discussions.post.postWithoutPreview": "No preview available",
"discussions.post.follow.description": "you are following this post",
"discussions.post.unfollow.description": "you are not following this post",
"discussions.topics.sort.message": "Sorted by {sortBy}",
"discussions.topics.sort.lastActivity": "Recent activity",
"discussions.topics.sort.commentCount": "Most activity",
"discussions.topics.sort.courseStructure": "Course Structure",
"discussions.topics.unnamed.label": "Unnamed category",
"discussions.subtopics.unnamed.label": "Unnamed subcategory",
"tour.action.advance": "Next",
"tour.action.dismiss": "Dismiss",
"tour.action.end": "Okay",
"tour.body.notRespondedFilter": "Now you can filter discussions to find posts with no response.",
"tour.title.notRespondedFilter": "New filtering option!",
"tour.body.responseSortTour": "Responses and comments are now sorted by newest first. Please use this option to change the sort order",
"tour.title.responseSortTour": "Sort Responses!"
}

View File

@@ -4,14 +4,17 @@ import 'regenerator-runtime/runtime';
import React from 'react';
import ReactDOM from 'react-dom';
import { messages as footerMessages } from '@edx/frontend-component-footer';
import { messages as headerMessages } from '@edx/frontend-component-header';
import {
APP_INIT_ERROR, APP_READY, initialize, mergeConfig,
subscribe,
} from '@edx/frontend-platform';
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
import { messages as paragonMessages } from '@edx/paragon';
import { DiscussionsHome } from './discussions';
import messages from './i18n';
import appMessages from './i18n';
import store from './store';
import './assets/favicon.ico';
@@ -32,7 +35,12 @@ subscribe(APP_INIT_ERROR, (error) => {
initialize({
requireAuthenticatedUser: true,
messages,
messages: [
headerMessages,
footerMessages,
appMessages,
paragonMessages,
],
handlers: {
config: () => {
mergeConfig({

View File

@@ -256,8 +256,7 @@ header {
}
.border-2 {
border: 2px solid #cccccc !important;
border-width: 2px !important;
border-width: 2px;
}
.post-form {
@@ -280,6 +279,7 @@ header {
line-height: 28px;
font-family: Inter, Helvetica Neue, Arial, sans-serif;
font-size: 18px !important;
height: 60px !important;
.user-dropdown {
button {

View File

@@ -20,21 +20,23 @@ Object.defineProperty(window, 'matchMedia', {
});
// Provides a mock editor component that functions like tinyMCE without the overhead
const MockEditor = ({
function MockEditor({
onBlur,
onEditorChange,
}) => (
// eslint-disable-next-line react/jsx-filename-extension
<textarea
data-testid="tinymce-editor"
onChange={(event) => {
onEditorChange(event.currentTarget.value);
}}
onBlur={event => {
onBlur(event.currentTarget.value);
}}
/>
);
}) {
return (
// eslint-disable-next-line react/jsx-filename-extension
<textarea
data-testid="tinymce-editor"
onChange={(event) => {
onEditorChange(event.currentTarget.value);
}}
onBlur={event => {
onBlur(event.currentTarget.value);
}}
/>
);
}
MockEditor.propTypes = {
onBlur: PropTypes.func.isRequired,