Repository navigation
M9: comment.list reports the names each message mentions (#1415) #1534
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
Changes from all commits
b1826da
30feaaf
ee0b1fd
7610edd
5dcf3be
7e4588c
8c5c033
6354e29
2a6da93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ | |
|
|
||
| use std::fmt::Write as _; | ||
|
|
||
| use figcraft_doc::{Comment, Document, Message, Node, NodeId, NodeKind, geom}; | ||
| use figcraft_doc::{Comment, Document, Message, Node, NodeId, NodeKind, geom, mentions}; | ||
| use serde_json::{Value, json}; | ||
|
|
||
| use super::{CommandSpec, ok}; | ||
|
|
@@ -326,25 +326,42 @@ fn local(doc: &Document, anchor: Option<NodeId>, x: f64, y: f64) -> (f64, f64) { | |
| } | ||
| } | ||
|
|
||
| fn message_json(m: &Message) -> Value { | ||
| /// The names an @mention resolves to: the file's people (`Document::comment_people`) and `user`, | ||
| /// as the thread view highlights them. | ||
| fn mentionable<'a>(people: &'a [String], user: &'a str) -> Vec<&'a str> { | ||
| people.iter().map(String::as_str).chain([user]).collect() | ||
| } | ||
|
|
||
| /// A message as JSON, with the names it @mentions: `{name, known, you}` each, resolved against | ||
| /// `people` (`mentionable`); `known: false` is a name nobody in the file has. | ||
| fn message_json(m: &Message, people: &[&str], user: &str) -> Value { | ||
| let mut named: Vec<Value> = Vec::new(); | ||
| for (r, who) in mentions(&m.text, people) { | ||
| let name = who.or_else(|| m.text.get(r.start + 1..r.end)).unwrap_or_default(); | ||
| let v = json!({"name": name, "known": who.is_some(), "you": who.is_some_and(|w| w.eq_ignore_ascii_case(user))}); | ||
| if !named.contains(&v) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deduplicate unknown names without regard to ASCII case. If the file knows neither spelling,
|
||
| named.push(v); | ||
| } | ||
| } | ||
| json!({ | ||
| "author": m.author, "agent": m.agent, "text": m.text, "created": m.created, "edited": m.edited, | ||
| "reactions": m.reactions.iter().map(|r| json!({"emoji": r.emoji, "users": r.users})).collect::<Vec<_>>(), | ||
| "mentions": named, | ||
| }) | ||
| } | ||
|
|
||
| /// JSON view of a comment as `user` sees it: its page position, frame, read state and link, the | ||
| /// first message's fields, and the replies. | ||
| pub fn comment_json(doc: &Document, c: &Comment, user: &str) -> Value { | ||
| /// first message's fields, and the replies; mentions resolve against `people` (`mentionable`). | ||
| pub fn comment_json(doc: &Document, c: &Comment, user: &str, people: &[&str]) -> Value { | ||
| let (x, y) = c.position(doc); | ||
| let frame = frame_of(doc, c); | ||
| let mut v = message_json(&c.first); | ||
| let mut v = message_json(&c.first, people, user); | ||
| let extra = json!({ | ||
| "id": c.id, "page": c.page, "pageName": doc.node(c.page).map(|n| n.name.as_str()), | ||
| "x": x, "y": y, "anchor": c.anchor, | ||
| "frame": frame, "frameName": frame.and_then(|f| doc.node(f)).map(|n| n.name.as_str()), | ||
| "resolved": c.resolved, "unread": c.is_unread(user), "link": link(c.id), | ||
| "replies": c.replies.iter().map(message_json).collect::<Vec<_>>(), | ||
| "replies": c.replies.iter().map(|m| message_json(m, people, user)).collect::<Vec<_>>(), | ||
| }); | ||
| if let (Some(m), Value::Object(e)) = (v.as_object_mut(), extra) { | ||
| m.extend(e); | ||
|
|
@@ -410,7 +427,8 @@ fn open(s: &mut Session, id: u64, how: &str, who: &str) -> crate::Result<Value> | |
| s.request_ui("comment.open", json!({"id": id})); | ||
| let doc = s.doc()?; | ||
| let c = doc.comment(id).ok_or_else(|| bad("comment.open", "no comment"))?; | ||
| Ok(comment_json(doc, c, who)) | ||
| let people = doc.comment_people(); | ||
| Ok(comment_json(doc, c, who, &mentionable(&people, who))) | ||
| } | ||
|
|
||
| /// A list's page (None: every page), filter, sort and search text from params. | ||
|
|
@@ -796,7 +814,9 @@ pub fn specs() -> Vec<CommandSpec> { | |
| let (page, filter, sort, text) = list_params("comment.list", s, p)?; | ||
| let who = author(s, p); | ||
| let q = Query { page, frame: id(p, "frame"), filter, sort, text: &text }; | ||
| let list: Vec<Value> = query(doc, &q, &who).into_iter().map(|c| comment_json(doc, c, &who)).collect(); | ||
| let people = doc.comment_people(); | ||
| let people = mentionable(&people, &who); | ||
| let list: Vec<Value> = query(doc, &q, &who).into_iter().map(|c| comment_json(doc, c, &who, &people)).collect(); | ||
| Ok(json!({"comments": list})) | ||
| } | ||
| ), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| //! `comment.list` reports the names each message @mentions, by the rule the thread view highlights | ||
| //! them with: resolved against the file's people where it can be, and whether the name is you. | ||
|
|
||
| use serde_json::{Value, json}; | ||
|
|
||
| use crate::Session; | ||
|
|
||
| fn mentions(s: &mut Session, p: Value) -> Vec<Value> { | ||
| let l = s.execute("comment.list", &p).unwrap(); | ||
| let c = &l["comments"][0]; | ||
| [&c["mentions"], &c["replies"][0]["mentions"]].into_iter().cloned().collect() | ||
| } | ||
|
|
||
| #[test] | ||
| fn a_message_lists_the_names_it_mentions_and_not_an_email_address() { | ||
| let mut s = Session::new(); | ||
| let id = s.execute("comment.add", &json!({"x": 0, "y": 0, "text": "Hi", "author": "Ada Lovelace"})).unwrap()["id"].as_u64().unwrap(); | ||
| let text = "@ada lovelace and @You: please check, or ask @Bob (mail a@b.com). Thanks @Ada Lovelace"; | ||
| s.execute("comment.reply", &json!({"id": id, "text": text, "author": "Sam"})).unwrap(); | ||
| let [first, reply] = <[Value; 2]>::try_from(mentions(&mut s, json!({}))).unwrap(); | ||
| assert_eq!(first, json!([])); | ||
| assert_eq!( | ||
| reply, | ||
| json!([ | ||
| {"name": "Ada Lovelace", "known": true, "you": false}, | ||
| {"name": "You", "known": true, "you": true}, | ||
| {"name": "Bob", "known": false, "you": false}, | ||
| ]), | ||
| "two people the file knows (once each, as the file spells them), one name it doesn't, no email" | ||
| ); | ||
| // `author` says whose list it is: then Ada is "you". | ||
| let reply = mentions(&mut s, json!({"author": "Ada Lovelace"})).remove(1); | ||
| assert_eq!(reply[0], json!({"name": "Ada Lovelace", "known": true, "you": true})); | ||
| } | ||
|
|
||
| #[test] | ||
| fn the_mention_rule_matches_the_highlighting_rule() { | ||
| let people = ["Sam", "Sam Lee"]; | ||
| let found = figcraft_doc::mentions("@Sam Lee, @sam, x@Sam, @Zoe!", &people); | ||
| let names: Vec<_> = found.iter().map(|(r, who)| (r.clone(), *who)).collect(); | ||
| assert_eq!(names, vec![(0..8, Some("Sam Lee")), (10..14, Some("Sam")), (23..27, None)]); | ||
| assert_eq!(figcraft_doc::mention_ranges("@Sam Lee, @sam, x@Sam, @Zoe!", &people), vec![0..8, 10..14]); | ||
| assert!(figcraft_doc::mentions("@ and a@b.com", &people).is_empty()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve the file spelling for a known person.
If the file contains
Ada Lovelaceandauthorisada lovelace,max_by_keyselects the appended acting name because it selects the last equal-length match. (doc.rust-lang.org) The response then reportsada lovelace, not the spelling incomment.people.Append
useronly when no file person matches it ignoring ASCII case.youcan still use the existing case-insensitive comparison.