- {(!loading) ? (
-
-
-
- ) : (
-
-
-
- )}
+ return (!loading) ? (
+
+
+
+ ) : (
+
+
);
};
-VideoUploadEditor.propTypes = {
- onClose: PropTypes.func.isRequired,
-};
-
export default VideoUploadEditor;
diff --git a/src/editors/containers/VideoUploadEditor/index.scss b/src/editors/containers/VideoUploadEditor/index.scss
index c1bd2f3be..0c678fb8e 100644
--- a/src/editors/containers/VideoUploadEditor/index.scss
+++ b/src/editors/containers/VideoUploadEditor/index.scss
@@ -1,3 +1,5 @@
+@import "@edx/paragon/scss/core/core";
+
.dropzone-middle {
border: 2px dashed #ccc;
@@ -11,12 +13,6 @@
width: 100%;
}
-.url-submit-button {
- position: absolute;
- margin-left: 17rem;
- font-size: 0.75rem;
-}
-
.video-id-prompt {
input::placeholder {
@@ -24,7 +20,6 @@
// color: #5E35B1;
font-weight: '500';
word-wrap: 'break-word';
- font-size: 0.875rem !important;
}
button {
@@ -32,21 +27,15 @@
background-color: #FFFFFF;
}
- .prompt-button {
- background: rgba(239, 234, 247, 0.70);
+ .btn-icon.url-submit-button {
+ &, &:active, &:hover {
+ background-color: transparent !important;
+ border: none !important;
+ color: $gray-500 !important;
+ }
}
-}
-.video-upload-input-group{
- .form-control {
- font-size: 0.875rem !important;
- width: 308px !important;
- height: 44px !important;
- }
-
- .pgn__icon.pgn__icon__lg {
- width: 3.625rem !important;
- height: 3.625rem !important;
+ .prompt-button {
+ background: rgba(239, 234, 247, 0.70);
}
}
-
diff --git a/src/editors/containers/VideoUploadEditor/index.test.jsx b/src/editors/containers/VideoUploadEditor/index.test.jsx
index bf032cd70..e200e9f3a 100644
--- a/src/editors/containers/VideoUploadEditor/index.test.jsx
+++ b/src/editors/containers/VideoUploadEditor/index.test.jsx
@@ -13,13 +13,12 @@ jest.unmock('@edx/paragon');
jest.unmock('@edx/paragon/icons');
describe('VideoUploadEditor', () => {
- const onCloseMock = jest.fn();
let store;
- const renderComponent = async (storeParam, onCloseMockParam) => render(
+ const renderComponent = async (storeParam) => render(
-
+
,
,
);
@@ -45,14 +44,20 @@ describe('VideoUploadEditor', () => {
});
it('renders as expected with default behavior', async () => {
- expect(await renderComponent(store, onCloseMock)).toMatchSnapshot();
+ expect(await renderComponent(store)).toMatchSnapshot();
});
- it('calls onClose when close button is clicked', async () => {
- const container = await renderComponent(store, onCloseMock);
+ it('calls window.history.back when close button is clicked', async () => {
+ const container = await renderComponent(store);
const closeButton = container.getAllByRole('button', { name: /close/i });
+ const oldHistoryBack = window.history.back;
+ window.history.back = jest.fn();
+
expect(closeButton).toHaveLength(1);
+ expect(window.history.back).not.toHaveBeenCalled();
closeButton.forEach((button) => fireEvent.click(button));
- expect(onCloseMock).toHaveBeenCalled();
+ expect(window.history.back).toHaveBeenCalled();
+
+ window.history.back = oldHistoryBack;
});
});
diff --git a/src/editors/data/constants/problem.js b/src/editors/data/constants/problem.js
index 1f0c378ae..5dcc8558c 100644
--- a/src/editors/data/constants/problem.js
+++ b/src/editors/data/constants/problem.js
@@ -193,7 +193,7 @@ export const ShowAnswerTypes = StrictDict({
export const RandomizationTypesKeys = StrictDict({
NEVER: 'never',
ALWAYS: 'always',
- ONRESET: 'on_reset',
+ ONRESET: 'onreset',
PERSTUDENT: 'per_student',
});
diff --git a/src/editors/data/constants/tinyMCE.js b/src/editors/data/constants/tinyMCE.js
index a14bc0e58..f98333580 100644
--- a/src/editors/data/constants/tinyMCE.js
+++ b/src/editors/data/constants/tinyMCE.js
@@ -68,7 +68,6 @@ export const plugins = listKeyStore([
'imagetools',
'quickbars',
'a11ychecker',
- 'a11ycheckerCss',
'powerpaste',
]);
diff --git a/src/editors/data/images/videoThumbnail.svg b/src/editors/data/images/videoThumbnail.svg
index 52fac3a82..82790d006 100644
--- a/src/editors/data/images/videoThumbnail.svg
+++ b/src/editors/data/images/videoThumbnail.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/src/editors/data/redux/problem/reducers.js b/src/editors/data/redux/problem/reducers.js
index 4173cbc9b..c672b85a3 100644
--- a/src/editors/data/redux/problem/reducers.js
+++ b/src/editors/data/redux/problem/reducers.js
@@ -2,7 +2,7 @@ import _ from 'lodash-es';
import { createSlice } from '@reduxjs/toolkit';
import { indexToLetterMap } from '../../../containers/ProblemEditor/data/OLXParser';
import { StrictDict } from '../../../utils';
-import { ProblemTypeKeys, RichTextProblems, ShowAnswerTypesKeys } from '../../constants/problem';
+import { ProblemTypeKeys, RichTextProblems } from '../../constants/problem';
import { ToleranceTypes } from '../../../containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Tolerance/constants';
const nextAlphaId = (lastId) => String.fromCharCode(lastId.charCodeAt(0) + 1);
@@ -28,10 +28,10 @@ const initialState = {
hints: [],
timeBetween: 0,
showAnswer: {
- on: ShowAnswerTypesKeys.FINISHED,
+ on: '',
afterAttempts: 0,
},
- showResetButton: false,
+ showResetButton: null,
solutionExplanation: '',
tolerance: {
value: null,
diff --git a/src/editors/data/redux/thunkActions/problem.js b/src/editors/data/redux/thunkActions/problem.js
index 4ed432660..9edf656b5 100644
--- a/src/editors/data/redux/thunkActions/problem.js
+++ b/src/editors/data/redux/thunkActions/problem.js
@@ -32,6 +32,7 @@ export const getDataFromOlx = ({ rawOLX, rawSettings, defaultSettings }) => {
olxParser = new OLXParser(rawOLX);
parsedProblem = olxParser.getParsedOLXData();
} catch (error) {
+ // eslint-disable-next-line no-console
console.error('The Problem Could Not Be Parsed from OLX. redirecting to Advanced editor.', error);
return { problemType: ProblemTypeKeys.ADVANCED, rawOLX, settings: parseSettings(rawSettings, defaultSettings) };
}
@@ -55,7 +56,7 @@ export const loadProblem = ({ rawOLX, rawSettings, defaultSettings }) => (dispat
};
export const fetchAdvancedSettings = ({ rawOLX, rawSettings }) => (dispatch) => {
- const advancedProblemSettingKeys = ['max_attempts', 'showanswer', 'show_reset_button'];
+ const advancedProblemSettingKeys = ['max_attempts', 'showanswer', 'show_reset_button', 'rerandomize'];
dispatch(requests.fetchAdvancedSettings({
onSuccess: (response) => {
const defaultSettings = {};
diff --git a/src/editors/data/redux/thunkActions/video.js b/src/editors/data/redux/thunkActions/video.js
index e3524e9f0..cc5b0797c 100644
--- a/src/editors/data/redux/thunkActions/video.js
+++ b/src/editors/data/redux/thunkActions/video.js
@@ -50,6 +50,7 @@ export const loadVideoData = (selectedVideoId, selectedVideoUrl) => (dispatch, g
// Use the selected video url first
const videoSourceUrl = selectedVideoUrl != null ? selectedVideoUrl : videoUrl;
const [licenseType, licenseOptions] = module.parseLicense({ licenseData: studioView, level: 'block' });
+ // eslint-disable-next-line no-console
console.log(licenseType);
const transcripts = rawVideoData.transcriptsFromSelected ? rawVideoData.transcriptsFromSelected
: module.parseTranscripts({ transcriptsData: studioView });
@@ -168,6 +169,7 @@ export const parseTranscripts = ({ transcriptsData }) => {
return Object.keys(transcriptsObj.value);
} catch (error) {
if (error instanceof SyntaxError) {
+ // eslint-disable-next-line no-console
console.error('Invalid JSON:', error.message);
} else {
throw error;
@@ -265,6 +267,7 @@ export const uploadThumbnail = ({ thumbnail, emptyCanvas }) => (dispatch, getSta
}));
}
},
+ // eslint-disable-next-line no-console
onFailure: (e) => console.log({ UploadFailure: e }, 'Resampling thumbnail upload'),
}));
};
@@ -404,6 +407,7 @@ export const uploadVideo = ({ supportedFiles, setLoadSpinner, postUploadRedirect
const uploadUrl = fileObj.upload_url;
const uploadFile = supportedFiles.find((file) => file.get('file').name === fileName);
if (!uploadFile) {
+ // eslint-disable-next-line no-console
console.error(`Could not find file object with name "${fileName}" in supportedFiles array.`);
return;
}
@@ -417,6 +421,7 @@ export const uploadVideo = ({ supportedFiles, setLoadSpinner, postUploadRedirect
},
})
.then(() => postUploadRedirect(edxVideoId))
+ // eslint-disable-next-line no-console
.catch((error) => console.error('Error uploading file:', error));
}));
setLoadSpinner(false);
diff --git a/src/editors/data/services/cms/types.js b/src/editors/data/services/cms/types.js
index c63d87a6c..f35ec2441 100644
--- a/src/editors/data/services/cms/types.js
+++ b/src/editors/data/services/cms/types.js
@@ -64,6 +64,7 @@ export const problemDataProps = {
max_attempts: PropTypes.number,
showanswer: PropTypes.string,
show_reset_button: PropTypes.bool,
+ rerandomize: PropTypes.string,
}),
}),
};
diff --git a/src/editors/sharedComponents/BaseModal/index.jsx b/src/editors/sharedComponents/BaseModal/index.jsx
index 388652b6d..85869da13 100644
--- a/src/editors/sharedComponents/BaseModal/index.jsx
+++ b/src/editors/sharedComponents/BaseModal/index.jsx
@@ -20,6 +20,7 @@ export const BaseModal = ({
size,
isFullscreenScroll,
bodyStyle,
+ className,
}) => (
@@ -59,6 +61,7 @@ BaseModal.defaultProps = {
size: 'lg',
isFullscreenScroll: true,
bodyStyle: null,
+ className: undefined,
};
BaseModal.propTypes = {
@@ -72,6 +75,7 @@ BaseModal.propTypes = {
size: PropTypes.string,
isFullscreenScroll: PropTypes.bool,
bodyStyle: PropTypes.shape({}),
+ className: PropTypes.string,
};
export default BaseModal;
diff --git a/src/editors/sharedComponents/SelectionModal/Gallery.jsx b/src/editors/sharedComponents/SelectionModal/Gallery.jsx
index 37e61e990..88a141144 100644
--- a/src/editors/sharedComponents/SelectionModal/Gallery.jsx
+++ b/src/editors/sharedComponents/SelectionModal/Gallery.jsx
@@ -23,6 +23,7 @@ export const Gallery = ({
showIdsOnCards,
height,
isLoaded,
+ thumbnailFallback,
}) => {
const intl = useIntl();
if (!isLoaded) {
@@ -66,7 +67,14 @@ export const Gallery = ({
type="radio"
value={highlighted}
>
- { displayList.map(asset => ) }
+ { displayList.map(asset => (
+
+ )) }
@@ -78,6 +86,7 @@ Gallery.defaultProps = {
showIdsOnCards: false,
height: '375px',
show: true,
+ thumbnailFallback: undefined,
};
Gallery.propTypes = {
show: PropTypes.bool,
@@ -90,6 +99,7 @@ Gallery.propTypes = {
emptyGalleryLabel: PropTypes.shape({}).isRequired,
showIdsOnCards: PropTypes.bool,
height: PropTypes.string,
+ thumbnailFallback: PropTypes.element,
};
export default Gallery;
diff --git a/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx b/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx
index 0a8c75353..f4ddc6420 100644
--- a/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx
+++ b/src/editors/sharedComponents/SelectionModal/GalleryCard.jsx
@@ -14,67 +14,81 @@ import LanguageNamesWidget from '../../containers/VideoEditor/components/VideoSe
export const GalleryCard = ({
asset,
-}) => (
-
-
-
-
- { asset.status && asset.statusBadgeVariant && (
-
- {asset.status}
-
- )}
- { asset.duration >= 0 && (
-
- {formatDuration(asset.duration)}
-
- )}
-
-
-
{asset.displayName}
- { asset.transcripts && (
-
-
{
+ const [thumbnailError, setThumbnailError] = React.useState(false);
+ return (
+
+
+
+ {(thumbnailError && thumbnailFallback) ? (
+
+ { thumbnailFallback }
+
+ ) : (
+
setThumbnailError(true))}
+ />
+ )}
+ { asset.status && asset.statusBadgeVariant && (
+
+ {asset.status}
+
+ )}
+ { asset.duration >= 0 && (
+
+ {formatDuration(asset.duration)}
+
+ )}
+
+
+
{asset.displayName}
+ { asset.transcripts && (
+
+
+
+ )}
+
+ ,
+ time: ,
+ }}
/>
-
- )}
-
- ,
- time: ,
- }}
- />
-
+
+
-
-
-);
+
+ );
+};
+GalleryCard.defaultProps = {
+ thumbnailFallback: undefined,
+};
GalleryCard.propTypes = {
asset: PropTypes.shape({
contentType: PropTypes.string,
@@ -91,6 +105,7 @@ GalleryCard.propTypes = {
statusBadgeVariant: PropTypes.string,
transcripts: PropTypes.arrayOf(PropTypes.string),
}).isRequired,
+ thumbnailFallback: PropTypes.element,
};
export default GalleryCard;
diff --git a/src/editors/sharedComponents/SelectionModal/GalleryCard.test.jsx b/src/editors/sharedComponents/SelectionModal/GalleryCard.test.jsx
index 5eb49456f..4b3ec843c 100644
--- a/src/editors/sharedComponents/SelectionModal/GalleryCard.test.jsx
+++ b/src/editors/sharedComponents/SelectionModal/GalleryCard.test.jsx
@@ -10,6 +10,7 @@ describe('GalleryCard component', () => {
displayName: 'props.img.displayName',
dateAdded: 12345,
};
+ const thumbnailFallback = (
Image failed to load );
let el;
beforeEach(() => {
el = shallow(
);
@@ -20,6 +21,15 @@ describe('GalleryCard component', () => {
it('loads Image with src from image external url', () => {
expect(el.find(Image).props().src).toEqual(asset.externalUrl);
});
+ it('snapshot with thumbnail fallback and load error', () => {
+ el = shallow(
);
+ el.find(Image).props().onError();
+ expect(el).toMatchSnapshot();
+ });
+ it('snapshot with thumbnail fallback and no error', () => {
+ el = shallow(
);
+ expect(el).toMatchSnapshot();
+ });
it('snapshot with status badge', () => {
el = shallow(
);
expect(el).toMatchSnapshot();
diff --git a/src/editors/sharedComponents/SelectionModal/MultiSelectFilterDropdown.jsx b/src/editors/sharedComponents/SelectionModal/MultiSelectFilterDropdown.jsx
deleted file mode 100644
index b6ec2fd6c..000000000
--- a/src/editors/sharedComponents/SelectionModal/MultiSelectFilterDropdown.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-
-import { useIntl } from '@edx/frontend-platform/i18n';
-import { Dropdown, DropdownToggle, Form } from '@edx/paragon';
-
-import PropTypes from 'prop-types';
-import { filterKeys, filterMessages } from '../../containers/VideoGallery/utils';
-
-const MultiSelectFilterDropdown = ({
- selected, onSelectionChange,
-}) => {
- const intl = useIntl();
- return (
-
-
- {intl.formatMessage(filterMessages.title)}
-
-
- {Object.keys(filterKeys).map(key => (
-
- {intl.formatMessage(filterMessages[key])}
-
- ))}
-
-
- );
-};
-
-MultiSelectFilterDropdown.propTypes = {
- selected: PropTypes.arrayOf(PropTypes.string).isRequired,
- onSelectionChange: PropTypes.func.isRequired,
-};
-export default MultiSelectFilterDropdown;
diff --git a/src/editors/sharedComponents/SelectionModal/SearchSort.jsx b/src/editors/sharedComponents/SelectionModal/SearchSort.jsx
index d9b00e554..619a44bfb 100644
--- a/src/editors/sharedComponents/SelectionModal/SearchSort.jsx
+++ b/src/editors/sharedComponents/SelectionModal/SearchSort.jsx
@@ -2,16 +2,16 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
- ActionRow, Form, Icon, IconButton, SelectMenu, MenuItem,
+ ActionRow, Dropdown, Form, Icon, IconButton, SelectMenu, MenuItem,
} from '@edx/paragon';
-import { Close, Search } from '@edx/paragon/icons';
+import { Check, Close, Search } from '@edx/paragon/icons';
import {
FormattedMessage,
useIntl,
} from '@edx/frontend-platform/i18n';
import messages from './messages';
-import MultiSelectFilterDropdown from './MultiSelectFilterDropdown';
+import './index.scss';
import { sortKeys, sortMessages } from '../../containers/VideoGallery/utils';
export const SearchSort = ({
@@ -22,6 +22,8 @@ export const SearchSort = ({
onSortClick,
filterBy,
onFilterClick,
+ filterKeys,
+ filterMessages,
showSwitch,
switchMessage,
onSwitchClick,
@@ -54,15 +56,42 @@ export const SearchSort = ({
{ !showSwitch &&
}
-
+
{Object.keys(sortKeys).map(key => (
-
+
+
+
+
+
))}
- {onFilterClick && }
+ { onFilterClick && (
+
+
+
+
+
+ {Object.keys(filterKeys).map(key => (
+
+
+
+ ))}
+
+
+ )}
{ showSwitch && (
<>
@@ -86,6 +115,8 @@ export const SearchSort = ({
SearchSort.defaultProps = {
filterBy: '',
onFilterClick: null,
+ filterKeys: null,
+ filterMessages: null,
showSwitch: false,
onSwitchClick: null,
};
@@ -96,8 +127,10 @@ SearchSort.propTypes = {
clearSearchString: PropTypes.func.isRequired,
sortBy: PropTypes.string.isRequired,
onSortClick: PropTypes.func.isRequired,
- filterBy: PropTypes.arrayOf(PropTypes.string),
+ filterBy: PropTypes.string,
onFilterClick: PropTypes.func,
+ filterKeys: PropTypes.shape({}),
+ filterMessages: PropTypes.shape({}),
showSwitch: PropTypes.bool,
switchMessage: PropTypes.shape({}).isRequired,
onSwitchClick: PropTypes.func,
diff --git a/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx b/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx
index 74899d3a2..980c38f6f 100644
--- a/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx
+++ b/src/editors/sharedComponents/SelectionModal/SearchSort.test.jsx
@@ -6,8 +6,9 @@ import {
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
-import { sortKeys, sortMessages } from '../ImageUploadModal/SelectImageModal/utils';
-import { filterMessages } from '../../containers/VideoGallery/utils';
+import {
+ filterKeys, filterMessages, sortKeys, sortMessages,
+} from '../../containers/VideoGallery/utils';
import { SearchSort } from './SearchSort';
import messages from './messages';
@@ -32,7 +33,10 @@ describe('SearchSort component', () => {
id: 'test.id',
defaultMessage: 'test message',
},
+ filterBy: filterKeys.anyStatus,
onFilterClick: jest.fn(),
+ filterKeys,
+ filterMessages,
showSwitch: true,
};
@@ -48,37 +52,36 @@ describe('SearchSort component', () => {
const { getByRole } = getComponent();
await act(() => {
fireEvent.click(screen.getByRole('button', {
- name: /by date added \(oldest\)/i,
+ name: /By oldest/i,
}));
});
Object.values(sortMessages)
.forEach(({ defaultMessage }) => {
- expect(getByRole('link', { name: defaultMessage }))
+ expect(getByRole('link', { name: `By ${defaultMessage}` }))
.toBeInTheDocument();
});
});
test('adds a sort option for each sortKey', async () => {
const { getByRole } = getComponent();
await act(() => {
- fireEvent.click(screen.getByRole('button', { name: /by date added \(oldest\)/i }));
+ fireEvent.click(screen.getByRole('button', { name: /oldest/i }));
});
Object.values(sortMessages)
.forEach(({ defaultMessage }) => {
- expect(getByRole('link', { name: defaultMessage }))
+ expect(getByRole('link', { name: `By ${defaultMessage}` }))
.toBeInTheDocument();
});
});
- test('adds a filter option for each filterKet', async () => {
- const { getByRole } = getComponent();
- await act(() => {
- fireEvent.click(screen.getByRole('button', { name: /video status/i }));
+ test('adds a filter option for each filter key', async () => {
+ const { getByTestId } = getComponent();
+ act(() => {
+ fireEvent.click(getByTestId('dropdown-filter'));
});
+
Object.keys(filterMessages)
.forEach((key) => {
- if (key !== 'title') {
- expect(getByRole('checkbox', { name: filterMessages[key].defaultMessage }))
- .toBeInTheDocument();
- }
+ expect(getByTestId(`dropdown-filter-${key}`))
+ .toBeInTheDocument();
});
});
test('searchbox should show clear message button when not empty', async () => {
diff --git a/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap b/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap
index 769e70144..810913c68 100644
--- a/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap
+++ b/src/editors/sharedComponents/SelectionModal/__snapshots__/GalleryCard.test.jsx.snap
@@ -253,6 +253,151 @@ exports[`GalleryCard component snapshot with status badge 1`] = `
`;
+exports[`GalleryCard component snapshot with thumbnail fallback and load error 1`] = `
+
+
+
+
+
+ Image failed to load
+
+
+
+
+
+ props.img.displayName
+
+
+ ,
+ "time": ,
+ }
+ }
+ />
+
+
+
+
+`;
+
+exports[`GalleryCard component snapshot with thumbnail fallback and no error 1`] = `
+
+
+
+
+
+
+
+ props.img.displayName
+
+
+ ,
+ "time": ,
+ }
+ }
+ />
+
+
+
+
+`;
+
exports[`GalleryCard component snapshot: dateAdded=12345 1`] = `
)}
+ className="selection-modal"
>
- {/* Error Alerts */}
-
-
-
-
-
+ {/*
+ If the modal dialog content is zero height, it shows a bottom shadow
+ as if there was content to scroll to, so make the min-height 1px.
+ */}
+
+ {/* Error Alerts */}
+
+
+
+
+
- {/* User Feedback Alerts */}
-
-
-
-
+ {/* User Feedback Alerts */}
+
+
+
{showGallery && }
diff --git a/src/editors/sharedComponents/SelectionModal/index.scss b/src/editors/sharedComponents/SelectionModal/index.scss
new file mode 100644
index 000000000..8e3225945
--- /dev/null
+++ b/src/editors/sharedComponents/SelectionModal/index.scss
@@ -0,0 +1,32 @@
+.search-sort-menu .pgn__menu-item-text {
+ text-transform: capitalize;
+}
+
+.search-sort-menu .pgn__menu .search-sort-menu-by {
+ display: none;
+}
+
+/* Sort options come in pairs of ascending and descending. */
+.search-sort-menu .pgn__menu > div:nth-child(even) {
+ border-bottom: 1px solid #f4f3f6;
+}
+
+.search-sort-menu .pgn__menu > div:last-child {
+ border-bottom: none;
+}
+
+.selection-modal .pgn__vstack > .alert {
+ margin-bottom: 0;
+ margin-top: 1.5rem;
+}
+
+/* Set top padding to 0 when there is an alert above. */
+.selection-modal .pgn__vstack > .alert + .gallery > .pgn__scrollable-body-content > .p-4 {
+ padding-top: 0 !important;
+}
+
+.selection-modal .pgn__vstack > .alert > .alert-icon {
+ /* Vertical margin equal to the vertical padding of the "Dismiss" button. */
+ margin-bottom: 0.4375rem;
+ margin-top: 0.4375rem;
+}
diff --git a/src/editors/sharedComponents/SelectionModal/messages.js b/src/editors/sharedComponents/SelectionModal/messages.js
index 2aa74f9e2..9e865930b 100644
--- a/src/editors/sharedComponents/SelectionModal/messages.js
+++ b/src/editors/sharedComponents/SelectionModal/messages.js
@@ -24,6 +24,11 @@ export const messages = {
defaultMessage: 'Added {date} at {time}',
description: 'File date-added string',
},
+ sortBy: {
+ id: 'authoring.selectionmodal.sortBy',
+ defaultMessage: 'By',
+ description: '"By" before sort option',
+ },
};
export default messages;
diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.js b/src/editors/sharedComponents/TinyMceWidget/hooks.js
index d41165cd1..ec657cac9 100644
--- a/src/editors/sharedComponents/TinyMceWidget/hooks.js
+++ b/src/editors/sharedComponents/TinyMceWidget/hooks.js
@@ -5,6 +5,7 @@ import {
useEffect,
} from 'react';
import { getLocale, isRtl } from '@edx/frontend-platform/i18n';
+import { a11ycheckerCss } from 'frontend-components-tinymce-advanced-plugins';
import tinyMCEStyles from '../../data/constants/tinyMCEStyles';
import { StrictDict } from '../../utils';
import pluginConfig from './pluginConfig';
@@ -252,7 +253,7 @@ export const editorConfig = ({
...config,
skin: false,
content_css: false,
- content_style: tinyMCEStyles,
+ content_style: tinyMCEStyles + a11ycheckerCss,
min_height: minHeight,
contextmenu: 'link table',
directionality: isLocaleRtl ? 'rtl' : 'ltr',
diff --git a/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js b/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js
index 72b2d2ce6..572b9e174 100644
--- a/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js
+++ b/src/editors/sharedComponents/TinyMceWidget/pluginConfig.js
@@ -31,6 +31,9 @@ const pluginConfig = ({ isLibrary, placeholder, editorType }) => {
image,
imageTools,
quickToolbar,
+ // disable the paid plugins for now
+ // plugins.a11ychecker,
+ // plugins.powerpaste,
].join(' '),
menubar: false,
toolbar: toolbar ? mapToolbars([
@@ -52,8 +55,7 @@ const pluginConfig = ({ isLibrary, placeholder, editorType }) => {
],
[imageUploadButton, buttons.link, buttons.unlink, buttons.blockQuote, buttons.codeBlock],
[buttons.table, buttons.emoticons, buttons.charmap, buttons.hr],
- [buttons.removeFormat, codeButton],
- [buttons.a11ycheck],
+ [buttons.removeFormat, codeButton, buttons.a11ycheck],
]) : false,
imageToolbar: mapToolbars([
// [buttons.rotate.left, buttons.rotate.right],
@@ -70,7 +72,7 @@ const pluginConfig = ({ isLibrary, placeholder, editorType }) => {
buttons.numlist,
],
[imageUploadButton, buttons.blockQuote, buttons.codeBlock],
- [buttons.table, buttons.emoticons, buttons.charmap, buttons.removeFormat],
+ [buttons.table, buttons.emoticons, buttons.charmap, buttons.removeFormat, buttons.a11ycheck],
]),
quickbarsSelectionToolbar: toolbar ? false : mapToolbars([
[buttons.undo, buttons.redo],
@@ -82,7 +84,7 @@ const pluginConfig = ({ isLibrary, placeholder, editorType }) => {
buttons.numlist,
],
[imageUploadButton, buttons.blockQuote, buttons.codeBlock],
- [buttons.table, buttons.emoticons, buttons.charmap, buttons.removeFormat],
+ [buttons.table, buttons.emoticons, buttons.charmap, buttons.removeFormat, buttons.a11ycheck],
]),
config: {
branding: false,
@@ -97,6 +99,10 @@ const pluginConfig = ({ isLibrary, placeholder, editorType }) => {
inline,
block_formats: 'Header 1=h1;Header 2=h2;Header 3=h3;Header 4=h4;Header 5=h5;Header 6=h6;Div=div;Paragraph=p;Preformatted=pre',
forced_root_block: defaultFormat,
+ powerpaste_allow_local_images: true,
+ powerpaste_word_import: 'prompt',
+ powerpaste_html_import: 'prompt',
+ powerpaste_googledoc_import: 'prompt',
},
})
);
diff --git a/src/setupTest.js b/src/setupTest.js
index 22dce5736..223569e2d 100644
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -155,4 +155,6 @@ jest.mock('react-redux', () => {
});
// Mock the plugins repo so jest will stop complaining about ES6 syntax
-jest.mock('frontend-components-tinymce-advanced-plugins', () => {});
+jest.mock('frontend-components-tinymce-advanced-plugins', () => ({
+ a11ycheckerCss: '',
+}));