Merge pull request #156 from fiatjaf/tag-stuff

Highlight npub/etc references in the textarea
This commit is contained in:
Michael Dilger 2023-01-19 10:12:28 +13:00 committed by GitHub
commit c3b60b227a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 2 deletions

23
Cargo.lock generated
View File

@ -1712,6 +1712,7 @@ dependencies = [
"image", "image",
"lazy_static", "lazy_static",
"linkify", "linkify",
"memoize",
"nostr-types", "nostr-types",
"parking_lot", "parking_lot",
"rand 0.8.5", "rand 0.8.5",
@ -2191,6 +2192,28 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "memoize"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c25d125e4063f313300d87c8f658e5b3d69257095df9a4221c12ba50b0421bff"
dependencies = [
"lazy_static",
"memoize-inner",
]
[[package]]
name = "memoize-inner"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7d5160e6ffcc59d4c571c38238ec5b7065bc91a5a24f511988dabcddda723"
dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.16" version = "0.3.16"

View File

@ -25,6 +25,7 @@ http = "0.2"
image = { version = "0.24", features = [ "png", "jpeg" ] } image = { version = "0.24", features = [ "png", "jpeg" ] }
lazy_static = "1.4" lazy_static = "1.4"
linkify = "0.9" linkify = "0.9"
memoize = "0.3"
nostr-types = { git = "https://github.com/mikedilger/nostr-types" } nostr-types = { git = "https://github.com/mikedilger/nostr-types" }
parking_lot = "0.12" parking_lot = "0.12"
rand = "0.8" rand = "0.8"

View File

@ -1,4 +1,9 @@
use crate::db::DbRelay; use crate::db::DbRelay;
use eframe::epaint::{
text::{LayoutJob, TextFormat},
Color32, FontFamily, FontId,
};
use memoize::memoize;
use nostr_types::{Id, PublicKey, PublicKeyHex, Tag}; use nostr_types::{Id, PublicKey, PublicKeyHex, Tag};
pub fn keys_from_text(text: &str) -> Vec<(String, PublicKey)> { pub fn keys_from_text(text: &str) -> Vec<(String, PublicKey)> {
@ -87,6 +92,76 @@ pub async fn add_event_to_tags(existing_tags: &mut Vec<Tag>, added: Id, marker:
} }
} }
enum HighlightType {
Nothing,
PublicKey,
Event,
}
#[memoize]
pub fn textarea_highlighter(text: String) -> LayoutJob {
let mut job = LayoutJob::default();
let ids = notes_from_text(&text);
let pks = keys_from_text(&text);
// we will gather indices such that we can split the text in chunks
let mut indices: Vec<(usize, HighlightType)> = vec![];
for pk in pks {
for m in text.match_indices(&pk.0) {
indices.push((m.0, HighlightType::Nothing));
indices.push((m.0 + pk.0.len(), HighlightType::PublicKey));
}
}
for id in ids {
for m in text.match_indices(&id.0) {
indices.push((m.0, HighlightType::Nothing));
indices.push((m.0 + id.0.len(), HighlightType::Event));
}
}
indices.sort_by_key(|x| x.0);
indices.dedup_by_key(|x| x.0);
// add a breakpoint at the end if it doesn't exist
if indices.is_empty() || indices[indices.len() - 1].0 != text.len() {
indices.push((text.len(), HighlightType::Nothing));
}
// now we will add each chunk back to the textarea with custom formatting
let mut curr = 0;
for (index, highlight) in indices {
let chunk = &text[curr..index];
job.append(
chunk,
0.0,
match highlight {
HighlightType::Nothing => TextFormat {
font_id: FontId::new(14.0, FontFamily::Proportional),
color: Color32::BLACK,
..Default::default()
},
HighlightType::PublicKey => TextFormat {
font_id: FontId::new(16.0, FontFamily::Monospace),
background: Color32::LIGHT_GRAY,
color: Color32::DARK_GREEN,
..Default::default()
},
HighlightType::Event => TextFormat {
font_id: FontId::new(16.0, FontFamily::Monospace),
background: Color32::LIGHT_GRAY,
color: Color32::DARK_RED,
..Default::default()
},
},
);
curr = index;
}
job
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -2,7 +2,7 @@ use super::{GossipUi, Page};
use crate::comms::ToOverlordMessage; use crate::comms::ToOverlordMessage;
use crate::feed::FeedKind; use crate::feed::FeedKind;
use crate::globals::{Globals, GLOBALS}; use crate::globals::{Globals, GLOBALS};
use crate::tags::keys_from_text; use crate::tags::{keys_from_text, textarea_highlighter};
use crate::ui::widgets::CopyButton; use crate::ui::widgets::CopyButton;
use crate::AVATAR_SIZE_F32; use crate::AVATAR_SIZE_F32;
use eframe::egui; use eframe::egui;
@ -185,11 +185,18 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram
}); });
// Text area // Text area
let mut layouter = |ui: &Ui, text: &str, wrap_width: f32| {
let mut layout_job = textarea_highlighter(text.to_owned());
layout_job.wrap.max_width = wrap_width;
ui.fonts().layout_job(layout_job)
};
ui.add( ui.add(
TextEdit::multiline(&mut app.draft) TextEdit::multiline(&mut app.draft)
.hint_text("Type your message here") .hint_text("Type your message here")
.desired_width(f32::INFINITY) .desired_width(f32::INFINITY)
.lock_focus(true), .lock_focus(true)
.layouter(&mut layouter),
); );
}); });