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:
1 parent
0ffa001a61
commit
6a9ce8a2c3
46 files changed
+5635
-193
No files matched your search
@@ -0,0 +1,121 @@
|
||||
use gpui::{App, ElementId, Entity, IntoElement, Keystroke, ParentElement, RenderOnce, Window};
|
||||
|
||||
use super::format::{Format, active, format};
|
||||
use crate::{
|
||||
buttons::{ToggleButton, ToggleItem, shortcut_text},
|
||||
forms::{Run, TextInput},
|
||||
primitives::IconName,
|
||||
shell::{Toolbar, ToolbarGroup, ToolbarSeparator},
|
||||
theme::ControlSize,
|
||||
};
|
||||
|
||||
/// A tool: the format it applies, its icon, its name and its key.
|
||||
type Tool = (Format, IconName, &'static str, Option<&'static str>);
|
||||
|
||||
/// The inline styles, which the floating toolbar shows too.
|
||||
pub(crate) const INLINE: &[Tool] = &[
|
||||
(Format::Bold, IconName::Bold, "Bold", Some("secondary-b")),
|
||||
(
|
||||
Format::Italic,
|
||||
IconName::Italic,
|
||||
"Italic",
|
||||
Some("secondary-i"),
|
||||
),
|
||||
(
|
||||
Format::Strike,
|
||||
IconName::Strikethrough,
|
||||
"Strikethrough",
|
||||
None,
|
||||
),
|
||||
(Format::Code, IconName::Code, "Code", None),
|
||||
(Format::Link, IconName::Link, "Link", Some("secondary-k")),
|
||||
];
|
||||
|
||||
const HEADINGS: &[Tool] = &[
|
||||
(Format::Heading(1), IconName::Heading1, "Heading 1", None),
|
||||
(Format::Heading(2), IconName::Heading2, "Heading 2", None),
|
||||
(Format::Heading(3), IconName::Heading3, "Heading 3", None),
|
||||
];
|
||||
|
||||
const BLOCKS: &[Tool] = &[
|
||||
(Format::Quote, IconName::Quote, "Quote", None),
|
||||
(Format::Bullet, IconName::List, "Bulleted list", None),
|
||||
(
|
||||
Format::Numbered,
|
||||
IconName::ListOrdered,
|
||||
"Numbered list",
|
||||
None,
|
||||
),
|
||||
(Format::Task, IconName::ListTodo, "To-do list", None),
|
||||
];
|
||||
|
||||
/// One tool as a toggle: pressed while `on`; a press applies its format, or opens the link editor.
|
||||
pub(crate) fn tool(
|
||||
id: &ElementId,
|
||||
field: &Entity<TextInput>,
|
||||
(format_of, icon, name, key): Tool,
|
||||
on: bool,
|
||||
on_link: Option<Run>,
|
||||
) -> ToggleButton {
|
||||
let tip = match key {
|
||||
Some(key) => {
|
||||
let stroke = Keystroke::parse(key).expect("a tool's key parses");
|
||||
format!("{name} {}", shortcut_text(&stroke))
|
||||
}
|
||||
None => name.to_string(),
|
||||
};
|
||||
let field = field.clone();
|
||||
ToggleButton::new(
|
||||
(id.clone(), name),
|
||||
ToggleItem::new(name).icon(icon).tooltip(tip),
|
||||
on,
|
||||
)
|
||||
.size(ControlSize::Sm)
|
||||
.on_toggle(move |_, window, cx| match (&on_link, format_of) {
|
||||
(Some(open), Format::Link) => open(window, cx),
|
||||
_ => format(&field, format_of, cx),
|
||||
})
|
||||
}
|
||||
|
||||
/// The formats a markdown field takes, in a row above it: inline styles, headings, quote and lists. A tool shows pressed while the selection holds its format; a press applies it and leaves focus in the field.
|
||||
#[derive(IntoElement)]
|
||||
pub struct FixedFormatToolbar {
|
||||
id: ElementId,
|
||||
field: Entity<TextInput>,
|
||||
on_link: Option<Run>,
|
||||
}
|
||||
|
||||
impl FixedFormatToolbar {
|
||||
pub fn new(id: impl Into<ElementId>, field: &Entity<TextInput>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
field: field.clone(),
|
||||
on_link: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The link tool opens the owner's link editor instead of writing `[text]()`.
|
||||
pub fn on_link(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_link = Some(std::rc::Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for FixedFormatToolbar {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let input = self.field.read(cx);
|
||||
let pressed = active(input.text(), input.selection());
|
||||
let group = |tools: &[Tool]| {
|
||||
ToolbarGroup::new().children(tools.iter().map(|tool_of| {
|
||||
let on = pressed.contains(&tool_of.0);
|
||||
tool(&self.id, &self.field, *tool_of, on, self.on_link.clone())
|
||||
}))
|
||||
};
|
||||
Toolbar::new()
|
||||
.child(group(INLINE))
|
||||
.child(ToolbarSeparator)
|
||||
.child(group(HEADINGS))
|
||||
.child(ToolbarSeparator)
|
||||
.child(group(BLOCKS))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user