Skip to content

Commit faf5bdc

Browse files
committed
refactor: Apply review comments
1 parent cbdf35b commit faf5bdc

File tree

5 files changed

+61
-13
lines changed

5 files changed

+61
-13
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,15 @@ Note: For `<Chatbox>` and `<Popup>`, you must provide exactly one of `conversati
286286

287287
Accepted props:
288288

289-
- `url: string` - The URL you want to load inside the HTML panel. The URL can be absolute or relative. We recommend using same origin pages to have better control of the page. Learn more about HTML Panels and same origin pages [here](https://talkjs.com/docs/Features/Customizations/HTML_Panels/)
289+
- `url: string` - The URL you want to load inside the HTML panel. The URL can be absolute or relative. Any child components provided to this component will only be rendered if `url` has the same origin as the parent page. Learn more about HTML Panels and same origin pages [here](https://talkjs.com/docs/Features/Customizations/HTML_Panels/)
290290

291291
- `height?: number` - Optional. The panel height in pixels. Defaults to `100px`.
292292

293-
- `show?: boolean` - Optional. Sets the visibility of the panel. Defaults to `true`.
293+
- `show?: boolean` - Optional. Sets the visibility of the panel. Defaults to `true`. Changing this prop is equivalent to calling [`HtmlPanel.show()`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/HtmlPanel/#HtmlPanel__show) and [`HtmlPanel.hide()`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/HtmlPanel/#HtmlPanel__hide), while re-rendering the component calls [`HtmlPanel.destroy()`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/HtmlPanel/#HtmlPanel__destroy) and [`createHtmlPanel()`](https://talkjs.com/docs/Reference/JavaScript_Chat_SDK/Chatbox/#Chatbox__createHtmlPanel) in the background.
294294

295295
- `conversationId?: string` - Optional. If given, the panel will only show up for the conversation that has an `id` matching the one given.
296296

297-
- `children: React.ReactNode` - The content that gets rendered inside the `<body>` of the panel.
297+
- `children?: React.ReactNode` - Optional. The content that gets rendered inside the `<body>` of the panel.
298298

299299

300300
## Contributing

example/App.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import "./App.css";
22

33
import { Session, Chatbox, HtmlPanel } from "../lib/main";
44
import Talk from "talkjs";
5-
import { ChangeEvent, useCallback, useMemo, useRef, useState } from "react";
5+
import React, {
6+
ChangeEvent,
7+
useCallback,
8+
useMemo,
9+
useRef,
10+
useState,
11+
} from "react";
612

713
const convIds = ["talk-react-94872948u429843", "talk-react-194872948u429843"];
814
const users = [

example/panel.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>Document</title>
6+
7+
<!--
8+
Put your app's CSS here. For instance, if your bundler generates a CSS
9+
file from all component styles, load it here as well and your components
10+
will be styled correctly inside the HTML Panel
11+
-->
12+
<link rel="stylesheet" href="./your-styles.css" />
13+
714
<style>
15+
html,
16+
body {
17+
margin: 0;
18+
padding: 0;
19+
}
820
body {
921
background-color: lightblue;
1022
}

lib/HtmlPanel.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type HtmlPanelProps = {
2121
conversationId?: string;
2222

2323
/** The content that gets rendered inside the `<body>` of the panel. */
24-
children: React.ReactNode;
24+
children?: React.ReactNode;
2525
};
2626

2727
export function HtmlPanel({
@@ -34,6 +34,10 @@ export function HtmlPanel({
3434
const [panel, setPanel] = useState<undefined | Talk.HtmlPanel>(undefined);
3535
const box = useContext(BoxContext);
3636

37+
const normalizedPanelUrl = new URL(url, document.baseURI);
38+
const baseUrl = new URL(document.baseURI);
39+
const isCrossOrigin = normalizedPanelUrl.origin !== baseUrl.origin;
40+
3741
useEffect(() => {
3842
async function run() {
3943
if (!box || panel) return Promise.resolve(panel);
@@ -42,12 +46,21 @@ export function HtmlPanel({
4246
url,
4347
conversation: conversationId,
4448
height,
45-
show: false,
49+
// If the frame is cross-origin, we can't render children into it anyway
50+
// so we show the panel straight away. If we can render, we hide it
51+
// first and wait for the DOMContentLoaded event to fire before showing
52+
// the panel to avoid a flash of content that's missing the React
53+
// portal.
54+
show: isCrossOrigin,
4655
});
4756

48-
await newPanel.DOMContentLoadedPromise;
49-
if (show) {
50-
newPanel.show();
57+
if (!isCrossOrigin) {
58+
// This promise will never resolve if the panel isn't on the same origin.
59+
// We skip the `await` if that's the case.
60+
await newPanel.DOMContentLoadedPromise;
61+
if (show) {
62+
newPanel.show();
63+
}
5164
}
5265

5366
setPanel(newPanel);
@@ -82,5 +95,9 @@ export function HtmlPanel({
8295
}
8396
}, [panel, show]);
8497

85-
return <>{panel && createPortal(children, panel.window.document.body)}</>;
98+
const shouldRender = !isCrossOrigin && panel && children;
99+
100+
return (
101+
<>{shouldRender && createPortal(children, panel.window.document.body)}</>
102+
);
86103
}

lib/util.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,23 @@ export function splitObjectByPrefix<P extends string, T extends object>(
6767
return [prefixed, unprefixed];
6868
}
6969

70-
export function validateChildrenAreHtmlPanels(children?: React.ReactNode) {
70+
const REACT_FRAGMENT_TYPE = React.createElement(React.Fragment).type;
71+
export function validateChildrenAreHtmlPanels(
72+
children?: React.ReactNode,
73+
): boolean {
7174
if (!children) return true;
7275

7376
return React.Children.toArray(children).every((x) => {
74-
return typeof x === "object" && (x as any).type === HtmlPanel;
77+
if (typeof x !== "object") return false;
78+
79+
const type = (x as any).type;
80+
if (type === HtmlPanel) {
81+
return true;
82+
}
83+
if (type === REACT_FRAGMENT_TYPE) {
84+
return validateChildrenAreHtmlPanels((x as any).props.children);
85+
}
86+
87+
return false;
7588
});
7689
}

0 commit comments

Comments
 (0)