Merge branch 'openedx:master' into master

This commit is contained in:
Jorg Are
2025-06-04 16:03:48 +01:00
committed by GitHub
11 changed files with 169 additions and 38 deletions

View File

@@ -0,0 +1,18 @@
# Run the workflow that adds new tickets that are labelled "release testing"
# to the org-wide BTR project board
name: Add release testing issues to the BTR project board
on:
issues:
types: [labeled]
# This workflow is triggered when an issue is labeled with 'release testing'.
# It adds the issue to the BTR project and applies the 'needs triage' label
# if it doesn't already have it.
jobs:
handle-release-testing:
uses: openedx/.github/.github/workflows/add-issue-to-btr-project.yml@master
secrets:
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}

18
package-lock.json generated
View File

@@ -7884,9 +7884,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001718",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
"version": "1.0.30001720",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz",
"integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==",
"funding": [
{
"type": "opencollective",
@@ -18018,9 +18018,9 @@
}
},
"node_modules/prebuild-install/node_modules/tar-fs": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz",
"integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz",
"integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
@@ -21186,9 +21186,9 @@
}
},
"node_modules/tar-fs": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
"integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz",
"integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0",

View File

@@ -65,6 +65,7 @@ const DateSummary = ({
)}
{!linkedTitle && dateBlock.link && (
<a
id={dateBlock.dateType === 'verified-upgrade-deadline' ? 'date-verified-upgrade-deadline' : ''}
href={dateBlock.link}
onClick={dateBlock.dateType === 'verified-upgrade-deadline' ? logVerifiedUpgradeClick : () => {}}
className="description-link"

View File

@@ -13,17 +13,18 @@ import SequenceExamWrapper from '@edx/frontend-lib-special-exams';
import PageLoading from '@src/generic/PageLoading';
import { useModel } from '@src/generic/model-store';
import { useSequenceBannerTextAlert, useSequenceEntranceExamAlert } from '@src/alerts/sequence-alerts/hooks';
import SequenceContainerSlot from '../../../plugin-slots/SequenceContainerSlot';
import SequenceContainerSlot from '@src/plugin-slots/SequenceContainerSlot';
import { CourseOutlineSidebarSlot } from '@src/plugin-slots/CourseOutlineSidebarSlot';
import { CourseOutlineSidebarTriggerSlot } from '@src/plugin-slots/CourseOutlineSidebarTriggerSlot';
import { NotificationsDiscussionsSidebarSlot } from '@src/plugin-slots/NotificationsDiscussionsSidebarSlot';
import SequenceNavigationSlot from '@src/plugin-slots/SequenceNavigationSlot';
import { getCoursewareOutlineSidebarSettings } from '../../data/selectors';
import CourseLicense from '../course-license';
import { NotificationsDiscussionsSidebarSlot } from '../../../plugin-slots/NotificationsDiscussionsSidebarSlot';
import messages from './messages';
import HiddenAfterDue from './hidden-after-due';
import { SequenceNavigation, UnitNavigation } from './sequence-navigation';
import { UnitNavigation } from './sequence-navigation';
import SequenceContent from './SequenceContent';
import { CourseOutlineSidebarSlot } from '../../../plugin-slots/CourseOutlineSidebarSlot';
import { CourseOutlineSidebarTriggerSlot } from '../../../plugin-slots/CourseOutlineSidebarTriggerSlot';
const Sequence = ({
unitId,
@@ -172,7 +173,7 @@ const Sequence = ({
<div className="sequence w-100">
{!isEnabledOutlineSidebar && (
<div className="sequence-navigation-container">
<SequenceNavigation
<SequenceNavigationSlot
sequenceId={sequenceId}
unitId={unitId}
nextHandler={() => {

View File

@@ -7,7 +7,6 @@ import {
ChevronLeft as ChevronLeftIcon,
} from '@openedx/paragon/icons';
import { useModel } from '@src/generic/model-store';
import { LOADING } from '@src/constants';
import PageLoading from '@src/generic/PageLoading';
import SidebarSection from './components/SidebarSection';
@@ -35,13 +34,13 @@ const CourseOutlineTray = () => {
sequences,
} = useCourseOutlineSidebar();
const {
sectionId: activeSectionId,
} = useModel('sequences', activeSequenceId);
const resolvedSectionId = selectedSection
|| Object.keys(sections).find(
(sectionId) => sections[sectionId].sequenceIds.includes(activeSequenceId),
);
const sectionsIds = Object.keys(sections);
const sequenceIds = sections[selectedSection || activeSectionId]?.sequenceIds || [];
const backButtonTitle = sections[selectedSection || activeSectionId]?.title;
const sequenceIds = sections[resolvedSectionId]?.sequenceIds || [];
const backButtonTitle = sections[resolvedSectionId]?.title;
const handleBackToSectionLevel = () => {
setDisplaySectionLevel();

View File

@@ -328,6 +328,7 @@
.unit-navigation {
display: flex;
justify-content: center;
gap: 5px;
max-width: 640px;
margin: 0 auto;
@@ -344,27 +345,12 @@
border-radius: 6px;
}
}
.next-button {
flex-basis: 75%;
@media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
flex-basis: 100%;
}
}
.previous-button {
flex-basis: 25%;
@media (max-width: -1 + map-get($grid-breakpoints, "sm")) {
flex-basis: 100%;
}
}
}
.top-unit-navigation {
display: flex;
max-width: 100%;
gap: 5px;
justify-content: flex-end;
.next-button,

View File

@@ -23,4 +23,5 @@
* [`org.openedx.frontend.learning.progress_tab_grade_breakdown.v1`](./ProgressTabGradeBreakdownSlot/)
* [`org.openedx.frontend.learning.progress_tab_related_links.v1`](./ProgressTabRelatedLinksSlot/)
* [`org.openedx.frontend.learning.sequence_container.v1`](./SequenceContainerSlot/)
* [`org.openedx.frontend.learning.sequence_navigation.v1`](./SequenceNavigationSlot/)
* [`org.openedx.frontend.learning.unit_title.v1`](./UnitTitleSlot/)

View File

@@ -0,0 +1,80 @@
# Sequence Navigation Slot
### Slot ID: `org.openedx.frontend.learning.sequence_navigation.v1`
### Props:
* `sequenceId` (string) — Current sequence identifier
* `unitId` (string) — Current unit identifier
* `nextHandler` (function) — Handler for next navigation action
* `onNavigate` (function) — Handler for direct unit navigation
* `previousHandler` (function) — Handler for previous navigation action
## Description
This slot is used to replace/modify/hide the sequence navigation component that controls navigation between units within a course sequence.
## Example
### Default content
![Sequence navigation slot with default content](./screenshot_default.png)
### Replaced with custom component
![📖 in sequence navigation slot](./screenshot_custom.png)
The following `env.config.jsx` will replace the sequence navigation with a custom implementation that uses all available props.
```js
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
const config = {
pluginSlots: {
'org.openedx.frontend.learning.sequence_navigation.v1': {
keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'custom_sequence_navigation',
type: DIRECT_PLUGIN,
RenderWidget: ({ sequenceId, unitId, nextHandler, onNavigate, previousHandler }) => {
// Mock unit data for demonstration
const units = ['unit-1', 'unit-2', 'unit-3'];
return (
<Stack gap={2} direction="horizontal" className="p-3 bg-light w-100">
<Button
className="flex-grow-1"
onClick={previousHandler}
>
Previous
</Button>
<Stack gap={2} direction="horizontal">
{units.map((unit, index) => (
<Button
variant="outline-primary"
key={unit}
className={`btn btn-sm ${unitId === unit ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => onNavigate(unit)}
>
{index + 1}
</Button>
))}
</Stack>
<Button
className="flex-grow-1"
onClick={nextHandler}
>
Next
</Button>
</Stack>
)
},
},
},
]
}
},
}
export default config;
```

View File

@@ -0,0 +1,45 @@
import React from 'react';
import PropTypes from 'prop-types';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import { SequenceNavigation } from '../../courseware/course/sequence/sequence-navigation';
const SequenceNavigationSlot = ({
sequenceId,
unitId,
nextHandler,
onNavigate,
previousHandler,
}) => (
<PluginSlot
id="org.openedx.frontend.learning.sequence_navigation.v1"
slotOptions={{
mergeProps: true,
}}
pluginProps={{
sequenceId,
unitId,
nextHandler,
onNavigate,
previousHandler,
}}
>
<SequenceNavigation
sequenceId={sequenceId}
unitId={unitId}
nextHandler={nextHandler}
onNavigate={onNavigate}
previousHandler={previousHandler}
/>
</PluginSlot>
);
SequenceNavigationSlot.propTypes = {
sequenceId: PropTypes.string.isRequired,
unitId: PropTypes.string.isRequired,
nextHandler: PropTypes.func.isRequired,
onNavigate: PropTypes.func.isRequired,
previousHandler: PropTypes.func.isRequired,
};
export default SequenceNavigationSlot;

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 KiB