@ -1,32 +1,212 @@
use pulldown_cmark ::{ Event , Options , Parser , Tag } ;
use stdweb ::{
unstable ::{ TryFrom , TryInto } ,
web ::{ event ::* , html_element ::* , * } ,
} ;
use CATALOG ;
macro_rules! mv {
( $( $var :ident ) , * = > $exp :expr ) = > {
{
$( let $var = $var . clone ( ) ; ) *
$exp
fn from_md ( md : & str ) {
let md_parser = Parser ::new_ext ( md , Options ::all ( ) ) ;
md_parser . fold (
document ( ) . get_element_by_id ( "editor-main" ) . unwrap ( ) ,
| last_elt , event | {
match event {
Event ::Start ( tag ) = > {
let new = match tag {
Tag ::Paragraph = > document ( ) . create_element ( "p" ) . unwrap ( ) ,
Tag ::Rule = > document ( ) . create_element ( "hr" ) . unwrap ( ) ,
Tag ::Header ( level ) = > {
document ( ) . create_element ( & format! ( "h{}" , level ) ) . unwrap ( )
}
Tag ::BlockQuote = > document ( ) . create_element ( "blockquote" ) . unwrap ( ) ,
Tag ::CodeBlock ( code ) = > {
let pre = document ( ) . create_element ( "pre" ) . unwrap ( ) ;
let code_elt = document ( ) . create_element ( "code" ) . unwrap ( ) ;
code_elt . append_child ( & document ( ) . create_text_node ( & code ) ) ;
pre . append_child ( & code_elt ) ;
pre
}
Tag ::List ( None ) = > document ( ) . create_element ( "ul" ) . unwrap ( ) ,
Tag ::List ( Some ( _start_index ) ) = > document ( ) . create_element ( "ol" ) . unwrap ( ) , // TODO: handle start_index
Tag ::Item = > document ( ) . create_element ( "li" ) . unwrap ( ) ,
Tag ::FootnoteDefinition ( def ) = > {
let note = document ( ) . create_element ( "div" ) . unwrap ( ) ;
note . class_list ( ) . add ( "footnote" ) ;
note . append_child ( & document ( ) . create_text_node ( & def ) ) ;
note
}
Tag ::HtmlBlock = > document ( ) . create_element ( "div" ) . unwrap ( ) ,
Tag ::Table ( _alignements ) = > document ( ) . create_element ( "table" ) . unwrap ( ) , // TODO: handle alignements
Tag ::TableHead = > document ( ) . create_element ( "th" ) . unwrap ( ) ,
Tag ::TableRow = > document ( ) . create_element ( "tr" ) . unwrap ( ) ,
Tag ::TableCell = > document ( ) . create_element ( "td" ) . unwrap ( ) ,
Tag ::Emphasis = > document ( ) . create_element ( "em" ) . unwrap ( ) ,
Tag ::Strong = > document ( ) . create_element ( "strong" ) . unwrap ( ) ,
Tag ::Strikethrough = > document ( ) . create_element ( "s" ) . unwrap ( ) ,
Tag ::Link ( _link_type , url , text ) = > {
let url : & str = & url ;
let text : & str = & text ;
let link = document ( ) . create_element ( "a" ) . unwrap ( ) ;
js ! {
@ { & link } . href = @ { url } ;
@ { & link } . title = @ { text } ;
} ;
link
}
Tag ::Image ( _link_type , url , text ) = > {
let url : & str = & url ;
let text : & str = & text ;
let img = document ( ) . create_element ( "img" ) . unwrap ( ) ;
js ! {
@ { & img } . src = @ { url } ;
@ { & img } . title = @ { text } ;
@ { & img } . alt = @ { text } ;
} ;
img
}
} ;
last_elt . append_child ( & new ) ;
new
}
Event ::End ( _ ) = > last_elt . parent_element ( ) . unwrap ( ) ,
Event ::Text ( text ) = > {
let node = document ( ) . create_text_node ( & text ) ;
last_elt . append_child ( & node ) ;
last_elt
}
Event ::Code ( code ) = > {
let elt = document ( ) . create_element ( "code" ) . unwrap ( ) ;
let content = document ( ) . create_text_node ( & code ) ;
elt . append_child ( & content ) ;
last_elt . append_child ( & elt ) ;
last_elt
}
Event ::Html ( html ) = > {
// TODO: sanitize it?
last_elt . set_attribute ( "innerHtml" , & html ) ;
last_elt
}
Event ::InlineHtml ( html ) = > {
let elt = document ( ) . create_element ( "span" ) . unwrap ( ) ;
elt . set_attribute ( "innerHtml" , & html ) ;
last_elt . append_child ( & elt ) ;
last_elt
}
Event ::FootnoteReference ( reference ) = > {
last_elt // TODO
}
Event ::SoftBreak = > {
last_elt . append_child ( & document ( ) . create_element ( "br" ) . unwrap ( ) ) ;
last_elt
}
Event ::HardBreak = > {
last_elt // TODO
}
Event ::TaskListMarker ( done ) = > {
last_elt // TODO
}
}
} ,
) ;
MutationObserver ::new ( | muts , _obs | {
for m in muts {
console ! ( log , "mut!!" ) ;
}
}
} )
. observe (
& document ( ) . get_element_by_id ( "editor-main" ) . unwrap ( ) ,
MutationObserverInit {
child_list : true ,
attributes : true ,
character_data : false ,
subtree : true ,
attribute_old_value : true ,
character_data_old_value : false ,
attribute_filter : None ,
} ,
) ;
}
fn get_elt_value ( id : & ' static str ) -> String {
let elt = document ( ) . get_element_by_id ( id ) . unwrap ( ) ;
let inp : Result < InputElement , _ > = elt . clone ( ) . try_into ( ) ;
let textarea : Result < TextAreaElement , _ > = elt . try_into ( ) ;
inp . map ( | i | i . raw_value ( ) )
. unwrap_or_else ( | _ | textarea . unwrap ( ) . value ( ) )
fn to_md ( ) -> String {
let root = document ( ) . get_element_by_id ( "editor-main" ) . unwrap ( ) ;
fold_children ( & root ) . join ( "" )
}
fn fold_children ( elt : & Element ) -> Vec < String > {
elt . child_nodes ( ) . iter ( ) . fold ( vec! [ ] , | mut blocks , node | {
blocks . push ( html_to_md ( & node ) ) ;
blocks
} )
}
fn html_to_md ( node : & Node ) -> String {
console ! ( log , node ) ;
if let Ok ( elt ) = Element ::try_from ( node . clone ( ) ) {
console ! ( log , elt . node_name ( ) . to_lowercase ( ) ) ;
match elt . node_name ( ) . to_lowercase ( ) . as_ref ( ) {
"hr" = > "---" . into ( ) ,
"h1" = > format! ( "# {}\n\n" , fold_children ( & elt ) . join ( "" ) ) ,
"h2" = > format! ( "## {}\n\n" , fold_children ( & elt ) . join ( "" ) ) ,
"h3" = > format! ( "### {}\n\n" , fold_children ( & elt ) . join ( "" ) ) ,
"h4" = > format! ( "#### {}\n\n" , fold_children ( & elt ) . join ( "" ) ) ,
"h5" = > format! ( "##### {}\n\n" , fold_children ( & elt ) . join ( "" ) ) ,
"h6" = > format! ( "###### {}\n\n" , fold_children ( & elt ) . join ( "" ) ) ,
"blockquote" = > format! ( "> {}\n\n" , fold_children ( & elt ) . join ( "> " ) ) ,
"pre" = > format! ( "```\n{}\n```\n\n" , node . text_content ( ) . unwrap_or_default ( ) ) ,
"li" = > match elt
. parent_element ( )
. unwrap ( )
. node_name ( )
. to_lowercase ( )
. as_ref ( )
{
"ol" = > format! (
"{}. {}\n" ,
elt . parent_element ( )
. unwrap ( )
. child_nodes ( )
. iter ( )
. position ( | n | Element ::try_from ( n ) . unwrap ( ) = = elt )
. unwrap_or_default ( ) ,
fold_children ( & elt ) . join ( "" ) ,
) ,
_ = > format! ( "- {}\n" , fold_children ( & elt ) . join ( "" ) ) ,
} ,
"em" = > format! ( "_{}_" , fold_children ( & elt ) . join ( "" ) ) ,
"strong" = > format! ( "**{}**" , fold_children ( & elt ) . join ( "" ) ) ,
"s" = > format! ( "~~{}~~" , fold_children ( & elt ) . join ( "" ) ) ,
"a" = > format! (
"[{}]({})" ,
fold_children ( & elt ) . join ( "" ) ,
String ::try_from ( js ! { return @ { & elt } . href } ) . unwrap ( )
) ,
"img" = > format! (
"![{}]({})" ,
String ::try_from ( js ! { return @ { & elt } . alt } ) . unwrap ( ) ,
String ::try_from ( js ! { return @ { & elt } . src } ) . unwrap ( )
) ,
other = > {
console ! ( log , "Warning: unhandled element:" , other ) ;
String ::new ( )
} // TODO: refs, tables, raw html
}
} else {
node . text_content ( ) . unwrap_or_default ( )
}
}
fn set_value < S : AsRef < str > > ( id : & ' static str , val : S ) {
fn get_elt_value( id : & ' static str ) -> String {
let elt = document ( ) . get_element_by_id ( id ) . unwrap ( ) ;
let inp : Result < InputElement , _ > = elt . clone ( ) . try_into ( ) ;
let select : Result < SelectElement , _ > = elt . clone ( ) . try_into ( ) ;
let textarea : Result < TextAreaElement , _ > = elt . try_into ( ) ;
inp . map ( | i | i . set_raw_value ( val . as_ref ( ) ) )
. unwrap_or_else ( | _ | textarea . unwrap ( ) . set_value ( val . as_ref ( ) ) )
let res = inp . map ( | i | i . raw_value ( ) ) . unwrap_or_else ( | _ | {
textarea
. map ( | t | t . value ( ) )
. unwrap_or_else ( | _ | select . unwrap ( ) . value ( ) . unwrap_or_default ( ) )
} ) ;
res
}
fn no_return ( evt : KeyDownEvent ) {
@ -63,33 +243,7 @@ impl From<stdweb::private::ConversionError> for EditorError {
}
}
fn init_widget (
parent : & Element ,
tag : & ' static str ,
placeholder_text : String ,
content : String ,
disable_return : bool ,
) -> Result < HtmlElement , EditorError > {
let widget = placeholder ( make_editable ( tag ) . try_into ( ) ? , & placeholder_text ) ;
if ! content . is_empty ( ) {
widget . dataset ( ) . insert ( "edited" , "true" ) ? ;
}
widget . append_child ( & document ( ) . create_text_node ( & content ) ) ;
if disable_return {
widget . add_event_listener ( no_return ) ;
}
parent . append_child ( & widget ) ;
// We need to do that to make sure the placeholder is correctly rendered
widget . focus ( ) ;
widget . blur ( ) ;
filter_paste ( & widget ) ;
Ok ( widget )
}
fn filter_paste ( elt : & HtmlElement ) {
fn filter_paste ( elt : & Element ) {
// Only insert text when pasting something
js ! {
@ { & elt } . addEventListener ( "paste" , function ( evt ) {
@ -127,64 +281,69 @@ pub fn init() -> Result<(), EditorError> {
fn init_editor ( ) -> Result < ( ) , EditorError > {
if let Some ( ed ) = document ( ) . get_element_by_id ( "plume-editor" ) {
document ( ) . body ( ) ? . set_attribute ( "id" , "editor" ) ? ;
let aside = document ( ) . get_element_by_id ( "plume-editor-aside" ) ? ;
// Show the editor
js ! { @ { & ed } . style . display = "block" ; } ;
js ! {
@ { & ed } . style . display = "grid" ;
@ { & aside } . style . display = "block" ;
} ;
// And hide the HTML-only fallback
let old_ed = document ( ) . get_element_by_id ( "plume-fallback-editor" ) ? ;
let old_title = document ( ) . get_element_by_id ( "plume-editor-title" ) ? ;
js ! {
@ { & old_ed } . style . display = "none" ;
@ { & old_title } . style . display = "none" ;
} ;
// Get content from the old editor (when editing an article for instance)
let title_val = get_elt_value ( "title" ) ;
let subtitle_val = get_elt_value ( "subtitle" ) ;
let content_val = get_elt_value ( "editor-content" ) ;
// And pre-fill the new editor with this values
let title = init_widget ( & ed , "h1" , i18n ! ( CATALOG , "Title" ) , title_val , true ) ? ;
let subtitle = init_widget (
& ed ,
"h2" ,
i18n ! ( CATALOG , "Subtitle, or summary" ) ,
subtitle_val ,
true ,
) ? ;
let content = init_widget (
& ed ,
"article" ,
i18n ! ( CATALOG , "Write your article here. Markdown is supported." ) ,
content_val . clone ( ) ,
false ,
) ? ;
js ! { @ { & content } . innerHTML = @ { content_val } ; } ;
// character counter
content . add_event_listener ( mv ! ( content = > move | _ : KeyDownEvent | {
window ( ) . set_timeout ( mv ! ( content = > move | | {
if let Some ( e ) = document ( ) . get_element_by_id ( "char-count" ) {
let count = chars_left ( "#plume-fallback-editor" , & content ) . unwrap_or_default ( ) ;
let text = i18n ! ( CATALOG , "Around {} characters left" ; count ) ;
HtmlElement ::try_from ( e ) . map ( | e | {
js ! { @ { e } . innerText = @ { text } } ;
} ) . ok ( ) ;
let title = document ( ) . get_element_by_id ( "editor-title" ) ? ;
let subtitle = document ( ) . get_element_by_id ( "editor-subtitle" ) ? ;
let source = get_elt_value ( "editor-content" ) ;
setup_toolbar ( ) ;
from_md ( & source ) ;
title . add_event_listener ( no_return ) ;
subtitle . add_event_listener ( no_return ) ;
filter_paste ( & title ) ;
filter_paste ( & subtitle ) ;
// TODO: filter_paste(&content);
document ( )
. get_element_by_id ( "publish" ) ?
. add_event_listener ( | _ : ClickEvent | {
let publish_page = document ( ) . get_element_by_id ( "publish-page" ) . unwrap ( ) ;
let options_page = document ( ) . get_element_by_id ( "options-page" ) . unwrap ( ) ;
js ! {
@ { & options_page } . style . display = "none" ;
@ { & publish_page } . style . display = "flex" ;
} ;
} ) ;
document ( )
. get_element_by_id ( "cancel-publish" ) ?
. add_event_listener ( | _ : ClickEvent | {
let publish_page = document ( ) . get_element_by_id ( "publish-page" ) . unwrap ( ) ;
let options_page = document ( ) . get_element_by_id ( "options-page" ) . unwrap ( ) ;
js ! {
@ { & publish_page } . style . display = "none" ;
@ { & options_page } . style . display = "flex" ;
} ;
} ) , 0 ) ;
} ) ) ;
document ( ) . get_element_by_id ( "publish" ) ? . add_event_listener (
mv ! ( title , subtitle , content , old_ed = > move | _ : ClickEvent | {
let popup = document ( ) . get_element_by_id ( "publish-popup" ) . or_else ( | |
init_popup ( & title , & subtitle , & content , & old_ed ) . ok ( )
) . unwrap ( ) ;
let bg = document ( ) . get_element_by_id ( "popup-bg" ) . or_else ( | |
init_popup_bg ( ) . ok ( )
) . unwrap ( ) ;
popup . class_list ( ) . add ( "show" ) . unwrap ( ) ;
bg . class_list ( ) . add ( "show" ) . unwrap ( ) ;
} ) ,
) ;
} ) ;
document ( )
. get_element_by_id ( "confirm-publish" ) ?
. add_event_listener ( | _ : ClickEvent | {
save ( false ) ;
} ) ;
document ( )
. get_element_by_id ( "save-draft" ) ?
. add_event_listener ( | _ : ClickEvent | {
save ( true ) ;
} ) ;
show_errors ( ) ;
setup_close_button ( ) ;
@ -192,6 +351,176 @@ fn init_editor() -> Result<(), EditorError> {
Ok ( ( ) )
}
fn select_style ( style : & str ) {
if let Some ( select ) = document ( )
. get_element_by_id ( "toolbar-style" )
. and_then ( | e | SelectElement ::try_from ( e ) . ok ( ) )
{
select . set_value ( Some ( style ) ) ;
}
}
fn setup_toolbar ( ) {
let toolbar = document ( ) . get_element_by_id ( "editor-toolbar" ) . unwrap ( ) ;
// List of styles (headings, quote, code, etc)
let style_select =
SelectElement ::try_from ( document ( ) . create_element ( "select" ) . unwrap ( ) ) . unwrap ( ) ;
let options = vec! [
( "p" , i18n ! ( CATALOG , "Paragraph" ) ) ,
( "ul" , i18n ! ( CATALOG , "List" ) ) ,
( "ol" , i18n ! ( CATALOG , "Ordered list" ) ) ,
( "h1" , i18n ! ( CATALOG , "Heading 1" ) ) ,
( "h2" , i18n ! ( CATALOG , "Heading 2" ) ) ,
( "h3" , i18n ! ( CATALOG , "Heading 3" ) ) ,
( "h4" , i18n ! ( CATALOG , "Heading 4" ) ) ,
( "h5" , i18n ! ( CATALOG , "Heading 5" ) ) ,
( "h6" , i18n ! ( CATALOG , "Heading 6" ) ) ,
( "blockquote" , i18n ! ( CATALOG , "Quote" ) ) ,
( "pre" , i18n ! ( CATALOG , "Code" ) ) ,
] ;
for ( tag , name ) in options . clone ( ) {
let opt = document ( ) . create_element ( "option" ) . unwrap ( ) ;
opt . set_attribute ( "value" , tag ) ;
opt . append_child ( & document ( ) . create_text_node ( & name ) ) ;
style_select . append_child ( & opt )
}
style_select . set_attribute ( "id" , "toolbar-style" ) ;
let options_clone = options . clone ( ) ;
document ( ) . add_event_listener ( move | _ : SelectionChangeEvent | {
let block = std ::iter ::successors (
window ( ) . get_selection ( ) . and_then ( | s | s . anchor_node ( ) ) ,
| node | {
let t = node . node_name ( ) . to_lowercase ( ) ;
if options_clone . iter ( ) . any ( | ( tag , _ ) | * tag = = & t ) {
None
} else {
node . parent_node ( )
}
} ,
)
. last ( ) ;
if let Some ( b ) = block {
select_style ( & b . node_name ( ) . to_lowercase ( ) ) ;
}
} ) ;
style_select . add_event_listener ( move | _ : ChangeEvent | {
let block = std ::iter ::successors (
window ( ) . get_selection ( ) . and_then ( | s | s . anchor_node ( ) ) ,
| node | {
let t = node . node_name ( ) . to_lowercase ( ) ;
if options . iter ( ) . any ( | ( tag , _ ) | * tag = = & t ) {
None
} else {
node . parent_node ( )
}
} ,
)
. last ( ) ;
if let Some ( block ) = block {
if let Some ( select ) = document ( )
. get_element_by_id ( "toolbar-style" )
. and_then ( | e | SelectElement ::try_from ( e ) . ok ( ) )
{
let tag = select . value ( ) ;
let new = document ( ) . create_element ( & tag . unwrap_or_default ( ) ) . unwrap ( ) ;
for ch in block . child_nodes ( ) {
block . remove_child ( & ch ) ;
new . append_child ( & ch ) ;
}
block . parent_node ( ) . unwrap ( ) . replace_child ( & new , & block ) ;
}
}
} ) ;
// Bold
// Italics
// Insert an image
toolbar . append_child ( & style_select ) ;
}
fn save ( is_draft : bool ) {
let req = XmlHttpRequest ::new ( ) ;
if bool ::try_from ( js ! { return window . editing } ) . unwrap_or ( false ) {
req . open (
"PUT" ,
& format! (
"/api/v1/posts/{}" ,
i32 ::try_from ( js ! { return window . post_id } ) . unwrap ( )
) ,
)
. unwrap ( ) ;
} else {
req . open ( "POST" , "/api/v1/posts" ) . unwrap ( ) ;
}
req . set_request_header ( "Accept" , "application/json" )
. unwrap ( ) ;
req . set_request_header ( "Content-Type" , "application/json" )
. unwrap ( ) ;
req . set_request_header (
"Authorization" ,
& format! (
"Bearer {}" ,
String ::try_from ( js ! { return window . api_token } ) . unwrap ( )
) ,
)
. unwrap ( ) ;
let req_clone = req . clone ( ) ;
req . add_event_listener ( move | _ : ProgressLoadEvent | {
if let Ok ( Some ( res ) ) = req_clone . response_text ( ) {
serde_json ::from_str ( & res )
. map ( | res : plume_api ::posts ::PostData | {
let url = res . url ;
js ! {
window . location . href = @ { url } ;
} ;
} )
. map_err ( | _ | {
let json : serde_json ::Value = serde_json ::from_str ( & res ) . unwrap ( ) ;
window ( ) . alert ( & format! (
"Error: {}" ,
json [ "error" ] . as_str ( ) . unwrap_or_default ( )
) ) ;
} )
. ok ( ) ;
}
} ) ;
console ! ( log , to_md ( ) ) ;
let data = plume_api ::posts ::NewPostData {
title : HtmlElement ::try_from ( document ( ) . get_element_by_id ( "editor-title" ) . unwrap ( ) )
. unwrap ( )
. inner_text ( ) ,
subtitle : document ( )
. get_element_by_id ( "editor-subtitle" )
. map ( | s | HtmlElement ::try_from ( s ) . unwrap ( ) . inner_text ( ) ) ,
source : to_md ( ) ,
author : String ::new ( ) , // it is ignored anyway (TODO: remove it ??)
blog_id : i32 ::try_from ( js ! { return window . blog_id } ) . ok ( ) ,
published : Some ( ! is_draft ) ,
creation_date : None ,
license : Some ( get_elt_value ( "license" ) ) ,
tags : Some (
get_elt_value ( "tags" )
. split ( ',' )
. map ( | t | t . trim ( ) . to_string ( ) )
. filter ( | t | ! t . is_empty ( ) )
. collect ( ) ,
) ,
cover_id : get_elt_value ( "cover" ) . parse ( ) . ok ( ) ,
} ;
let json = serde_json ::to_string ( & data ) . unwrap ( ) ;
req . send_with_string ( & json ) . unwrap ( ) ;
}
fn setup_close_button ( ) {
if let Some ( button ) = document ( ) . get_element_by_id ( "close-editor" ) {
button . add_event_listener ( | _ : ClickEvent | {
@ -223,200 +552,3 @@ fn show_errors() {
. unwrap ( ) ;
}
}
fn init_popup (
title : & HtmlElement ,
subtitle : & HtmlElement ,
content : & HtmlElement ,
old_ed : & Element ,
) -> Result < Element , EditorError > {
let popup = document ( ) . create_element ( "div" ) ? ;
popup . class_list ( ) . add ( "popup" ) ? ;
popup . set_attribute ( "id" , "publish-popup" ) ? ;
let tags = get_elt_value ( "tags" )
. split ( ',' )
. map ( str ::trim )
. map ( str ::to_string )
. collect ::< Vec < _ > > ( ) ;
let license = get_elt_value ( "license" ) ;
make_input ( & i18n ! ( CATALOG , "Tags" ) , "popup-tags" , & popup ) . set_raw_value ( & tags . join ( ", " ) ) ;
make_input ( & i18n ! ( CATALOG , "License" ) , "popup-license" , & popup ) . set_raw_value ( & license ) ;
let cover_label = document ( ) . create_element ( "label" ) ? ;
cover_label . append_child ( & document ( ) . create_text_node ( & i18n ! ( CATALOG , "Cover" ) ) ) ;
cover_label . set_attribute ( "for" , "cover" ) ? ;
let cover = document ( ) . get_element_by_id ( "cover" ) ? ;
cover . parent_element ( ) ? . remove_child ( & cover ) . ok ( ) ;
popup . append_child ( & cover_label ) ;
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" ) ? ;
js ! {
@ { & button } . type = "submit" ;
@ { & button } . value = @ { 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 | {
title . focus ( ) ; // Remove the placeholder before publishing
set_value ( "title" , title . inner_text ( ) ) ;
subtitle . focus ( ) ;
set_value ( "subtitle" , subtitle . inner_text ( ) ) ;
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" ) ) ;
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 ( ) ;
cover . parent_element ( ) . unwrap ( ) . remove_child ( & cover ) . ok ( ) ;
old_ed . append_child ( & cover ) ;
set_value ( "license" , get_elt_value ( "popup-license" ) ) ;
js ! {
@ { & old_ed } . submit ( ) ;
} ;
} ) ,
) ;
popup . append_child ( & button ) ;
document ( ) . body ( ) ? . append_child ( & popup ) ;
Ok ( popup )
}
fn init_popup_bg ( ) -> Result < Element , EditorError > {
let bg = document ( ) . create_element ( "div" ) ? ;
bg . class_list ( ) . add ( "popup-bg" ) ? ;
bg . set_attribute ( "id" , "popup-bg" ) ? ;
document ( ) . body ( ) ? . append_child ( & bg ) ;
bg . add_event_listener ( | _ : ClickEvent | close_popup ( ) ) ;
Ok ( bg )
}
fn chars_left ( selector : & str , content : & HtmlElement ) -> Option < i32 > {
match document ( ) . query_selector ( selector ) {
Ok ( Some ( form ) ) = > HtmlElement ::try_from ( form ) . ok ( ) . and_then ( | form | {
if let Some ( len ) = form
. get_attribute ( "content-size" )
. and_then ( | s | s . parse ::< i32 > ( ) . ok ( ) )
{
( js ! {
let x = encodeURIComponent ( @ { content } . innerHTML )
. replace ( / % 20 / g , "+" )
. replace ( / % 0 A / g , "%0D%0A" )
. replace ( new RegExp ( "[!'*()]" , "g" ) , "XXX" ) // replace exceptions of encodeURIComponent with placeholder
. length + 2 ;
console . log ( x ) ;
return x ;
} )
. try_into ( )
. map ( | c : i32 | len - c )
. ok ( )
} else {
None
}
} ) ,
_ = > None ,
}
}
fn close_popup ( ) {
let hide = | x : Element | x . class_list ( ) . remove ( "show" ) ;
document ( ) . get_element_by_id ( "publish-popup" ) . map ( hide ) ;
document ( ) . get_element_by_id ( "popup-bg" ) . map ( hide ) ;
}
fn make_input ( label_text : & str , name : & ' static str , form : & Element ) -> InputElement {
let label = document ( ) . create_element ( "label" ) . unwrap ( ) ;
label . append_child ( & document ( ) . create_text_node ( label_text ) ) ;
label . set_attribute ( "for" , name ) . unwrap ( ) ;
let inp : InputElement = document ( )
. create_element ( "input" )
. unwrap ( )
. try_into ( )
. unwrap ( ) ;
inp . set_attribute ( "name" , name ) . unwrap ( ) ;
inp . set_attribute ( "id" , name ) . unwrap ( ) ;
form . append_child ( & label ) ;
form . append_child ( & inp ) ;
inp
}
fn make_editable ( tag : & ' static str ) -> Element {
let elt = document ( )
. create_element ( tag )
. expect ( "Couldn't create editable element" ) ;
elt . set_attribute ( "contenteditable" , "true" )
. expect ( "Couldn't make the element editable" ) ;
elt
}
fn placeholder ( elt : HtmlElement , text : & str ) -> HtmlElement {
elt . dataset ( ) . insert ( "placeholder" , text ) . unwrap ( ) ;
elt . dataset ( ) . insert ( "edited" , "false" ) . unwrap ( ) ;
elt . add_event_listener ( mv ! ( elt = > move | _ : FocusEvent | {
if elt . dataset ( ) . get ( "edited" ) . unwrap ( ) . as_str ( ) ! = "true" {
clear_children ( & elt ) ;
}
} ) ) ;
elt . add_event_listener ( mv ! ( elt = > move | _ : BlurEvent | {
if elt . dataset ( ) . get ( "edited" ) . unwrap ( ) . as_str ( ) ! = "true" {
clear_children ( & elt ) ;
let ph = document ( ) . create_element ( "span" ) . expect ( "Couldn't create placeholder" ) ;
ph . class_list ( ) . add ( "placeholder" ) . expect ( "Couldn't add class" ) ;
ph . append_child ( & document ( ) . create_text_node ( & elt . dataset ( ) . get ( "placeholder" ) . unwrap_or_default ( ) ) ) ;
elt . append_child ( & ph ) ;
}
} ) ) ;
elt . add_event_listener ( mv ! ( elt = > move | _ : KeyUpEvent | {
elt . dataset ( ) . insert ( "edited" , if elt . inner_text ( ) . trim_matches ( '\n' ) . is_empty ( ) {
"false"
} else {
"true"
} ) . expect ( "Couldn't update edition state" ) ;
} ) ) ;
elt
}
fn clear_children ( elt : & HtmlElement ) {
for child in elt . child_nodes ( ) {
elt . remove_child ( & child ) . unwrap ( ) ;
}
}