feat: add support for custom emojis (NIP-30) (#63)
Reviewed-on: #63
This commit was merged in pull request #63.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::{
|
||||
AnyElement, App, ElementId, HighlightStyle, IntoElement, SharedString, StyleRefinement,
|
||||
Styled as _, Window,
|
||||
AnyElement, App, ElementId, HighlightStyle, IntoElement, SharedString, SharedUri,
|
||||
StyleRefinement, Styled as _, Window,
|
||||
};
|
||||
use gpui_base::{TextView, TextViewStyle};
|
||||
use theme::ActiveTheme as _;
|
||||
@@ -12,6 +12,8 @@ use theme::ActiveTheme as _;
|
||||
pub struct InlineReplacement {
|
||||
pub range: Range<usize>,
|
||||
pub text: SharedString,
|
||||
/// When set, the span renders as this image (NIP-30 custom emoji)
|
||||
pub image: Option<SharedUri>,
|
||||
}
|
||||
|
||||
impl InlineReplacement {
|
||||
@@ -19,6 +21,19 @@ impl InlineReplacement {
|
||||
Self {
|
||||
range,
|
||||
text: text.into(),
|
||||
image: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image(
|
||||
range: Range<usize>,
|
||||
alt: impl Into<SharedString>,
|
||||
url: impl Into<SharedUri>,
|
||||
) -> Self {
|
||||
Self {
|
||||
range,
|
||||
text: alt.into(),
|
||||
image: Some(url.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,11 +61,17 @@ impl RenderedText {
|
||||
}
|
||||
|
||||
text.push_str(&content[last..range.start]);
|
||||
text.push_str(&format!(
|
||||
"[{}](<{}>)",
|
||||
escape_link_text(&replacement.text),
|
||||
&content[range.start..range.end]
|
||||
));
|
||||
match &replacement.image {
|
||||
Some(url) => text.push_str(&format!(
|
||||
"",
|
||||
escape_link_text(&replacement.text)
|
||||
)),
|
||||
None => text.push_str(&format!(
|
||||
"[{}](<{}>)",
|
||||
escape_link_text(&replacement.text),
|
||||
&content[range.start..range.end]
|
||||
)),
|
||||
}
|
||||
last = range.end;
|
||||
}
|
||||
text.push_str(&content[last..]);
|
||||
|
||||
@@ -370,26 +370,36 @@ impl RenderOnce for MessageMedia {
|
||||
/// Reactions of one emoji with the count of authors and whether the current user reacted.
|
||||
pub struct ReactionGroup {
|
||||
pub emoji: SharedString,
|
||||
/// Image of a custom emoji (NIP-30) reaction
|
||||
pub image: Option<SharedUri>,
|
||||
pub count: usize,
|
||||
pub mine: bool,
|
||||
}
|
||||
|
||||
/// Group (emoji, author) pairs by emoji, sorted by emoji, marking groups containing `me`.
|
||||
/// Group (emoji, image, author) triples by emoji, sorted by emoji.
|
||||
pub fn group_reactions<A: PartialEq>(
|
||||
reactions: impl IntoIterator<Item = (SharedString, A)>,
|
||||
reactions: impl IntoIterator<Item = (SharedString, Option<SharedUri>, A)>,
|
||||
me: Option<A>,
|
||||
) -> Vec<ReactionGroup> {
|
||||
let mut groups: BTreeMap<SharedString, (usize, bool)> = BTreeMap::new();
|
||||
let mut groups: BTreeMap<SharedString, (usize, bool, Option<SharedUri>)> = BTreeMap::new();
|
||||
|
||||
for (emoji, author) in reactions {
|
||||
for (emoji, image, author) in reactions {
|
||||
let group = groups.entry(emoji).or_default();
|
||||
group.0 += 1;
|
||||
group.1 |= me.as_ref() == Some(&author);
|
||||
if group.2.is_none() {
|
||||
group.2 = image;
|
||||
}
|
||||
}
|
||||
|
||||
groups
|
||||
.into_iter()
|
||||
.map(|(emoji, (count, mine))| ReactionGroup { emoji, count, mine })
|
||||
.map(|(emoji, (count, mine, image))| ReactionGroup {
|
||||
emoji,
|
||||
image,
|
||||
count,
|
||||
mine,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -438,7 +448,10 @@ impl RenderOnce for ReactionBar {
|
||||
.border_color(cx.theme().element_active)
|
||||
})
|
||||
.when(!group.mine, |this| this.border_color(cx.theme().border))
|
||||
.child(group.emoji)
|
||||
.when(group.image.is_none(), |this| this.child(group.emoji))
|
||||
.when_some(group.image, |this, image| {
|
||||
this.child(img(image).size(px(16.)).flex_shrink_0())
|
||||
})
|
||||
.child(SharedString::from(group.count.to_string()))
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user