Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ab0e0d7
refactor: remove custom order function from course libraries list (#1…
navinkarkera May 1, 2025
b375806
perf: use Library search results to populate container card preview […
pomegranited May 2, 2025
8ffafc0
fix: manage access modal on duplicated xblock (#1874)
ihor-romaniuk May 7, 2025
d5e36cf
fix: unit pages ux bugs [FC-0083] (#1884) (#1916)
rpenido May 7, 2025
79f865b
fix: UX issues in unit page (#1913) (#1923)
navinkarkera May 8, 2025
6c4634e
fix: invalidate search results when publishing all changes in library…
pomegranited May 9, 2025
a162929
fix: improve focus/selected style on library authoring (#1918) (#1930)
rpenido May 12, 2025
fab786a
fix: review/sync bugs [FC-0083] (#1905) (#1941)
rpenido May 12, 2025
3d6e221
fix: Issue with read-only units in libraries & published version of u…
ChrisChV May 13, 2025
1919eb4
fix: search modal refresh on typing (#1938) (#1948)
navinkarkera May 14, 2025
403dfa1
[Teak] backport #1949, #1999 and #2002 (#2006)
navinkarkera May 21, 2025
976dfca
fix: change InplaceTextEditor style and add optimistic update (#1953)…
rpenido May 21, 2025
dd731a0
fix: rename library publish button (#2015)
rpenido May 21, 2025
944d131
fix: do open editor of new xblock when duplicating (#2017)
DanielVZ96 May 22, 2025
212a54f
[Teak] fix: Inconsistent publish status filter menu placement & fix: …
ChrisChV May 22, 2025
317bc75
fix: refresh xblock inline after accepting/rejecting library sync (#2…
pomegranited May 23, 2025
e34df7f
fix: set maxHeight on TextEditor TinyMce widget [FC-0090] (#2024) (#2…
pomegranited May 26, 2025
7dfd93d
fix: upstreamInfo is not always provided (#2041) (#2042)
pomegranited May 29, 2025
d325a92
fix: selection card wiggle (#2047)
rpenido May 29, 2025
2beb91c
fix: set unit preview readonly on sidebar (#2008) (#2059)
rpenido Jun 2, 2025
19ef805
fix: backport changes for html button in text component markdown edit…
tonybusa Jun 4, 2025
1ff5e5b
fix: markdown editor issues in modal (#2076)
Anas12091101 Jun 4, 2025
efb1a28
fix: Expand all now expands subsections (#2085)
vmnavarro94 Jun 5, 2025
fcdf1fd
fix: files & uploads menu was truncated due to overflow-x (#2071) (#2…
diana-villalvazo-wgu Jun 5, 2025
3e737b5
fix: (backport) remove an extra editing xblock modal on unit page (#2…
ihor-romaniuk Jun 11, 2025
1968d14
fix: (backport) enable markdown editor in libraries (#2098)
bradenmacdonald Jun 12, 2025
86d0a7e
fix: remove icon and empty breadcrumb from libraries (#2129) (#2133)
diana-villalvazo-wgu Jun 12, 2025
4ba8cde
fix: (backport) text truncate issue in the search modal (#2151)
bydawen Jun 16, 2025
c9896a8
[Teak] fix: published name in unit sidebar in container picker & Issu…
ChrisChV Jun 18, 2025
b6bd94c
feat: add `v2` `CourseAuthoringUnitSidebarSlot` (#2000)
tecoholic Jun 10, 2025
92c59cb
fix: advanced-settings api should not camel-case return value (backpo…
jignaciopm Jun 19, 2025
bdc99fd
fix: clear selection on files & uploads page after deleting (backport…
bra-i-am Jul 7, 2025
2973614
fix: loading unit page directly from link after logging in in Teak (#…
Anas12091101 Jul 9, 2025
4bc34c2
fix: pages and resources plugins not rendered (#1885)
jansenk Apr 30, 2025
7e0b7f9
docs: (backport) adding comprehensive readme documentation for plugin…
jacobo-dominguez-wgu Jul 29, 2025
06497bf
fix: publish btn doesn't show after component edit
Jun 27, 2025
4f1ac9a
fix: making some adjustments to resolve conflicts
DeimerM Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/course-unit/CourseUnit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const CourseUnit = ({ courseId }) => {
courseUnit,
isLoading,
sequenceId,
courseUnitLoadingStatus,
unitTitle,
unitCategory,
errorMessage,
Expand Down Expand Up @@ -210,6 +211,7 @@ const CourseUnit = ({ courseId }) => {
courseId={courseId}
blockId={blockId}
isUnitVerticalType={isUnitVerticalType}
courseUnitLoadingStatus={courseUnitLoadingStatus}
unitXBlockActions={unitXBlockActions}
courseVerticalChildren={courseVerticalChildren.children}
handleConfigureSubmit={handleConfigureSubmit}
Expand Down
13 changes: 8 additions & 5 deletions src/course-unit/course-sequence/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ export function useSequenceNavigationMetadata(courseId, currentSequenceId, curre
const isLastUnit = !nextUrl;
const sequenceIds = useSelector(getSequenceIds);
const sequenceIndex = sequenceIds.indexOf(currentSequenceId);
const unitIndex = sequence.unitIds.indexOf(currentUnitId);
let unitIndex = sequence?.unitIds.indexOf(currentUnitId);

const nextSequenceId = sequenceIndex < sequenceIds.length - 1 ? sequenceIds[sequenceIndex + 1] : null;
const previousSequenceId = sequenceIndex > 0 ? sequenceIds[sequenceIndex - 1] : null;

if (!unitIndex) {
// Handle case where unitIndex is not found
unitIndex = 0;
}
let nextLink;
const nextIndex = unitIndex + 1;

if (nextIndex < sequence.unitIds.length) {
const nextUnitId = sequence.unitIds[nextIndex];
if (nextIndex < sequence?.unitIds.length) {
const nextUnitId = sequence?.unitIds[nextIndex];
nextLink = `/course/${courseId}/container/${nextUnitId}/${currentSequenceId}`;
} else if (nextSequenceId) {
const pathToNextUnit = decodeURIComponent(nextUrl);
Expand All @@ -32,7 +35,7 @@ export function useSequenceNavigationMetadata(courseId, currentSequenceId, curre
const previousIndex = unitIndex - 1;

if (previousIndex >= 0) {
const previousUnitId = sequence.unitIds[previousIndex];
const previousUnitId = sequence?.unitIds[previousIndex];
previousLink = `/course/${courseId}/container/${previousUnitId}/${currentSequenceId}`;
} else if (previousSequenceId) {
const pathToPreviousUnit = decodeURIComponent(prevUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ const SequenceNavigation = ({

const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < breakpoints.small.minWidth;
const renderUnitButtons = () => {
if (sequence.unitIds?.length === 0 || unitId === null) {
if (sequence?.unitIds?.length === 0 || unitId === null) {
return (
<div style={{ flexBasis: '100%', minWidth: 0, borderBottom: 'solid 1px #EAEAEA' }} />
);
}

return (
<SequenceNavigationTabs
unitIds={sequence.unitIds || []}
unitIds={sequence?.unitIds || []}
unitId={unitId}
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
showPasteUnit={showPasteUnit}
Expand Down
2 changes: 1 addition & 1 deletion src/course-unit/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const getCourseVerticalChildren = (state) => state.courseUnit.courseVerti
export const getCourseOutlineInfo = (state) => state.courseUnit.courseOutlineInfo;
export const getCourseOutlineInfoLoadingStatus = (state) => state.courseUnit.courseOutlineInfoLoadingStatus;
export const getMovedXBlockParams = (state) => state.courseUnit.movedXBlockParams;
const getLoadingStatuses = (state) => state.courseUnit.loadingStatus;
export const getLoadingStatuses = (state) => state.courseUnit.loadingStatus;
export const getIsLoading = createSelector(
[getLoadingStatuses],
loadingStatus => Object.values(loadingStatus)
Expand Down
21 changes: 21 additions & 0 deletions src/course-unit/hooks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
getSavingStatus,
getSequenceStatus,
getStaticFileNotices,
getLoadingStatuses,
} from './data/selectors';
import {
changeEditTitleFormOpen,
Expand All @@ -51,6 +52,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
const [isMoveModalOpen, openMoveModal, closeMoveModal] = useToggle(false);

const courseUnit = useSelector(getCourseUnitData);
const courseUnitLoadingStatus = useSelector(getLoadingStatuses);
const savingStatus = useSelector(getSavingStatus);
const isLoading = useSelector(getIsLoading);
const errorMessage = useSelector(getErrorMessage);
Expand Down Expand Up @@ -215,9 +217,28 @@ export const useCourseUnit = ({ courseId, blockId }) => {
}
}, [isMoveModalOpen]);

useEffect(() => {
const handlePageRefreshUsingStorage = (event) => {
// ignoring tests for if block, because it triggers when someone
// edits the component using editor which has a separate store
/* istanbul ignore next */
if (event.key === 'courseRefreshTriggerOnComponentEditSave') {
dispatch(fetchCourseSectionVerticalData(blockId, sequenceId));
dispatch(fetchCourseVerticalChildrenData(blockId, isSplitTestType));
localStorage.removeItem(event.key);
}
};

window.addEventListener('storage', handlePageRefreshUsingStorage);
return () => {
window.removeEventListener('storage', handlePageRefreshUsingStorage);
};
}, [blockId, sequenceId, isSplitTestType]);

return {
sequenceId,
courseUnit,
courseUnitLoadingStatus,
unitTitle,
unitCategory,
errorMessage,
Expand Down
2 changes: 1 addition & 1 deletion src/course-unit/sidebar/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ export const getIconVariant = (visibilityState, published, hasChanges) => {
* @param {string} id - The course unit ID.
* @returns {string} The clear course unit ID extracted from the provided data.
*/
export const extractCourseUnitId = (id) => id.match(/block@(.+)$/)[1];
export const extractCourseUnitId = (id) => id?.match(/block@(.+)$/)[1];
26 changes: 25 additions & 1 deletion src/course-unit/xblock-container-iframe/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,16 @@ import { useIframeContent } from '../../generic/hooks/useIframeContent';
import { useIframeMessages } from '../../generic/hooks/useIframeMessages';
import VideoSelectorPage from '../../editors/VideoSelectorPage';
import EditorPage from '../../editors/EditorPage';
import { RequestStatus } from '../../data/constants';

const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
courseId, blockId, unitXBlockActions, courseVerticalChildren, handleConfigureSubmit, isUnitVerticalType,
courseId,
blockId,
unitXBlockActions,
courseVerticalChildren,
handleConfigureSubmit,
isUnitVerticalType,
courseUnitLoadingStatus,
}) => {
const intl = useIntl();
const dispatch = useDispatch();
Expand Down Expand Up @@ -70,6 +77,23 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
setIframeRef(iframeRef);
}, [setIframeRef]);

useEffect(() => {
const iframe = iframeRef?.current;
if (!iframe) { return undefined; }

const handleIframeLoad = () => {
if (courseUnitLoadingStatus.fetchUnitLoadingStatus === RequestStatus.FAILED) {
window.location.reload();
}
};

iframe.addEventListener('load', handleIframeLoad);

return () => {
iframe.removeEventListener('load', handleIframeLoad);
};
}, [iframeRef]);

const onXBlockSave = useCallback(/* istanbul ignore next */ () => {
closeXBlockEditorModal();
closeVideoSelectorModal();
Expand Down
5 changes: 5 additions & 0 deletions src/course-unit/xblock-container-iframe/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export interface XBlockContainerIframeProps {
courseId: string;
blockId: string;
isUnitVerticalType: boolean,
courseUnitLoadingStatus: {
fetchUnitLoadingStatus: string;
fetchVerticalChildrenLoadingStatus: string;
fetchXBlockDataLoadingStatus: string;
};
unitXBlockActions: {
handleDelete: (XBlockId: string | null) => void;
handleDuplicate: (XBlockId: string | null) => void;
Expand Down
10 changes: 10 additions & 0 deletions src/editors/data/redux/thunkActions/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ export const saveBlock = (content, returnToUnit) => (dispatch) => {
content,
onSuccess: (response) => {
dispatch(actions.app.setSaveResponse(response));
const parsedData = JSON.parse(response.config.data);
if (parsedData?.has_changes) {
const storageKey = 'courseRefreshTriggerOnComponentEditSave';
localStorage.setItem(storageKey, Date.now());

window.dispatchEvent(new StorageEvent('storage', {
key: storageKey,
newValue: Date.now().toString(),
}));
}
returnToUnit(response.data);
},
}));
Expand Down
6 changes: 5 additions & 1 deletion src/editors/data/redux/thunkActions/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,11 @@ describe('app thunkActions', () => {
});
it('dispatches actions.app.setSaveResponse with response and then calls returnToUnit', () => {
dispatch.mockClear();
const response = 'testRESPONSE';
const mockParsedData = { has_changes: true };
const response = {
config: { data: JSON.stringify(mockParsedData) },
data: {},
};
calls[1][0].saveBlock.onSuccess(response);
expect(dispatch).toHaveBeenCalledWith(actions.app.setSaveResponse(response));
expect(returnToUnit).toHaveBeenCalled();
Expand Down
9 changes: 0 additions & 9 deletions src/files-and-videos/files-page/FilesPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,6 @@ const mockStore = async (
}
renderComponent();
await executeThunk(fetchAssets(courseId), store.dispatch);

// Finish loading the expected files into the data table before returning,
// because loading new files can disrupt things like accessing file menus.
if (status === RequestStatus.SUCCESSFUL) {
const numFiles = skipNextPageFetch ? 13 : 15;
await waitFor(() => {
expect(screen.getByText(`Showing ${numFiles} of ${numFiles}`)).toBeInTheDocument();
});
}
};

const emptyMockStore = async (status) => {
Expand Down
2 changes: 1 addition & 1 deletion src/files-and-videos/files-page/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const slice = createSlice({
if (isEmpty(state.assetIds)) {
state.assetIds = payload.assetIds;
} else {
state.assetIds = [...state.assetIds, ...payload.assetIds];
state.assetIds = [...new Set([...state.assetIds, ...payload.assetIds])];
}
},
setSortedAssetIds: (state, { payload }) => {
Expand Down
12 changes: 10 additions & 2 deletions src/files-and-videos/generic/DeleteConfirmationModal.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
Expand All @@ -7,6 +7,7 @@ import {
AlertModal,
Button,
Collapsible,
DataTableContext,
Hyperlink,
Truncate,
} from '@openedx/paragon';
Expand All @@ -22,6 +23,13 @@ const DeleteConfirmationModal = ({
// injected
intl,
}) => {
const { clearSelection } = useContext(DataTableContext);

const handleConfirmDeletion = () => {
handleBulkDelete();
clearSelection();
};

const firstSelectedRow = selectedRows[0]?.original;
let activeContentRows = [];
if (Array.isArray(selectedRows)) {
Expand Down Expand Up @@ -73,7 +81,7 @@ const DeleteConfirmationModal = ({
<Button variant="tertiary" onClick={closeDeleteConfirmation}>
{intl.formatMessage(messages.cancelButtonLabel)}
</Button>
<Button onClick={handleBulkDelete}>
<Button onClick={handleConfirmDeletion}>
{intl.formatMessage(messages.deleteFileButtonLabel)}
</Button>
</ActionRow>
Expand Down
20 changes: 11 additions & 9 deletions src/files-and-videos/generic/FileTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@ const FileTable = ({
setSelectedRows={setSelectedRows}
fileType={fileType}
/>

<DeleteConfirmationModal
{...{
isDeleteConfirmationOpen,
closeDeleteConfirmation,
handleBulkDelete,
selectedRows,
fileType,
}}
/>
</DataTable>
<FileInput key="generic-file-upload" fileInput={fileInputControl} supportedFileFormats={supportedFileFormats} />
{!isEmpty(selectedRows) && (
Expand All @@ -286,15 +296,7 @@ const FileTable = ({
sidebar={infoModalSidebar}
/>
)}
<DeleteConfirmationModal
{...{
isDeleteConfirmationOpen,
closeDeleteConfirmation,
handleBulkDelete,
selectedRows,
fileType,
}}
/>

</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ const TableActions = ({
intl,
}) => {
const [isSortOpen, openSort, closeSort] = useToggle(false);
const { state } = useContext(DataTableContext);
const { state, clearSelection } = useContext(DataTableContext);

// This useEffect saves DataTable state so it can persist after table re-renders due to data reload.
useEffect(() => {
setInitialState(state);
}, [state]);

const handleOpenFileSelector = () => {
fileInputControl.click();
clearSelection();
};

return (
<>
<Button variant="outline-primary" onClick={openSort} iconBefore={Tune}>
Expand Down Expand Up @@ -71,7 +76,7 @@ const TableActions = ({
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Button iconBefore={Add} onClick={fileInputControl.click}>
<Button iconBefore={Add} onClick={handleOpenFileSelector}>
{intl.formatMessage(messages.addFilesButtonLabel, { fileType })}
</Button>
<SortAndFilterModal {...{ isSortOpen, closeSort, handleSort }} />
Expand Down
Loading
Loading