diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx index 0da11922fc..bc91e3e4eb 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx @@ -17,6 +17,7 @@ import QuestionWidget from './QuestionWidget'; import EditorContainer from '../../../EditorContainer'; import RawEditor from '../../../../sharedComponents/RawEditor'; import { ProblemTypeKeys } from '../../../../data/constants/problem'; +import { blockTypes } from '../../../../data/constants/app'; import { checkIfEditorsDirty, parseState, saveWarningModalToggle, getContent, @@ -29,6 +30,7 @@ import { saveBlock } from '../../../../hooks'; import { selectors } from '../../../../data/redux'; import { ProblemEditorContextProvider } from './ProblemEditorContext'; +import { ProblemEditorPluginSlot } from '../../../../../plugin-slots/ProblemEditorPluginSlot'; const EditProblemView = ({ returnFunction }) => { const intl = useIntl(); @@ -128,6 +130,7 @@ const EditProblemView = ({ returnFunction }) => { ) : ( + diff --git a/src/editors/containers/TextEditor/index.jsx b/src/editors/containers/TextEditor/index.jsx index 03fb498e9f..d04948ca29 100644 --- a/src/editors/containers/TextEditor/index.jsx +++ b/src/editors/containers/TextEditor/index.jsx @@ -10,6 +10,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform'; import { actions, selectors } from '../../data/redux'; +import { blockTypes } from '../../data/constants/app'; import { RequestKeys } from '../../data/constants/requests'; import EditorContainer from '../EditorContainer'; @@ -18,6 +19,7 @@ import * as hooks from './hooks'; import messages from './messages'; import TinyMceWidget from '../../sharedComponents/TinyMceWidget'; import { prepareEditorRef, replaceStaticWithAsset } from '../../sharedComponents/TinyMceWidget/hooks'; +import { TextEditorPluginSlot } from '../../../plugin-slots/TextEditorPluginSlot'; const TextEditor = ({ onClose, @@ -97,7 +99,12 @@ const TextEditor = ({ screenreadertext={intl.formatMessage(messages.spinnerScreenReaderText)} /> - ) : (selectEditor())} + ) : ( + <> + + {selectEditor()} + + )} ); diff --git a/src/plugin-slots/ProblemEditorPluginSlot/README.md b/src/plugin-slots/ProblemEditorPluginSlot/README.md new file mode 100644 index 0000000000..cf7bb2d7b2 --- /dev/null +++ b/src/plugin-slots/ProblemEditorPluginSlot/README.md @@ -0,0 +1,105 @@ +# ProblemEditorPluginSlot + +### Slot ID: `org.openedx.frontend.authoring.problem_editor_plugin.v1` + +### Slot ID Aliases +* `problem_editor_plugin_slot` + +### Plugin Props: + +* `blockType` - String. The type of problem block being edited (e.g., `problem-single-select`, `problem-multi-select`, `problem`, `advanced`). + +## Description + +The `ProblemEditorPluginSlot` is rendered inside the Problem Editor modal window for all major +problem XBlock types: + +* single-select +* multi-select +* dropdown +* numerical-input +* text-input + +It is a **generic extension point** that can host any React component, such as: + +- **Problem authoring helpers** (validation, hints, accessibility tips) +- **Preview or analysis tools** (show how a problem will render, check grading logic) +- **Integrations** (external content sources, tagging, metadata editors) + +Your component is responsible for interacting with the editor state (if needed) using +Redux, `window.tinymce`, CodeMirror, or other utilities provided by `frontend-app-authoring`. + +## Examples + +### Default content + +![Problem editor with default content](./images/screenshot_default.png) + +### Replaced with custom component + +The following `env.config.tsx` will add a centered `h1` tag im Problem editor. + +![🦶 in Problem editor slot](./images/screenshot_custom.png) + +```tsx +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.authoring.problem_editor_plugin.v1': { + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'my-problem-editor-helper', + type: DIRECT_PLUGIN, + RenderWidget: () => ( +

🦶

+ ), + }, + }, + ] + } + }, +} + +export default config; +``` + +### Custom component with plugin props + +![Paragon Alert component in Problem editor slot](./images/screenshot_with_alert.png) + +The following `env.config.tsx` example demonstrates how to add a custom component to the Problem Editor plugin slot that receives the plugin props. The example shows a Paragon Alert component that renders the current `blockType` provided by the slot: + +```jsx +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { Alert } from '@openedx/paragon'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.authoring.problem_editor_plugin.v1': { + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom-problem-editor-assistant', + priority: 1, + type: DIRECT_PLUGIN, + RenderWidget: ({ blockType }) => { + return ( + + Custom component for {blockType} problem editor 🤗🤗🤗 + + ); + }, + }, + op: PLUGIN_OPERATIONS.Insert, + }, + ] + } + }, +} + +export default config; +``` diff --git a/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_custom.png b/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_custom.png new file mode 100644 index 0000000000..bb13f110ac Binary files /dev/null and b/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_custom.png differ diff --git a/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_default.png b/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_default.png new file mode 100644 index 0000000000..4161e7e75d Binary files /dev/null and b/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_default.png differ diff --git a/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_with_alert.png b/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_with_alert.png new file mode 100644 index 0000000000..4d0c335aab Binary files /dev/null and b/src/plugin-slots/ProblemEditorPluginSlot/images/screenshot_with_alert.png differ diff --git a/src/plugin-slots/ProblemEditorPluginSlot/index.tsx b/src/plugin-slots/ProblemEditorPluginSlot/index.tsx new file mode 100644 index 0000000000..074df64027 --- /dev/null +++ b/src/plugin-slots/ProblemEditorPluginSlot/index.tsx @@ -0,0 +1,17 @@ +import { PluginSlot } from '@openedx/frontend-plugin-framework'; + +interface ProblemEditorPluginSlotProps { + blockType: string | null; +} + +export const ProblemEditorPluginSlot = ({ + blockType, +}: ProblemEditorPluginSlotProps) => ( + +); \ No newline at end of file diff --git a/src/plugin-slots/README.md b/src/plugin-slots/README.md index 6d6603dafd..d6c1cfb2bc 100644 --- a/src/plugin-slots/README.md +++ b/src/plugin-slots/README.md @@ -9,6 +9,8 @@ ## Course Unit page * [`org.openedx.frontend.authoring.course_unit_header_actions.v1`](./CourseUnitHeaderActionsSlot/) * [`org.openedx.frontend.authoring.course_unit_sidebar.v1`](./CourseAuthoringUnitSidebarSlot/) +* [`org.openedx.frontend.authoring.text_editor_plugin.v1`](./TextEditorPluginSlot/) +* [`org.openedx.frontend.authoring.problem_editor_plugin.v1`](./ProblemEditorPluginSlot/) ## Other Slots * [`org.openedx.frontend.authoring.additional_course_content_plugin.v1`](./AdditionalCourseContentPluginSlot/) diff --git a/src/plugin-slots/TextEditorPluginSlot/README.md b/src/plugin-slots/TextEditorPluginSlot/README.md new file mode 100644 index 0000000000..425e014a5f --- /dev/null +++ b/src/plugin-slots/TextEditorPluginSlot/README.md @@ -0,0 +1,104 @@ +# TextEditorPluginSlot + +### Slot ID: `org.openedx.frontend.authoring.text_editor_plugin.v1` + +### Slot ID Aliases +* `text_editor_plugin_slot` + +### Plugin Props: + +* `blockType` - String. The type of block being edited (e.g., `html`). + +## Description + +The `TextEditorPluginSlot` is rendered inside the Text Editor modal window for HTML XBlocks. +It is intended as a generic extension point that can host **any React component** – for example: + +- **Contextual helpers** (tips, validation messages, writing guides) +- **Content utilities** (templates, reusable snippets, glossary insert tools) +- **Integrations** (linking to external systems, analytics, metadata editors) + +By default, the slot is **empty**. Widgets are attached via `env.config.jsx` using the +`@openedx/frontend-plugin-framework`. + +The only prop your component receives from the slot is: + +- `blockType` – the current editor block type (for this slot it will typically be `html`). + +Your component is responsible for interacting with the editor (if needed) using Redux state, +DOM APIs, or other utilities provided by `frontend-app-authoring`. + +## Examples + +### Default content + +![HTML editor with default content](./images/screenshot_default.png) + +### Replaced with custom component + +The following `env.config.tsx` will add a centered `h1` tag im HTML editor. + +![🦶 in HTML editor slot](./images/screenshot_custom.png) + +```tsx +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.authoring.text_editor_plugin.v1': { + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'my-html-editor-helper', + type: DIRECT_PLUGIN, + RenderWidget: () => ( +

