feat(task-comments): enhance comment processing with linkification and sanitization

- Added a utility to linkify URLs in comments, allowing users to click and open links.
- Refactored comment content processing to handle both mentions and links, improving user interaction.
- Updated the sanitizeHtml function to allow <span> tags for mentions, enhancing content display.
This commit is contained in:
shancds
2025-06-19 13:23:40 +05:30
parent b47b3253f6
commit ece614941e
2 changed files with 35 additions and 8 deletions

View File

@@ -18,6 +18,7 @@ import { colors } from '@/styles/colors';
import AttachmentsGrid from '../attachments/attachments-grid';
import { TFunction } from 'i18next';
import SingleAvatar from '@/components/common/single-avatar/single-avatar';
import { sanitizeHtml } from '@/utils/sanitizeInput';
// Helper function to format date for time separators
const formatDateForSeparator = (date: string) => {
@@ -56,6 +57,32 @@ const processMentions = (content: string) => {
return content.replace(/@(\w+)/g, '<span class="mentions">@$1</span>');
};
// Utility to linkify URLs in text
const linkify = (text: string) => {
if (!text) return '';
// Regex to match URLs (http, https, www)
return text.replace(/(https?:\/\/[^\s]+|www\.[^\s]+)/g, (url) => {
let href = url;
if (!href.startsWith('http')) {
href = 'http://' + href;
}
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${url}</a>`;
});
};
// Helper function to process mentions and links in content
const processContent = (content: string) => {
if (!content) return '';
// First, linkify URLs
let processed = linkify(content);
// Then, process mentions (if not already processed)
if (!hasProcessedMentions(processed)) {
processed = processMentions(processed);
}
// Sanitize the final HTML (allowing <a> and <span class="mentions">)
return sanitizeHtml(processed);
};
const TaskComments = ({ taskId, t }: { taskId?: string, t: TFunction }) => {
const [loading, setLoading] = useState(true);
const [comments, setComments] = useState<ITaskCommentViewModel[]>([]);
@@ -80,10 +107,10 @@ const TaskComments = ({ taskId, t }: { taskId?: string, t: TFunction }) => {
return dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? -1 : 1;
});
// Process mentions in content
// Process content (mentions and links)
sortedComments.forEach(comment => {
if (comment.content && !hasProcessedMentions(comment.content)) {
comment.content = processMentions(comment.content);
if (comment.content) {
comment.content = processContent(comment.content);
}
});
@@ -184,9 +211,9 @@ const TaskComments = ({ taskId, t }: { taskId?: string, t: TFunction }) => {
const commentUpdated = (comment: ITaskCommentViewModel) => {
comment.edit = false;
// Process mentions in updated content
if (comment.content && !hasProcessedMentions(comment.content)) {
comment.content = processMentions(comment.content);
// Process content (mentions and links) in updated comment
if (comment.content) {
comment.content = processContent(comment.content);
}
setComments([...comments]); // Force re-render
};

View File

@@ -29,7 +29,7 @@ export const sanitizeHtml = (input: string): string => {
if (!input) return '';
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'span'],
ALLOWED_ATTR: ['href', 'target', 'rel', 'class'],
});
};