-
Notifications
You must be signed in to change notification settings - Fork 9.5k
Fix(widget) clicked links in widget open new tab #15676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -236,7 +236,7 @@ function EmbedDialog({ | |||||||||||||||||||||||||
| return src.toString(); | ||||||||||||||||||||||||||
| }, [beta, from, token, values]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const text = useMemo(() => { | ||||||||||||||||||||||||||
| const generateEmbedCode = useCallback(() => { | ||||||||||||||||||||||||||
| const iframeSrc = generateIframeSrc(); | ||||||||||||||||||||||||||
| const { embedType } = values; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -248,7 +248,9 @@ function EmbedDialog({ | |||||||||||||||||||||||||
| allow="microphone;camera" | ||||||||||||||||||||||||||
| ></iframe> | ||||||||||||||||||||||||||
| <script> | ||||||||||||||||||||||||||
| const newTabLink = true; | ||||||||||||||||||||||||||
| window.addEventListener('message',e=>{ | ||||||||||||||||||||||||||
| console.log(e) | ||||||||||||||||||||||||||
| if(e.origin!=='${location.origin.replace(/:\d+/, ':9222')}')return; | ||||||||||||||||||||||||||
| if(e.data.type==='CREATE_CHAT_WINDOW'){ | ||||||||||||||||||||||||||
| if(document.getElementById('chat-win'))return; | ||||||||||||||||||||||||||
|
|
@@ -261,6 +263,10 @@ window.addEventListener('message',e=>{ | |||||||||||||||||||||||||
| const w=document.getElementById('chat-win'); | ||||||||||||||||||||||||||
| if(w)w.style.display=e.data.isOpen?'block':'none'; | ||||||||||||||||||||||||||
| }else if(e.data.type==='SCROLL_PASSTHROUGH')window.scrollBy(0,e.data.deltaY); | ||||||||||||||||||||||||||
| else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){ | ||||||||||||||||||||||||||
| if(newTabLink)window.open(e.data.url,'_blank'); | ||||||||||||||||||||||||||
| else window.location.href=e.data.url; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+266
to
+269
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
file="web/src/components/embed-dialog/index.tsx"
# show line numbers around 240-330
python3 - <<'PY'
import itertools,sys
path="web/src/components/embed-dialog/index.tsx"
start=220; end=330
with open(path,'r',encoding='utf-8') as f:
for i,line in enumerate(f,1):
if start<=i<=end:
print(f"{i:4d}:{line.rstrip()}")
PY
# also search for CHAT_LINK_CLICK occurrences and any window.open with noopener usage
rg -n "CHAT_LINK_CLICK" web/src/components/embed-dialog/index.tsx web/src/components/embed-dialog -S || true
rg -n "window\.open\(|noopener|noreferrer" web/src/components/embed-dialog/index.tsx -S || true
# find where newTabLink is defined and whether e.data.url is derived/validated earlier in the postMessage pipeline
rg -n "newTabLink" web/src/components/embed-dialog/index.tsx -n -S || true
rg -n "postMessage|e\.data\.url|data\.url" web/src/components/embed-dialog/index.tsx -S || trueRepository: infiniflow/ragflow Length of output: 4989 🏁 Script executed: #!/bin/bash
set -euo pipefail
file="web/src/components/embed-dialog/index.tsx"
# show line numbers around 240-330
python3 - <<'PY'
import itertools,sys
path="web/src/components/embed-dialog/index.tsx"
start=220; end=330
with open(path,'r',encoding='utf-8') as f:
for i,line in enumerate(f,1):
if start<=i<=end:
print(f"{i:4d}:{line.rstrip()}")
PY
# also search for CHAT_LINK_CLICK occurrences and any window.open with noopener usage
rg -n "CHAT_LINK_CLICK" web/src/components/embed-dialog/index.tsx web/src/components/embed-dialog -S || true
rg -n "window\.open\(|noopener|noreferrer" web/src/components/embed-dialog/index.tsx -S || true
# find where newTabLink is defined and whether e.data.url is derived/validated earlier in the postMessage pipeline
rg -n "newTabLink" web/src/components/embed-dialog/index.tsx -n -S || true
rg -n "postMessage|e\.data\.url|data\.url" web/src/components/embed-dialog/index.tsx -S || trueRepository: infiniflow/ragflow Length of output: 4989 Sanitize
Suggested fix- else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
- if(newTabLink)window.open(e.data.url,'_blank');
- else window.location.href=e.data.url;
- }
+ else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
+ let parsed;
+ try { parsed = new URL(e.data.url, window.location.href); } catch { return; }
+ if (!['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol)) return;
+ const safeUrl = parsed.toString();
+ if (newTabLink) window.open(safeUrl, '_blank', 'noopener,noreferrer');
+ else window.location.assign(safeUrl);
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| </script> | ||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||
|
|
@@ -274,10 +280,32 @@ window.addEventListener('message',e=>{ | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| }, [generateIframeSrc, values]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const text = useMemo(() => generateEmbedCode(), [generateEmbedCode]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const handleOpenInNewTab = useCallback(() => { | ||||||||||||||||||||||||||
| const iframeSrc = generateIframeSrc(); | ||||||||||||||||||||||||||
| window.open(iframeSrc, '_blank'); | ||||||||||||||||||||||||||
| }, [generateIframeSrc]); | ||||||||||||||||||||||||||
| if (values.embedType === 'widget') { | ||||||||||||||||||||||||||
| const html = `<!doctype html> | ||||||||||||||||||||||||||
| <html lang="en"> | ||||||||||||||||||||||||||
| <head> | ||||||||||||||||||||||||||
| <meta charset="UTF-8" /> | ||||||||||||||||||||||||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||||||||||||||||||||||
| <title>RAGFlow Chat Widget Preview</title> | ||||||||||||||||||||||||||
| </head> | ||||||||||||||||||||||||||
| <body style="margin:0;min-height:100vh;"> | ||||||||||||||||||||||||||
| ${generateEmbedCode()} | ||||||||||||||||||||||||||
| </body> | ||||||||||||||||||||||||||
| </html>`; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const blob = new Blob([html], { type: 'text/html' }); | ||||||||||||||||||||||||||
| const previewUrl = URL.createObjectURL(blob); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| window.open(previewUrl, '_blank', 'noopener,noreferrer'); | ||||||||||||||||||||||||||
| window.setTimeout(() => URL.revokeObjectURL(previewUrl), 60_000); | ||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| window.open(generateIframeSrc(), '_blank', 'noopener,noreferrer'); | ||||||||||||||||||||||||||
| }, [generateEmbedCode, generateIframeSrc, values.embedType]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const handleSaveWidgetSettings = useCallback(async () => { | ||||||||||||||||||||||||||
| if (!onSaveWidgetSettings) { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,6 @@ import { | |
| currentReg, | ||
| parseCitationIndex, | ||
| preprocessLaTeX, | ||
| replaceRetrievingToSection, | ||
| replaceTextByOldReg, | ||
| replaceThinkToSection, | ||
| showImage, | ||
|
|
@@ -36,7 +35,8 @@ import { | |
| } from 'react-syntax-highlighter/dist/esm/styles/prism'; | ||
| import rehypeKatex from 'rehype-katex'; | ||
| import rehypeRaw from 'rehype-raw'; | ||
| import { MarkdownRemarkPlugins } from '@/constants/markdown-remark-plugins'; | ||
| import remarkGfm from 'remark-gfm'; | ||
| import remarkMath from 'remark-math'; | ||
| import { visitParents } from 'unist-util-visit-parents'; | ||
| import styles from './floating-chat-widget-markdown.module.less'; | ||
| import { useIsDarkTheme } from './theme-provider'; | ||
|
|
@@ -47,6 +47,18 @@ import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; | |
| const getChunkIndex = (match: string) => | ||
| parseCitationIndex(match.replace(/\[|\]/g, '')); | ||
|
|
||
| const emitChatLinkClick = (url: string) => { | ||
| const target = window.self !== window.top ? window.parent : window; | ||
|
|
||
| target.postMessage( | ||
| { | ||
| type: 'CHAT_LINK_CLICK', | ||
| url, | ||
| }, | ||
| '*', | ||
| ); | ||
| }; | ||
|
|
||
| const FloatingChatWidgetMarkdown = ({ | ||
| reference, | ||
| clickDocumentButton, | ||
|
|
@@ -66,7 +78,7 @@ const FloatingChatWidgetMarkdown = ({ | |
| const contentWithCursor = useMemo(() => { | ||
| const text = content === '' ? t('chat.searching') : content; | ||
| const nextText = replaceTextByOldReg(text); | ||
| return pipe(replaceThinkToSection, replaceRetrievingToSection, preprocessLaTeX)(nextText); | ||
| return pipe(replaceThinkToSection, preprocessLaTeX)(nextText); | ||
| }, [content, t]); | ||
|
|
||
| useEffect(() => { | ||
|
|
@@ -291,15 +303,32 @@ const FloatingChatWidgetMarkdown = ({ | |
| <div className="floating-chat-widget" dir={dir}> | ||
| <Markdown | ||
| rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]} | ||
| remarkPlugins={MarkdownRemarkPlugins} | ||
| remarkPlugins={[remarkGfm, remarkMath]} | ||
| className="text-sm leading-relaxed space-y-2 prose-sm max-w-full" | ||
| components={ | ||
| { | ||
| p: (props: any) => { | ||
| const { children, node, ...rest } = props; | ||
| void node; | ||
| return <p {...rest}>{children}</p>; | ||
| }, | ||
| p: ({ children, ...props }: any) => <p {...props}>{children}</p>, | ||
| a: ({ href, onClick, children, ...props }: any) => ( | ||
| <a | ||
| {...props} | ||
| href={href} | ||
| onClick={(event) => { | ||
| onClick?.(event); | ||
|
|
||
| const clickedUrl = | ||
| event.currentTarget.href || | ||
| href || | ||
| event.currentTarget.getAttribute('href') || | ||
| ''; | ||
|
|
||
| if (clickedUrl) { | ||
| emitChatLinkClick(clickedUrl); | ||
| } | ||
| }} | ||
|
Comment on lines
+315
to
+327
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="web/src/components/floating-chat-widget-markdown.tsx"
# Show the relevant section with line numbers
nl -ba "$FILE" | sed -n '280,360p'
# Find where emitChatLinkClick is defined/used
rg -n "emitChatLinkClick" "$FILE"
# Check for any existing navigation-prevention patterns in the file
rg -n "preventDefault|stopPropagation" "$FILE"
# Check how anchors are rendered/processed (look for custom link component / preprocessing)
rg -n "anchor|link|remark|rehype|remarkGfm|remarkMath|onClick" "$FILE"Repository: infiniflow/ragflow Length of output: 105 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE="web/src/components/floating-chat-widget-markdown.tsx"
# Basic sanity checks
test -f "$FILE"
echo "== file =="
wc -l "$FILE"
echo
echo "== excerpt around 295-345 (best-effort) =="
cat -n "$FILE" | sed -n '295,345p'
echo
echo "== emitChatLinkClick occurrences =="
rg -n "emitChatLinkClick" "$FILE" || true
echo
echo "== preventDefault/stopPropagation occurrences in file =="
rg -n "preventDefault|stopPropagation" "$FILE" || true
echo
echo "== onClick handler locations =="
rg -n "onClick=\{\(" "$FILE" || true
rg -n "onClick=\{\s*\(" "$FILE" || true
echoRepository: infiniflow/ragflow Length of output: 2657 Prevent default anchor navigation when emitting The custom Suggested fix onClick={(event) => {
onClick?.(event);
+ if (event.defaultPrevented) return;
+ event.preventDefault();
const clickedUrl =
event.currentTarget.href ||
href ||
event.currentTarget.getAttribute('href') ||
'';
if (clickedUrl) {
emitChatLinkClick(clickedUrl);
}
}}🤖 Prompt for AI Agents |
||
| > | ||
| {children} | ||
| </a> | ||
| ), | ||
| 'custom-typography': ({ children }: { children: string }) => | ||
| renderReference(children), | ||
| code(props: any) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove debug logging from the generated embed script.
Line 253 logs all
messageevents and payloads in host pages; this can leak user/session data into browser logs and creates noise in production.Suggested fix
- console.log(e)📝 Committable suggestion
🤖 Prompt for AI Agents