mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-05-13 15:10:05 +00:00
Merge pull request #947 from Jasonzhu1207/refactor/ui-rebuild
fix: Reply Setting
This commit is contained in:
@@ -2566,6 +2566,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ghost preview — appears on hover, frosted glass
|
// Ghost preview — appears on hover, frosted glass
|
||||||
|
&.preview-below {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.reply-ghost {
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
bottom: auto;
|
||||||
|
transform: translateY(-4px) scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .reply-ghost {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.preview-above {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.reply-ghost {
|
.reply-ghost {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: calc(100% + 6px);
|
bottom: calc(100% + 6px);
|
||||||
|
|||||||
@@ -1511,6 +1511,7 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
const [groupMemberSearchKeyword, setGroupMemberSearchKeyword] = useState('')
|
const [groupMemberSearchKeyword, setGroupMemberSearchKeyword] = useState('')
|
||||||
const [copiedField, setCopiedField] = useState<string | null>(null)
|
const [copiedField, setCopiedField] = useState<string | null>(null)
|
||||||
const [highlightedMessageKeys, setHighlightedMessageKeys] = useState<string[]>([])
|
const [highlightedMessageKeys, setHighlightedMessageKeys] = useState<string[]>([])
|
||||||
|
const [quoteLayout, setQuoteLayout] = useState<configService.QuoteLayout>('quote-top')
|
||||||
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false)
|
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false)
|
||||||
const [foldedView, setFoldedView] = useState(false) // 是否在"折叠的群聊"视图
|
const [foldedView, setFoldedView] = useState(false) // 是否在"折叠的群聊"视图
|
||||||
const [bizView, setBizView] = useState(false) // 是否在"公众号"视图
|
const [bizView, setBizView] = useState(false) // 是否在"公众号"视图
|
||||||
@@ -3067,6 +3068,33 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let canceled = false
|
||||||
|
const loadQuoteLayout = () => {
|
||||||
|
void configService.getQuoteLayout()
|
||||||
|
.then((layout) => {
|
||||||
|
if (!canceled) setQuoteLayout(layout)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!canceled) setQuoteLayout('quote-top')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadQuoteLayout()
|
||||||
|
const handleFocus = () => loadQuoteLayout()
|
||||||
|
const handleQuoteLayoutChanged = (event: Event) => {
|
||||||
|
const layout = (event as CustomEvent<configService.QuoteLayout>).detail
|
||||||
|
setQuoteLayout(layout === 'quote-bottom' ? 'quote-bottom' : 'quote-top')
|
||||||
|
}
|
||||||
|
window.addEventListener('focus', handleFocus)
|
||||||
|
window.addEventListener('quote-layout-changed', handleQuoteLayoutChanged)
|
||||||
|
return () => {
|
||||||
|
canceled = true
|
||||||
|
window.removeEventListener('focus', handleFocus)
|
||||||
|
window.removeEventListener('quote-layout-changed', handleQuoteLayoutChanged)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
void (async () => {
|
void (async () => {
|
||||||
@@ -6850,6 +6878,7 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
myAvatarUrl={myAvatarUrl}
|
myAvatarUrl={myAvatarUrl}
|
||||||
myWxid={myWxid}
|
myWxid={myWxid}
|
||||||
isGroupChat={isCurrentSessionGroup}
|
isGroupChat={isCurrentSessionGroup}
|
||||||
|
quoteLayout={quoteLayout}
|
||||||
autoTranscribeVoiceEnabled={autoTranscribeVoiceEnabled}
|
autoTranscribeVoiceEnabled={autoTranscribeVoiceEnabled}
|
||||||
onRequireModelDownload={handleRequireModelDownload}
|
onRequireModelDownload={handleRequireModelDownload}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
@@ -6870,6 +6899,7 @@ function ChatPage(props: ChatPageProps) {
|
|||||||
myAvatarUrl,
|
myAvatarUrl,
|
||||||
myWxid,
|
myWxid,
|
||||||
isCurrentSessionGroup,
|
isCurrentSessionGroup,
|
||||||
|
quoteLayout,
|
||||||
autoTranscribeVoiceEnabled,
|
autoTranscribeVoiceEnabled,
|
||||||
handleRequireModelDownload,
|
handleRequireModelDownload,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
@@ -8434,6 +8464,7 @@ function MessageBubble({
|
|||||||
myAvatarUrl,
|
myAvatarUrl,
|
||||||
myWxid,
|
myWxid,
|
||||||
isGroupChat,
|
isGroupChat,
|
||||||
|
quoteLayout,
|
||||||
autoTranscribeVoiceEnabled,
|
autoTranscribeVoiceEnabled,
|
||||||
onRequireModelDownload,
|
onRequireModelDownload,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
@@ -8449,6 +8480,7 @@ function MessageBubble({
|
|||||||
myAvatarUrl?: string;
|
myAvatarUrl?: string;
|
||||||
myWxid?: string;
|
myWxid?: string;
|
||||||
isGroupChat?: boolean;
|
isGroupChat?: boolean;
|
||||||
|
quoteLayout: configService.QuoteLayout;
|
||||||
autoTranscribeVoiceEnabled?: boolean;
|
autoTranscribeVoiceEnabled?: boolean;
|
||||||
onRequireModelDownload?: (sessionId: string, messageId: string) => void;
|
onRequireModelDownload?: (sessionId: string, messageId: string) => void;
|
||||||
onContextMenu?: (e: React.MouseEvent, message: Message) => void;
|
onContextMenu?: (e: React.MouseEvent, message: Message) => void;
|
||||||
@@ -9728,17 +9760,26 @@ function MessageBubble({
|
|||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
onJumpToQuotedMessage(quotedJumpTarget)
|
onJumpToQuotedMessage(quotedJumpTarget)
|
||||||
}, [isSelectionMode, onJumpToQuotedMessage, quotedJumpTarget])
|
}, [isSelectionMode, onJumpToQuotedMessage, quotedJumpTarget])
|
||||||
// Ambient Reply: single fixed layout (anchor above, message below)
|
const isQuoteBelow = quoteLayout === 'quote-bottom'
|
||||||
const renderBubbleWithQuote = useCallback((quotedNode: React.ReactNode, messageNode: React.ReactNode) => (
|
const renderBubbleWithQuote = useCallback((quotedNode: React.ReactNode, messageNode: React.ReactNode) => (
|
||||||
<div className="bubble-content">
|
<div className={`bubble-content ${isQuoteBelow ? 'quote-layout-bottom' : 'quote-layout-top'}`}>
|
||||||
{quotedNode}
|
{isQuoteBelow ? (
|
||||||
{messageNode}
|
<>
|
||||||
|
{messageNode}
|
||||||
|
{quotedNode}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{quotedNode}
|
||||||
|
{messageNode}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
), [])
|
), [isQuoteBelow])
|
||||||
|
|
||||||
// Ambient Reply: render reply-anchor + ghost preview
|
// Ambient Reply: render reply-anchor + ghost preview
|
||||||
const renderQuotedMessageBlock = useCallback((contentNode: React.ReactNode) => (
|
const renderQuotedMessageBlock = useCallback((contentNode: React.ReactNode) => (
|
||||||
<div className="ambient-reply-wrapper">
|
<div className={`ambient-reply-wrapper ${isQuoteBelow ? 'preview-below' : 'preview-above'}`}>
|
||||||
{/* Reply anchor - always visible, subtle */}
|
{/* Reply anchor - always visible, subtle */}
|
||||||
<div
|
<div
|
||||||
className={`reply-anchor ${quotedJumpTarget ? 'jumpable' : ''}`}
|
className={`reply-anchor ${quotedJumpTarget ? 'jumpable' : ''}`}
|
||||||
@@ -9761,7 +9802,7 @@ function MessageBubble({
|
|||||||
<div className="reply-ghost-text">{contentNode}</div>
|
<div className="reply-ghost-text">{contentNode}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
), [displayQuotedSenderName, handleQuotedJumpClick, handleQuotedJumpKeyDown, isSelectionMode, quotedJumpTarget])
|
), [displayQuotedSenderName, handleQuotedJumpClick, handleQuotedJumpKeyDown, isQuoteBelow, isSelectionMode, quotedJumpTarget])
|
||||||
|
|
||||||
const handlePlayVideo = useCallback(async () => {
|
const handlePlayVideo = useCallback(async () => {
|
||||||
if (!videoInfo?.videoUrl) return
|
if (!videoInfo?.videoUrl) return
|
||||||
@@ -11024,6 +11065,7 @@ const MemoMessageBubble = React.memo(MessageBubble, (prevProps, nextProps) => {
|
|||||||
if (prevProps.myAvatarUrl !== nextProps.myAvatarUrl) return false
|
if (prevProps.myAvatarUrl !== nextProps.myAvatarUrl) return false
|
||||||
if (prevProps.myWxid !== nextProps.myWxid) return false
|
if (prevProps.myWxid !== nextProps.myWxid) return false
|
||||||
if (prevProps.isGroupChat !== nextProps.isGroupChat) return false
|
if (prevProps.isGroupChat !== nextProps.isGroupChat) return false
|
||||||
|
if (prevProps.quoteLayout !== nextProps.quoteLayout) return false
|
||||||
if (prevProps.autoTranscribeVoiceEnabled !== nextProps.autoTranscribeVoiceEnabled) return false
|
if (prevProps.autoTranscribeVoiceEnabled !== nextProps.autoTranscribeVoiceEnabled) return false
|
||||||
if (prevProps.isSelectionMode !== nextProps.isSelectionMode) return false
|
if (prevProps.isSelectionMode !== nextProps.isSelectionMode) return false
|
||||||
if (prevProps.isSelected !== nextProps.isSelected) return false
|
if (prevProps.isSelected !== nextProps.isSelected) return false
|
||||||
|
|||||||
@@ -1698,6 +1698,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
|||||||
if (selected) return
|
if (selected) return
|
||||||
setQuoteLayout(option.value)
|
setQuoteLayout(option.value)
|
||||||
await configService.setQuoteLayout(option.value)
|
await configService.setQuoteLayout(option.value)
|
||||||
|
window.dispatchEvent(new CustomEvent('quote-layout-changed', { detail: option.value }))
|
||||||
showMessage(option.successMessage, true)
|
showMessage(option.successMessage, true)
|
||||||
}}
|
}}
|
||||||
role="radio"
|
role="radio"
|
||||||
|
|||||||
Reference in New Issue
Block a user