fix: improve message composer and comment read handling
- Updated MessageComposer to disable input when provisioning is active, enhancing user experience during team launches. - Refactored useMarkCommentsRead hook to simplify comment read logic by removing IntersectionObserver, ensuring comments are marked as read on mount and when the comments list changes.
This commit is contained in:
parent
5e704ddc78
commit
d090a57e24
2 changed files with 24 additions and 44 deletions
|
|
@ -189,6 +189,7 @@ export const MessageComposer = ({
|
||||||
trimmed.length > 0 &&
|
trimmed.length > 0 &&
|
||||||
trimmed.length <= MAX_TEXT_LENGTH &&
|
trimmed.length <= MAX_TEXT_LENGTH &&
|
||||||
!sending &&
|
!sending &&
|
||||||
|
!isProvisioning &&
|
||||||
!attachmentsBlocked &&
|
!attachmentsBlocked &&
|
||||||
(!isCrossTeam || onCrossTeamSend !== undefined);
|
(!isCrossTeam || onCrossTeamSend !== undefined);
|
||||||
|
|
||||||
|
|
@ -621,7 +622,7 @@ export const MessageComposer = ({
|
||||||
minRows={2}
|
minRows={2}
|
||||||
maxRows={6}
|
maxRows={6}
|
||||||
maxLength={MAX_TEXT_LENGTH}
|
maxLength={MAX_TEXT_LENGTH}
|
||||||
disabled={sending}
|
disabled={sending || isProvisioning}
|
||||||
cornerAction={
|
cornerAction={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* NOTE: ContextRing disabled — usage formula is inaccurate */}
|
{/* NOTE: ContextRing disabled — usage formula is inaccurate */}
|
||||||
|
|
|
||||||
|
|
@ -1,61 +1,40 @@
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { markAsRead } from '@renderer/services/commentReadStorage';
|
import { markAsRead } from '@renderer/services/commentReadStorage';
|
||||||
|
|
||||||
import type { TaskComment } from '@shared/types';
|
import type { TaskComment } from '@shared/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks task comments as read when the component is mounted and
|
||||||
|
* whenever the comments list changes while mounted.
|
||||||
|
*
|
||||||
|
* Previously used IntersectionObserver, but since the component
|
||||||
|
* is only rendered inside a CollapsibleTeamSection (conditional
|
||||||
|
* mount/unmount controls visibility), a simple effect is both
|
||||||
|
* simpler and more reliable — especially inside Dialog portals
|
||||||
|
* where IntersectionObserver can miss the initial intersection.
|
||||||
|
*
|
||||||
|
* Returns a ref callback for the comments container (kept for
|
||||||
|
* API compatibility with TaskCommentsSection).
|
||||||
|
*/
|
||||||
export function useMarkCommentsRead(
|
export function useMarkCommentsRead(
|
||||||
teamName: string,
|
teamName: string,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
comments: TaskComment[]
|
comments: TaskComment[]
|
||||||
): (node: HTMLElement | null) => void {
|
): (node: HTMLElement | null) => void {
|
||||||
const isVisibleRef = useRef(false);
|
const nodeRef = useRef<HTMLElement | null>(null);
|
||||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
|
||||||
|
|
||||||
// Mark comments as read if section is visible
|
// Mark as read on mount and whenever comments change
|
||||||
const markIfVisible = useCallback(() => {
|
useEffect(() => {
|
||||||
if (!isVisibleRef.current || comments.length === 0) return;
|
if (comments.length === 0) return;
|
||||||
const latest = Math.max(...comments.map((c) => new Date(c.createdAt).getTime()));
|
const latest = Math.max(...comments.map((c) => new Date(c.createdAt).getTime()));
|
||||||
if (latest > 0) markAsRead(teamName, taskId, latest);
|
if (latest > 0) markAsRead(teamName, taskId, latest);
|
||||||
}, [teamName, taskId, comments]);
|
}, [teamName, taskId, comments]);
|
||||||
|
|
||||||
// Re-mark when new comments arrive while section is visible
|
// Stable ref callback (no dependencies — just stores the node)
|
||||||
useEffect(() => {
|
const refCallback = useRef((node: HTMLElement | null) => {
|
||||||
markIfVisible();
|
nodeRef.current = node;
|
||||||
}, [markIfVisible]);
|
}).current;
|
||||||
|
|
||||||
// IntersectionObserver ref callback
|
|
||||||
const refCallback = useCallback(
|
|
||||||
(node: HTMLElement | null) => {
|
|
||||||
// Cleanup previous
|
|
||||||
if (observerRef.current) {
|
|
||||||
observerRef.current.disconnect();
|
|
||||||
observerRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
isVisibleRef.current = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
observerRef.current = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
isVisibleRef.current = entry.isIntersecting;
|
|
||||||
if (entry.isIntersecting) markIfVisible();
|
|
||||||
},
|
|
||||||
{ threshold: 0.1 }
|
|
||||||
);
|
|
||||||
observerRef.current.observe(node);
|
|
||||||
},
|
|
||||||
[markIfVisible]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
observerRef.current?.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return refCallback;
|
return refCallback;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue