T21a: Documents, editing
- markdown read with pulldown-cmark: marks for live styling, blocks, links, words - forms::Highlight gains weight, slant and strike; measure and paint share runs - Inter Italic embedded, so emphasis reads slanted - forms::mention's popup and keys become Suggestions, shared with the editors - RichTextEditor with FixedFormatToolbar, the floating toolbar, LinkEditor, @ and : suggestions, WordCount and ReadingTime - MarkdownEditor: visual (a pressed block opens as source), source, split - BlockEditor: prose, lists, to-dos, quotes, callouts, toggles, code, math, diagrams the app draws, dividers, tables that grow and merge, pictures to resize and align, cards for video and pages, columns, synced text; slash menu, markdown shortcuts, handles that drag and open a menu, one undo for all - TodoItem and Checklist - icons: heading-1..3, workflow, merge - gallery: the Documents page with scripted states
This commit is contained in:
46 files changed
+5635
-193
No files matched your search
@@ -0,0 +1,104 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{App, ElementId, Entity, SharedString, Window};
|
||||
|
||||
use crate::forms::{
|
||||
Choice, Pick, Suggestions, TextInput, active_trigger, emoji_found, handles_matching,
|
||||
};
|
||||
|
||||
/// A trigger of the owner's: from the query and where it runs, the rows and what a pick does.
|
||||
pub(crate) type Offer<'a> = &'a dyn Fn(&str, usize, usize) -> (Vec<Choice>, Pick);
|
||||
|
||||
/// An emoji name needs this many letters before `:` suggests, so a colon in prose stays quiet.
|
||||
const EMOJI_QUERY: usize = 2;
|
||||
|
||||
/// Emoji shown for a `:` query.
|
||||
const EMOJI_SHOWN: usize = 6;
|
||||
|
||||
/// An offer of nothing: no rows, so no pick can come.
|
||||
fn nothing() -> (Vec<Choice>, Pick) {
|
||||
(
|
||||
Vec::new(),
|
||||
Rc::new(|_: usize, _: &mut Window, _: &mut App| unreachable!("an empty offer has no row")),
|
||||
)
|
||||
}
|
||||
|
||||
/// Replaces the trigger and its query, `at..caret`, with `text`, as one undo step.
|
||||
pub(crate) fn replace_trigger(
|
||||
field: &Entity<TextInput>,
|
||||
at: usize,
|
||||
caret: usize,
|
||||
text: &str,
|
||||
cx: &mut App,
|
||||
) {
|
||||
field.update(cx, |input, cx| {
|
||||
input.select(at..caret, cx);
|
||||
input.insert(text, cx);
|
||||
});
|
||||
}
|
||||
|
||||
/// What `@` and `:` offer at the field's caret: people by handle, emoji by name; `extra` adds a trigger of the owner's, such as `/` in blocks.
|
||||
pub(crate) fn offers(
|
||||
id: ElementId,
|
||||
field: &Entity<TextInput>,
|
||||
people: &[SharedString],
|
||||
extra: Option<(char, Offer)>,
|
||||
cx: &App,
|
||||
) -> Suggestions {
|
||||
let input = field.read(cx);
|
||||
let (text, caret) = (input.text().to_string(), input.cursor());
|
||||
let mut marks = vec!['@', ':'];
|
||||
marks.extend(extra.as_ref().map(|(mark, _)| *mark));
|
||||
let active = active_trigger(&text, caret, &marks);
|
||||
let query = active.map_or("", |(at, mark)| &text[at + mark.len_utf8()..caret]);
|
||||
let (rows, pick): (Vec<Choice>, Pick) = match active {
|
||||
Some((at, '@')) => {
|
||||
let found = handles_matching(people, query);
|
||||
let rows = found
|
||||
.iter()
|
||||
.map(|name| Choice::new(name.clone(), format!("@{name}")))
|
||||
.collect();
|
||||
let field = field.clone();
|
||||
(
|
||||
rows,
|
||||
Rc::new(move |pick: usize, _: &mut Window, cx: &mut App| {
|
||||
log::info!("mention: @{}", found[pick]);
|
||||
replace_trigger(&field, at, caret, &format!("@{} ", found[pick]), cx);
|
||||
}),
|
||||
)
|
||||
}
|
||||
Some((at, ':')) if query.len() >= EMOJI_QUERY => {
|
||||
let found: Vec<&'static emojis::Emoji> = emoji_found(&query.to_lowercase())
|
||||
.into_iter()
|
||||
.take(EMOJI_SHOWN)
|
||||
.collect();
|
||||
let rows = found
|
||||
.iter()
|
||||
.map(|emoji| {
|
||||
let name = emoji.shortcode().unwrap_or(emoji.name());
|
||||
Choice::new(name, format!("{} {name}", emoji.as_str()))
|
||||
})
|
||||
.collect();
|
||||
let field = field.clone();
|
||||
(
|
||||
rows,
|
||||
Rc::new(move |pick: usize, _: &mut Window, cx: &mut App| {
|
||||
log::info!("emoji: {}", found[pick].name());
|
||||
replace_trigger(&field, at, caret, found[pick].as_str(), cx);
|
||||
}),
|
||||
)
|
||||
}
|
||||
Some((at, mark)) => match &extra {
|
||||
Some((extra_mark, build)) if *extra_mark == mark => build(query, at, caret),
|
||||
_ => nothing(),
|
||||
},
|
||||
None => nothing(),
|
||||
};
|
||||
Suggestions {
|
||||
id,
|
||||
state: field.clone(),
|
||||
trigger: active.map(|(at, _)| at),
|
||||
rows,
|
||||
pick,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user