improve wrap into tag, improving text styles
This commit is contained in:
parent
63302fef71
commit
9cc34f16bb
@ -60,6 +60,7 @@ features = [
|
||||
"FormData",
|
||||
"FileReader",
|
||||
"FileReaderSync",
|
||||
"DocumentFragment",
|
||||
"Range",
|
||||
# events
|
||||
"EventTarget",
|
||||
|
@ -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 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);
|
||||
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(¤t, &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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user