aboutsummaryrefslogtreecommitdiff
path: root/editor/editor.ml
blob: d32288cf13adca1497d95e6f054bcdbc5f054d66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
open Js_of_ocaml
open Brr
module PM = Prosemirror

let change_level
  : PM.t -> PM.Model.resolved_pos Js.t -> int -> (int -> bool) -> PM.State.command
  = fun pm res incr pred state dispatch ->
    let parent = res##.parent in
    let attributes = parent##.attrs in

    let current_level = if Jv.is_none attributes##.level then
        0
      else
        attributes##.level in
    let t, props = match pred current_level with
      | false ->
        (PM.O.get state##.schema##.nodes "heading"
        , (object%js
          val level = current_level + incr
        end :> < > Js.t ))
      | true ->
        ( PM.O.get state##.schema##.nodes "paragraph"
        , object%js end) in
    match t with
    | None -> Js._false
    | Some t ->
      PM.Commands.set_block_type pm t props state dispatch

(** Increase the title level by one when pressing # at the begining of a line *)
let handle_sharp pm state dispatch =

  let res = PM.State.selection_to (state##.selection) in
  match Js.Opt.to_option res##.nodeBefore with
  | Some _ -> Js._false
  | None -> (* Line start *)
    begin match Jstr.to_string res##.parent##._type##.name with
      | "heading"   -> change_level pm res 1 (fun x -> x > 5) state dispatch
      | "paragraph" -> change_level pm res 1 (fun _ -> false) state dispatch
      | _ -> Js._false
    end

let handle_backspace pm state dispatch =

  let res = PM.State.selection_to (state##.selection) in
  match Js.Opt.to_option res##.nodeBefore with
  | Some _ -> Js._false
  | None -> (* Line start *)
    begin match Jstr.to_string res##.parent##._type##.name with
      | "heading" -> change_level pm res (-1) (fun x -> x <= 1) state dispatch
      | _ -> Js._false
    end

let default_plugins pm schema =

  let props = PM.Example.options schema in
  props##.menuBar := Js.some Js._true;
  props##.floatingMenu := Js.some Js._true;
  let setup = PM.Example.example_setup pm props in

  let keymaps =
    PM.Keymap.keymap pm
      [| "Backspace", (handle_backspace pm)
       ; "#", (handle_sharp pm)
      |] in

  (* Add the custom keymaps in the list *)
  let _ = setup##unshift keymaps in

  Js.some setup

let create_new_state pm mySchema content =
  let module PM = Prosemirror in

  let doc = PM.Model.(
      DOMParser.parse
        (DOMParser.from_schema pm mySchema)
        (Jv.Id.of_jv content)) in

  let props = PM.State.creation_prop () in
  props##.doc := Js.some doc;
  props##.plugins := default_plugins pm mySchema;

  PM.State.create
    pm
    props

let storage_key = (Jstr.v "editor")
let prosemirror id content =
  begin match (Jv.is_none id), (Jv.is_none content) with
    | false, false ->

      let module PM = Prosemirror in
      let pm = PM.v () in

      let specs = PM.Model.schema_spec
          (PM.SchemaList.add_list_nodes
             pm
             ((PM.SchemaBasic.schema pm)##.spec##.nodes)
             (Jstr.v "paragraph block*")
             (Some (Jstr.v "block")))
          (Some (PM.SchemaBasic.schema pm)##.spec##.marks)
          None in

      let mySchema = PM.Model.schema pm specs in


      (* Create the initial state *)
      let storage = Brr_io.Storage.local G.window in
      let opt_data = Brr_io.Storage.get_item storage storage_key in
      let state = match opt_data with
        | None -> create_new_state pm mySchema content
        | Some contents ->
          (* Try to load from the storage *)
          begin match Json.decode contents with
            | Error _ -> create_new_state pm mySchema content
            | Ok json ->
              Console.(log [Jstr.v "Loading json"]);

              let history = PM.History.(history pm (history_prop ()) ) in
              let _ = history in

              let obj = PM.State.configuration_prop () in
              obj##.plugins := default_plugins pm mySchema;
              obj##.schema := Js.some mySchema;
              PM.State.fromJSON pm obj json
          end
      in

      let props = PM.View.direct_editor_props () in
      props##.state := state;


      let view = PM.View.editor_view
          pm
          (Jv.Id.of_jv id)
          props in


      view##setProps props;

      (* Attach an event on focus out *)
      let _out_event = Brr_note.Evr.on_el
          (Ev.focusout)
          (fun _ ->
             let contents = view##.state##toJSON () in

             let storage = Brr_io.Storage.local G.window in
             Brr_io.Storage.set_item
               storage
               storage_key
               (Json.encode @@ contents)
             |> Console.log_if_error ~use:()



          )
          (Jv.Id.of_jv id) in
      ()

    | _, _-> Console.(error [str "No element with id '%s' '%s' found"; id ; content])

  end

let () =

  let open Jv in
  let editor = obj
      [| "attach_prosemirror", (repr prosemirror)
      |] in

  set global "editor" editor