Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
846f949
feat: container sidebar in outline
navinkarkera Jan 15, 2026
e1ab06f
feat: library reference card
navinkarkera Jan 16, 2026
ae89bb7
refactor: unlink modal state and handlers
navinkarkera Jan 16, 2026
bb981fe
refactor: unlink action
navinkarkera Jan 19, 2026
ee22234
feat: handle sync issues in various situations
navinkarkera Jan 20, 2026
a2bf67a
refactor: simplify container edit in outline
navinkarkera Jan 20, 2026
baed4ba
refactor: outline children state syncing
navinkarkera Jan 20, 2026
b20c7e3
fix: rebase issues
navinkarkera Jan 20, 2026
5208f42
refactor: work on publish
navinkarkera Jan 21, 2026
620d48c
refactor: current item tracking
navinkarkera Jan 21, 2026
3bc53be
refactor: container selection in sidebar
navinkarkera Jan 22, 2026
632379b
fix: container selection usage
navinkarkera Jan 23, 2026
d9e39fd
feat: unit preview
navinkarkera Jan 23, 2026
c2b5956
feat: open button
navinkarkera Jan 23, 2026
d88c747
refactor: align sidebar respects selection
navinkarkera Jan 23, 2026
2031b84
fix: lint and type issues
navinkarkera Jan 23, 2026
f903fa4
fix: tests
navinkarkera Jan 27, 2026
1d0ec6d
fix: lint issues
navinkarkera Jan 27, 2026
e0c40c4
fix: lint issues
navinkarkera Jan 27, 2026
1c735ac
test: improve coverage
navinkarkera Jan 27, 2026
93d4540
feat: resizable and responsive sidebar
navinkarkera Jan 27, 2026
114fb03
refactor: parent calc
navinkarkera Jan 28, 2026
0fdc480
refactor: add sidebar actions
navinkarkera Jan 28, 2026
035ac0f
feat: alert
navinkarkera Jan 28, 2026
e499f06
feat: new unit handler
navinkarkera Jan 29, 2026
e8a5691
refactor: currentflow
navinkarkera Jan 29, 2026
f9acc9f
fix: sync issues in unlink, publish and general updates
navinkarkera Jan 29, 2026
9027db1
fix: course outline status bar unpublished badge sync
navinkarkera Jan 29, 2026
28bd0f5
fix: lint issues
navinkarkera Jan 29, 2026
1b5bd86
refactor: delete
navinkarkera Feb 1, 2026
b835505
fix: rebase issues and delete typo
navinkarkera Feb 1, 2026
f078c6f
fix: lint issues
navinkarkera Feb 1, 2026
a72cd7f
fix: failing tests
navinkarkera Feb 2, 2026
065831c
test: add tests
navinkarkera Feb 2, 2026
fa93bc6
refactor: add sidebar tests
navinkarkera Feb 2, 2026
97d80bc
fix: lint issues
navinkarkera Feb 2, 2026
1e53fcf
fix: test
navinkarkera Feb 2, 2026
56b9254
fix: coverage
navinkarkera Feb 3, 2026
4462389
fix: coverage
navinkarkera Feb 3, 2026
3301fc9
fix: lint issues
navinkarkera Feb 3, 2026
79f78c9
fix: coverage
navinkarkera Feb 3, 2026
87d0347
fix: flaky test
navinkarkera Feb 3, 2026
2a704a4
fix: coverage
navinkarkera Feb 3, 2026
4ff8a16
refactor: close title edit form only after update is complete
navinkarkera Feb 4, 2026
39f5380
chore: apply review suggestions
navinkarkera Feb 4, 2026
88e606f
fix: outline sidebar page provider env variable check
navinkarkera Feb 4, 2026
c9c55c1
fix: lint issues
navinkarkera Feb 4, 2026
32f9e8f
fix: tests
navinkarkera Feb 4, 2026
102034e
fix: cycle import
navinkarkera Feb 4, 2026
77608f7
fix: tests
navinkarkera Feb 4, 2026
ee9fa58
refactor: remove pages props from page context
navinkarkera Feb 5, 2026
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
91 changes: 85 additions & 6 deletions src/CourseAuthoringContext.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { getConfig } from '@edx/frontend-platform';
import { createContext, useContext, useMemo } from 'react';
import {
createContext, useContext, useMemo, useState,
} from 'react';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { useCreateCourseBlock } from '@src/course-outline/data/apiHooks';
import { getCourseItem } from '@src/course-outline/data/api';
import { useDispatch, useSelector } from 'react-redux';
import { addSection, addSubsection, updateSavingStatus } from '@src/course-outline/data/slice';
import {
addSection, addSubsection, addUnit, updateSavingStatus,
} from '@src/course-outline/data/slice';
import { useNavigate } from 'react-router';
import { getOutlineIndexData } from '@src/course-outline/data/selectors';
import { RequestStatus, RequestStatusType } from './data/constants';
import { useCourseDetails, useWaffleFlags } from './data/apiHooks';
import { useToggleWithValue } from '@src/hooks';
import { SelectionState, XBlock } from '@src/data/types';
import { CourseDetailsData } from './data/api';
import { useCourseDetails, useWaffleFlags } from './data/apiHooks';
import { RequestStatus, RequestStatusType } from './data/constants';

type ModalState = {
value: XBlock;
subsectionId?: string;
sectionId?: string;
};

