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([]);