feat: mentions in lives_with and text_body fields
Some checks failed
/ integration-test--firefox (push) Failing after 3m7s

This commit is contained in:
Robert Perce 2026-01-26 15:25:45 -06:00
parent fd5f1899c1
commit d42adbe274
10 changed files with 369 additions and 200 deletions

View file

@ -1,15 +1,12 @@
use chrono::NaiveDate;
use maud::{Markup, PreEscaped, html};
use regex::Regex;
use maud::{Markup, html};
use serde_json::json;
use sqlx::sqlite::{SqlitePool, SqliteRow};
use sqlx::{FromRow, Row};
use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use super::contact::MentionTrie;
use crate::AppError;
use crate::db::DbId;
use crate::switchboard::{MentionHost, MentionHostType};
#[derive(Debug)]
pub struct JournalEntry {
@ -18,84 +15,19 @@ pub struct JournalEntry {
pub date: NaiveDate,
}
#[derive(Debug, PartialEq, Eq, Hash, FromRow)]
pub struct Mention {
pub entry_id: DbId,
pub url: String,
pub input_text: String,
pub byte_range_start: u32,
pub byte_range_end: u32,
impl<'a> Into<MentionHost<'a>> for &'a JournalEntry {
fn into(self) -> MentionHost<'a> {
MentionHost {
entity_id: self.id,
entity_type: MentionHostType::JournalEntry as DbId,
input: &self.value,
}
}
}
impl JournalEntry {
pub fn extract_mentions(&self, trie: &MentionTrie) -> HashSet<Mention> {
let name_re = Regex::new(r"\[\[(.+?)\]\]").unwrap();
name_re
.captures_iter(&self.value)
.map(|caps| {
let range = caps.get_match().range();
trie.get(&caps[1]).map(|url| Mention {
entry_id: self.id,
url: url.to_string(),
input_text: caps[1].to_string(),
byte_range_start: u32::try_from(range.start).unwrap(),
byte_range_end: u32::try_from(range.end).unwrap(),
})
})
.filter(|o| o.is_some())
.map(|o| o.unwrap())
.collect()
}
pub async fn insert_mentions(
&self,
trie: Arc<RwLock<MentionTrie>>,
pool: &SqlitePool,
) -> Result<HashSet<Mention>, AppError> {
let mentions = {
let trie = trie.read().unwrap();
self.extract_mentions(&trie)
};
for mention in &mentions {
sqlx::query!(
"insert into journal_mentions(
entry_id, url, input_text,
byte_range_start, byte_range_end
) values ($1, $2, $3, $4, $5)",
mention.entry_id,
mention.url,
mention.input_text,
mention.byte_range_start,
mention.byte_range_end
)
.execute(pool)
.await?;
}
Ok(mentions)
}
pub async fn to_html(&self, pool: &SqlitePool) -> Result<Markup, AppError> {
// important to sort desc so that changing contents early in the string
// doesn't break inserting mentions at byte offsets further in
let mentions: Vec<Mention> = sqlx::query_as(
"select * from journal_mentions
where entry_id = $1 order by byte_range_start desc",
)
.bind(self.id)
.fetch_all(pool)
.await?;
let mut value = self.value.clone();
for mention in mentions {
tracing::debug!("url ({})", mention.url);
value.replace_range(
(mention.byte_range_start as usize)..(mention.byte_range_end as usize),
&format!("[{}]({})", mention.input_text, mention.url),
);
}
let rendered = Into::<MentionHost>::into(self).format_pool(pool).await?;
let entry_url = format!("/journal_entry/{}", self.id);
let date = self.date.to_string();
@ -103,7 +35,7 @@ impl JournalEntry {
.entry {
.view ":class"="{ hide: edit }" {
.date { (date) }
.content { (PreEscaped(markdown::to_html(&value))) }
.content { (rendered) }
}
form .edit ":class"="{ hide: !edit }" x-data=(json!({ "date": date, "initial_date": date, "value": self.value, "initial_value": self.value })) {
input name="date" x-model="date";