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(() => {