diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74e4db0cb..5d23bb1f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Dark mode for instuctions code block (#1187)
- Change markdown links to open in new tab (#1188)
- Update demo instructions text (#1189)
+- Syntax highlighting for custom instructions in Code Editor for Education (#1190)
### Changed
@@ -35,7 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Fixed
- Fix AWS CLI in deploy script to 2.22.35 to workaround cloudflare issue (See https://developers.cloudflare.com/r2/examples/aws/aws-cli/) (#1178)
-- Padding on instructions code block (#1184)
+- Padding on instructions code block (#1184, 1190)
## [0.28.14] - 2025-01-06
diff --git a/src/assets/stylesheets/Instructions.scss b/src/assets/stylesheets/Instructions.scss
index a2bad17f3..82d7b99bf 100644
--- a/src/assets/stylesheets/Instructions.scss
+++ b/src/assets/stylesheets/Instructions.scss
@@ -40,6 +40,13 @@
}
}
+ code {
+ color: $rpf-white;
+ background-color: $rpf-grey-700;
+ border-radius: 8px;
+ padding: calc(0.75 * $space-0-125) $space-0-5;
+ }
+
pre {
background-color: $rpf-grey-700;
border: 1px solid $rpf-grey-600;
@@ -47,13 +54,10 @@
padding: $space-0-5 $space-1;
overflow: auto;
margin: $space-1 0;
- }
- code {
- color: $rpf-white;
- background-color: $rpf-grey-700;
- border-radius: 8px;
- padding-block: calc(0.75 * $space-0-125);
+ code {
+ padding-inline: 0;
+ }
}
.c-project-code {
@@ -125,6 +129,27 @@
}
}
+ .language-javascript {
+ .number,
+ .boolean {
+ color: $rpf-syntax-1;
+ }
+ .keyword {
+ color: $rpf-syntax-4;
+ }
+ .string,
+ .char {
+ color: $rpf-syntax-2;
+ }
+ .comment {
+ color: $rpf-syntax-3;
+ }
+
+ .keyword-print {
+ color: $rpf-white;
+ }
+ }
+
.language-css {
color: $rpf-syntax-1;
diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx
index 0d3403fce..e52723b8f 100644
--- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx
+++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx
@@ -18,6 +18,8 @@ import DesignSystemButton from "../../../DesignSystemButton/DesignSystemButton";
import { setProjectInstructions } from "../../../../redux/EditorSlice";
import demoInstructions from "../../../../assets/markdown/demoInstructions.md";
import RemoveInstructionsModal from "../../../Modals/RemoveInstructionsModal";
+import Prism from "prismjs";
+import "prismjs/components/prism-python";
const InstructionsPanel = () => {
const [showModal, setShowModal] = useState(false);
@@ -50,11 +52,15 @@ const InstructionsPanel = () => {
const applySyntaxHighlighting = (container) => {
const codeElements = container.querySelectorAll(
- ".language-python, .language-html, .language-css",
+ ".language-python, .language-html, .language-css, .language-javascript",
);
codeElements.forEach((element) => {
- window.Prism.highlightElement(element);
+ if (window.syntaxHighlight) {
+ window.syntaxHighlight.highlightElement(element);
+ } else {
+ Prism.highlightElement(element);
+ }
});
};
diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js
index 167af5152..79227804c 100644
--- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js
+++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.test.js
@@ -5,11 +5,13 @@ import configureStore from "redux-mock-store";
import { setProjectInstructions } from "../../../../redux/EditorSlice";
import { act } from "react";
import Modal from "react-modal";
+import Prism from "prismjs";
window.HTMLElement.prototype.scrollTo = jest.fn();
-window.Prism = {
+jest.mock("prismjs", () => ({
+ ...jest.requireActual("prismjs"),
highlightElement: jest.fn(),
-};
+}));
describe("When instructionsEditable is true", () => {
describe("When there are instructions", () => {
@@ -222,6 +224,7 @@ describe("When instructions are not editable", () => {
print('hello')
Hello world
.hello { color: purple }
+ const element = document.getElementById("my-element")
`,
},
],
@@ -262,17 +265,67 @@ describe("When instructions are not editable", () => {
test("Applies syntax highlighting to python code", () => {
const codeElement = document.getElementsByClassName("language-python")[0];
- expect(window.Prism.highlightElement).toHaveBeenCalledWith(codeElement);
+ expect(Prism.highlightElement).toHaveBeenCalledWith(codeElement);
});
test("Applies syntax highlighting to HTML code", () => {
const codeElement = document.getElementsByClassName("language-html")[0];
- expect(window.Prism.highlightElement).toHaveBeenCalledWith(codeElement);
+ expect(Prism.highlightElement).toHaveBeenCalledWith(codeElement);
});
test("Applies syntax highlighting to CSS code", () => {
const codeElement = document.getElementsByClassName("language-css")[0];
- expect(window.Prism.highlightElement).toHaveBeenCalledWith(codeElement);
+ expect(Prism.highlightElement).toHaveBeenCalledWith(codeElement);
+ });
+
+ test("Applies syntax highlighting to javascript code", () => {
+ const codeElement = document.getElementsByClassName(
+ "language-javascript",
+ )[0];
+ expect(Prism.highlightElement).toHaveBeenCalledWith(codeElement);
+ });
+ });
+
+ describe("When window.syntaxHighlight is defined", () => {
+ beforeEach(() => {
+ window.syntaxHighlight = {
+ highlightElement: jest.fn(),
+ };
+ const mockStore = configureStore([]);
+ const initialState = {
+ editor: {
+ project: {},
+ instructionsEditable: false,
+ },
+ instructions: {
+ project: {
+ steps: [
+ {
+ content: "print('hello')
",
+ },
+ ],
+ },
+ quiz: {},
+ currentStepPosition: 0,
+ },
+ };
+ const store = mockStore(initialState);
+ render(
+
+
+ ,
+ );
+ });
+
+ test("Applies syntax highlighting using window.syntaxHighlight", () => {
+ const codeElement = document.getElementsByClassName("language-python")[0];
+ expect(window.syntaxHighlight.highlightElement).toHaveBeenCalledWith(
+ codeElement,
+ );
+ });
+
+ afterEach(() => {
+ delete window.syntaxHighlight;
});
});
@@ -354,7 +407,7 @@ describe("When instructions are not editable", () => {
test("Applies syntax highlighting", () => {
const codeElement = document.getElementsByClassName("language-python")[0];
- expect(window.Prism.highlightElement).toHaveBeenCalledWith(codeElement);
+ expect(Prism.highlightElement).toHaveBeenCalledWith(codeElement);
});
test("Fires a quizIsReady event", () => {
diff --git a/src/containers/WebComponentLoader.jsx b/src/containers/WebComponentLoader.jsx
index 353b5ac41..d6cae9700 100644
--- a/src/containers/WebComponentLoader.jsx
+++ b/src/containers/WebComponentLoader.jsx
@@ -161,6 +161,24 @@ const WebComponentLoader = (props) => {
dispatch(setReadOnly(readOnly));
}, [readOnly, dispatch]);
+ useEffect(() => {
+ // Create a script element to save the existing Prism object if there is one
+ const script = document.createElement("script");
+ script.textContent = `
+ if (window.Prism) {
+ window.syntaxHighlight = window.Prism;
+ }
+ `;
+
+ // Append the script to the document body
+ document.body.appendChild(script);
+
+ // Clean up the script when the component unmounts
+ return () => {
+ document.body.removeChild(script);
+ };
+ }, []);
+
const renderSuccessState = () => (
<>
{
beforeEach(() => {
document.dispatchEvent = jest.fn();
+ window.Prism = jest.fn();
const middlewares = [localStorageUserMiddleware(setUser)];
const mockStore = configureStore(middlewares);
const initialState = {
@@ -85,6 +86,10 @@ describe("When initially rendered", () => {
);
});
+ test("It saves window.Prism to window.syntaxHighlight", () => {
+ expect(window.syntaxHighlight).toEqual(window.Prism);
+ });
+
describe("react app API endpoint", () => {
describe("when react app API endpoint isn't set", () => {
beforeEach(() => {