add @mentions — parsed server-side, rendered as highlighted spans
- Backend: parse_mentions() extracts @username from content - Message API response includes mentions: string[] field - Frontend: renderContent() highlights @mentions in hot orange - Works with alphanumeric + hyphens + underscores Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ pub struct Message {
|
||||
pub content: String,
|
||||
#[ts(optional)]
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
pub mentions: Vec<String>,
|
||||
#[ts(optional)]
|
||||
pub reply_to: Option<Uuid>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
@@ -2,6 +2,16 @@ use chrono::{DateTime, Utc};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Extract @mentions from message content
|
||||
fn parse_mentions(content: &str) -> Vec<String> {
|
||||
content
|
||||
.split_whitespace()
|
||||
.filter(|w| w.starts_with('@') && w.len() > 1)
|
||||
.map(|w| w.trim_start_matches('@').trim_end_matches(|c: char| !c.is_alphanumeric() && c != '_' && c != '-').to_string())
|
||||
.filter(|m| !m.is_empty())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// DB row types — these map directly to SQL tables.
|
||||
/// Separate from API types in colony-types.
|
||||
|
||||
@@ -87,6 +97,7 @@ impl MessageRow {
|
||||
} else {
|
||||
self.content.clone()
|
||||
},
|
||||
mentions: parse_mentions(&self.content),
|
||||
metadata: self
|
||||
.metadata
|
||||
.as_ref()
|
||||
|
||||
@@ -26,6 +26,21 @@ function timeAgo(dateStr: string): string {
|
||||
return `${Math.floor(hrs / 24)}d`;
|
||||
}
|
||||
|
||||
function renderContent(text: string) {
|
||||
// Split on @mentions and render them as highlighted spans
|
||||
const parts = text.split(/(@[\w-]+)/g);
|
||||
return parts.map((part, i) => {
|
||||
if (part.startsWith("@")) {
|
||||
return (
|
||||
<span key={i} className="text-primary font-bold cursor-default">
|
||||
{part}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return part;
|
||||
});
|
||||
}
|
||||
|
||||
export function MessageItem({ message, replyTarget, onReply }: Props) {
|
||||
const [metaOpen, setMetaOpen] = useState(false);
|
||||
const isAgent = message.user.role === "agent";
|
||||
@@ -110,7 +125,7 @@ export function MessageItem({ message, replyTarget, onReply }: Props) {
|
||||
{isDeleted ? (
|
||||
<span className="italic text-muted-foreground/40">[deleted]</span>
|
||||
) : (
|
||||
message.content
|
||||
renderContent(message.content)
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user