🦶

+ ), + }, + }, + ] + } + }, +} + +export default config; +``` + +### Custom component with plugin props + +![Paragon Alert component in HTML editor slot](./images/screenshot_with_alert.png) + +The following `env.config.tsx` example demonstrates how to add a custom component to the HTML Editor plugin slot that receives the plugin props. The example shows a Paragon Alert component that renders the current `blockType` provided by the slot: + +```jsx +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { Alert } from '@openedx/paragon'; + +const config = { + pluginSlots: { + 'org.openedx.frontend.authoring.text_editor_plugin.v1': { + plugins: [ + { + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom-html-editor-assistant', + priority: 1, + type: DIRECT_PLUGIN, + RenderWidget: ({ blockType }) => { + return ( + + Custom component for {blockType} HTML editor 🤗🤗🤗 + + ); + }, + }, + op: PLUGIN_OPERATIONS.Insert, + }, + ] + } + }, +} + +export default config; +``` diff --git a/src/plugin-slots/TextEditorPluginSlot/images/screenshot_custom.png b/src/plugin-slots/TextEditorPluginSlot/images/screenshot_custom.png new file mode 100644 index 0000000000..7679a92a17 Binary files /dev/null and b/src/plugin-slots/TextEditorPluginSlot/images/screenshot_custom.png differ diff --git a/src/plugin-slots/TextEditorPluginSlot/images/screenshot_default.png b/src/plugin-slots/TextEditorPluginSlot/images/screenshot_default.png new file mode 100644 index 0000000000..09c8dd6da8 Binary files /dev/null and b/src/plugin-slots/TextEditorPluginSlot/images/screenshot_default.png differ diff --git a/src/plugin-slots/TextEditorPluginSlot/images/screenshot_with_alert.png b/src/plugin-slots/TextEditorPluginSlot/images/screenshot_with_alert.png new file mode 100644 index 0000000000..fb3c8927f2 Binary files /dev/null and b/src/plugin-slots/TextEditorPluginSlot/images/screenshot_with_alert.png differ diff --git a/src/plugin-slots/TextEditorPluginSlot/index.tsx b/src/plugin-slots/TextEditorPluginSlot/index.tsx new file mode 100644 index 0000000000..949796e6be --- /dev/null +++ b/src/plugin-slots/TextEditorPluginSlot/index.tsx @@ -0,0 +1,17 @@ +import { PluginSlot } from '@openedx/frontend-plugin-framework'; + +interface TextEditorPluginSlotProps { + blockType: string; +} + +export const TextEditorPluginSlot = ({ + blockType, +}: TextEditorPluginSlotProps) => ( + +); \ No newline at end of file