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",
"FileReader",
"FileReaderSync",
"DocumentFragment",
"Range",
# events
"EventTarget",

View File

@ -1,6 +1,5 @@
use seed::prelude::*;
use seed::*;
use tracing::{debug, info};
use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_icon::{Icon, StyledIcon};
@ -42,6 +41,31 @@ impl HeadingSize {
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)]
@ -55,12 +79,7 @@ pub enum RteMsg {
Bold,
Italic,
Underscore,
Undo,
Redo,
Strikethrough,
Copy,
Paste,
Cut,
JustifyFull,
JustifyCenter,
JustifyLeft,
@ -107,15 +126,10 @@ impl<'l> ExecCommand<'l> {
impl RteMsg {
pub fn to_command(&self) -> Option<ExecCommand> {
match self {
RteMsg::Bold => Some(ExecCommand::new("bold")),
RteMsg::Italic => Some(ExecCommand::new("italic")),
RteMsg::Underscore => Some(ExecCommand::new("underline")),
RteMsg::Undo => Some(ExecCommand::new("undo")),
RteMsg::Redo => Some(ExecCommand::new("redo")),
RteMsg::Bold => None,
RteMsg::Italic => None,
RteMsg::Underscore => None,
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::JustifyCenter => Some(ExecCommand::new("justifyCenter")),
RteMsg::JustifyLeft => Some(ExecCommand::new("justifyLeft")),
@ -127,17 +141,14 @@ impl RteMsg {
| HeadingSize::H3
| HeadingSize::H4
| HeadingSize::H5
| HeadingSize::H6 => {
// Some(ExecCommand::new_with_param("heading", heading.as_str()))
None
}
HeadingSize::Normal => Some(ExecCommand::new_with_param("formatBlock", "div")),
| HeadingSize::H6 => None,
HeadingSize::Normal => None,
},
RteMsg::InsertUnorderedList => Some(ExecCommand::new("insertUnorderedList")),
RteMsg::InsertOrderedList => Some(ExecCommand::new("insertOrderedList")),
RteMsg::RemoveFormat => Some(ExecCommand::new("removeFormat")),
RteMsg::Subscript => Some(ExecCommand::new("subscript")),
RteMsg::Superscript => Some(ExecCommand::new("superscript")),
RteMsg::RemoveFormat => None,
RteMsg::Subscript => None,
RteMsg::Superscript => None,
RteMsg::InsertTable { .. } => None,
// code
RteMsg::InsertCode(_) => None,
@ -284,12 +295,13 @@ impl StyledRteState {
if self.restore_range().is_err() {
return;
}
self.schedule_focus(orders);
}
_ => match m {
RteMsg::InsertHeading(heading) => {
wrap_into(heading.as_str());
}
RteMsg::Subscript => wrap_into("SUB"),
RteMsg::Superscript => wrap_into("SUP"),
RteMsg::Bold => wrap_into("B"),
RteMsg::Underscore => wrap_into("U"),
RteMsg::InsertHeading(heading) => wrap_into(heading.as_str()),
// code
RteMsg::InsertCode(b) => {
if *b {
@ -394,7 +406,7 @@ impl StyledRteState {
let sel = sel
.unwrap_or(None)
.ok_or_else(|| "Restoring selection failed. Unable to obtain select".to_string())?;
sel.collapse_to_start();
sel.collapse_to_end().ok();
let r = self
.range
.as_ref()
@ -436,89 +448,6 @@ impl<'component> Default for StyledRte<'component> {
impl<'outer> StyledRte<'outer> {
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 click_handler = {
@ -543,18 +472,7 @@ impl<'outer> StyledRte<'outer> {
"justifyCenter" => RteMsg::JustifyCenter,
"justifyLeft" => RteMsg::JustifyLeft,
"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,
"listingNumber" => RteMsg::InsertOrderedList,
"table" => RteMsg::TableSetVisibility(true),
@ -600,21 +518,12 @@ impl<'outer> StyledRte<'outer> {
C!["bar"],
Self::first_row(click_handler.clone()),
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![
C!["editorWrapper"],
div![
C!["editor", self.field_id.to_str()],
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());
let undo_button =
Self::styled_rte_button("Undo", ButtonId::Undo, Icon::Undo, click_handler.clone());
/*let field_id = values.field_id.clone();
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,
]
div![C!["group system"], undo_button, redo_button,]
};
let formatting = {
let remove_formatting = Self::styled_rte_button(
"Remove format",
ButtonId::RemoveFormat,
Icon::EraserAlt,
click_handler.clone(),
);
let bold_button =
Self::styled_rte_button("Bold", ButtonId::Bold, Icon::Bold, click_handler.clone());
let italic_button = Self::styled_rte_button(
@ -749,7 +618,6 @@ impl<'outer> StyledRte<'outer> {
strike_through_button,
subscript_button,
superscript_button,
remove_formatting,
]
};
@ -761,45 +629,7 @@ impl<'outer> StyledRte<'outer> {
click_handler: EventHandler<Msg>,
change_handler: EventHandler<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_button =
Self::styled_rte_button("Font", ButtonId::Font, Icon::Font, click_handler.clone());
let options: Vec<Node<Msg>> = HeadingSize::all()
.iter()
.map(|h| {
@ -814,36 +644,30 @@ impl<'outer> StyledRte<'outer> {
..Default::default()
}
.render();
span![C!["headingOption"], button]
option![
attrs!["value" => h.as_u32(), "title" => "Text styles"],
C!["headingOption"],
button
]
})
.collect();
let heading_button = span![C!["headingList"], options];
/*let _field_id = values.field_id.clone();
let _small_cap_button = styled_rte_button(
"Small Cap",
Icon::SmallCap,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);
let _field_id = values.field_id.clone();
let _all_caps_button = styled_rte_button(
"All caps",
Icon::AllCaps,
mouse_ev(Ev::Click, move |ev| {
ev.prevent_default();
None as Option<Msg>
}),
);*/
div![
C!["group font"],
// font_button,
heading_button,
/* small_cap_button,
* all_caps_button */
]
let field_id = self.field_id.clone();
let on_change = ev("change", |ev| {
let target = ev.target().expect("is selected");
let select = seed::to_select(&target);
let value: String = select.value();
let value = value.parse::<u32>().ok().unwrap_or_default();
Msg::Rte(
field_id,
RteMsg::InsertHeading(HeadingSize::from_u32(value)),
)
});
let heading_button = select![C!["headingList"], options, on_change,];
div![C!["group font"], heading_button]
};
let insert_group = {
@ -861,16 +685,6 @@ impl<'outer> StyledRte<'outer> {
Icon::ListingNumber,
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(
"Table",
ButtonId::Table,
@ -879,12 +693,6 @@ impl<'outer> StyledRte<'outer> {
);
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(
"Insert code",
ButtonId::CodeAlt,
@ -895,12 +703,10 @@ impl<'outer> StyledRte<'outer> {
div![
C!["group insert"],
paragraph_button,
table_button,
code_alt_button,
listing_dots,
listing_number,
// sub_listing_button,
]
};
@ -919,7 +725,6 @@ impl<'outer> StyledRte<'outer> {
div![
C!["row secondRow"],
font_group,
// align_group,
insert_group,
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 r = sel.get_range_at(0).ok()?;
let start = r.start_container();
let end = r.end_container();
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);
}
sel.collapse(Some(node)).ok()
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
}
}