Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -236,5 +236,19 @@ describe('components/form-elements/draft-js-mention-selector/utils', () => {

expect(getFormattedCommentText(dummyEditorState)).toEqual(expected);
});

test.each`
input | expected
${' hello '} | ${'hello'}
${'\n\nhello\n\n'} | ${'hello'}
${'\thello\t'} | ${'hello'}
${' \t\nhello\n '} | ${'hello'}
${'hello world'} | ${'hello world'}
${' hello world '} | ${'hello world'}
`('should trim leading and trailing whitespace from "$input"', ({ input, expected }) => {
const dummyEditorState = EditorState.createWithContent(ContentState.createFromText(input));

expect(getFormattedCommentText(dummyEditorState)).toEqual({ text: expected, hasMention: false });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ function getFormattedCommentText(editorState: EditorState): { hasMention: boolea

// Concatenate the array of block strings with newlines
// (Each block represents a paragraph)
return { text: resultStringArr.join('\n'), hasMention };
return { text: resultStringArr.join('\n').trim(), hasMention };
}

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@
button svg {
pointer-events: auto;
}

&-mentionEmpty {
padding: var(--bp-space-030) var(--bp-space-040);
}
}
49 changes: 38 additions & 11 deletions src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

import * as React from 'react';
import noop from 'lodash/noop';
import { useIntl } from 'react-intl';
import { FormattedMessage, useIntl } from 'react-intl';

import { ActivityFeed, useActivityFeedScroll } from '@box/activity-feed';
import { serializeMentionMarkup } from '@box/threaded-annotations';

import TaskModal from '../TaskModal';

import FeedItemRow from './FeedItemRow';
import { serializeEditorContent } from './helpers';
import { transformFeedItem } from './transformers';

import type { ActivityFeedV2Props, TransformedFeedItem, UserContact } from './types';
Expand All @@ -24,6 +24,7 @@ import type { TaskAssigneeCollection, TaskNew } from '../../../common/types/task
import { TASK_COMPLETION_RULE_ALL, TASK_EDIT_MODE_EDIT, TASK_TYPE_APPROVAL } from '../../../constants';

import commonMessages from '../../common/messages';
import draftJsMentionSelectorMessages from '../../../components/form-elements/draft-js-mention-selector/messages';
import messages from '../messages';

import './ActivityFeedV2.scss';
Expand Down Expand Up @@ -68,14 +69,16 @@ const ActivityFeedV2 = ({

const scrolledEntryIdRef = React.useRef<string | null>(null);
const hasScrolledToEndRef = React.useRef(false);
const knownIdsBeforePostRef = React.useRef<Set<string> | null>(null);

const fetchUsers = React.useCallback(
async (inputValue: string): Promise<UserContact[]> => {
if (!getMentionAsync) {
const trimmed = inputValue.trim();
if (!trimmed || !getMentionAsync) {
return [];
}
try {
const entries = await getMentionAsync(inputValue);
const entries = await getMentionAsync(trimmed);
return entries.map((c: Record<string, unknown>) => ({
email: (c.email as string) ?? (c.login as string) ?? '',
id: Number(c.id) || 0,
Expand Down Expand Up @@ -114,10 +117,20 @@ const ActivityFeedV2 = ({

const userSelectorProps = React.useMemo(
() => ({
allowEmptyQuery: true,
ariaRoleDescription: intl.formatMessage(messages.mentionUserSelectorRoleDescription),
fetchAvatarUrls,
fetchUsers,
loadingAriaLabel: intl.formatMessage(messages.mentionUserSelectorLoading),
renderEmpty: (value: string) => (
<div className="bcs-NewActivityFeed-mentionEmpty">
<FormattedMessage
{...(value.trim()
? draftJsMentionSelectorMessages.noUsersFound
: draftJsMentionSelectorMessages.startMention)}
/>
</div>
),
}),
[fetchAvatarUrls, fetchUsers, intl],
);
Expand Down Expand Up @@ -247,19 +260,33 @@ const ActivityFeedV2 = ({
}
}, [activeFeedEntryId, filteredItems, scrollHandle]);

// Scroll only to items added since the post snapshot, so a concurrent push from
// another user doesn't hijack the viewport.
React.useEffect(() => {
const knownIds = knownIdsBeforePostRef.current;
if (!knownIds || !scrollHandle) return;
const newItem = filteredItems.find(item => !knownIds.has(item.id));
if (!newItem) return;
if (scrollHandle.scrollTo(newItem.id)) {
knownIdsBeforePostRef.current = null;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}, [filteredItems, scrollHandle]);

const handleCommentPost = React.useCallback(
async (content: unknown) => {
if (!onCommentCreate) return;
let serialized;
const serialized = serializeEditorContent(content);
if (!serialized || !serialized.text) return;
try {
serialized = serializeMentionMarkup(content as Parameters<typeof serializeMentionMarkup>[0]);
} catch {
return;
const snapshot = new Set(filteredItems.map(item => item.id));
await onCommentCreate(serialized.text, serialized.hasMention);
knownIdsBeforePostRef.current = snapshot;
} catch (error) {
// eslint-disable-next-line no-console
console.error('ActivityFeedV2: failed to post comment', error);
}
if (!serialized.text.trim()) return;
onCommentCreate(serialized.text, serialized.hasMention);
},
[onCommentCreate],
[filteredItems, onCommentCreate],
);

return (
Expand Down
Loading
Loading