Merge pull request #177 from Dvorinka/fix/custom-html-scripts

fix(frontend): execute scripts in custom HTML modules
pull/3587/head
Tomáš Dvořák 2 weeks ago committed by GitHub
commit b78cd29616
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -165,7 +165,7 @@
"customHTML": "Custom HTML",
"customHTMLDes": "Insert custom HTML content at the preset position of the site.",
"sidebarBottom": "Sidebar bottom",
"sidebarBottomDes": "Custom HTML content displayed at the bottom of the sidebar.",
"sidebarBottomDes": "Custom HTML content displayed at the bottom of the sidebar. <script> tags are executed.",
"addNavItem": "Add navigation item",
"customNavItems": "Custom sidebar items",
"customNavItemsDes": "You can add custom items to the sidebar, and users will be redirected to the corresponding link when clicked.",

@ -165,7 +165,7 @@
"customHTML": "自定义 HTML",
"customHTMLDes": "在站点的预设位置插入展示自定义的 HTML 内容。",
"sidebarBottom": "侧边栏底部",
"sidebarBottomDes": "在侧边栏底部展示的自定义 HTML 内容。",
"sidebarBottomDes": "在侧边栏底部展示的自定义 HTML 内容,支持执行 <script> 脚本。",
"addNavItem": "添加导航条目",
"customNavItems": "自定义侧边导航栏",
"customNavItemsDes": "你可以在左侧导航栏中添加自定义的条目,用户点击后会跳转到对应的链接。",

@ -0,0 +1,33 @@
import { useEffect, useRef } from "react";
export interface CustomHTMLContentProps {
html: string;
}
// CustomHTMLContent renders administrator-supplied HTML. Scripts inside the
// markup do not run when injected via innerHTML, so each <script> element is
// re-created as a fresh node — dynamically inserted scripts execute normally.
// The content is trusted by definition (only admins can configure it).
const CustomHTMLContent = ({ html }: CustomHTMLContentProps) => {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const container = ref.current;
if (!container) {
return;
}
container.querySelectorAll("script").forEach((oldScript) => {
const script = document.createElement("script");
Array.from(oldScript.attributes).forEach((attr) => {
script.setAttribute(attr.name, attr.value);
});
script.textContent = oldScript.textContent;
oldScript.replaceWith(script);
});
}, [html]);
return <div ref={ref} dangerouslySetInnerHTML={{ __html: html }} />;
};
export default CustomHTMLContent;

@ -3,6 +3,7 @@ import { Outlet, useNavigation } from "react-router-dom";
import { useAppSelector } from "../../redux/hooks.ts";
import AutoHeight from "../Common/AutoHeight.tsx";
import CircularProgress from "../Common/CircularProgress.tsx";
import CustomHTMLContent from "../Common/CustomHTMLContent.tsx";
import LanguageSwitcher from "../Common/LanguageSwitcher.tsx";
import PoweredBy from "./PoweredBy.tsx";
import Logo from "../Common/Logo.tsx";
@ -93,7 +94,7 @@ const HeadlessFrame = () => {
<Outlet />
{headless_bottom && (
<Box sx={{ width: "100%" }}>
<div dangerouslySetInnerHTML={{ __html: headless_bottom }} />
<CustomHTMLContent html={headless_bottom} />
</Box>
)}
</Box>
@ -105,7 +106,7 @@ const HeadlessFrame = () => {
<PoweredBy />
{headless_footer && (
<Box sx={{ width: "100%", mb: 2 }}>
<div dangerouslySetInnerHTML={{ __html: headless_footer }} />
<CustomHTMLContent html={headless_footer} />
</Box>
)}
</Grid>

@ -2,6 +2,7 @@ import { Box, Drawer, Popover, PopoverProps, Stack, useMediaQuery, useTheme } fr
import { useContext, useRef } from "react";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks.ts";
import SessionManager from "../../../session";
import CustomHTMLContent from "../../Common/CustomHTMLContent.tsx";
import TreeNavigation from "../../FileManager/TreeView/TreeNavigation.tsx";
import { PageVariant, PageVariantContext } from "../NavBarFrame.tsx";
import DrawerHeader from "./DrawerHeader.tsx";
@ -41,7 +42,7 @@ const DrawerContent = () => {
{isDashboard && <AdminPageNavigation />}
{sidebar_bottom && (
<Box sx={{ width: "100%" }}>
<div dangerouslySetInnerHTML={{ __html: sidebar_bottom }} />
<CustomHTMLContent html={sidebar_bottom} />
</Box>
)}
</Stack>

Loading…
Cancel
Save