From 72e3b16bbd258e63f047392c973ba5e8f0a823c8 Mon Sep 17 00:00:00 2001
From: Sébastien Dailly <sebastien@chimrod.com>
Date: Tue, 1 Jun 2021 13:11:58 +0200
Subject: Added export button in editor

---
 editor/actions/actions.ml        | 133 ----------------------------------
 editor/actions/delete_page.ml    |   7 +-
 editor/actions/dune              |   3 +-
 editor/actions/editor_actions.ml | 150 +++++++++++++++++++++++++++++++++++++++
 editor/actions/export.ml         |  46 ++++++++++++
 editor/dune                      |   6 +-
 editor/editor.ml                 |   6 +-
 editor/forms/delete_page.ml      |  18 -----
 editor/forms/delete_page.mli     |   5 --
 editor/forms/dune                |   4 --
 editor/forms/validation.ml       |  13 ++++
 editor/forms/validation.mli      |   5 ++
 editor/state/storage.ml          |  21 ++++++
 editor/state/storage.mli         |   3 +
 14 files changed, 248 insertions(+), 172 deletions(-)
 delete mode 100755 editor/actions/actions.ml
 create mode 100755 editor/actions/editor_actions.ml
 create mode 100755 editor/actions/export.ml
 delete mode 100755 editor/forms/delete_page.ml
 delete mode 100755 editor/forms/delete_page.mli
 create mode 100755 editor/forms/validation.ml
 create mode 100755 editor/forms/validation.mli

(limited to 'editor')

