From e016bbbdec2325495aba042e56ab0b26bfd29e2b Mon Sep 17 00:00:00 2001 From: abcampo-iry <261805581+abcampo-iry@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:01:00 +0200 Subject: [PATCH 1/3] Set default info tab feedback link --- .../Menus/Sidebar/InfoPanel/InfoPanel.jsx | 7 +- .../Menus/Sidebar/InfoPanel/InfoPanel.test.js | 67 ++++++++++++------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx index 81e22cf10..f41299bf9 100644 --- a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx +++ b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx @@ -4,7 +4,10 @@ import SidebarPanel from "../SidebarPanel"; import "../../../../assets/stylesheets/InfoPanel.scss"; -const InfoPanel = () => { +const CODE_EDITOR_FEEDBACK_URL = + "https://form.raspberrypi.org/f/code-editor-feedback"; + +const InfoPanel = ({ feedbackFormUrl = CODE_EDITOR_FEEDBACK_URL }) => { const { t } = useTranslation(); const links = [ { @@ -15,7 +18,7 @@ const InfoPanel = () => { { id: "feedback", text: t("sidebar.feedback"), - href: "https://form.raspberrypi.org/f/code-editor-feedback", + href: feedbackFormUrl, }, { id: "privacy", diff --git a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js index 14d131ebc..a32d34390 100644 --- a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js +++ b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js @@ -6,40 +6,44 @@ import { MemoryRouter } from "react-router-dom"; import InfoPanel from "./InfoPanel"; -describe("Info panel", () => { - beforeEach(() => { - const middlewares = []; - const mockStore = configureStore(middlewares); - const initialState = { - editor: { - project: { - components: [ - { - name: "main", - extension: "py", - }, - ], - }, +const renderInfoPanel = (props = {}) => { + const middlewares = []; + const mockStore = configureStore(middlewares); + const initialState = { + editor: { + project: { + components: [ + { + name: "main", + extension: "py", + }, + ], }, - }; - const store = mockStore(initialState); - - render( - - - {}} /> - - , - ); - }); + }, + }; + const store = mockStore(initialState); + + render( + + + + + , + ); +}; +describe("Info panel", () => { test("Links are rendered", () => { + renderInfoPanel(); + expect(screen.queryByText("sidebar.help")).toBeInTheDocument(); expect(screen.queryByText("sidebar.feedback")).toBeInTheDocument(); expect(screen.queryByText("sidebar.privacy")).toBeInTheDocument(); }); - test("Links have the expected source", () => { + test("Links have the expected default source", () => { + renderInfoPanel(); + expect(screen.queryByText("sidebar.feedback")).toHaveAttribute( "href", "https://form.raspberrypi.org/f/code-editor-feedback", @@ -49,4 +53,15 @@ describe("Info panel", () => { "https://help.editor.raspberrypi.org/hc/en-us", ); }); + + test("Uses the supplied feedback form source", () => { + renderInfoPanel({ + feedbackFormUrl: "https://form.raspberrypi.org/4873801", + }); + + expect(screen.queryByText("sidebar.feedback")).toHaveAttribute( + "href", + "https://form.raspberrypi.org/4873801", + ); + }); }); From 3f0debeee4fe1759bba49438b0b49844dc8dc9ab Mon Sep 17 00:00:00 2001 From: abcampo-iry <261805581+abcampo-iry@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:01:36 +0200 Subject: [PATCH 2/3] Expose info tab feedback form URL --- README.md | 1 + src/components/Editor/Project/Project.jsx | 2 ++ src/components/Menus/Sidebar/Sidebar.jsx | 9 +++++++-- src/components/Mobile/MobileProject/MobileProject.jsx | 7 ++++++- .../WebComponentProject/WebComponentProject.jsx | 3 +++ src/containers/WebComponentLoader.jsx | 5 +++++ src/web-component.js | 1 + 7 files changed, 25 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cd5a57912..52bd2966b 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ The `editor-wc` tag accepts the following attributes, which must be provided as - `code`: A preset blob of code to show in the editor pane (overrides content of `main.py`/`index.html`) - `editable_instructions`: Boolean whether to show edit panel for instructions - `embedded`: Enable embedded mode which hides some functionality (defaults to `false`) +- `feedback_form_url`: URL used by the Feedback link in the info panel (defaults to the Code Editor feedback form) - `host_styles`: Styles passed into the web component from the host page - `identifier`: Load the project with this identifier from the database - `instructions`: Stringified JSON containing steps to be displayed in the instructions panel in the sidebar diff --git a/src/components/Editor/Project/Project.jsx b/src/components/Editor/Project/Project.jsx index 6bc0e0c71..c0533e202 100644 --- a/src/components/Editor/Project/Project.jsx +++ b/src/components/Editor/Project/Project.jsx @@ -24,6 +24,7 @@ const Project = (props) => { withProjectbar = true, withSidebar = true, sidebarOptions = [], + feedbackFormUrl, sidebarPlugins = [], } = props; const saving = useSelector((state) => state.editor.saving); @@ -73,6 +74,7 @@ const Project = (props) => { )} diff --git a/src/components/Menus/Sidebar/Sidebar.jsx b/src/components/Menus/Sidebar/Sidebar.jsx index 9dcdfde65..939cc3ce6 100644 --- a/src/components/Menus/Sidebar/Sidebar.jsx +++ b/src/components/Menus/Sidebar/Sidebar.jsx @@ -26,7 +26,12 @@ import InstructionsPanel from "./InstructionsPanel/InstructionsPanel"; import SidebarPanel from "./SidebarPanel"; import { setSidebarOption } from "../../../redux/EditorSlice"; -const Sidebar = ({ options = [], plugins = [], allowMobileView = true }) => { +const Sidebar = ({ + options = [], + plugins = [], + feedbackFormUrl, + allowMobileView = true, +}) => { const { t } = useTranslation(); const dispatch = useDispatch(); const projectType = useSelector((state) => state.editor.project.project_type); @@ -91,7 +96,7 @@ const Sidebar = ({ options = [], plugins = [], allowMobileView = true }) => { icon: InfoIcon, title: t("sidebar.information"), position: "bottom", - panel: InfoPanel, + panel: () => , }, ].filter((option) => { if (!options.includes(option.name)) return false; diff --git a/src/components/Mobile/MobileProject/MobileProject.jsx b/src/components/Mobile/MobileProject/MobileProject.jsx index 2ba61a290..959dbfcd5 100644 --- a/src/components/Mobile/MobileProject/MobileProject.jsx +++ b/src/components/Mobile/MobileProject/MobileProject.jsx @@ -17,6 +17,7 @@ import { showSidebar } from "../../../redux/EditorSlice"; const MobileProject = ({ withSidebar, sidebarOptions = [], + feedbackFormUrl, sidebarPlugins = [], }) => { const projectType = useSelector((state) => state.editor.project.project_type); @@ -52,7 +53,11 @@ const MobileProject = ({ > {withSidebar && ( - + )} diff --git a/src/components/WebComponentProject/WebComponentProject.jsx b/src/components/WebComponentProject/WebComponentProject.jsx index 3d2fcd386..2456573a5 100644 --- a/src/components/WebComponentProject/WebComponentProject.jsx +++ b/src/components/WebComponentProject/WebComponentProject.jsx @@ -35,6 +35,7 @@ const WebComponentProject = ({ outputOnly = false, outputPanels = ["text", "visual"], outputSplitView = false, + feedbackFormUrl, sidebarPlugins = [], }) => { const loading = useSelector((state) => state.editor.loading); @@ -161,6 +162,7 @@ const WebComponentProject = ({ ) : ( @@ -169,6 +171,7 @@ const WebComponentProject = ({ withProjectbar={withProjectbar} withSidebar={withSidebar} sidebarOptions={sidebarOptions} + feedbackFormUrl={feedbackFormUrl} sidebarPlugins={sidebarPlugins} /> ))} diff --git a/src/containers/WebComponentLoader.jsx b/src/containers/WebComponentLoader.jsx index df4aa7cc4..dc75a5fcb 100644 --- a/src/containers/WebComponentLoader.jsx +++ b/src/containers/WebComponentLoader.jsx @@ -35,6 +35,9 @@ import { projectOwnerLoadedEvent, } from "../events/WebComponentCustomEvents"; +const CODE_EDITOR_FEEDBACK_URL = + "https://form.raspberrypi.org/f/code-editor-feedback"; + const TOAST_CONTAINER_DEFAULTS = { ...(ToastContainer.defaultProps || {}), }; @@ -53,6 +56,7 @@ const WebComponentLoader = (props) => { code, embedded = false, editableInstructions, + feedbackFormUrl = CODE_EDITOR_FEEDBACK_URL, hostStyles, // Pass in styles from the host identifier, instructions, @@ -253,6 +257,7 @@ const WebComponentLoader = (props) => { outputPanels={outputPanels} outputSplitView={outputSplitView} editableInstructions={editableInstructions} + feedbackFormUrl={feedbackFormUrl} sidebarPlugins={sidebarPlugins} /> {errorModalShowing && } diff --git a/src/web-component.js b/src/web-component.js index dd13d40ba..96d8b6dc8 100644 --- a/src/web-component.js +++ b/src/web-component.js @@ -59,6 +59,7 @@ class WebComponent extends HTMLElement { "code", "editable_instructions", "embedded", + "feedback_form_url", "host_styles", "identifier", "initial_project", From f751772cbddf115eadedf6542471ae15604117b7 Mon Sep 17 00:00:00 2001 From: abcampo-iry <261805581+abcampo-iry@users.noreply.github.com> Date: Tue, 2 Jun 2026 17:41:56 +0200 Subject: [PATCH 3/3] address issues from copilot --- .../Menus/Sidebar/InfoPanel/InfoPanel.jsx | 17 ++++++++++++- .../Menus/Sidebar/InfoPanel/InfoPanel.test.js | 18 ++++++++++++++ src/components/Menus/Sidebar/Sidebar.jsx | 10 ++++++-- src/components/Menus/Sidebar/Sidebar.test.js | 24 +++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx index f41299bf9..73ed77e00 100644 --- a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx +++ b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.jsx @@ -7,6 +7,21 @@ import "../../../../assets/stylesheets/InfoPanel.scss"; const CODE_EDITOR_FEEDBACK_URL = "https://form.raspberrypi.org/f/code-editor-feedback"; +const feedbackUrl = (url) => { + if (typeof url !== "string" || url.trim() === "") { + return CODE_EDITOR_FEEDBACK_URL; + } + + try { + const parsedUrl = new URL(url.trim()); + return parsedUrl.protocol === "https:" + ? parsedUrl.href + : CODE_EDITOR_FEEDBACK_URL; + } catch { + return CODE_EDITOR_FEEDBACK_URL; + } +}; + const InfoPanel = ({ feedbackFormUrl = CODE_EDITOR_FEEDBACK_URL }) => { const { t } = useTranslation(); const links = [ @@ -18,7 +33,7 @@ const InfoPanel = ({ feedbackFormUrl = CODE_EDITOR_FEEDBACK_URL }) => { { id: "feedback", text: t("sidebar.feedback"), - href: feedbackFormUrl, + href: feedbackUrl(feedbackFormUrl), }, { id: "privacy", diff --git a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js index a32d34390..7a343cada 100644 --- a/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js +++ b/src/components/Menus/Sidebar/InfoPanel/InfoPanel.test.js @@ -64,4 +64,22 @@ describe("Info panel", () => { "https://form.raspberrypi.org/4873801", ); }); + + test.each([ + `java${"script"}:alert(1)`, + "http://example.com", + "ftp://example.com", + "not-a-url", + "", + ])( + "Falls back to the default feedback form for invalid feedback form source %p", + (feedbackFormUrl) => { + renderInfoPanel({ feedbackFormUrl }); + + expect(screen.queryByText("sidebar.feedback")).toHaveAttribute( + "href", + "https://form.raspberrypi.org/f/code-editor-feedback", + ); + }, + ); }); diff --git a/src/components/Menus/Sidebar/Sidebar.jsx b/src/components/Menus/Sidebar/Sidebar.jsx index 939cc3ce6..e990294a0 100644 --- a/src/components/Menus/Sidebar/Sidebar.jsx +++ b/src/components/Menus/Sidebar/Sidebar.jsx @@ -96,7 +96,8 @@ const Sidebar = ({ icon: InfoIcon, title: t("sidebar.information"), position: "bottom", - panel: () => , + panel: InfoPanel, + panelProps: { feedbackFormUrl }, }, ].filter((option) => { if (!options.includes(option.name)) return false; @@ -208,7 +209,12 @@ const Sidebar = ({ instructions={instructionsSteps} allowMobileView={allowMobileView} /> - {activeOption && } + {activeOption && ( + + )} ); }; diff --git a/src/components/Menus/Sidebar/Sidebar.test.js b/src/components/Menus/Sidebar/Sidebar.test.js index 7b26990d0..c0f927978 100644 --- a/src/components/Menus/Sidebar/Sidebar.test.js +++ b/src/components/Menus/Sidebar/Sidebar.test.js @@ -511,6 +511,30 @@ describe("When plugins are provided", () => { }); }); +describe("When a feedback form URL is provided", () => { + test("Passes the feedback form URL to the info panel", () => { + const initialState = { + editor: { + project: { + components: [], + image_list: [], + }, + }, + instructions: {}, + }; + renderSidebarWithState(initialState, { + feedbackFormUrl: "https://form.raspberrypi.org/4873801", + }); + + fireEvent.click(screen.getByTitle("sidebar.information")); + + expect(screen.queryByText("sidebar.feedback")).toHaveAttribute( + "href", + "https://form.raspberrypi.org/4873801", + ); + }); +}); + describe("When the project type is code_editor_scratch", () => { beforeEach(() => { const mockStore = configureStore([]);