Fix input, fix submit handle, add validations
This commit is contained in:
parent
2f870a5a0a
commit
b22210d55a
@ -1,9 +1,8 @@
|
|||||||
#login > .styledForm {
|
#login > .styledForm {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 124.5px auto 24px;
|
margin: 0 auto 24px;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
/*padding: 32px 40px;*/
|
|
||||||
background: rgb(255, 255, 255) none repeat scroll 0 0;
|
background: rgb(255, 255, 255) none repeat scroll 0 0;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: rgba(0, 0, 0, 0.1) 0 0 10px;
|
box-shadow: rgba(0, 0, 0, 0.1) 0 0 10px;
|
||||||
@ -11,6 +10,23 @@
|
|||||||
color: var(--textMedium);
|
color: var(--textMedium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login > .styledForm:first-of-type {
|
||||||
|
margin-top: 124.5px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login > .styledForm:last-of-type {
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login > .styledForm:first-of-type > .formElement {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login > .styledForm:last-of-type > .formElement {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#login > .styledForm > .formElement > .formHeading {
|
#login > .styledForm > .formElement > .formHeading {
|
||||||
color: var(--textMedium);
|
color: var(--textMedium);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use jirs_data::WsMsg;
|
||||||
|
|
||||||
use crate::api::send_ws_msg;
|
use crate::api::send_ws_msg;
|
||||||
use crate::model::{LoginPage, Page, PageContent};
|
use crate::model::{LoginPage, Page, PageContent};
|
||||||
@ -6,12 +11,9 @@ use crate::shared::styled_button::StyledButton;
|
|||||||
use crate::shared::styled_field::StyledField;
|
use crate::shared::styled_field::StyledField;
|
||||||
use crate::shared::styled_form::StyledForm;
|
use crate::shared::styled_form::StyledForm;
|
||||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||||
use crate::shared::styled_textarea::StyledTextarea;
|
use crate::shared::styled_input::StyledInput;
|
||||||
use crate::shared::{outer_layout, write_auth_token, ToNode};
|
use crate::shared::{outer_layout, write_auth_token, ToNode};
|
||||||
use crate::{model, FieldId, LoginFieldId, Msg};
|
use crate::{model, FieldId, LoginFieldId, Msg};
|
||||||
use jirs_data::WsMsg;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
if model.page != Page::Login {
|
if model.page != Page::Login {
|
||||||
@ -31,12 +33,15 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
|
|||||||
match msg {
|
match msg {
|
||||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Username), value) => {
|
Msg::InputChanged(FieldId::Login(LoginFieldId::Username), value) => {
|
||||||
page.username = value;
|
page.username = value;
|
||||||
|
page.username_touched = true;
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Email), value) => {
|
Msg::InputChanged(FieldId::Login(LoginFieldId::Email), value) => {
|
||||||
page.email = value;
|
page.email = value;
|
||||||
|
page.email_touched = true;
|
||||||
}
|
}
|
||||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Token), value) => {
|
Msg::InputChanged(FieldId::Login(LoginFieldId::Token), value) => {
|
||||||
page.token = value;
|
page.token = value;
|
||||||
|
page.token_touched = true;
|
||||||
}
|
}
|
||||||
Msg::SignInRequest => {
|
Msg::SignInRequest => {
|
||||||
send_ws_msg(WsMsg::AuthenticateRequest(
|
send_ws_msg(WsMsg::AuthenticateRequest(
|
||||||
@ -77,11 +82,10 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let username = StyledTextarea::build()
|
let username = StyledInput::build(FieldId::Login(LoginFieldId::Username))
|
||||||
.one_line()
|
|
||||||
.update_on(Ev::Change)
|
|
||||||
.value(page.username.as_str())
|
.value(page.username.as_str())
|
||||||
.build(FieldId::Login(LoginFieldId::Username))
|
.valid(!page.username_touched || page.username.len() > 1)
|
||||||
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let username_field = StyledField::build()
|
let username_field = StyledField::build()
|
||||||
.label("Username")
|
.label("Username")
|
||||||
@ -89,11 +93,10 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let email = StyledTextarea::build()
|
let email = StyledInput::build(FieldId::Login(LoginFieldId::Email))
|
||||||
.one_line()
|
|
||||||
.update_on(Ev::Change)
|
|
||||||
.value(page.email.as_str())
|
.value(page.email.as_str())
|
||||||
.build(FieldId::Login(LoginFieldId::Email))
|
.valid(!page.email_touched || is_email(page.email.as_str()))
|
||||||
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let email_field = StyledField::build()
|
let email_field = StyledField::build()
|
||||||
.label("E-Mail")
|
.label("E-Mail")
|
||||||
@ -102,7 +105,9 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let submit = if page.login_success {
|
let submit = if page.login_success {
|
||||||
StyledButton::build().success().text("✓")
|
StyledButton::build()
|
||||||
|
.success()
|
||||||
|
.text("✓ Please check your mail")
|
||||||
} else {
|
} else {
|
||||||
StyledButton::build()
|
StyledButton::build()
|
||||||
.primary()
|
.primary()
|
||||||
@ -126,11 +131,24 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
span!["Why I don't see password?"]
|
span!["Why I don't see password?"]
|
||||||
];
|
];
|
||||||
|
|
||||||
let token = StyledTextarea::build()
|
let sign_in_form = StyledForm::build()
|
||||||
.one_line()
|
.heading("Sign In to your account")
|
||||||
.update_on(Ev::Input)
|
.on_submit(ev(Ev::Submit, |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
Msg::SignInRequest
|
||||||
|
}))
|
||||||
|
.add_field(username_field)
|
||||||
|
.add_field(email_field)
|
||||||
|
.add_field(submit_field)
|
||||||
|
.add_field(no_pass_section)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let token = StyledInput::build(FieldId::Login(LoginFieldId::Token))
|
||||||
.value(page.token.as_str())
|
.value(page.token.as_str())
|
||||||
.build(FieldId::Login(LoginFieldId::Token))
|
.valid(!page.token_touched || is_token(page.token.as_str()))
|
||||||
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let token_field = StyledField::build()
|
let token_field = StyledField::build()
|
||||||
.label("Single use token")
|
.label("Single use token")
|
||||||
@ -145,17 +163,42 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.into_node();
|
.into_node();
|
||||||
let submit_token_field = StyledField::build().input(submit_token).build().into_node();
|
let submit_token_field = StyledField::build().input(submit_token).build().into_node();
|
||||||
|
|
||||||
let form = StyledForm::build()
|
let bind_token_form = StyledForm::build()
|
||||||
.heading("Sign In to your account")
|
.on_submit(ev(Ev::Submit, |ev| {
|
||||||
.add_field(username_field)
|
ev.stop_propagation();
|
||||||
.add_field(email_field)
|
ev.prevent_default();
|
||||||
.add_field(submit_field)
|
Msg::BindClientRequest
|
||||||
.add_field(no_pass_section)
|
}))
|
||||||
.add_field(token_field)
|
.add_field(token_field)
|
||||||
.add_field(submit_token_field)
|
.add_field(submit_token_field)
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let children = vec![form];
|
let children = vec![sign_in_form, bind_token_form];
|
||||||
outer_layout(model, "login", children)
|
outer_layout(model, "login", children)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_token(s: &str) -> bool {
|
||||||
|
uuid::Uuid::from_str(s).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_email(s: &str) -> bool {
|
||||||
|
let mut has_at = false;
|
||||||
|
let mut has_dot = false;
|
||||||
|
|
||||||
|
for c in s.chars() {
|
||||||
|
match c {
|
||||||
|
'\n' | ' ' | '\t' | '\r' => return false,
|
||||||
|
'@' if !has_at => {
|
||||||
|
has_at = true;
|
||||||
|
}
|
||||||
|
'@' if has_at => return false,
|
||||||
|
'.' if has_at => {
|
||||||
|
has_dot = true;
|
||||||
|
}
|
||||||
|
_ if has_dot => return true,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -151,9 +151,9 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let description = StyledTextarea::build()
|
let description = StyledTextarea::build(FieldId::AddIssueModal(IssueFieldId::Description))
|
||||||
.height(110)
|
.height(110)
|
||||||
.build(FieldId::AddIssueModal(IssueFieldId::Description))
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let description_field = StyledField::build()
|
let description_field = StyledField::build()
|
||||||
.label("Description")
|
.label("Description")
|
||||||
|
@ -372,15 +372,15 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
..
|
..
|
||||||
} = modal;
|
} = modal;
|
||||||
|
|
||||||
let title = StyledTextarea::build()
|
let title = StyledTextarea::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||||
.value(payload.title.as_str())
|
IssueFieldId::Title,
|
||||||
.add_class("textarea")
|
)))
|
||||||
.max_height(48)
|
.value(payload.title.as_str())
|
||||||
.height(0)
|
.add_class("textarea")
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
.max_height(48)
|
||||||
IssueFieldId::Title,
|
.height(0)
|
||||||
)))
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let description_text = payload.description.as_ref().cloned().unwrap_or_default();
|
let description_text = payload.description.as_ref().cloned().unwrap_or_default();
|
||||||
let description = StyledEditor::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
let description = StyledEditor::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||||
@ -468,13 +468,13 @@ fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
|
|||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
let text_area = StyledTextarea::build()
|
let text_area = StyledTextarea::build(FieldId::EditIssueModal(EditIssueModalSection::Comment(
|
||||||
.value(form.body.as_str())
|
CommentFieldId::Body,
|
||||||
.placeholder("Add a comment...")
|
)))
|
||||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Comment(
|
.value(form.body.as_str())
|
||||||
CommentFieldId::Body,
|
.placeholder("Add a comment...")
|
||||||
)))
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let submit = StyledButton::build()
|
let submit = StyledButton::build()
|
||||||
.variant(ButtonVariant::Primary)
|
.variant(ButtonVariant::Primary)
|
||||||
|
@ -225,6 +225,10 @@ pub struct LoginPage {
|
|||||||
pub token: String,
|
pub token: String,
|
||||||
pub login_success: bool,
|
pub login_success: bool,
|
||||||
pub bad_token: String,
|
pub bad_token: String,
|
||||||
|
// touched
|
||||||
|
pub username_touched: bool,
|
||||||
|
pub email_touched: bool,
|
||||||
|
pub token_touched: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -84,12 +84,12 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
PageContent::ProjectSettings(page) => page,
|
PageContent::ProjectSettings(page) => page,
|
||||||
_ => return empty![],
|
_ => return empty![],
|
||||||
};
|
};
|
||||||
let name = StyledTextarea::build()
|
let name = StyledTextarea::build(FieldId::ProjectSettings(ProjectFieldId::Name))
|
||||||
.value(page.payload.name.as_ref().cloned().unwrap_or_default())
|
.value(page.payload.name.as_ref().cloned().unwrap_or_default())
|
||||||
.height(39)
|
.height(39)
|
||||||
.max_height(39)
|
.max_height(39)
|
||||||
.disable_auto_resize()
|
.disable_auto_resize()
|
||||||
.build(FieldId::ProjectSettings(ProjectFieldId::Name))
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let name_field = StyledField::build()
|
let name_field = StyledField::build()
|
||||||
.label("Name")
|
.label("Name")
|
||||||
@ -98,12 +98,12 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let url = StyledTextarea::build()
|
let url = StyledTextarea::build(FieldId::ProjectSettings(ProjectFieldId::Url))
|
||||||
.height(39)
|
.height(39)
|
||||||
.max_height(39)
|
.max_height(39)
|
||||||
.disable_auto_resize()
|
.disable_auto_resize()
|
||||||
.value(page.payload.url.as_ref().cloned().unwrap_or_default())
|
.value(page.payload.url.as_ref().cloned().unwrap_or_default())
|
||||||
.build(FieldId::ProjectSettings(ProjectFieldId::Url))
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
let url_field = StyledField::build()
|
let url_field = StyledField::build()
|
||||||
.label("Url")
|
.label("Url")
|
||||||
|
@ -1,185 +1,185 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use crate::shared::styled_textarea::StyledTextarea;
|
use crate::shared::styled_textarea::StyledTextarea;
|
||||||
use crate::shared::ToNode;
|
use crate::shared::ToNode;
|
||||||
use crate::{FieldChange, FieldId, Msg};
|
use crate::{FieldChange, FieldId, Msg};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialOrd, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialOrd, PartialEq, Hash)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Editor,
|
Editor,
|
||||||
View,
|
View,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StyledEditorState {
|
pub struct StyledEditorState {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StyledEditor {
|
pub struct StyledEditor {
|
||||||
id: FieldId,
|
id: FieldId,
|
||||||
text: String,
|
text: String,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
update_event: Ev,
|
update_event: Ev,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledEditor {
|
impl StyledEditor {
|
||||||
pub fn build(id: FieldId) -> StyledEditorBuilder {
|
pub fn build(id: FieldId) -> StyledEditorBuilder {
|
||||||
StyledEditorBuilder {
|
StyledEditorBuilder {
|
||||||
id,
|
id,
|
||||||
text: String::new(),
|
text: String::new(),
|
||||||
mode: Mode::View,
|
mode: Mode::View,
|
||||||
update_event: None,
|
update_event: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StyledEditorBuilder {
|
pub struct StyledEditorBuilder {
|
||||||
id: FieldId,
|
id: FieldId,
|
||||||
text: String,
|
text: String,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
update_event: Option<Ev>,
|
update_event: Option<Ev>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledEditorBuilder {
|
impl StyledEditorBuilder {
|
||||||
pub fn text<S>(mut self, text: S) -> Self
|
pub fn text<S>(mut self, text: S) -> Self
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
self.text = text.into();
|
self.text = text.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mode(mut self, mode: Mode) -> Self {
|
pub fn mode(mut self, mode: Mode) -> Self {
|
||||||
self.mode = mode;
|
self.mode = mode;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> StyledEditor {
|
pub fn build(self) -> StyledEditor {
|
||||||
StyledEditor {
|
StyledEditor {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
text: self.text,
|
text: self.text,
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
update_event: self.update_event.unwrap_or_else(|| Ev::KeyUp),
|
update_event: self.update_event.unwrap_or_else(|| Ev::KeyUp),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_on(mut self, ev: Ev) -> Self {
|
pub fn update_on(mut self, ev: Ev) -> Self {
|
||||||
self.update_event = Some(ev);
|
self.update_event = Some(ev);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToNode for StyledEditor {
|
impl ToNode for StyledEditor {
|
||||||
fn into_node(self) -> Node<Msg> {
|
fn into_node(self) -> Node<Msg> {
|
||||||
render(self)
|
render(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(values: StyledEditor) -> Node<Msg> {
|
pub fn render(values: StyledEditor) -> Node<Msg> {
|
||||||
let StyledEditor {
|
let StyledEditor {
|
||||||
id,
|
id,
|
||||||
text,
|
text,
|
||||||
mode,
|
mode,
|
||||||
update_event,
|
update_event,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
let on_editor_clicked = mouse_ev(Ev::Click, move |ev| {
|
let on_editor_clicked = mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::Editor))
|
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::Editor))
|
||||||
});
|
});
|
||||||
|
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
let on_view_clicked = mouse_ev(Ev::Click, move |ev| {
|
let on_view_clicked = mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::View))
|
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::View))
|
||||||
});
|
});
|
||||||
|
|
||||||
let editor_id = format!("editor-{}", id);
|
let editor_id = format!("editor-{}", id);
|
||||||
let view_id = format!("view-{}", id);
|
let view_id = format!("view-{}", id);
|
||||||
let name = format!("styled-editor-{}", id);
|
let name = format!("styled-editor-{}", id);
|
||||||
|
|
||||||
let text_area = StyledTextarea::build()
|
let text_area = StyledTextarea::build(id)
|
||||||
.height(40)
|
.height(40)
|
||||||
.update_on(update_event)
|
.update_on(update_event)
|
||||||
.value(text.as_str())
|
.value(text.as_str())
|
||||||
.build(id)
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
let parsed = comrak::markdown_to_html(
|
let parsed = comrak::markdown_to_html(
|
||||||
text.as_str(),
|
text.as_str(),
|
||||||
&comrak::ComrakOptions {
|
&comrak::ComrakOptions {
|
||||||
hardbreaks: false,
|
hardbreaks: false,
|
||||||
smart: true,
|
smart: true,
|
||||||
github_pre_lang: true,
|
github_pre_lang: true,
|
||||||
width: 0,
|
width: 0,
|
||||||
default_info_string: None,
|
default_info_string: None,
|
||||||
unsafe_: false,
|
unsafe_: false,
|
||||||
ext_strikethrough: true,
|
ext_strikethrough: true,
|
||||||
ext_tagfilter: true,
|
ext_tagfilter: true,
|
||||||
ext_table: true,
|
ext_table: true,
|
||||||
ext_autolink: true,
|
ext_autolink: true,
|
||||||
ext_tasklist: true,
|
ext_tasklist: true,
|
||||||
ext_superscript: true,
|
ext_superscript: true,
|
||||||
ext_header_ids: None,
|
ext_header_ids: None,
|
||||||
ext_footnotes: true,
|
ext_footnotes: true,
|
||||||
ext_description_lists: true,
|
ext_description_lists: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let parsed_node = Node::from_html(parsed.as_str());
|
let parsed_node = Node::from_html(parsed.as_str());
|
||||||
|
|
||||||
let (editor_radio_node, view_radio_node) = match mode {
|
let (editor_radio_node, view_radio_node) = match mode {
|
||||||
Mode::Editor => (
|
Mode::Editor => (
|
||||||
seed::input![
|
seed::input![
|
||||||
id![editor_id.as_str()],
|
id![editor_id.as_str()],
|
||||||
attrs![At::Type => "radio"; At::Name => name.as_str(); At::Class => "editorRadio"; At::Checked => true],
|
attrs![At::Type => "radio"; At::Name => name.as_str(); At::Class => "editorRadio"; At::Checked => true],
|
||||||
],
|
],
|
||||||
seed::input![
|
seed::input![
|
||||||
id![view_id.as_str()],
|
id![view_id.as_str()],
|
||||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Class => "viewRadio";],
|
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Class => "viewRadio";],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Mode::View => (
|
Mode::View => (
|
||||||
seed::input![
|
seed::input![
|
||||||
id![editor_id.as_str()],
|
id![editor_id.as_str()],
|
||||||
class!["editorRadio"],
|
class!["editorRadio"],
|
||||||
attrs![At::Type => "radio"; At::Name => name.as_str();],
|
attrs![At::Type => "radio"; At::Name => name.as_str();],
|
||||||
],
|
],
|
||||||
seed::input![
|
seed::input![
|
||||||
id![view_id.as_str()],
|
id![view_id.as_str()],
|
||||||
class!["viewRadio"],
|
class!["viewRadio"],
|
||||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Checked => true],
|
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Checked => true],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "styledEditor"],
|
attrs![At::Class => "styledEditor"],
|
||||||
label![
|
label![
|
||||||
if mode == Mode::Editor {
|
if mode == Mode::Editor {
|
||||||
class!["navbar editorTab activeTab"]
|
class!["navbar editorTab activeTab"]
|
||||||
} else {
|
} else {
|
||||||
class!["navbar editorTab"]
|
class!["navbar editorTab"]
|
||||||
},
|
},
|
||||||
attrs![At::For => editor_id.as_str()],
|
attrs![At::For => editor_id.as_str()],
|
||||||
"Editor",
|
"Editor",
|
||||||
on_editor_clicked
|
on_editor_clicked
|
||||||
],
|
],
|
||||||
label![
|
label![
|
||||||
if mode == Mode::View {
|
if mode == Mode::View {
|
||||||
class!["navbar viewTab activeTab"]
|
class!["navbar viewTab activeTab"]
|
||||||
} else {
|
} else {
|
||||||
class!["navbar viewTab"]
|
class!["navbar viewTab"]
|
||||||
},
|
},
|
||||||
attrs![At::For => view_id.as_str()],
|
attrs![At::For => view_id.as_str()],
|
||||||
"View",
|
"View",
|
||||||
on_view_clicked
|
on_view_clicked
|
||||||
],
|
],
|
||||||
editor_radio_node,
|
editor_radio_node,
|
||||||
text_area,
|
text_area,
|
||||||
view_radio_node,
|
view_radio_node,
|
||||||
div![attrs![At::Class => "view"], parsed_node],
|
div![attrs![At::Class => "view"], parsed_node],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,79 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use crate::shared::ToNode;
|
use crate::shared::ToNode;
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StyledForm {
|
pub struct StyledForm {
|
||||||
heading: String,
|
heading: String,
|
||||||
fields: Vec<Node<Msg>>,
|
fields: Vec<Node<Msg>>,
|
||||||
}
|
on_submit: Option<EventHandler<Msg>>,
|
||||||
|
}
|
||||||
impl StyledForm {
|
|
||||||
pub fn build() -> StyledFormBuilder {
|
impl StyledForm {
|
||||||
StyledFormBuilder::default()
|
pub fn build() -> StyledFormBuilder {
|
||||||
}
|
StyledFormBuilder::default()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
impl ToNode for StyledForm {
|
|
||||||
fn into_node(self) -> Node<Msg> {
|
impl ToNode for StyledForm {
|
||||||
render(self)
|
fn into_node(self) -> Node<Msg> {
|
||||||
}
|
render(self)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct StyledFormBuilder {
|
#[derive(Debug, Default)]
|
||||||
fields: Vec<Node<Msg>>,
|
pub struct StyledFormBuilder {
|
||||||
heading: String,
|
fields: Vec<Node<Msg>>,
|
||||||
}
|
heading: String,
|
||||||
|
on_submit: Option<EventHandler<Msg>>,
|
||||||
impl StyledFormBuilder {
|
}
|
||||||
pub fn add_field(mut self, node: Node<Msg>) -> Self {
|
|
||||||
self.fields.push(node);
|
impl StyledFormBuilder {
|
||||||
self
|
pub fn add_field(mut self, node: Node<Msg>) -> Self {
|
||||||
}
|
self.fields.push(node);
|
||||||
|
self
|
||||||
pub fn heading<S>(mut self, heading: S) -> Self
|
}
|
||||||
where
|
|
||||||
S: Into<String>,
|
pub fn heading<S>(mut self, heading: S) -> Self
|
||||||
{
|
where
|
||||||
self.heading = heading.into();
|
S: Into<String>,
|
||||||
self
|
{
|
||||||
}
|
self.heading = heading.into();
|
||||||
|
self
|
||||||
pub fn build(self) -> StyledForm {
|
}
|
||||||
StyledForm {
|
|
||||||
heading: self.heading,
|
pub fn on_submit(mut self, on_submit: EventHandler<Msg>) -> Self {
|
||||||
fields: self.fields,
|
self.on_submit = Some(on_submit);
|
||||||
}
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn build(self) -> StyledForm {
|
||||||
pub fn render(values: StyledForm) -> Node<Msg> {
|
StyledForm {
|
||||||
let StyledForm { heading, fields } = values;
|
heading: self.heading,
|
||||||
div![
|
fields: self.fields,
|
||||||
attrs![At::Class => "styledForm"],
|
on_submit: self.on_submit,
|
||||||
div![
|
}
|
||||||
class!["formElement"],
|
}
|
||||||
div![class!["formHeading"], heading],
|
}
|
||||||
fields
|
|
||||||
],
|
pub fn render(values: StyledForm) -> Node<Msg> {
|
||||||
]
|
let StyledForm {
|
||||||
}
|
heading,
|
||||||
|
fields,
|
||||||
|
on_submit,
|
||||||
|
} = values;
|
||||||
|
let handlers = match on_submit {
|
||||||
|
Some(handler) => vec![handler],
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
seed::form![
|
||||||
|
handlers,
|
||||||
|
attrs![At::Class => "styledForm"],
|
||||||
|
div![
|
||||||
|
class!["formElement"],
|
||||||
|
div![class!["formHeading"], heading],
|
||||||
|
fields
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
@ -94,17 +94,17 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
_ => empty![],
|
_ => empty![],
|
||||||
};
|
};
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
let change_handler = keyboard_ev(Ev::KeyUp, move |event| {
|
let change_handler = ev(Ev::Input, move |event| {
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
let value = event
|
let target = event.target().unwrap();
|
||||||
.target()
|
let input = seed::to_input(&target);
|
||||||
.unwrap()
|
let value = input.value();
|
||||||
.dyn_ref::<web_sys::HtmlInputElement>()
|
|
||||||
.unwrap()
|
|
||||||
.value();
|
|
||||||
Msg::InputChanged(field_id, value)
|
Msg::InputChanged(field_id, value)
|
||||||
});
|
});
|
||||||
|
let key_handler = ev(Ev::KeyUp, move |event| {
|
||||||
|
event.stop_propagation();
|
||||||
|
Msg::NoOp
|
||||||
|
});
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs!(At::Class => wrapper_class_list.join(" ")),
|
attrs!(At::Class => wrapper_class_list.join(" ")),
|
||||||
@ -116,6 +116,7 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
|||||||
At::Type => input_type.unwrap_or_else(|| "text".to_string()),
|
At::Type => input_type.unwrap_or_else(|| "text".to_string()),
|
||||||
],
|
],
|
||||||
change_handler,
|
change_handler,
|
||||||
|
key_handler,
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -22,13 +22,24 @@ impl ToNode for StyledTextarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StyledTextarea {
|
impl StyledTextarea {
|
||||||
pub fn build() -> StyledTextareaBuilder {
|
pub fn build(field_id: FieldId) -> StyledTextareaBuilder {
|
||||||
StyledTextareaBuilder::default()
|
StyledTextareaBuilder {
|
||||||
|
id: field_id,
|
||||||
|
height: None,
|
||||||
|
max_height: None,
|
||||||
|
on_change: None,
|
||||||
|
value: "".to_string(),
|
||||||
|
class_list: vec![],
|
||||||
|
update_event: None,
|
||||||
|
placeholder: None,
|
||||||
|
disable_auto_resize: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct StyledTextareaBuilder {
|
pub struct StyledTextareaBuilder {
|
||||||
|
id: FieldId,
|
||||||
height: Option<usize>,
|
height: Option<usize>,
|
||||||
max_height: Option<usize>,
|
max_height: Option<usize>,
|
||||||
on_change: Option<EventHandler<Msg>>,
|
on_change: Option<EventHandler<Msg>>,
|
||||||
@ -40,11 +51,6 @@ pub struct StyledTextareaBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StyledTextareaBuilder {
|
impl StyledTextareaBuilder {
|
||||||
#[inline]
|
|
||||||
pub fn one_line(self) -> Self {
|
|
||||||
self.disable_auto_resize().height(39).max_height(39)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn height(mut self, height: usize) -> Self {
|
pub fn height(mut self, height: usize) -> Self {
|
||||||
self.height = Some(height);
|
self.height = Some(height);
|
||||||
@ -94,9 +100,9 @@ impl StyledTextareaBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build(self, id: FieldId) -> StyledTextarea {
|
pub fn build(self) -> StyledTextarea {
|
||||||
StyledTextarea {
|
StyledTextarea {
|
||||||
id,
|
id: self.id,
|
||||||
value: self.value,
|
value: self.value,
|
||||||
height: self.height.unwrap_or(110),
|
height: self.height.unwrap_or(110),
|
||||||
class_list: self.class_list,
|
class_list: self.class_list,
|
||||||
@ -145,34 +151,51 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
|||||||
|
|
||||||
if disable_auto_resize {
|
if disable_auto_resize {
|
||||||
style_list.push("resize: none".to_string());
|
style_list.push("resize: none".to_string());
|
||||||
|
style_list.push(format!(
|
||||||
|
"height: {h}px; max-height: {h}px; min-height: {h}px",
|
||||||
|
h = max_height
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut handlers = vec![];
|
|
||||||
|
|
||||||
let handler_disable_auto_resize = disable_auto_resize;
|
let handler_disable_auto_resize = disable_auto_resize;
|
||||||
let resize_handler = ev(Ev::KeyUp, move |event| {
|
let resize_handler = ev(Ev::KeyUp, move |event| {
|
||||||
use wasm_bindgen::JsCast;
|
event.stop_propagation();
|
||||||
|
if handler_disable_auto_resize {
|
||||||
|
return Msg::NoOp;
|
||||||
|
}
|
||||||
|
|
||||||
let target = match event.target() {
|
let target = event.target().unwrap();
|
||||||
Some(el) => el,
|
let textarea = seed::to_textarea(&target);
|
||||||
_ => return Msg::NoOp,
|
let value = textarea.value();
|
||||||
};
|
|
||||||
let text_area = target.dyn_ref::<web_sys::HtmlTextAreaElement>().unwrap();
|
|
||||||
let value: String = text_area.value();
|
|
||||||
let min_height = get_min_height(value.as_str(), height as f64, handler_disable_auto_resize);
|
let min_height = get_min_height(value.as_str(), height as f64, handler_disable_auto_resize);
|
||||||
|
|
||||||
text_area
|
textarea
|
||||||
.style()
|
.style()
|
||||||
.set_css_text(format!("height: {min_height}px", min_height = min_height).as_str());
|
.set_css_text(format!("height: {min_height}px", min_height = min_height).as_str());
|
||||||
Msg::NoOp
|
Msg::NoOp
|
||||||
});
|
});
|
||||||
handlers.push(resize_handler);
|
|
||||||
|
let handler_disable_auto_resize = disable_auto_resize;
|
||||||
let text_input_handler = ev(update_event, move |event| {
|
let text_input_handler = ev(update_event, move |event| {
|
||||||
|
event.stop_propagation();
|
||||||
|
|
||||||
let target = event.target().unwrap();
|
let target = event.target().unwrap();
|
||||||
let text = seed::to_textarea(&target).value();
|
let textarea = seed::to_textarea(&target);
|
||||||
Msg::InputChanged(id, text)
|
let value = textarea.value();
|
||||||
|
|
||||||
|
if handler_disable_auto_resize && value.contains("\n") {
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
Msg::InputChanged(
|
||||||
|
id,
|
||||||
|
if handler_disable_auto_resize {
|
||||||
|
value.trim().to_string()
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
},
|
||||||
|
)
|
||||||
});
|
});
|
||||||
handlers.push(text_input_handler);
|
|
||||||
|
|
||||||
class_list.push("textAreaInput".to_string());
|
class_list.push("textAreaInput".to_string());
|
||||||
|
|
||||||
@ -185,9 +208,11 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
|||||||
At::AutoFocus => "true";
|
At::AutoFocus => "true";
|
||||||
At::Style => style_list.join(";");
|
At::Style => style_list.join(";");
|
||||||
At::Placeholder => placeholder.unwrap_or_default();
|
At::Placeholder => placeholder.unwrap_or_default();
|
||||||
|
At::Rows => if disable_auto_resize { "1" } else { "auto" }
|
||||||
],
|
],
|
||||||
value,
|
value,
|
||||||
handlers,
|
resize_handler,
|
||||||
|
text_input_handler,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user