From e5de51a5019f8565325a51ffd818789e69089cc9 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 4 Feb 2025 11:31:40 +0000 Subject: [PATCH 1/6] use imported prism while continuing support for existing projects site highlighting mechanism --- .../InstructionsPanel/InstructionsPanel.jsx | 8 +++++++- src/containers/WebComponentLoader.jsx | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index 2371ec5a9..c07278b7c 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -17,6 +17,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); @@ -53,7 +55,11 @@ const InstructionsPanel = () => { ); codeElements.forEach((element) => { - window.Prism.highlightElement(element); + if (window.syntaxHighlight) { + window.syntaxHighlight.highlightElement(element); + } else { + Prism.highlightElement(element); + } }); }; diff --git a/src/containers/WebComponentLoader.jsx b/src/containers/WebComponentLoader.jsx index 353b5ac41..1d5b0579d 100644 --- a/src/containers/WebComponentLoader.jsx +++ b/src/containers/WebComponentLoader.jsx @@ -161,6 +161,25 @@ 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 = ` + console.log("saving existing prism"); + 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 = () => ( <> Date: Tue, 4 Feb 2025 12:30:23 +0000 Subject: [PATCH 2/6] initial javascript support and padding fix --- src/assets/stylesheets/Instructions.scss | 18 +++++++++++------- .../InstructionsPanel/InstructionsPanel.jsx | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/assets/stylesheets/Instructions.scss b/src/assets/stylesheets/Instructions.scss index 461efb162..e36bfd696 100644 --- a/src/assets/stylesheets/Instructions.scss +++ b/src/assets/stylesheets/Instructions.scss @@ -40,19 +40,23 @@ } } + 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-radius: 8px; 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 { @@ -101,7 +105,7 @@ min-inline-size: 100%; } - .language-python { + .language-python, .language-javascript { .number, .boolean, .function { diff --git a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx index c07278b7c..40aeda89b 100644 --- a/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx +++ b/src/components/Menus/Sidebar/InstructionsPanel/InstructionsPanel.jsx @@ -51,7 +51,7 @@ 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) => { From 6e96cc631ee85da5946bbf71df06d0a6d0b37f2c Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 4 Feb 2025 12:52:14 +0000 Subject: [PATCH 3/6] testing --- .../InstructionsPanel.test.js | 65 +++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) 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", () => { From 710cc308537758fcbbcf92c2ead42b6e66a526af Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 4 Feb 2025 12:53:52 +0000 Subject: [PATCH 4/6] updating changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f878b51..3b95f7204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Instructions tabs for edit and viewing (#1167) - Add remove instructions button modal (#1176) - Dark mode colours (#1182) +- Syntax highlighting for custom instructions in Code Editor for Education ### Changed @@ -32,7 +33,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 From 3dd76c378719ac53330c3c057a47ee266940185b Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 4 Feb 2025 14:10:13 +0000 Subject: [PATCH 5/6] separate out javascript syntax highlighting in instructions --- src/assets/stylesheets/Instructions.scss | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/assets/stylesheets/Instructions.scss b/src/assets/stylesheets/Instructions.scss index f428e2e1c..82d7b99bf 100644 --- a/src/assets/stylesheets/Instructions.scss +++ b/src/assets/stylesheets/Instructions.scss @@ -107,7 +107,7 @@ min-inline-size: 100%; } - .language-python, .language-javascript { + .language-python { .number, .boolean, .function { @@ -129,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; From e5aeb22bb14d74dc6feb563c23220fe3996067c5 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Tue, 4 Feb 2025 14:15:21 +0000 Subject: [PATCH 6/6] tidying and testing --- src/containers/WebComponentLoader.jsx | 1 - src/containers/WebComponentLoader.test.js | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/containers/WebComponentLoader.jsx b/src/containers/WebComponentLoader.jsx index 1d5b0579d..d6cae9700 100644 --- a/src/containers/WebComponentLoader.jsx +++ b/src/containers/WebComponentLoader.jsx @@ -165,7 +165,6 @@ const WebComponentLoader = (props) => { // Create a script element to save the existing Prism object if there is one const script = document.createElement("script"); script.textContent = ` - console.log("saving existing prism"); if (window.Prism) { window.syntaxHighlight = window.Prism; } diff --git a/src/containers/WebComponentLoader.test.js b/src/containers/WebComponentLoader.test.js index b82bcd9f5..c8a7df9d0 100644 --- a/src/containers/WebComponentLoader.test.js +++ b/src/containers/WebComponentLoader.test.js @@ -40,6 +40,7 @@ const user = { access_token: "my_token" }; describe("When initially rendered", () => { 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(() => {