diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx
index 21d35831d..8ba7c4d6b 100644
--- a/src/course-outline/CourseOutline.jsx
+++ b/src/course-outline/CourseOutline.jsx
@@ -105,7 +105,6 @@ const CourseOutline = ({ courseId }) => {
handleNewUnitSubmit,
getUnitUrl,
handleVideoSharingOptionChange,
- handleCopyToClipboardClick,
handlePasteClipboardClick,
notificationDismissUrl,
discussionsSettings,
@@ -419,7 +418,6 @@ const CourseOutline = ({ courseId }) => {
onDuplicateSubmit={handleDuplicateUnitSubmit}
getTitleLink={getUnitUrl}
onOrderChange={updateUnitOrderByIndex}
- onCopyToClipboardClick={handleCopyToClipboardClick}
discussionsSettings={discussionsSettings}
/>
))}
diff --git a/src/course-outline/CourseOutline.test.jsx b/src/course-outline/CourseOutline.test.jsx
index ac8a4d8e1..e71178837 100644
--- a/src/course-outline/CourseOutline.test.jsx
+++ b/src/course-outline/CourseOutline.test.jsx
@@ -2182,9 +2182,6 @@ describe('', () => {
.onPost(getClipboardUrl(), {
usage_key: unit.id,
}).reply(200, clipboardUnit);
- // check that initialUserClipboard state is empty
- const { initialUserClipboard } = store.getState().courseOutline;
- expect(initialUserClipboard).toBeUndefined();
// find menu button and click on it to open menu
const menu = await within(unitElement).findByTestId('unit-card-header__menu-button');
@@ -2194,9 +2191,6 @@ describe('', () => {
const copyButton = await within(unitElement).findByText(cardHeaderMessages.menuCopy.defaultMessage);
await act(async () => fireEvent.click(copyButton));
- // check that initialUserClipboard state is updated
- expect(store.getState().generic.clipboardData).toEqual(clipboardUnit);
-
[subsectionElement] = await within(sectionElement).findAllByTestId('subsection-card');
// find clipboard content label
const clipboardLabel = await within(subsectionElement).findByText(
diff --git a/src/course-outline/data/api.js b/src/course-outline/data/api.js
index fc61f3c11..bd2c95254 100644
--- a/src/course-outline/data/api.js
+++ b/src/course-outline/data/api.js
@@ -28,7 +28,6 @@ export const getCourseReindexApiUrl = (reindexLink) => `${getApiBaseUrl()}${rein
export const getXBlockBaseApiUrl = () => `${getApiBaseUrl()}/xblock/`;
export const getCourseItemApiUrl = (itemId) => `${getXBlockBaseApiUrl()}${itemId}`;
export const getXBlockApiUrl = (blockId) => `${getXBlockBaseApiUrl()}outline/${blockId}`;
-export const getClipboardUrl = () => `${getApiBaseUrl()}/api/content-staging/v1/clipboard/`;
export const exportTags = (courseId) => `${getApiBaseUrl()}/api/content_tagging/v1/object_tags/${courseId}/export/`;
/**
diff --git a/src/course-outline/data/thunk.js b/src/course-outline/data/thunk.js
index 457d03911..fc5499896 100644
--- a/src/course-outline/data/thunk.js
+++ b/src/course-outline/data/thunk.js
@@ -1,5 +1,4 @@
import { RequestStatus } from '../../data/constants';
-import { updateClipboardData } from '../../generic/data/slice';
import { NOTIFICATION_MESSAGES } from '../../constants';
import { COURSE_BLOCK_NAMES } from '../constants';
import {
@@ -71,7 +70,6 @@ export function fetchCourseOutlineIndexQuery(courseId) {
},
} = outlineIndex;
dispatch(fetchOutlineIndexSuccess(outlineIndex));
- dispatch(updateClipboardData(outlineIndex.initialUserClipboard));
dispatch(updateStatusBar({
courseReleaseDate,
highlightsEnabledForMessaging,
diff --git a/src/course-outline/hooks.jsx b/src/course-outline/hooks.jsx
index 900522656..eaa992e88 100644
--- a/src/course-outline/hooks.jsx
+++ b/src/course-outline/hooks.jsx
@@ -4,7 +4,6 @@ import { useNavigate } from 'react-router-dom';
import { useToggle } from '@openedx/paragon';
import { getConfig } from '@edx/frontend-platform';
-import { copyToClipboard } from '../generic/data/thunks';
import { getSavingStatus as getGenericSavingStatus } from '../generic/data/selectors';
import { getWaffleFlags } from '../data/selectors';
import { RequestStatus } from '../data/constants';
@@ -74,6 +73,7 @@ const useCourseOutline = ({ courseId }) => {
mfeProctoredExamSettingsUrl,
advanceSettingsUrl,
} = useSelector(getOutlineIndexData);
+
const { outlineIndexLoadingStatus, reIndexLoadingStatus } = useSelector(getLoadingStatus);
const statusBarData = useSelector(getStatusBarData);
const savingStatus = useSelector(getSavingStatus);
@@ -97,10 +97,6 @@ const useCourseOutline = ({ courseId }) => {
const isSavingStatusFailed = savingStatus === RequestStatus.FAILED || genericSavingStatus === RequestStatus.FAILED;
- const handleCopyToClipboardClick = (usageKey) => {
- dispatch(copyToClipboard(usageKey));
- };
-
const handlePasteClipboardClick = (parentLocator, sectionId) => {
dispatch(pasteClipboardContent(parentLocator, sectionId));
};
@@ -342,7 +338,6 @@ const useCourseOutline = ({ courseId }) => {
openUnitPage,
handleNewUnitSubmit,
handleVideoSharingOptionChange,
- handleCopyToClipboardClick,
handlePasteClipboardClick,
notificationDismissUrl,
discussionsSettings,
diff --git a/src/course-outline/subsection-card/SubsectionCard.jsx b/src/course-outline/subsection-card/SubsectionCard.jsx
index 9f134fdbe..032178618 100644
--- a/src/course-outline/subsection-card/SubsectionCard.jsx
+++ b/src/course-outline/subsection-card/SubsectionCard.jsx
@@ -16,7 +16,7 @@ import { RequestStatus } from '../../data/constants';
import CardHeader from '../card-header/CardHeader';
import SortableItem from '../../generic/drag-helper/SortableItem';
import { DragContext } from '../../generic/drag-helper/DragContextProvider';
-import { useCopyToClipboard, PasteComponent } from '../../generic/clipboard';
+import { useClipboard, PasteComponent } from '../../generic/clipboard';
import TitleButton from '../card-header/TitleButton';
import XBlockStatus from '../xblock-status/XBlockStatus';
import { getItemStatus, getItemStatusBorder, scrollToElement } from '../utils';
@@ -49,7 +49,7 @@ const SubsectionCard = ({
const isScrolledToElement = locatorId === subsection.id;
const [isFormOpen, openForm, closeForm] = useToggle(false);
const namePrefix = 'subsection';
- const { sharedClipboardData, showPasteUnit } = useCopyToClipboard();
+ const { sharedClipboardData, showPasteUnit } = useClipboard();
const {
id,
@@ -233,7 +233,7 @@ const SubsectionCard = ({
>
{intl.formatMessage(messages.newUnitButton)}
- {enableCopyPasteUnits && showPasteUnit && (
+ {enableCopyPasteUnits && showPasteUnit && sharedClipboardData && (
{
const currentRef = useRef(null);
@@ -41,6 +41,8 @@ const UnitCard = ({
const [isFormOpen, openForm, closeForm] = useToggle(false);
const namePrefix = 'unit';
+ const { copyToClipboard } = useClipboard();
+
const {
id,
category,
@@ -98,7 +100,7 @@ const UnitCard = ({
};
const handleCopyClick = () => {
- onCopyToClipboardClick(unit.id);
+ copyToClipboard(id);
};
const titleComponent = (
@@ -241,7 +243,6 @@ UnitCard.propTypes = {
onOrderChange: PropTypes.func.isRequired,
isSelfPaced: PropTypes.bool.isRequired,
isCustomRelativeDatesActive: PropTypes.bool.isRequired,
- onCopyToClipboardClick: PropTypes.func.isRequired,
discussionsSettings: PropTypes.shape({
providerType: PropTypes.string,
enableGradedUnits: PropTypes.bool,
diff --git a/src/course-outline/unit-card/UnitCard.test.jsx b/src/course-outline/unit-card/UnitCard.test.jsx
index 192963375..db0ed71aa 100644
--- a/src/course-outline/unit-card/UnitCard.test.jsx
+++ b/src/course-outline/unit-card/UnitCard.test.jsx
@@ -1,4 +1,3 @@
-import React from 'react';
import {
act, render, fireEvent, within,
} from '@testing-library/react';
@@ -48,6 +47,13 @@ const unit = {
const queryClient = new QueryClient();
+const clipboardBroadcastChannelMock = {
+ postMessage: jest.fn(),
+ close: jest.fn(),
+};
+
+global.BroadcastChannel = jest.fn(() => clipboardBroadcastChannelMock);
+
const renderComponent = (props) => render(
@@ -62,7 +68,6 @@ const renderComponent = (props) => render(
onOpenPublishModal={jest.fn()}
onOpenDeleteModal={jest.fn()}
onOpenConfigureModal={jest.fn()}
- onCopyToClipboardClick={jest.fn()}
savingStatus=""
onEditSubmit={jest.fn()}
onDuplicateSubmit={jest.fn()}
diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx
index 121a95c6a..5212d1cd6 100644
--- a/src/course-unit/CourseUnit.test.jsx
+++ b/src/course-unit/CourseUnit.test.jsx
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
act, render, waitFor, within, screen,
} from '@testing-library/react';
@@ -67,6 +68,7 @@ import messages from './messages';
let axiosMock;
let store;
+let queryClient;
const courseId = '123';
const blockId = '567890';
const unitDisplayName = courseUnitIndexMock.metadata.display_name;
@@ -91,45 +93,6 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockedUsedNavigate,
}));
-jest.mock('@tanstack/react-query', () => ({
- useQuery: jest.fn(({ queryKey }) => {
- const taxonomyApiHooksModule = jest.requireActual('../taxonomy/data/apiHooks');
- const actualQueryKeys = taxonomyApiHooksModule.taxonomyQueryKeys;
-
- if (queryKey[0] === 'contentTaxonomyTags') {
- return {
- data: {
- taxonomies: [],
- },
- isSuccess: true,
- };
- } if (queryKey[0] === 'contentTagsCount') {
- return {
- data: 17,
- isSuccess: true,
- };
- }
- if (actualQueryKeys.all.includes(queryKey[0])) {
- return {
- data: {
- results: [],
- },
- isSuccess: true,
- };
- }
- return {
- data: {},
- isSuccess: true,
- };
- }),
- useQueryClient: jest.fn(() => ({
- setQueryData: jest.fn(),
- })),
- useMutation: jest.fn(() => ({
- mutateAsync: jest.fn(),
- })),
-}));
-
const clipboardBroadcastChannelMock = {
postMessage: jest.fn(),
close: jest.fn(),
@@ -157,7 +120,9 @@ const RootWrapper = () => (
-
+
+
+
@@ -176,6 +141,13 @@ describe('', () => {
window.scrollTo = jest.fn();
global.localStorage.clear();
store = initializeStore();
+ queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+ });
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock
.onGet(getClipboardUrl())
@@ -194,7 +166,7 @@ describe('', () => {
await executeThunk(fetchCourseVerticalChildrenData(blockId), store.dispatch);
axiosMock
.onGet(getContentTaxonomyTagsApiUrl(blockId))
- .reply(200, {});
+ .reply(200, { taxonomies: [] });
axiosMock
.onGet(getContentTaxonomyTagsCountApiUrl(blockId))
.reply(200, 17);
@@ -1312,13 +1284,6 @@ describe('', () => {
enable_copy_paste_units: true,
});
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- user_clipboard: clipboardUnit,
- });
-
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
@@ -1368,11 +1333,9 @@ describe('', () => {
});
axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- user_clipboard: clipboardXBlock,
- });
+ .onGet(getClipboardUrl())
+ .reply(200, clipboardXBlock);
+
axiosMock
.onGet(getCourseUnitApiUrl(courseId))
.reply(200, {
@@ -1443,13 +1406,6 @@ describe('', () => {
enable_copy_paste_units: true,
});
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- user_clipboard: clipboardUnit,
- });
-
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
@@ -1502,13 +1458,6 @@ describe('', () => {
enable_copy_paste_units: true,
});
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- user_clipboard: clipboardUnit,
- });
-
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
@@ -1563,13 +1512,6 @@ describe('', () => {
enable_copy_paste_units: true,
});
- axiosMock
- .onGet(getCourseSectionVerticalApiUrl(blockId))
- .reply(200, {
- ...courseSectionVerticalMock,
- user_clipboard: clipboardUnit,
- });
-
await executeThunk(fetchCourseUnitQuery(courseId), store.dispatch);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
diff --git a/src/course-unit/data/thunk.js b/src/course-unit/data/thunk.js
index c6cdde318..c931bf506 100644
--- a/src/course-unit/data/thunk.js
+++ b/src/course-unit/data/thunk.js
@@ -8,7 +8,6 @@ import { handleResponseErrors } from '../../generic/saving-error-alert';
import { RequestStatus } from '../../data/constants';
import { NOTIFICATION_MESSAGES } from '../../constants';
import { updateModel, updateModels } from '../../generic/model-store';
-import { updateClipboardData } from '../../generic/data/slice';
import { messageTypes } from '../constants';
import {
getCourseUnitData,
@@ -77,7 +76,6 @@ export function fetchCourseSectionVerticalData(courseId, sequenceId) {
}));
dispatch(fetchStaticFileNoticesSuccess(JSON.parse(localStorage.getItem('staticFileNotices'))));
localStorage.removeItem('staticFileNotices');
- dispatch(updateClipboardData(courseSectionVerticalData.userClipboard));
dispatch(fetchSequenceSuccess({ sequenceId }));
return true;
} catch (error) {
@@ -230,8 +228,6 @@ export function deleteUnitItemQuery(itemId, xblockId, sendMessageToIframe) {
try {
await deleteUnitItem(xblockId);
sendMessageToIframe(messageTypes.completeXBlockDeleting, { locator: xblockId });
- const { userClipboard } = await getCourseSectionVerticalData(itemId);
- dispatch(updateClipboardData(userClipboard));
const courseUnit = await getCourseUnitData(itemId);
dispatch(fetchCourseItemSuccess(courseUnit));
dispatch(hideProcessingNotification());
diff --git a/src/course-unit/hooks.jsx b/src/course-unit/hooks.jsx
index 915aa9ff0..14045fb0c 100644
--- a/src/course-unit/hooks.jsx
+++ b/src/course-unit/hooks.jsx
@@ -6,7 +6,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import { useToggle } from '@openedx/paragon';
import { RequestStatus } from '../data/constants';
-import { useCopyToClipboard } from '../generic/clipboard';
+import { useClipboard } from '../generic/clipboard';
import { useEventListener } from '../generic/hooks';
import { COURSE_BLOCK_NAMES } from '../constants';
import { messageTypes, PUBLISH_TYPES } from './constants';
@@ -62,7 +62,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
const courseOutlineInfo = useSelector(getCourseOutlineInfo);
const movedXBlockParams = useSelector(getMovedXBlockParams);
const { currentlyVisibleToStudents } = courseUnit;
- const { sharedClipboardData, showPasteXBlock, showPasteUnit } = useCopyToClipboard(canEdit);
+ const { sharedClipboardData, showPasteXBlock, showPasteUnit } = useClipboard(canEdit);
const { canPasteComponent } = courseVerticalChildren;
const { displayName: unitTitle, category: unitCategory } = xblockInfo;
const sequenceId = courseUnit.ancestorInfo?.ancestors[0].id;
diff --git a/src/course-unit/sidebar/components/sidebar-footer/ActionButtons.jsx b/src/course-unit/sidebar/components/sidebar-footer/ActionButtons.jsx
index 5f78ae761..645651a27 100644
--- a/src/course-unit/sidebar/components/sidebar-footer/ActionButtons.jsx
+++ b/src/course-unit/sidebar/components/sidebar-footer/ActionButtons.jsx
@@ -1,15 +1,14 @@
import PropTypes from 'prop-types';
-import { useDispatch, useSelector } from 'react-redux';
+import { useSelector } from 'react-redux';
import { Button } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Divider } from '../../../../generic/divider';
import { getCanEdit, getCourseUnitData } from '../../../data/selectors';
-import { copyToClipboard } from '../../../../generic/data/thunks';
+import { useClipboard } from '../../../../generic/clipboard';
import messages from '../../messages';
const ActionButtons = ({ openDiscardModal, handlePublishing }) => {
- const dispatch = useDispatch();
const intl = useIntl();
const {
id,
@@ -18,6 +17,7 @@ const ActionButtons = ({ openDiscardModal, handlePublishing }) => {
enableCopyPasteUnits,
} = useSelector(getCourseUnitData);
const canEdit = useSelector(getCanEdit);
+ const { copyToClipboard } = useClipboard();
return (
<>
@@ -40,7 +40,7 @@ const ActionButtons = ({ openDiscardModal, handlePublishing }) => {
<>