Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add t tags for hashtags #592

Merged
merged 5 commits into from
Jan 14, 2025
Merged
Changes from all 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
71 changes: 63 additions & 8 deletions crates/notedeck_columns/src/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ impl NewPost {
}

pub fn to_note(&self, seckey: &[u8; 32]) -> Note {
add_client_tag(NoteBuilder::new())
let mut builder = add_client_tag(NoteBuilder::new())
.kind(1)
.content(&self.content)
.sign(seckey)
.build()
.expect("note should be ok")
.content(&self.content);

for hashtag in Self::extract_hashtags(&self.content) {
builder = builder.start_tag().tag_str("t").tag_str(&hashtag);
}

builder.sign(seckey).build().expect("note should be ok")
}

pub fn to_reply(&self, seckey: &[u8; 32], replying_to: &Note) -> Note {
Expand Down Expand Up @@ -106,9 +109,13 @@ impl NewPost {
enostr::NoteId::new(*quoting.id()).to_bech().unwrap()
);

NoteBuilder::new()
.kind(1)
.content(&new_content)
let mut builder = NoteBuilder::new().kind(1).content(&new_content);

for hashtag in Self::extract_hashtags(&self.content) {
builder = builder.start_tag().tag_str("t").tag_str(&hashtag);
}

builder
.start_tag()
.tag_str("q")
.tag_str(&hex::encode(quoting.id()))
Expand All @@ -119,4 +126,52 @@ impl NewPost {
.build()
.expect("expected build to work")
}

fn extract_hashtags(content: &str) -> HashSet<String> {
let mut hashtags = HashSet::new();
for word in
content.split(|c: char| c.is_whitespace() || (c.is_ascii_punctuation() && c != '#'))
{
if word.starts_with('#') && word.len() > 1 {
let tag = word[1..].to_lowercase();
if !tag.is_empty() {
hashtags.insert(tag);
}
}
}
hashtags
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_extract_hashtags() {
let test_cases = vec![
("Hello #world", vec!["world"]),
("Multiple #tags #in #one post", vec!["tags", "in", "one"]),
("No hashtags here", vec![]),
("#tag1 with #tag2!", vec!["tag1", "tag2"]),
("Ignore # empty", vec![]),
("Testing emoji #🍌banana", vec!["🍌banana"]),
("Testing emoji #🍌", vec!["🍌"]),
("Duplicate #tag #tag #tag", vec!["tag"]),
("Mixed case #TaG #tag #TAG", vec!["tag"]),
(
"#tag1, #tag2, #tag3 with commas",
vec!["tag1", "tag2", "tag3"],
),
("Separated by commas #tag1,#tag2", vec!["tag1", "tag2"]),
("Separated by periods #tag1.#tag2", vec!["tag1", "tag2"]),
("Separated by semicolons #tag1;#tag2", vec!["tag1", "tag2"]),
];
jb55 marked this conversation as resolved.
Show resolved Hide resolved

for (input, expected) in test_cases {
let result = NewPost::extract_hashtags(input);
let expected: HashSet<String> = expected.into_iter().map(String::from).collect();
assert_eq!(result, expected, "Failed for input: {}", input);
}
}
}