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,235 @@
|
||||
use gpui::{
|
||||
AnyElement, Context, FontWeight, InteractiveElement, IntoElement, MouseButton, ParentElement,
|
||||
SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::*,
|
||||
};
|
||||
|
||||
use super::{Block, BlockEditor, BlockKind, media, table, view::Look};
|
||||
use crate::{
|
||||
buttons::CopyButton,
|
||||
forms::Checkbox,
|
||||
primitives::{Disclosure, Icon, IconName},
|
||||
theme::IconSize,
|
||||
typography::Latex,
|
||||
};
|
||||
|
||||
impl BlockEditor {
|
||||
/// What a block shows, by kind.
|
||||
pub(crate) fn content(
|
||||
&self,
|
||||
block: &Block,
|
||||
number: usize,
|
||||
look: &Look,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let colors = look.colors.clone();
|
||||
let (key, id) = (block.key, Self::id(cx));
|
||||
let marker = |text: SharedString| {
|
||||
div()
|
||||
.flex_none()
|
||||
.min_w_5()
|
||||
.text_color(colors.fg_muted)
|
||||
.child(text)
|
||||
};
|
||||
let row = |marker: AnyElement, field: AnyElement| {
|
||||
div()
|
||||
.flex()
|
||||
.gap_1p5()
|
||||
.child(marker)
|
||||
.child(div().flex_1().min_w_0().child(field))
|
||||
};
|
||||
match &block.kind {
|
||||
BlockKind::Paragraph => self.field_view(block, 0, window, cx),
|
||||
BlockKind::Heading(level) => div()
|
||||
.pt_2()
|
||||
.text_size(look.headings[(*level as usize).clamp(1, 3) - 1])
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.child(self.field_view(block, 0, window, cx))
|
||||
.into_any_element(),
|
||||
BlockKind::Bullet => row(
|
||||
marker("•".into()).into_any_element(),
|
||||
self.field_view(block, 0, window, cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
BlockKind::Numbered => row(
|
||||
marker(format!("{number}.").into()).into_any_element(),
|
||||
self.field_view(block, 0, window, cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
BlockKind::Todo(done) => {
|
||||
let (entity, done) = (cx.entity(), *done);
|
||||
let check = Checkbox::new((id.clone(), format!("todo-{key}")), done).on_change(
|
||||
move |on, _, cx| {
|
||||
entity.update(cx, |editor, cx| {
|
||||
editor.set_kind(key, BlockKind::Todo(on), cx)
|
||||
})
|
||||
},
|
||||
);
|
||||
row(
|
||||
check.into_any_element(),
|
||||
self.field_view(block, 0, window, cx),
|
||||
)
|
||||
.when(done, |row| row.text_color(colors.fg_subtle))
|
||||
.into_any_element()
|
||||
}
|
||||
BlockKind::Quote => div()
|
||||
.pl_3()
|
||||
.border_l_2()
|
||||
.border_color(colors.border_strong)
|
||||
.text_color(colors.fg_muted)
|
||||
.child(self.field_view(block, 0, window, cx))
|
||||
.into_any_element(),
|
||||
BlockKind::Callout => div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.p_3()
|
||||
.rounded(look.radius)
|
||||
.bg(colors.sunken)
|
||||
.child(
|
||||
Icon::new(IconName::Lightbulb)
|
||||
.size(IconSize::Sm)
|
||||
.color(colors.warning),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.child(self.field_view(block, 0, window, cx)),
|
||||
)
|
||||
.into_any_element(),
|
||||
BlockKind::Toggle(open) => {
|
||||
let (entity, open) = (cx.entity(), *open);
|
||||
let chevron = div()
|
||||
.id((id.clone(), format!("toggle-{key}")))
|
||||
.flex_none()
|
||||
.cursor_pointer()
|
||||
.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
|
||||
.on_click(move |_, _, cx| {
|
||||
entity.update(cx, |editor, cx| {
|
||||
editor.set_kind(key, BlockKind::Toggle(!open), cx)
|
||||
})
|
||||
})
|
||||
.child(Disclosure::new(
|
||||
(id.clone(), format!("disclosure-{key}")),
|
||||
open,
|
||||
));
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(row(
|
||||
chevron.into_any_element(),
|
||||
self.field_view(block, 0, window, cx),
|
||||
))
|
||||
.when(open, |toggle| {
|
||||
toggle.child(div().pl_6().child(self.field_view(block, 1, window, cx)))
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
BlockKind::Code => {
|
||||
let text = block.fields[0].read(cx).text().to_string();
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.p_3()
|
||||
.rounded(look.radius)
|
||||
.bg(colors.sunken)
|
||||
.font_family(look.mono.clone())
|
||||
.text_size(look.small)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.text_size(look.tiny)
|
||||
.text_color(colors.fg_subtle)
|
||||
.child("Code")
|
||||
.child(CopyButton::new((id.clone(), format!("copy-{key}")), text)),
|
||||
)
|
||||
.child(self.field_view(block, 0, window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
BlockKind::Math => {
|
||||
let tex = block.fields[0].read(cx).text().to_string();
|
||||
let shown = match Latex::parse(&tex) {
|
||||
_ if tex.trim().is_empty() => div().into_any_element(),
|
||||
Ok(math) => div()
|
||||
.flex()
|
||||
.justify_center()
|
||||
.py_2()
|
||||
.child(math)
|
||||
.into_any_element(),
|
||||
Err(error) => div()
|
||||
.text_color(colors.danger)
|
||||
.child(error.to_string())
|
||||
.into_any_element(),
|
||||
};
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.p_3()
|
||||
.rounded(look.radius)
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.child(
|
||||
div()
|
||||
.font_family(look.mono.clone())
|
||||
.text_size(look.small)
|
||||
.text_color(colors.fg_muted)
|
||||
.child(self.field_view(block, 0, window, cx)),
|
||||
)
|
||||
.child(shown)
|
||||
.into_any_element()
|
||||
}
|
||||
BlockKind::Diagram => media::diagram(self, block, look, window, cx),
|
||||
BlockKind::Divider => div()
|
||||
.py_2()
|
||||
.child(div().h_0().border_t_1().border_color(colors.border))
|
||||
.into_any_element(),
|
||||
BlockKind::Table { .. } => table::table(self, block, look, window, cx),
|
||||
BlockKind::Image(_) | BlockKind::Video(_) | BlockKind::Embed(_) => {
|
||||
media::media(self, block, look, window, cx)
|
||||
}
|
||||
BlockKind::Columns(_) => div()
|
||||
.flex()
|
||||
.gap_4()
|
||||
.children((0..block.fields.len()).map(|ix| {
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.when(ix > 0, |column| {
|
||||
column.pl_4().border_l_1().border_color(colors.border)
|
||||
})
|
||||
.child(self.field_view(block, ix, window, cx))
|
||||
}))
|
||||
.into_any_element(),
|
||||
BlockKind::Synced(name) => div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded(look.radius)
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.text_size(look.tiny)
|
||||
.text_color(colors.fg_subtle)
|
||||
.child(
|
||||
Icon::new(IconName::RefreshCw)
|
||||
.size(IconSize::Xs)
|
||||
.color(colors.fg_subtle),
|
||||
)
|
||||
.child(format!("Synced · {name}")),
|
||||
)
|
||||
.child(self.field_view(block, 0, window, cx))
|
||||
.into_any_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user