notifications
This commit is contained in:
parent
9b62063962
commit
3275b8b6c9
@ -74,6 +74,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Notification message styles */
|
||||||
|
.notification-message {
|
||||||
|
max-width: 100%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message p {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message img {
|
||||||
|
max-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message a.userMention {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #3b82f6;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* Email display styles */
|
/* Email display styles */
|
||||||
.email-content-display {
|
.email-content-display {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
|
import { SafeHTML } from '@/components/safe-html';
|
||||||
|
|
||||||
interface NotificationBadgeProps {
|
interface NotificationBadgeProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -168,7 +169,7 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
|
|||||||
<DropdownMenuItem key={notification.id} className="px-4 py-3 cursor-default">
|
<DropdownMenuItem key={notification.id} className="px-4 py-3 cursor-default">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div>
|
<div className="max-w-[90%]">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
{notification.title}
|
{notification.title}
|
||||||
{!notification.isRead && (
|
{!notification.isRead && (
|
||||||
@ -179,7 +180,7 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
|
|||||||
{formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}
|
{formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1 ml-2">
|
||||||
{!notification.isRead && (
|
{!notification.isRead && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -201,7 +202,10 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs mt-1">{notification.message}</p>
|
<SafeHTML
|
||||||
|
html={notification.message}
|
||||||
|
className="text-xs mt-1 notification-message"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
29
components/safe-html.tsx
Normal file
29
components/safe-html.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
|
interface SafeHTMLProps {
|
||||||
|
html: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SafeHTML({ html, className }: SafeHTMLProps) {
|
||||||
|
const sanitizedHTML = DOMPurify.sanitize(html, {
|
||||||
|
USE_PROFILES: { html: true },
|
||||||
|
ALLOWED_TAGS: [
|
||||||
|
'a', 'p', 'br', 'b', 'i', 'em', 'strong', 'span', 'div',
|
||||||
|
'img', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||||
|
'code', 'pre', 'blockquote'
|
||||||
|
],
|
||||||
|
ALLOWED_ATTR: [
|
||||||
|
'href', 'target', 'class', 'id', 'style', 'src', 'alt',
|
||||||
|
'data-tagged-user-id', 'data-mention'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className}
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
package-lock.json
generated
1
package-lock.json
generated
@ -57,6 +57,7 @@
|
|||||||
"cookies-next": "^5.1.0",
|
"cookies-next": "^5.1.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"dompurify": "^3.2.5",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"embla-carousel-react": "8.5.1",
|
"embla-carousel-react": "8.5.1",
|
||||||
"fullcalendar": "^6.1.15",
|
"fullcalendar": "^6.1.15",
|
||||||
|
|||||||
@ -58,6 +58,7 @@
|
|||||||
"cookies-next": "^5.1.0",
|
"cookies-next": "^5.1.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"dompurify": "^3.2.5",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"embla-carousel-react": "8.5.1",
|
"embla-carousel-react": "8.5.1",
|
||||||
"fullcalendar": "^6.1.15",
|
"fullcalendar": "^6.1.15",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user