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