Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c82482d
feat: container sidebar in outline
navinkarkera Jan 15, 2026
3d1287e
feat: library reference card
navinkarkera Jan 16, 2026
d521e62
refactor: unlink modal state and handlers
navinkarkera Jan 16, 2026
ad81b8a
refactor: unlink action
navinkarkera Jan 19, 2026
d9a2fe2
feat: handle sync issues in various situations
navinkarkera Jan 20, 2026
6642f18
refactor: simplify container edit in outline
navinkarkera Jan 20, 2026
669c387
refactor: outline children state syncing
navinkarkera Jan 20, 2026
9b8fbc4
fix: rebase issues
navinkarkera Jan 20, 2026
84bced5
refactor: work on publish
navinkarkera Jan 21, 2026
f506510
refactor: current item tracking
navinkarkera Jan 21, 2026
bb292a2
refactor: container selection in sidebar
navinkarkera Jan 22, 2026
281e528
fix: container selection usage
navinkarkera Jan 23, 2026
a0e0808
feat: unit preview
navinkarkera Jan 23, 2026
1d0729a
feat: open button
navinkarkera Jan 23, 2026
aeefd84
refactor: align sidebar respects selection
navinkarkera Jan 23, 2026
aa6baa4
fix: lint and type issues
navinkarkera Jan 23, 2026
4cd7199
fix: tests
navinkarkera Jan 27, 2026
90fd540
fix: lint issues
navinkarkera Jan 27, 2026
1ec02ce
fix: lint issues
navinkarkera Jan 27, 2026
3c46197
test: improve coverage
navinkarkera Jan 27, 2026
38dc9da
feat: resizable and responsive sidebar
navinkarkera Jan 27, 2026
92cfd1b
refactor: parent calc
navinkarkera Jan 28, 2026
91ec93b
refactor: add sidebar actions
navinkarkera Jan 28, 2026
05059fc
feat: alert
navinkarkera Jan 28, 2026
58ea789
feat: new unit handler
navinkarkera Jan 29, 2026
3152bba
refactor: currentflow
navinkarkera Jan 29, 2026
ac3727d
fix: sync issues in unlink, publish and general updates
navinkarkera Jan 29, 2026
6c0c682
fix: course outline status bar unpublished badge sync
navinkarkera Jan 29, 2026
9ba08ce
fix: lint issues
navinkarkera Jan 29, 2026
def69a1
refactor: delete
navinkarkera Feb 1, 2026
5f22fe0
fix: rebase issues and delete typo
navinkarkera Feb 1, 2026
a715da2
fix: lint issues
navinkarkera Feb 1, 2026
e4fd32f
fix: failing tests
navinkarkera Feb 2, 2026
ec2ee95
test: add tests
navinkarkera Feb 2, 2026
3cbd51f
refactor: add sidebar tests
navinkarkera Feb 2, 2026
c1c579c
fix: lint issues
navinkarkera Feb 2, 2026
0ddd682
fix: test
navinkarkera Feb 2, 2026
858b876
fix: coverage
navinkarkera Feb 3, 2026
ba9c7aa
fix: coverage
navinkarkera Feb 3, 2026
c44cfb4
fix: lint issues
navinkarkera Feb 3, 2026
9793515
fix: coverage
navinkarkera Feb 3, 2026
cd461c5
fix: flaky test
navinkarkera Feb 3, 2026
e9d904b
fix: coverage
navinkarkera Feb 3, 2026
2ab660e
refactor: close title edit form only after update is complete
navinkarkera Feb 4, 2026
60930ca
chore: apply review suggestions
navinkarkera Feb 4, 2026
72f7956
fix: outline sidebar page provider env variable check
navinkarkera Feb 4, 2026
5025793
fix: lint issues
navinkarkera Feb 4, 2026
eebafe3
fix: tests
navinkarkera Feb 4, 2026
4e68013
fix: cycle import
navinkarkera Feb 4, 2026
e2cdfa8
fix: tests
navinkarkera Feb 4, 2026
5e63a5f
refactor: remove pages props from page context
navinkarkera Feb 5, 2026
c84d667
fix: rebase conflicts
navinkarkera Feb 6, 2026
8a915c4
refactor: centralize course outline data invalidation
navinkarkera Feb 6, 2026
6ec390f
refactor: fix accessibility
navinkarkera Feb 6, 2026
480e2fa
refactor: move sidebar info section related files in a folder
navinkarkera Feb 6, 2026
56152fc
chore: add comment
navinkarkera Feb 6, 2026
4c07979
fix: failing test
navinkarkera Feb 6, 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, 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 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