Editor improvements (#486)

- Make it possible to insert new paragraphs in the article body
- Make it impossible to copy formatted HTML (to make media insertion from markdown code work correctly)

TODO:

- [x] make it possible to escape draft mode
- [x] display errors from the server
- [x] button to go back to the "normal" editor
- [x] Avoid publishing placeholders
This commit is contained in:
Baptiste Gelez 2019-04-06 14:20:12 +01:00 committed by GitHub
parent 38701c8a40
commit 1f7ff62c19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 208 additions and 34 deletions

View File

@ -84,10 +84,46 @@ fn init_widget(
widget.focus(); widget.focus();
widget.blur(); widget.blur();
filter_paste(&widget);
Ok(widget) Ok(widget)
} }
fn filter_paste(elt: &HtmlElement) {
// Only insert text when pasting something
js! {
@{&elt}.addEventListener("paste", function (evt) {
evt.preventDefault();
document.execCommand("insertText", false, evt.clipboardData.getData("text"));
});
};
}
pub fn init() -> Result<(), EditorError> { pub fn init() -> Result<(), EditorError> {
// Check if the user wants to use the basic editor
if let Some(basic_editor) = window().local_storage().get("basic-editor") {
if basic_editor == "true" {
if let Some(editor) = document().get_element_by_id("plume-fallback-editor") {
if let Ok(Some(title_label)) = document().query_selector("label[for=title]") {
let editor_button = document().create_element("a")?;
js!{ @{&editor_button}.href = "#"; }
editor_button.add_event_listener(|_: ClickEvent| {
window().local_storage().remove("basic-editor");
window().history().go(0).ok(); // refresh
});
editor_button.append_child(&document().create_text_node(&i18n!(CATALOG, "Open the rich text editor")));
editor.insert_before(&editor_button, &title_label).ok();
return Ok(());
}
}
}
}
// If we didn't returned above
init_editor()
}
fn init_editor() -> Result<(), EditorError> {
if let Some(ed) = document().get_element_by_id("plume-editor") { if let Some(ed) = document().get_element_by_id("plume-editor") {
// Show the editor // Show the editor
js! { @{&ed}.style.display = "block"; }; js! { @{&ed}.style.display = "block"; };
@ -117,7 +153,7 @@ pub fn init() -> Result<(), EditorError> {
"article", "article",
i18n!(CATALOG, "Write your article here. Markdown is supported."), i18n!(CATALOG, "Write your article here. Markdown is supported."),
content_val.clone(), content_val.clone(),
true, false,
)?; )?;
js! { @{&content}.innerHTML = @{content_val}; }; js! { @{&content}.innerHTML = @{content_val}; };
@ -134,23 +170,45 @@ pub fn init() -> Result<(), EditorError> {
}), 0); }), 0);
})); }));
document().get_element_by_id("publish")?.add_event_listener( document().get_element_by_id("publish")?.add_event_listener(mv!(title, subtitle, content, old_ed => move |_: ClickEvent| {
mv!(title, subtitle, content, old_ed => move |_: ClickEvent| { let popup = document().get_element_by_id("publish-popup").or_else(||
let popup = document().get_element_by_id("publish-popup").or_else(|| init_popup(&title, &subtitle, &content, &old_ed).ok()
init_popup(&title, &subtitle, &content, &old_ed).ok() ).unwrap();
).unwrap(); let bg = document().get_element_by_id("popup-bg").or_else(||
let bg = document().get_element_by_id("popup-bg").or_else(|| init_popup_bg().ok()
init_popup_bg().ok() ).unwrap();
).unwrap();
popup.class_list().add("show").unwrap(); popup.class_list().add("show").unwrap();
bg.class_list().add("show").unwrap(); bg.class_list().add("show").unwrap();
}), }));
);
show_errors();
setup_close_button();
} }
Ok(()) Ok(())
} }
fn setup_close_button() {
if let Some(button) = document().get_element_by_id("close-editor") {
button.add_event_listener(|_: ClickEvent| {
window().local_storage().insert("basic-editor", "true").unwrap();
window().history().go(0).unwrap(); // Refresh the page
});
}
}
fn show_errors() {
if let Ok(Some(header)) = document().query_selector("header") {
let list = document().create_element("header").unwrap();
list.class_list().add("messages").unwrap();
for error in document().query_selector_all("p.error").unwrap() {
error.parent_element().unwrap().remove_child(&error).unwrap();
list.append_child(&error);
}
header.parent_element().unwrap().insert_before(&list, &header.next_sibling().unwrap()).unwrap();
}
}
fn init_popup( fn init_popup(
title: &HtmlElement, title: &HtmlElement,
subtitle: &HtmlElement, subtitle: &HtmlElement,
@ -178,6 +236,23 @@ fn init_popup(
popup.append_child(&cover_label); popup.append_child(&cover_label);
popup.append_child(&cover); popup.append_child(&cover);
if let Some(draft_checkbox) = document().get_element_by_id("draft") {
let draft_label = document().create_element("label")?;
draft_label.set_attribute("for", "popup-draft")?;
let draft = document().create_element("input").unwrap();
js!{
@{&draft}.id = "popup-draft";
@{&draft}.name = "popup-draft";
@{&draft}.type = "checkbox";
@{&draft}.checked = @{&draft_checkbox}.checked;
};
draft_label.append_child(&draft);
draft_label.append_child(&document().create_text_node(&i18n!(CATALOG, "This is a draft")));
popup.append_child(&draft_label);
}
let button = document().create_element("input")?; let button = document().create_element("input")?;
js! { js! {
@{&button}.type = "submit"; @{&button}.type = "submit";
@ -185,10 +260,31 @@ fn init_popup(
}; };
button.append_child(&document().create_text_node(&i18n!(CATALOG, "Publish"))); button.append_child(&document().create_text_node(&i18n!(CATALOG, "Publish")));
button.add_event_listener(mv!(title, subtitle, content, old_ed => move |_: ClickEvent| { button.add_event_listener(mv!(title, subtitle, content, old_ed => move |_: ClickEvent| {
title.focus(); // Remove the placeholder before publishing
set_value("title", title.inner_text()); set_value("title", title.inner_text());
subtitle.focus();
set_value("subtitle", subtitle.inner_text()); set_value("subtitle", subtitle.inner_text());
set_value("editor-content", js!{ return @{&content}.innerHTML }.as_str().unwrap_or_default()); content.focus();
set_value("editor-content", content.child_nodes().iter().fold(String::new(), |md, ch| {
let to_append = match ch.node_type() {
NodeType::Element => {
if js!{ return @{&ch}.tagName; } == "DIV" {
(js!{ return @{&ch}.innerHTML; }).try_into().unwrap_or_default()
} else {
(js!{ return @{&ch}.outerHTML; }).try_into().unwrap_or_default()
}
},
NodeType::Text => ch.node_value().unwrap_or_default(),
_ => unreachable!(),
};
format!("{}\n\n{}", md, to_append)
}));
set_value("tags", get_elt_value("popup-tags")); set_value("tags", get_elt_value("popup-tags"));
if let Some(draft) = document().get_element_by_id("popup-draft") {
js!{
document.getElementById("draft").checked = @{draft}.checked;
};
}
let cover = document().get_element_by_id("cover").unwrap(); let cover = document().get_element_by_id("cover").unwrap();
cover.parent_element().unwrap().remove_child(&cover).ok(); cover.parent_element().unwrap().remove_child(&cover).ok();
old_ed.append_child(&cover); old_ed.append_child(&cover);

View File

@ -12,6 +12,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
# plume-front/src/editor.rs:110
msgid "Open the rich text editor"
msgstr ""
# plume-front/src/editor.rs:57 # plume-front/src/editor.rs:57
msgid "Title" msgid "Title"
msgstr "Title" msgstr "Title"
@ -40,6 +44,10 @@ msgstr ""
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:167
msgid "This is a draft"
msgstr ""
# plume-front/src/editor.rs:111 # plume-front/src/editor.rs:111
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""

View File

@ -12,6 +12,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
# plume-front/src/editor.rs:110
msgid "Open the rich text editor"
msgstr ""
# plume-front/src/editor.rs:57 # plume-front/src/editor.rs:57
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
@ -40,6 +44,10 @@ msgstr ""
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:167
msgid "This is a draft"
msgstr ""
# plume-front/src/editor.rs:111 # plume-front/src/editor.rs:111
msgid "Publish" msgid "Publish"
msgstr "Publier" msgstr "Publier"

View File

@ -12,34 +12,42 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
# plume-front/src/editor.rs:103 # plume-front/src/editor.rs:114
msgid "Title" msgid "Open the rich text editor"
msgstr ""
# plume-front/src/editor.rs:104
msgid "Subtitle or summary"
msgstr ""
# plume-front/src/editor.rs:105
msgid "Write your article here. Markdown is supported."
msgstr ""
# plume-front/src/editor.rs:113
msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:143 # plume-front/src/editor.rs:143
msgid "Tags" msgid "Title"
msgstr ""
# plume-front/src/editor.rs:144
msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:147 # plume-front/src/editor.rs:147
msgid "Subtitle or summary"
msgstr ""
# plume-front/src/editor.rs:154
msgid "Write your article here. Markdown is supported."
msgstr ""
# plume-front/src/editor.rs:165
msgid "Around {} characters left"
msgstr ""
# plume-front/src/editor.rs:228
msgid "Tags"
msgstr ""
# plume-front/src/editor.rs:229
msgid "License"
msgstr ""
# plume-front/src/editor.rs:232
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:157 # plume-front/src/editor.rs:252
msgid "This is a draft"
msgstr ""
# plume-front/src/editor.rs:259
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""

View File

@ -568,6 +568,9 @@ msgstr "اسم المستخدم أو عنوان البريد الالكترون
msgid "Publish" msgid "Publish"
msgstr "انشر" msgstr "انشر"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "العنوان الثانوي" msgstr "العنوان الثانوي"

View File

@ -571,6 +571,9 @@ msgstr "Nutzername oder E-Mail"
msgid "Publish" msgid "Publish"
msgstr "Veröffentlichen" msgstr "Veröffentlichen"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "Untertitel" msgstr "Untertitel"

View File

@ -542,6 +542,9 @@ msgstr ""
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
msgid "Classic editor (any changes will be lost)"
msgstr ""
# src/template_utils.rs:144 # src/template_utils.rs:144
msgid "Subtitle" msgid "Subtitle"
msgstr "" msgstr ""

View File

@ -539,6 +539,9 @@ msgstr "Nombre de usuario o correo electrónico"
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "" msgstr ""

View File

@ -567,6 +567,9 @@ msgstr "Nom dutilisateur ou adresse électronique"
msgid "Publish" msgid "Publish"
msgstr "Publier" msgstr "Publier"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "Sous-titre" msgstr "Sous-titre"

View File

@ -567,6 +567,9 @@ msgstr "Usuaria ou correo-e"
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "Subtítulo" msgstr "Subtítulo"

View File

@ -570,6 +570,9 @@ msgstr "Nome utente o email"
msgid "Publish" msgid "Publish"
msgstr "Pubblica" msgstr "Pubblica"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "Sottotitolo" msgstr "Sottotitolo"

View File

@ -574,6 +574,9 @@ msgstr "ユーザー名またはメールアドレス"
msgid "Publish" msgid "Publish"
msgstr "公開" msgstr "公開"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "サブタイトル" msgstr "サブタイトル"

View File

@ -594,6 +594,9 @@ msgstr "Brukernavn eller epost"
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
msgid "Classic editor (any changes will be lost)"
msgstr ""
#, fuzzy #, fuzzy
msgid "Subtitle" msgid "Subtitle"
msgstr "Tittel" msgstr "Tittel"

View File

@ -549,6 +549,9 @@ msgstr "Nazwa użytkownika lub adres e-mail"
msgid "Publish" msgid "Publish"
msgstr "Opublikuj" msgstr "Opublikuj"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "Podtytuł" msgstr "Podtytuł"

View File

@ -532,6 +532,9 @@ msgstr ""
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
msgid "Classic editor (any changes will be lost)"
msgstr ""
# src/template_utils.rs:217 # src/template_utils.rs:217
msgid "Subtitle" msgid "Subtitle"
msgstr "" msgstr ""

View File

@ -568,6 +568,9 @@ msgstr "Nome de usuário ou e-mail"
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "Subtítulo" msgstr "Subtítulo"

View File

@ -574,6 +574,9 @@ msgstr "Имя пользователя или адрес электронной
msgid "Publish" msgid "Publish"
msgstr "Опубликовать" msgstr "Опубликовать"
msgid "Classic editor (any changes will be lost)"
msgstr ""
msgid "Subtitle" msgid "Subtitle"
msgstr "Подзаголовок" msgstr "Подзаголовок"

View File

@ -273,6 +273,7 @@ main .article-meta {
flex-direction: row-reverse; flex-direction: row-reverse;
background: transparent; background: transparent;
align-items: center; align-items: center;
justify-content: space-between;
button { button {
flex: 0 0 10em; flex: 0 0 10em;
font-size: 1.25em; font-size: 1.25em;

View File

@ -74,6 +74,19 @@ header {
} }
} }
.messages {
& > * {
padding: 1em 20%;
}
p.error {
color: $red;
background: lighten($red, 40%);
margin: 0;
max-width: initial;
}
}
// Only enable label animations on normal screens // Only enable label animations on normal screens
@media screen and (min-width: 900px) { @media screen and (min-width: 900px) {
header nav a { header nav a {

View File

@ -17,6 +17,7 @@
<header> <header>
<button id="publish" class="button">@i18n!(ctx.1, "Publish")</button> <button id="publish" class="button">@i18n!(ctx.1, "Publish")</button>
<p id="char-count">@content_len</p> <p id="char-count">@content_len</p>
<a href="#" id="close-editor">@i18n!(ctx.1, "Classic editor (any changes will be lost)")</a>
</header> </header>
</div> </div>
@if let Some(article) = article { @if let Some(article) = article {