diff --git a/package.json b/package.json index 6cac6183..e7199e4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vocechat-web", - "version": "0.9.61", + "version": "0.9.62", "homepage": "https://voce.chat", "dependencies": { "@metamask/onboarding": "^1.0.1", diff --git a/public/widget.js b/public/widget.js index bd49360f..729b2a2d 100644 --- a/public/widget.js +++ b/public/widget.js @@ -18,6 +18,7 @@ popupSubtitle = "Our staff are always ready to help!", popupImage = "", popupClosable = "true", + showPopup = "true", useShadowDom = "true" } = d.currentScript.dataset; const _src = d.currentScript.src; @@ -74,6 +75,7 @@ // 加载并执行 widget 脚本 const script = d.createElement("script"); + script.type = "text/javascript"; script.src = `${origin}${manifest.files["widget.js"]}`; script.onload = () => { // 脚本加载完成后,需要手动初始化 React 应用到 shadow DOM @@ -91,6 +93,7 @@ popupSubtitle, popupImage, popupClosable, + showPopup, embed: "true", closeWidth, closeHeight, @@ -131,7 +134,7 @@ Object.assign(wrapper.style, baseStyles); wrapper.src = `${origin}/widget.html?id=${id}&host=${hostId}&autoReg=${autoReg}&token=${loginToken}&themeColor=${encodeURIComponent( themeColor - )}&from=${encodeURIComponent(location.hostname)}&welcome=${encodeURIComponent(welcome)}&title=${encodeURIComponent(title)}&logo=${encodeURIComponent(logo)}&popupTitle=${encodeURIComponent(popupTitle)}&popupSubtitle=${encodeURIComponent(popupSubtitle)}&popupImage=${encodeURIComponent(popupImage)}&popupClosable=${popupClosable}`; + )}&from=${encodeURIComponent(location.hostname)}&welcome=${encodeURIComponent(welcome)}&title=${encodeURIComponent(title)}&logo=${encodeURIComponent(logo)}&popupTitle=${encodeURIComponent(popupTitle)}&popupSubtitle=${encodeURIComponent(popupSubtitle)}&popupImage=${encodeURIComponent(popupImage)}&popupClosable=${popupClosable}&showPopup=${showPopup}`; wrapper.width = actualCloseWidth; wrapper.height = actualCloseHeight; wrapper.frameborder = 0; diff --git a/src/index-widget.tsx b/src/index-widget.tsx index 1186ccf4..a12c775c 100644 --- a/src/index-widget.tsx +++ b/src/index-widget.tsx @@ -1,3 +1,18 @@ +// 在 Shadow DOM 模式下,动态设置 webpack 的 publicPath +// 这样 webpack 运行时加载的动态资源(如代码分割的 chunk)会从正确的源服务器加载 +if (typeof document !== 'undefined') { + const currentScript = document.currentScript as HTMLScriptElement; + if (currentScript && currentScript.src) { + try { + const scriptUrl = new URL(currentScript.src); + // 设置为脚本所在的目录路径 + __webpack_public_path__ = scriptUrl.origin + '/'; + } catch (e) { + console.warn('Failed to set webpack public path:', e); + } + } +} + import ReactDOM from "react-dom/client"; import { Toaster } from "react-hot-toast"; import { Provider } from "react-redux"; diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 8fedfa42..9a30cefc 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -33,6 +33,10 @@ interface BeforeInstallPromptEvent extends Event { export declare global { import { PrecacheEntry } from "workbox-precaching/src/_types"; import localforage from "localforage"; + + // Webpack public path for dynamic imports + var __webpack_public_path__: string; + interface Document { webkitHidden: boolean; } diff --git a/src/widget/Icon.tsx b/src/widget/Icon.tsx index ed96d271..a72359a7 100644 --- a/src/widget/Icon.tsx +++ b/src/widget/Icon.tsx @@ -10,7 +10,7 @@ type Props = { const Icon = ({ handleClick }: Props) => { const serverLogo = useAppSelector((store) => store.server.logo, shallowEqual); - const { popupTitle, popupSubtitle, popupImage, logo: customLogo, popupClosable } = useWidget(); + const { popupTitle, popupSubtitle, popupImage, logo: customLogo, popupClosable, showPopup } = useWidget(); const [tooltipVisible, setTooltipVisible] = useState(true); // 优先使用自定义 logo,否则使用服务器 logo @@ -19,8 +19,8 @@ const Icon = ({ handleClick }: Props) => { // 检测是否是移动端 const isMobile = typeof window !== 'undefined' && window.innerWidth < 768; - // 是否显示 tooltip(桌面端 + 有内容 + 用户未关闭) - const showTooltip = !isMobile && tooltipVisible && (popupTitle || popupSubtitle); + // 是否显示 tooltip(桌面端 + 有内容 + 用户未关闭 + showPopup 开关打开) + const showTooltip = !isMobile && tooltipVisible && (popupTitle || popupSubtitle) && showPopup; if (!logo) return null; diff --git a/src/widget/WidgetContext.tsx b/src/widget/WidgetContext.tsx index e1fbaaa9..9b184e4e 100644 --- a/src/widget/WidgetContext.tsx +++ b/src/widget/WidgetContext.tsx @@ -22,6 +22,7 @@ const popupTitle = decodeURIComponent(query.get("popupTitle") || "Need help?"); const popupSubtitle = decodeURIComponent(query.get("popupSubtitle") || "Our staff are always ready to help!"); const popupImage = decodeURIComponent(query.get("popupImage") || ""); const popupClosable = decodeURIComponent(query.get("popupClosable") || "true") == "true"; +const showPopup = decodeURIComponent(query.get("showPopup") || "true") == "true"; const fgColor = getContrastColor(color); // 从 URL 参数读取 embed 状态,如果没有则检测是否在 iframe 中 const embedParam = query.get("embed"); @@ -49,6 +50,7 @@ const WidgetContext = createContext({ popupSubtitle, popupImage, popupClosable, + showPopup, serverVersion: "", openWidth, openHeight, @@ -84,6 +86,7 @@ function WidgetProvider({ children }: { children: ReactNode }) { popupSubtitle, popupImage, popupClosable, + showPopup, serverVersion: data, openWidth, openHeight,