diff --git a/web/app/(commonLayout)/remark-directive-test/page.tsx b/web/app/(commonLayout)/remark-directive-test/page.tsx index d5c38fcb31..1a6bc4b254 100644 --- a/web/app/(commonLayout)/remark-directive-test/page.tsx +++ b/web/app/(commonLayout)/remark-directive-test/page.tsx @@ -1,4 +1,5 @@ 'use client' +import InSiteMessage from '@/app/components/app/in-site-message' import { MarkdownWithDirective } from '@/app/components/base/markdown-with-directive' const markdown1 = ` @@ -39,19 +40,60 @@ Dify swag :::: ` +const inSiteMessageMain = ` +We’re speaking with technical teams to better understand: + +- How you discovered Dify +- What resonated — and what didn’t +- How we can improve the experience + +As a thank-you for your time: + +::::withIconCardList + +:::withIconCardItem {icon="https://assets.dify.ai/images/gift-card.png"} +$100 Amazon gift card +::: + +:::withIconCardItem {icon="https://assets.dify.ai/images/dify-swag.png"} +Exclusive Dify swag +::: + +:::: +` + export default function RemarkDirectiveTestPage() { return (

remark-directive test page

-
+
-
+
+ +
) } diff --git a/web/app/components/app/in-site-message.tsx b/web/app/components/app/in-site-message.tsx new file mode 100644 index 0000000000..8859fb3604 --- /dev/null +++ b/web/app/components/app/in-site-message.tsx @@ -0,0 +1,128 @@ +'use client' + +import { useMemo, useState } from 'react' +import Button from '@/app/components/base/button' +import { MarkdownWithDirective } from '@/app/components/base/markdown-with-directive' +import { cn } from '@/utils/classnames' + +type InSiteMessageAction = 'link' | 'close' +type InSiteMessageButtonType = 'primary' | 'default' + +export type InSiteMessageActionItem = { + action: InSiteMessageAction + data?: unknown + text: string + type: InSiteMessageButtonType +} + +type InSiteMessageProps = { + actions: InSiteMessageActionItem[] + className?: string + main: string + subtitle: string + title: string + title_pic_url?: string +} + +function normalizeLinkData(data: unknown): { href: string, rel?: string, target?: string } | null { + if (typeof data === 'string') + return { href: data, target: '_blank' } + + if (!data || typeof data !== 'object') + return null + + const candidate = data as { href?: unknown, rel?: unknown, target?: unknown } + if (typeof candidate.href !== 'string' || !candidate.href) + return null + + return { + href: candidate.href, + rel: typeof candidate.rel === 'string' ? candidate.rel : undefined, + target: typeof candidate.target === 'string' ? candidate.target : '_blank', + } +} + +function InSiteMessage({ + actions, + className, + main, + subtitle, + title, + title_pic_url, +}: InSiteMessageProps) { + const [visible, setVisible] = useState(true) + + const headerStyle = useMemo(() => { + if (!title_pic_url) { + return { + background: 'linear-gradient(180deg, #3268f4 0%, #194ccf 100%)', + } + } + + return { + backgroundImage: `linear-gradient(180deg, rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.4) 100%), url(${title_pic_url})`, + backgroundPosition: 'center', + backgroundSize: 'cover', + } + }, [title_pic_url]) + + const handleAction = (item: InSiteMessageActionItem) => { + if (item.action === 'close') { + setVisible(false) + return + } + + const linkData = normalizeLinkData(item.data) + if (!linkData) + return + + const target = linkData.target ?? '_blank' + if (target === '_self') { + window.location.assign(linkData.href) + return + } + + window.open(linkData.href, target, linkData.rel ?? 'noopener,noreferrer') + } + + if (!visible) + return null + + return ( +
+
+
+ {title} +
+
+ {subtitle} +
+
+ +
+ +
+ +
+ {actions.map(item => ( + + ))} +
+
+ ) +} + +export default InSiteMessage diff --git a/web/app/components/base/markdown-with-directive/components/with-icon-card-item.tsx b/web/app/components/base/markdown-with-directive/components/with-icon-card-item.tsx index 1deebb3a85..289fe6d5b6 100644 --- a/web/app/components/base/markdown-with-directive/components/with-icon-card-item.tsx +++ b/web/app/components/base/markdown-with-directive/components/with-icon-card-item.tsx @@ -9,7 +9,7 @@ type WithIconItemProps = WithIconCardItemProps & { function WithIconCardItem({ icon, children }: WithIconItemProps) { return (
- icon + icon
{children}