Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
3dae004
feat: container sidebar in outline
navinkarkera Jan 15, 2026
c37f86f
feat: library reference card
navinkarkera Jan 16, 2026
1740fce
refactor: unlink modal state and handlers
navinkarkera Jan 16, 2026
18a45ab
refactor: unlink action
navinkarkera Jan 19, 2026
8873821
feat: handle sync issues in various situations
navinkarkera Jan 20, 2026
7f6621a
refactor: simplify container edit in outline
navinkarkera Jan 20, 2026
d656b59
refactor: outline children state syncing
navinkarkera Jan 20, 2026
207eba2
fix: rebase issues
navinkarkera Jan 20, 2026
3ce5429
refactor: work on publish
navinkarkera Jan 21, 2026
9ede050
refactor: current item tracking
navinkarkera Jan 21, 2026
895d300
refactor: container selection in sidebar
navinkarkera Jan 22, 2026
fd3a700
fix: container selection usage
navinkarkera Jan 23, 2026
7714610
feat: unit preview
navinkarkera Jan 23, 2026
011b712
feat: open button
navinkarkera Jan 23, 2026
e63d1bc
refactor: align sidebar respects selection
navinkarkera Jan 23, 2026
dd47229
fix: lint and type issues
navinkarkera Jan 23, 2026
1668e2f
fix: tests
navinkarkera Jan 27, 2026
e37dbe1
fix: lint issues
navinkarkera Jan 27, 2026
9176253
fix: lint issues
navinkarkera Jan 27, 2026
61a7d5a
test: improve coverage
navinkarkera Jan 27, 2026
53d699f
feat: resizable and responsive sidebar
navinkarkera Jan 27, 2026
12caa47
refactor: parent calc
navinkarkera Jan 28, 2026
f3d1b3f
refactor: add sidebar actions
navinkarkera Jan 28, 2026
b4f0d43
feat: alert
navinkarkera Jan 28, 2026
a9814ac
feat: new unit handler
navinkarkera Jan 29, 2026
90b1d6c
refactor: currentflow
navinkarkera Jan 29, 2026
f19c2c5
fix: sync issues in unlink, publish and general updates
navinkarkera Jan 29, 2026
bc9ab4d
fix: course outline status bar unpublished badge sync
navinkarkera Jan 29, 2026
48dd188
fix: lint issues
navinkarkera Jan 29, 2026
1436c27
refactor: delete
navinkarkera Feb 1, 2026
4d34deb
fix: rebase issues and delete typo
navinkarkera Feb 1, 2026
a56961f
fix: lint issues
navinkarkera Feb 1, 2026
4976d0f
fix: failing tests
navinkarkera Feb 2, 2026
796e460
test: add tests
navinkarkera Feb 2, 2026
45dedab
refactor: add sidebar tests
navinkarkera Feb 2, 2026
922d40a
fix: lint issues
navinkarkera Feb 2, 2026
cddfe7c
fix: test
navinkarkera Feb 2, 2026
5b90b16
fix: coverage
navinkarkera Feb 3, 2026
e2db9d5
fix: coverage
navinkarkera Feb 3, 2026
1e942dc
fix: lint issues
navinkarkera Feb 3, 2026
46e694e
fix: coverage
navinkarkera Feb 3, 2026
581ec20
fix: flaky test
navinkarkera Feb 3, 2026
54dd134
fix: coverage
navinkarkera Feb 3, 2026
bc81321
refactor: close title edit form only after update is complete
navinkarkera Feb 4, 2026
4f8c4aa
chore: apply review suggestions
navinkarkera Feb 4, 2026
081a652
fix: outline sidebar page provider env variable check
navinkarkera Feb 4, 2026
588c68d
fix: lint issues
navinkarkera Feb 4, 2026
11afd97
fix: tests
navinkarkera Feb 4, 2026
11358a2
fix: cycle import
navinkarkera Feb 4, 2026
39428b1
fix: tests
navinkarkera Feb 4, 2026
08b3f41
refactor: remove pages props from page context
navinkarkera Feb 5, 2026
49fae1c
fix: rebase conflicts
navinkarkera Feb 6, 2026
f8a6255
refactor: centralize course outline data invalidation
navinkarkera Feb 6, 2026
d5d18e1
refactor: fix accessibility
navinkarkera Feb 6, 2026
ce7cb79
refactor: move sidebar info section related files in a folder
navinkarkera Feb 6, 2026
242c914
chore: add comment
navinkarkera Feb 6, 2026
a6a3e0a
fix: failing test
navinkarkera Feb 6, 2026
12b2569
fix: xblock unit typing and unlink sync issue
navinkarkera Feb 9, 2026
22a4e07
refactor: rename sharedComponents -> InfoSection
navinkarkera Feb 9, 2026
399212f
fix: coverage
navinkarkera Feb 9, 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
96 changes: 86 additions & 10 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, type UnitXBlock, type 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 | UnitXBlock;
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 All @@ -72,10 +115,9 @@ export const CourseAuthoringProvider = ({
}
};

const addSectionToCourse = async (locator: string) => {
const addSectionToCourse = /* istanbul ignore next */ async (locator: string) => {
try {
const data = await getCourseItem(locator);
// instanbul ignore next
// Page should scroll to newly added section.
data.shouldScroll = true;
dispatch(addSection(data));
Expand All @@ -84,23 +126,35 @@ export const CourseAuthoringProvider = ({
}
};

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

const addUnitToCourse = /* istanbul ignore next */ async (locator: string, parentLocator: string) => {
try {
const data = await getCourseItem(locator);
// Page should scroll to newly added subsection.
data.shouldScroll = true;
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 +165,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 +187,19 @@ export const CourseAuthoringProvider = ({
handleAddSection,
handleAddSubsection,
handleAddUnit,
handleAddAndOpenUnit,
getUnitUrl,
openUnitPage,
isUnlinkModalOpen,
openUnlinkModal,
closeUnlinkModal,
currentUnlinkModalData,
isPublishModalOpen,
currentPublishModalData,
openPublishModal,
closePublishModal,
currentSelection,
setCurrentSelection,
]);

return (
Expand Down
10 changes: 8 additions & 2 deletions src/CourseAuthoringRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import VideoSelectorContainer from './selectors/VideoSelectorContainer';
import CustomPages from './custom-pages';
import { FilesPage, VideosPage } from './files-and-videos';
import { AdvancedSettings } from './advanced-settings';
import { CourseOutline, OutlineSidebarPagesProvider } from './course-outline';
import {
CourseOutline,
OutlineSidebarProvider,
OutlineSidebarPagesProvider,
} from './course-outline';
import ScheduleAndDetails from './schedule-and-details';
import { GradingSettings } from './grading-settings';
import CourseTeam from './course-team/CourseTeam';
Expand Down Expand Up @@ -61,7 +65,9 @@ const CourseAuthoringRoutes = () => {
element={(
<PageWrap>
<OutlineSidebarPagesProvider>
<CourseOutline />
<OutlineSidebarProvider>
<CourseOutline />
</OutlineSidebarProvider>
</OutlineSidebarPagesProvider>
</PageWrap>
)}
Expand Down
Loading