diff --git a/editor/actions/actions.ml b/editor/actions/actions.ml
deleted file mode 100755
index f35beef..0000000
--- a/editor/actions/actions.ml
+++ /dev/null
@@ -1,133 +0,0 @@
-open StdLabels
-open Js_of_ocaml
-open Brr
-open Brr_note
-
-type button_actions =
-  { delete : State.event Note.event
-  ; redirect : State.event Note.event
-  ; add: State.event Note.event
-  }
-
-let populate_menu
-  : unit -> button_actions option
-  = fun () ->
-    match Blog.Sidebar.get () with
-    | None -> None
-    | Some element ->
-
-      let () = Blog.Sidebar.clean element in
-
-      let delete_button = El.button
-          ~at:At.[ class' (Jstr.v "action-button") ]
-          [ El.i []
-              ~at:At.[ class' (Jstr.v "fa")
-                     ; class' (Jstr.v "fa-2x")
-                     ; class' (Jstr.v "fa-trash") ] ]
-
-      and home_button = El.button
-          ~at:At.[ class' (Jstr.v "action-button") ]
-          [ El.i []
-              ~at:At.[ class' (Jstr.v "fa")
-                     ; class' (Jstr.v "fa-2x")
-                     ; class' (Jstr.v "fa-home") ] ]
-
-      and add_button = El.button
-          ~at:At.[ class' (Jstr.v "action-button") ]
-          [ El.i []
-              ~at:At.[ class' (Jstr.v "fa")
-                     ; class' (Jstr.v "fa-2x")
-                     ; class' (Jstr.v "fa-plus") ] ]
-      in
-
-      (* We are waiting for event inside another event ( form validation inside
-         popup creation.
-
-         Note.E.join is used here in order to get only te popup validation. *)
-      let delete_event =
-        Note.E.join (
-          Evr.on_el
-            Ev.click
-            (fun _ -> Delete_page.create ())
-            delete_button)
-
-      (* Event on popup creation *)
-      and add_event =
-        Note.E.join (
-          Evr.on_el
-            Ev.click
-            (fun _ -> Add_page.create ())
-            add_button) in
-
-
-      let stored_pages = State.Storage.get_ids () in
-      let pages =
-        List.map
-          stored_pages
-          ~f:(fun id ->
-
-              let name_opt = (State.Storage.load (Some id))##.title in
-              let name = Js.Opt.get
-                  name_opt
-                  (fun () -> id) in
-
-              let target = Jstr.v "#" in
-              El.li
-                [ El.a
-                    ~at:[At.href target]
-                    [ El.txt name ] ]) in
-
-      (* Wait for a click on an existing page in order to sent the associated
-         event.
-
-         We compose the resulting event with both :
-         - the home button
-         - the list for all the pages presents in the sidebar *)
-
-      let redirect_handler =
-        (module Load_page.M : State.Event with type t = Load_page.M.t ) in
-
-      let redirect_event = Note.E.select
-          (( Evr.on_el
-               Ev.click
-               (fun _ -> State.E (None, redirect_handler))
-               home_button
-           ) :: (
-             List.map2 stored_pages pages
-               ~f:(fun name el ->
-                   Evr.on_el
-                     Ev.click
-                     (fun _ -> State.E ((Some name), redirect_handler))
-                     el ))) in
-
-      let childs =
-        [ home_button
-        ; add_button
-        ; El.button
-            ~at:At.[class' (Jstr.v "action-button")]
-            [ El.i
-                []
-                ~at:At.[ class' (Jstr.v "fa")
-                       ; class' (Jstr.v "fa-2x")
-                       ; class' (Jstr.v "fa-download") ]
-            ]
-        ; delete_button
-        ; El.button
-            ~at:At.[class' (Jstr.v "action-button")]
-            [ El.i
-                []
-                ~at:At.[ class' (Jstr.v "fa")
-                       ; class' (Jstr.v "fa-2x")
-                       ; class' (Jstr.v "fa-cog") ]
-            ]
-        ; El.hr ()
-        ; El.ul
-            pages
-        ] in
-
-      let () = El.append_children element childs in
-      Some
-        { delete = delete_event
-        ; redirect = redirect_event
-        ; add = add_event
-        }
diff --git a/editor/actions/delete_page.ml b/editor/actions/delete_page.ml
index 9086fc3..fb600bd 100755
--- a/editor/actions/delete_page.ml
+++ b/editor/actions/delete_page.ml
@@ -17,10 +17,13 @@ end
 let create
   : unit -> State.event Note.event
   = fun () ->
-    let title = Jstr.v "Confirmation" in
+    let title = Jstr.v "Confirmation"
+    and message =
+      Jstr.v "La page sera définitivement supprimée"
+    in
     let ev = Elements.Popup.create
         ~title
-        ~form:(Some (Forms.Delete_page.create () ))
+        ~form:(Some (Forms.Validation.create message ))
     in
     Note.E.map
       (fun v -> State.E
diff --git a/editor/actions/dune b/editor/actions/dune
index 5d269c4..4044b52 100755
--- a/editor/actions/dune
+++ b/editor/actions/dune
@@ -1,11 +1,10 @@
 (library
- (name actions)
+ (name editor_actions)
  (libraries 
    brr
    brr.note
    elements
    blog
-   js_lib
    forms
    state
    )
diff --git a/editor/actions/editor_actions.ml b/editor/actions/editor_actions.ml
new file mode 100755
index 0000000..8c47363
--- /dev/null
+++ b/editor/actions/editor_actions.ml
@@ -0,0 +1,150 @@
+open StdLabels
+open Js_of_ocaml
+open Brr
+open Brr_note
+
+let populate_menu
+  : unit -> State.event Note.event option
+  = fun () ->
+    match Blog.Sidebar.get () with
+    | None -> None
+    | Some element ->
+
+      let () = Blog.Sidebar.clean element in
+
+      let delete_button = El.button
+          ~at:At.[ class' (Jstr.v "action-button") ]
+          [ El.i []
+              ~at:At.[ class' (Jstr.v "fa")
+                     ; class' (Jstr.v "fa-2x")
+                     ; class' (Jstr.v "fa-trash") ] ]
+
+      and home_button = El.button
+          ~at:At.[ class' (Jstr.v "action-button") ]
+          [ El.i []
+              ~at:At.[ class' (Jstr.v "fa")
+                     ; class' (Jstr.v "fa-2x")
+                     ; class' (Jstr.v "fa-home") ] ]
+
+      and add_button = El.button
+          ~at:At.[ class' (Jstr.v "action-button") ]
+          [ El.i []
+              ~at:At.[ class' (Jstr.v "fa")
+                     ; class' (Jstr.v "fa-2x")
+                     ; class' (Jstr.v "fa-plus") ] ]
+
+      and export_button = El.button
+          ~at:At.[class' (Jstr.v "action-button")]
+          [ El.i
+              []
+              ~at:At.[ class' (Jstr.v "fa")
+                     ; class' (Jstr.v "fa-2x")
+                     ; class' (Jstr.v "fa-download") ]
+          ]
+
+      and load_button = El.button
+          ~at:At.[class' (Jstr.v "action-button")]
+          [ El.i
+              []
+              ~at:At.[ class' (Jstr.v "fa")
+                     ; class' (Jstr.v "fa-2x")
+                     ; class' (Jstr.v "fa-upload") ]
+          ]
+      in
+
+      (* We are waiting for event inside another event ( form validation inside
+         popup creation.
+
+         Note.E.join is used here in order to get only te popup validation. *)
+      let delete_event =
+        Note.E.join (
+          Evr.on_el
+            Ev.click
+            (fun _ -> Delete_page.create ())
+            delete_button)
+
+      (* Event on popup creation *)
+      and add_event =
+        Note.E.join (
+          Evr.on_el
+            Ev.click
+            (fun _ -> Add_page.create ())
+            add_button)
+
+      and export_event =
+        Note.E.join (
+          Evr.on_el
+            Ev.click
+            (fun _ -> Export.create ())
+            export_button)
+      in
+
+
+      let stored_pages = State.Storage.get_ids () in
+      let pages =
+        List.map
+          stored_pages
+          ~f:(fun id ->
+
+              let name_opt = (State.Storage.load (Some id))##.title in
+              let name = Js.Opt.get
+                  name_opt
+                  (fun () -> id) in
+
+              let target = Jstr.v "#" in
+              El.li
+                [ El.a
+                    ~at:[At.href target]
+                    [ El.txt name ] ]) in
+
+      (* Wait for a click on an existing page in order to sent the associated
+         event.
+
+         We compose the resulting event with both :
+         - the home button
+         - the list for all the pages presents in the sidebar *)
+
+      let redirect_handler =
+        (module Load_page.M : State.Event with type t = Load_page.M.t ) in
+
+      let redirect_event = Note.E.select
+          (( Evr.on_el
+               Ev.click
+               (fun _ -> State.E (None, redirect_handler))
+               home_button
+           ) :: (
+             List.map2 stored_pages pages
+               ~f:(fun name el ->
+                   Evr.on_el
+                     Ev.click
+                     (fun _ -> State.E ((Some name), redirect_handler))
+                     el ))) in
+
+      let childs =
+        [ home_button
+        ; add_button
+        ; export_button
+        ; load_button
+        ; delete_button
+        ; El.button
+            ~at:At.[class' (Jstr.v "action-button")]
+            [ El.i
+                []
+                ~at:At.[ class' (Jstr.v "fa")
+                       ; class' (Jstr.v "fa-2x")
+                       ; class' (Jstr.v "fa-cog") ]
+            ]
+        ; El.hr ()
+        ; El.ul
+            pages
+        ] in
+
+      let () = El.append_children element childs in
+
+      Some (
+        Note.E.select
+          [ delete_event
+          ; redirect_event
+          ; add_event
+          ; export_event
+          ])
diff --git a/editor/actions/export.ml b/editor/actions/export.ml
new file mode 100755
index 0000000..a97eeac
--- /dev/null
+++ b/editor/actions/export.ml
@@ -0,0 +1,46 @@
+module Js = Js_of_ocaml.Js
+
+module M = struct
+
+  type t = unit
+
+  let update
+    : t -> State.t -> State.t
+    = fun _ state ->
+
+      (* Save this as a json element. The text may contains UTF-16 characters,
+         which will raise an error in the btoa function.
+
+         As an easy solution, we convert them into UTF-8 through the native
+         OCaml representation of string.
+      *)
+      let json = State.Storage.to_json ()
+                 |> Jstr.to_string (* Encode into UTF-8 *)
+                 |> Obj.magic      (* Then type the element again as a string. *)
+      in
+      Elements.Transfert.send
+        ~mime_type:(Jstr.v "application/json")
+        ~filename:(Jstr.v "export.json")
+        json;
+
+      (* The function does not actually update the state, and return it
+         unchanged *)
+      state
+
+end
+
+(** Create a new element *)
+let create
+  : unit -> State.event Note.event
+  = fun () ->
+    let title = Jstr.v "Confirmation"
+    and message = Jstr.v "Exporter les notes" in
+    let ev = Elements.Popup.create
+        ~title
+        ~form:(Some (Forms.Validation.create message ))
+    in
+    Note.E.map
+      (fun v -> State.E
+          ( v
+          , (module M : State.Event with type t = M.t )))
+      (Note.E.Option.on_some ev)
diff --git a/editor/dune b/editor/dune
index 8f2e3d1..6b13228 100755
--- a/editor/dune
+++ b/editor/dune
@@ -3,15 +3,11 @@
  (libraries 
    brr
    brr.note
-   elements
-   js_lib
    prosemirror
    blog
-   application
    state
    plugins
-   forms
-   actions
+   editor_actions
    )
  (modes js)
  (preprocess (pps js_of_ocaml-ppx))
diff --git a/editor/editor.ml b/editor/editor.ml
index 79ad54a..c80b426 100755
--- a/editor/editor.ml
+++ b/editor/editor.ml
@@ -2,6 +2,8 @@ open Brr
 module PM = Prosemirror
 module Js = Js_of_ocaml.Js
 
+module Actions = Editor_actions
+
 (** Create a new editor view
 
     [build_view element state] will create the editor and attach it to [element].
@@ -131,9 +133,7 @@ let app id content =
                  (State.E
                     ( ()
                     , (module Store:State.Event with type t = Store.t)))) editor
-           ; Note.E.map (fun ev -> ev) btn_events.Actions.delete
-           ; Note.E.map (fun ev -> ev) btn_events.Actions.add
-           ; Note.E.map (fun v  -> v)  btn_events.Actions.redirect
+           ; btn_events
            ]) in
 
     let () =
diff --git a/editor/forms/delete_page.ml b/editor/forms/delete_page.ml
deleted file mode 100755
index 37b1c32..0000000
--- a/editor/forms/delete_page.ml
+++ /dev/null
@@ -1,18 +0,0 @@
-open Brr
-open Note
-
-type t = unit
-
-let create
-  : unit -> t Note.signal * El.t
-  = fun () ->
-    let state = S.const () in
-
-    let message = begin
-      let open Jstr in
-
-      (v "La page sera définitivement supprimée")
-    end in
-
-    ( state
-    , El.txt message )
diff --git a/editor/forms/delete_page.mli b/editor/forms/delete_page.mli
deleted file mode 100755
index 0a3d9f9..0000000
--- a/editor/forms/delete_page.mli
+++ /dev/null
@@ -1,5 +0,0 @@
-type t = unit
-
-val create
-  : unit -> t Note.signal * Brr.El.t
-
diff --git a/editor/forms/dune b/editor/forms/dune
index 124ce01..5aaf249 100755
--- a/editor/forms/dune
+++ b/editor/forms/dune
@@ -3,10 +3,6 @@
  (libraries 
    brr
    brr.note
-   elements
-   js_lib
-   blog
-   application
    state
    )
  (preprocess (pps js_of_ocaml-ppx))
diff --git a/editor/forms/validation.ml b/editor/forms/validation.ml
new file mode 100755
index 0000000..cda7db8
--- /dev/null
+++ b/editor/forms/validation.ml
@@ -0,0 +1,13 @@
+open Brr
+open Note
+
+type t = unit
+
+let create
+  : Jstr.t -> t Note.signal * El.t
+  = fun message  ->
+    let state = S.const () in
+
+
+    ( state
+    , El.txt message )
diff --git a/editor/forms/validation.mli b/editor/forms/validation.mli
new file mode 100755
index 0000000..680b3ed
--- /dev/null
+++ b/editor/forms/validation.mli
@@ -0,0 +1,5 @@
+type t = unit
+
+val create
+  : Jstr.t -> t Note.signal * Brr.El.t
+
diff --git a/editor/state/storage.ml b/editor/state/storage.ml
index b0c00de..a790a9d 100755
--- a/editor/state/storage.ml
+++ b/editor/state/storage.ml
@@ -1,4 +1,5 @@
 open Brr
+open StdLabels
 module Js = Js_of_ocaml.Js
 
 let storage_key = (Jstr.v "editor")
@@ -136,3 +137,23 @@ let get_ids
 
     in
     add_element [] items
+
+let save_for_id
+  : Jstr.t option -> 'a Js.t
+  = fun id ->
+    let element = load id in
+    object%js
+      val title = element##.title
+      val content = element##.content
+      val date = element##.date
+      val id = id
+    end
+
+let to_json
+  : unit -> Jstr.t
+  = fun () ->
+    let keys = get_ids () in
+    let pages = List.map ~f:(fun id -> save_for_id (Some id)) keys in
+    (* Also add the home page *)
+    let pages = Array.of_list @@ (save_for_id None)::pages in
+    Brr.Json.encode (Jv.Id.to_jv pages)
diff --git a/editor/state/storage.mli b/editor/state/storage.mli
index 5b7e0a0..50e164e 100755
--- a/editor/state/storage.mli
+++ b/editor/state/storage.mli
@@ -34,3 +34,6 @@ val delete
 (** Collect all the keys to the existing pages *)
 val get_ids
   : unit -> Jstr.t list
+
+val to_json
+  : unit -> Jstr.t
-- 
cgit v1.2.3