improve wrap into tag, improving text styles

This commit is contained in:
Adrian Woźniak 2023-04-06 16:02:05 +02:00
parent 63302fef71
commit 9cc34f16bb
2 changed files with 129 additions and 262 deletions

View File

@ -60,6 +60,7 @@ features = [
"FormData", "FormData",
"FileReader", "FileReader",
"FileReaderSync", "FileReaderSync",
"DocumentFragment",
"Range", "Range",
# events # events
"EventTarget", "EventTarget",

View File

@ -1,6 +1,5 @@
use seed::prelude::*; use seed::prelude::*;
use seed::*; use seed::*;
use tracing::{debug, info};
use crate::components::styled_button::{ButtonVariant, StyledButton}; use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_icon::{Icon, StyledIcon}; use crate::components::styled_icon::{Icon, StyledIcon};
@ -42,6 +41,31 @@ impl HeadingSize {
H6 => "H6", H6 => "H6",
} }
} }
fn as_u32(&self) -> u32 {
match self {
HeadingSize::Normal => 0,
HeadingSize::H1 => 1,
HeadingSize::H2 => 2,
HeadingSize::H3 => 3,
HeadingSize::H4 => 4,
HeadingSize::H5 => 5,
HeadingSize::H6 => 6,
}
}
fn from_u32(n: u32) -> Self {
match n {
0 => HeadingSize::Normal,
1 => HeadingSize::H1,
2 => HeadingSize::H2,
3 => HeadingSize::H3,
4 => HeadingSize::H4,
5 => HeadingSize::H5,
6 => HeadingSize::H6,
_ => HeadingSize::Normal,
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -55,12 +79,7 @@ pub enum RteMsg {
Bold, Bold,
Italic, Italic,
Underscore, Underscore,
Undo,
Redo,
Strikethrough, Strikethrough,
Copy,
Paste,
Cut,
JustifyFull, JustifyFull,
JustifyCenter, JustifyCenter,
JustifyLeft, JustifyLeft,
@ -107,15 +126,10 @@ impl<'l> ExecCommand<'l> {
impl RteMsg { impl RteMsg {
pub fn to_command(&self) -> Option<ExecCommand> { pub fn to_command(&self) -> Option<ExecCommand> {
match self { match self {
RteMsg::Bold => Some(ExecCommand::new("bold")), RteMsg::Bold => None,
RteMsg::Italic => Some(ExecCommand::new("italic")), RteMsg::Italic => None,
RteMsg::Underscore => Some(ExecCommand::new("underline")), RteMsg::Underscore => None,
RteMsg::Undo => Some(ExecCommand::new("undo")),
RteMsg::Redo => Some(ExecCommand::new("redo")),
RteMsg::Strikethrough => Some(ExecCommand::new("strikeThrough")), RteMsg::Strikethrough => Some(ExecCommand::new("strikeThrough")),
RteMsg::Copy => Some(ExecCommand::new("copy")),
RteMsg::Paste => Some(ExecCommand::new("paste")),
RteMsg::Cut => Some(ExecCommand::new("cut")),
RteMsg::JustifyFull => Some(ExecCommand::new("justifyFull")), RteMsg::JustifyFull => Some(ExecCommand::new("justifyFull")),
RteMsg::JustifyCenter => Some(ExecCommand::new("justifyCenter")), RteMsg::JustifyCenter => Some(ExecCommand::new("justifyCenter")),
RteMsg::JustifyLeft => Some(ExecCommand::new("justifyLeft")), RteMsg::JustifyLeft => Some(ExecCommand::new("justifyLeft")),
@ -127,17 +141,14 @@ impl RteMsg {
| HeadingSize::H3 | HeadingSize::H3
| HeadingSize::H4 | HeadingSize::H4
| HeadingSize::H5 | HeadingSize::H5
| HeadingSize::H6 => { | HeadingSize::H6 => None,
// Some(ExecCommand::new_with_param("heading", heading.as_str())) HeadingSize::Normal => None,
None
}
HeadingSize::Normal => Some(ExecCommand::new_with_param("formatBlock", "div")),
}, },
RteMsg::InsertUnorderedList => Some(ExecCommand::new("insertUnorderedList")), RteMsg::InsertUnorderedList => Some(ExecCommand::new("insertUnorderedList")),
RteMsg::InsertOrderedList => Some(ExecCommand::new("insertOrderedList")), RteMsg::InsertOrderedList => Some(ExecCommand::new("insertOrderedList")),
RteMsg::RemoveFormat => Some(ExecCommand::new("removeFormat")), RteMsg::RemoveFormat => None,
RteMsg::Subscript => Some(ExecCommand::new("subscript")), RteMsg::Subscript => None,
RteMsg::Superscript => Some(ExecCommand::new("superscript")), RteMsg::Superscript => None,
RteMsg::InsertTable { .. } => None, RteMsg::InsertTable { .. } => None,
// code // code
RteMsg::InsertCode(_) => None, RteMsg::InsertCode(_) => None,
@ -284,12 +295,13 @@ impl StyledRteState {
if self.restore_range().is_err() { if self.restore_range().is_err() {
return; return;
} }
self.schedule_focus(orders);
} }
_ => match m { _ => match m {
RteMsg::InsertHeading(heading) => { RteMsg::Subscript => wrap_into("SUB"),
wrap_into(heading.as_str()); RteMsg::Superscript => wrap_into("SUP"),
} RteMsg::Bold => wrap_into("B"),
RteMsg::Underscore => wrap_into("U"),
RteMsg::InsertHeading(heading) => wrap_into(heading.as_str()),
// code // code
RteMsg::InsertCode(b) => { RteMsg::InsertCode(b) => {
if *b { if *b {
@ -394,7 +406,7 @@ impl StyledRteState {
let sel = sel let sel = sel
.unwrap_or(None) .unwrap_or(None)
.ok_or_else(|| "Restoring selection failed. Unable to obtain select".to_string())?; .ok_or_else(|| "Restoring selection failed. Unable to obtain select".to_string())?;
sel.collapse_to_start(); sel.collapse_to_end().ok();
let r = self let r = self
.range .range
.as_ref() .as_ref()
@ -436,89 +448,6 @@ impl<'component> Default for StyledRte<'component> {
impl<'outer> StyledRte<'outer> { impl<'outer> StyledRte<'outer> {
pub fn render(self) -> Node<Msg> { pub fn render(self) -> Node<Msg> {
/*{
let _brush_button = styled_rte_button(
"Brush",
Icon::Brush,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _color_bucket_button = styled_rte_button(
"Color bucket",
Icon::ColorBucket,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _color_picker_button = styled_rte_button(
"Color picker",
Icon::ColorPicker,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _link_broken_button = styled_rte_button(
"Link broken",
Icon::LinkBroken,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _pin_button = styled_rte_button(
"Pin",
Icon::Pin,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _rotation_button = styled_rte_button(
"Rotation",
Icon::Rotation,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _save_button = styled_rte_button(
"Save",
Icon::Save,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _text_height_button = styled_rte_button(
"Text height",
Icon::TextHeight,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _text_width_button = styled_rte_button(
"Text width",
Icon::TextWidth,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
}*/
// let field_id = values.field_id.clone();
// let capture_change = ev(Ev::Input, |ev| {
// ev.stop_propagation();
// Some(Msg::StrInputChanged(field_id, "".to_string()))
// });
let id = self.identifier.unwrap_or_default().to_string(); let id = self.identifier.unwrap_or_default().to_string();
let click_handler = { let click_handler = {
@ -543,18 +472,7 @@ impl<'outer> StyledRte<'outer> {
"justifyCenter" => RteMsg::JustifyCenter, "justifyCenter" => RteMsg::JustifyCenter,
"justifyLeft" => RteMsg::JustifyLeft, "justifyLeft" => RteMsg::JustifyLeft,
"justifyRight" => RteMsg::JustifyRight, "justifyRight" => RteMsg::JustifyRight,
"undo" => RteMsg::Undo,
"redo" => RteMsg::Redo,
"removeFormat" => RteMsg::RemoveFormat,
"bold" => RteMsg::Bold,
"italic " => RteMsg::Italic,
"underscore" => RteMsg::Underscore,
"strikethrough" => RteMsg::Strikethrough,
"subscript" => RteMsg::Subscript,
"superscript" => RteMsg::Superscript,
// "font" => RteMsg::, // Some(RteMsg::Font),
"listingDots" => RteMsg::InsertUnorderedList, "listingDots" => RteMsg::InsertUnorderedList,
"listingNumber" => RteMsg::InsertOrderedList, "listingNumber" => RteMsg::InsertOrderedList,
"table" => RteMsg::TableSetVisibility(true), "table" => RteMsg::TableSetVisibility(true),
@ -600,21 +518,12 @@ impl<'outer> StyledRte<'outer> {
C!["bar"], C!["bar"],
Self::first_row(click_handler.clone()), Self::first_row(click_handler.clone()),
self.second_row(click_handler, change_handler), self.second_row(click_handler, change_handler),
/* brush_button,
* color_bucket_button,
* color_picker_button,
* link_broken_button,
* pin_button,
* save_button,
* text_height_button,
* text_width_button, */
], ],
div![ div![
C!["editorWrapper"], C!["editorWrapper"],
div![ div![
C!["editor", self.field_id.to_str()], C!["editor", self.field_id.to_str()],
attrs![At::ContentEditable => true], attrs![At::ContentEditable => true],
// capture_change
], ],
] ]
] ]
@ -660,50 +569,10 @@ impl<'outer> StyledRte<'outer> {
Self::styled_rte_button("Redo", ButtonId::Redo, Icon::Redo, click_handler.clone()); Self::styled_rte_button("Redo", ButtonId::Redo, Icon::Redo, click_handler.clone());
let undo_button = let undo_button =
Self::styled_rte_button("Undo", ButtonId::Undo, Icon::Undo, click_handler.clone()); Self::styled_rte_button("Undo", ButtonId::Undo, Icon::Undo, click_handler.clone());
/*let field_id = values.field_id.clone(); div![C!["group system"], undo_button, redo_button,]
let clip_board_button = styled_rte_button(
"Paste",
Icon::ClipBoard,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
Some(Msg::Rte(RteMsg::Paste, field_id))
}),
);
let field_id = values.field_id.clone();
let copy_button = styled_rte_button(
"Copy",
Icon::Copy,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
Some(Msg::Rte(RteMsg::Copy, field_id))
}),
);
let field_id = values.field_id.clone();
let cut_button = styled_rte_button(
"Cut",
Icon::Cut,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
Some(Msg::Rte(RteMsg::Cut, field_id))
}),
);*/
div![
C!["group system"],
// clip_board_button,
// copy_button,
// cut_button,
undo_button,
redo_button,
]
}; };
let formatting = { let formatting = {
let remove_formatting = Self::styled_rte_button(
"Remove format",
ButtonId::RemoveFormat,
Icon::EraserAlt,
click_handler.clone(),
);
let bold_button = let bold_button =
Self::styled_rte_button("Bold", ButtonId::Bold, Icon::Bold, click_handler.clone()); Self::styled_rte_button("Bold", ButtonId::Bold, Icon::Bold, click_handler.clone());
let italic_button = Self::styled_rte_button( let italic_button = Self::styled_rte_button(
@ -749,7 +618,6 @@ impl<'outer> StyledRte<'outer> {
strike_through_button, strike_through_button,
subscript_button, subscript_button,
superscript_button, superscript_button,
remove_formatting,
] ]
}; };
@ -761,45 +629,7 @@ impl<'outer> StyledRte<'outer> {
click_handler: EventHandler<Msg>, click_handler: EventHandler<Msg>,
change_handler: EventHandler<Msg>, change_handler: EventHandler<Msg>,
) -> Node<Msg> { ) -> Node<Msg> {
/*let align_group = {
let field_id = values.field_id.clone();
let align_center_button = styled_rte_button(
"Align Center",
Icon::AlignCenter,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let field_id = values.field_id.clone();
let align_left_button = styled_rte_button(
"Align Left",
Icon::AlignLeft,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let field_id = values.field_id.clone();
let align_right_button = styled_rte_button(
"Align Right",
Icon::AlignRight,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
div![
C!["group align"],
align_center_button,
align_left_button,
align_right_button,
]
};*/
let font_group = { let font_group = {
let _font_button =
Self::styled_rte_button("Font", ButtonId::Font, Icon::Font, click_handler.clone());
let options: Vec<Node<Msg>> = HeadingSize::all() let options: Vec<Node<Msg>> = HeadingSize::all()
.iter() .iter()
.map(|h| { .map(|h| {
@ -814,36 +644,30 @@ impl<'outer> StyledRte<'outer> {
..Default::default() ..Default::default()
} }
.render(); .render();
span![C!["headingOption"], button] option![
attrs!["value" => h.as_u32(), "title" => "Text styles"],
C!["headingOption"],
button
]
}) })
.collect(); .collect();
let heading_button = span![C!["headingList"], options];
/*let _field_id = values.field_id.clone(); let field_id = self.field_id.clone();
let _small_cap_button = styled_rte_button( let on_change = ev("change", |ev| {
"Small Cap", let target = ev.target().expect("is selected");
Icon::SmallCap, let select = seed::to_select(&target);
mouse_ev(Ev::Click, move |ev| { let value: String = select.value();
ev.prevent_default();
None as Option<Msg> let value = value.parse::<u32>().ok().unwrap_or_default();
}), Msg::Rte(
); field_id,
let _field_id = values.field_id.clone(); RteMsg::InsertHeading(HeadingSize::from_u32(value)),
let _all_caps_button = styled_rte_button( )
"All caps", });
Icon::AllCaps,
mouse_ev(Ev::Click, move |ev| { let heading_button = select![C!["headingList"], options, on_change,];
ev.prevent_default();
None as Option<Msg> div![C!["group font"], heading_button]
}),
);*/
div![
C!["group font"],
// font_button,
heading_button,
/* small_cap_button,
* all_caps_button */
]
}; };
let insert_group = { let insert_group = {
@ -861,16 +685,6 @@ impl<'outer> StyledRte<'outer> {
Icon::ListingNumber, Icon::ListingNumber,
click_handler.clone(), click_handler.clone(),
); );
/*let field_id = values.field_id.clone();
let sub_listing_button = styled_rte_button(
"Sub Listing",
Icon::SubListing,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);*/
let mut table_button = Self::styled_rte_button( let mut table_button = Self::styled_rte_button(
"Table", "Table",
ButtonId::Table, ButtonId::Table,
@ -879,12 +693,6 @@ impl<'outer> StyledRte<'outer> {
); );
table_button.add_child(table_tooltip); table_button.add_child(table_tooltip);
let paragraph_button = Self::styled_rte_button(
"Paragraph",
ButtonId::Paragraph,
Icon::Paragraph,
click_handler.clone(),
);
let mut code_alt_button = Self::styled_rte_button( let mut code_alt_button = Self::styled_rte_button(
"Insert code", "Insert code",
ButtonId::CodeAlt, ButtonId::CodeAlt,
@ -895,12 +703,10 @@ impl<'outer> StyledRte<'outer> {
div![ div![
C!["group insert"], C!["group insert"],
paragraph_button,
table_button, table_button,
code_alt_button, code_alt_button,
listing_dots, listing_dots,
listing_number, listing_number,
// sub_listing_button,
] ]
}; };
@ -919,7 +725,6 @@ impl<'outer> StyledRte<'outer> {
div![ div![
C!["row secondRow"], C!["row secondRow"],
font_group, font_group,
// align_group,
insert_group, insert_group,
indent_outdent indent_outdent
] ]
@ -1125,14 +930,75 @@ impl<'outer> StyledRte<'outer> {
} }
} }
fn wrap_into(name: &str) -> Option<()> { fn wrap_into(name: &str) {
try_wrap_into(name);
}
fn try_wrap_into(name: &str) -> Option<()> {
let sel = document().get_selection().ok()??; let sel = document().get_selection().ok()??;
let r = sel.get_range_at(0).ok()?; let r = sel.get_range_at(0).ok()?;
let el: web_sys::Element = document().create_element(name).unwrap(); let start = r.start_container();
let node = el.dyn_ref::<web_sys::Node>().unwrap(); let end = r.end_container();
if let Err(e) = r.surround_contents(&node) {
error!("{}", e); if let Some(node) = is_wrapped(start.clone().ok(), end.clone().ok(), name) {
let el = node.dyn_ref::<web_sys::HtmlElement>()?;
let parent = el.parent_element()?;
let children = el.child_nodes();
let mut len: u32 = children.length();
len = len.checked_sub(1)?;
let last = children.item(len)?;
parent.replace_child(&last, &node).ok();
r.set_end_after(&last).ok();
let mut prev = last;
while len > 0 {
len -= 1;
let current = children.item(len)?;
parent.replace_child(&current, &prev).ok();
prev = current;
}
sel.collapse_to_end().ok()
} else {
let el: web_sys::Element = document().create_element(name).unwrap();
let node = el.dyn_ref::<web_sys::Node>().unwrap();
if let Err(e) = r.surround_contents(&node) {
error!("{}", e);
}
r.set_end_after(node).ok();
sel.collapse_to_end().ok()
}
}
fn is_wrapped(
start: Option<web_sys::Node>,
end: Option<web_sys::Node>,
name: &str,
) -> Option<web_sys::Element> {
let start = start?;
let end: web_sys::Node = end?;
if start == end
&& start
.dyn_ref::<web_sys::HtmlElement>()
.filter(|x| x.tag_name() == name)
.is_some()
{
return Some(
start
.dyn_into::<web_sys::Element>()
.expect("All HTMLElement are by definition Element"),
);
}
let start = start.parent_element()?;
let start_parent = start.dyn_ref::<web_sys::HtmlElement>()?;
let end = end.parent_element()?;
let end_parent = end.dyn_ref::<web_sys::HtmlElement>()?;
if start_parent == end_parent && start_parent.tag_name().as_str() == name {
Some(start)
} else {
None
} }
sel.collapse(Some(node)).ok()
} }