export type CourseAuthoringContextData = {
/** The ID of the current course */
Expand All @@ -20,9 +32,20 @@ export type CourseAuthoringContextData = {
canChangeProviders: boolean;
handleAddSection: ReturnType<typeof useCreateCourseBlock>;
handleAddSubsection: ReturnType<typeof useCreateCourseBlock>;
handleAddAndOpenUnit: ReturnType<typeof useCreateCourseBlock>;
handleAddUnit: ReturnType<typeof useCreateCourseBlock>;
openUnitPage: (locator: string) => void;
getUnitUrl: (locator: string) => string;
isUnlinkModalOpen: boolean;
currentUnlinkModalData?: ModalState;
openUnlinkModal: (value: ModalState) => void;
closeUnlinkModal: () => void;
isPublishModalOpen: boolean;
currentPublishModalData?: ModalState;
openPublishModal: (value: ModalState) => void;
closePublishModal: () => void;
currentSelection?: SelectionState;
setCurrentSelection: React.Dispatch<React.SetStateAction<SelectionState | undefined>>;
};

/**
Expand Down Expand Up @@ -50,6 +73,26 @@ export const CourseAuthoringProvider = ({
const canChangeProviders = getAuthenticatedUser().administrator || new Date(courseDetails?.start ?? 0) > new Date();
const { courseStructure } = useSelector(getOutlineIndexData);
const { id: courseUsageKey } = courseStructure || {};
const [
isUnlinkModalOpen,
currentUnlinkModalData,
openUnlinkModal,
closeUnlinkModal,
] = useToggleWithValue<ModalState>();
const [
isPublishModalOpen,
currentPublishModalData,
openPublishModal,
closePublishModal,
] = useToggleWithValue<ModalState>();
/**
* This will hold the state of current item that is being operated on,
* For example:
* - the details of container that is being edited.
* - the details of container of which see more dropdown is open.
* It is mostly used in modals which should be soon be replaced with its equivalent in sidebar.
*/
const [currentSelection, setCurrentSelection] = useState<SelectionState | undefined>();

const getUnitUrl = (locator: string) => {
if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) {
Expand All @@ -62,7 +105,7 @@ export const CourseAuthoringProvider = ({
/**
* Open the unit page for a given locator.
*/
const openUnitPage = (locator: string) => {
const openUnitPage = async (locator: string) => {
const url = getUnitUrl(locator);
if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) {
// instanbul ignore next
Expand Down Expand Up @@ -95,12 +138,26 @@ export const CourseAuthoringProvider = ({
}
};

const addUnitToCourse = async (locator: string, parentLocator: string) => {
try {
const data = await getCourseItem(locator);
// istanbul ignore next
data.shouldScroll = true;
// istanbul ignore next
// Page should scroll to newly added subsection.
dispatch(addUnit({ parentLocator, data }));
} catch {
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
}
};

const handleAddSection = useCreateCourseBlock(addSectionToCourse);
const handleAddSubsection = useCreateCourseBlock(addSubsectionToCourse);
/**
* import a unit block from library and redirect user to this unit page.
*/
const handleAddUnit = useCreateCourseBlock(openUnitPage);
const handleAddAndOpenUnit = useCreateCourseBlock(openUnitPage);
const handleAddUnit = useCreateCourseBlock(addUnitToCourse);

const context = useMemo<CourseAuthoringContextData>(() => ({
courseId,
Expand All @@ -111,8 +168,19 @@ export const CourseAuthoringProvider = ({
handleAddSection,
handleAddSubsection,
handleAddUnit,
handleAddAndOpenUnit,
getUnitUrl,
openUnitPage,
isUnlinkModalOpen,
openUnlinkModal,
closeUnlinkModal,
currentUnlinkModalData,
isPublishModalOpen,
currentPublishModalData,
openPublishModal,
closePublishModal,
currentSelection,
setCurrentSelection,
}), [
courseId,
courseUsageKey,
Expand All @@ -122,8 +190,19 @@ export const CourseAuthoringProvider = ({
handleAddSection,
handleAddSubsection,
handleAddUnit,
handleAddAndOpenUnit,
getUnitUrl,
openUnitPage,
isUnlinkModalOpen,
openUnlinkModal,
closeUnlinkModal,
currentUnlinkModalData,
isPublishModalOpen,
currentPublishModalData,
openPublishModal,
closePublishModal,
currentSelection,
setCurrentSelection,
]);

return (
Expand Down
13 changes: 11 additions & 2 deletions src/CourseAuthoringRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
} from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { PageWrap } from '@edx/frontend-platform/react';
import { OutlineSidebarPagesProvider } from '@src/course-outline/outline-sidebar/OutlineSidebarPagesContext';
import { Textbooks } from './textbooks';
import CourseAuthoringPage from './CourseAuthoringPage';
import { PagesAndResources } from './pages-and-resources';
Expand All @@ -11,7 +12,7 @@ import VideoSelectorContainer from './selectors/VideoSelectorContainer';
import CustomPages from './custom-pages';
import { FilesPage, VideosPage } from './files-and-videos';
import { AdvancedSettings } from './advanced-settings';
import { CourseOutline } from './course-outline';
import { CourseOutline, OutlineSidebarProvider } from './course-outline';
import ScheduleAndDetails from './schedule-and-details';
import { GradingSettings } from './grading-settings';
import CourseTeam from './course-team/CourseTeam';
Expand Down Expand Up @@ -58,7 +59,15 @@ const CourseAuthoringRoutes = () => {
<Routes>
<Route
path="/"
element={<PageWrap><CourseOutline /></PageWrap>}
element={(
<PageWrap>
<OutlineSidebarPagesProvider>
<OutlineSidebarProvider>
<CourseOutline />
</OutlineSidebarProvider>
</OutlineSidebarPagesProvider>
</PageWrap>
)}
/>
<Route
path="course_info"
Expand Down
Loading
Loading