aboutsummaryrefslogtreecommitdiff
path: root/editor/link_editor.ml
blob: 454dacd86680e58deaca2db7ee66122f1fc9be90 (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


type binded_field =
  { field: El.t
  ; button: El.t
  }


(** Build a button which allow to activate or desactivate the given Element.

    The function f is called when the user validate the input.

*)
let build_field
  : El.t -> (Jstr.t -> bool) -> binded_field
  = fun field f ->

    let button_content =
      [ El.i
          ~at:At.[ class' (Jstr.v "fas")
                 ; class' (Jstr.v "fa-pen") ]
          []
      ] in

    let button = El.button
        button_content
    in

    Ev.listen Ev.click
      (fun _ ->
         match El.at (Jstr.v "contenteditable") field with
         | Some value when (Jstr.equal value (Jstr.v "true")) ->
           let new_value = El.prop
               (El.Prop.jstr (Jstr.v "textContent"))
               field in
           begin match f new_value with
             | true ->
               El.set_at (Jstr.v "contenteditable") None field;
               El.set_children button button_content
             | false -> ()
           end
         | _ ->
           El.set_at (Jstr.v "contenteditable")
             (Some (Jstr.v "true")) field;
           El.set_children button
             [ El.i
                 ~at:At.[ class' (Jstr.v "fas")
                        ; class' (Jstr.v "fa-check") ]
                 []
             ]
      )
      (El.as_target button);

    { field
    ; button = button
    }


let link_edit
  : PM.View.editor_view Js.t -> < .. > Js.t
  = fun view ->

    let popin = El.div []
        ~at:At.([class' (Jstr.v "popin")]) in

    El.set_inline_style El.Style.display (Jstr.v "none") popin;

    let parent = Jv.(Id.of_jv @@ get (Jv.Id.to_jv view##.dom) "parentNode") in
    let () = El.append_children parent [popin] in

    let hide
      : unit -> unit
      = fun () ->
        El.set_inline_style El.Style.display (Jstr.v "none") popin
    in

    let update
      : PM.View.editor_view Js.t -> PM.State.editor_state Js. t Js.opt -> unit
      = fun view _state_opt ->

        (* Compare the previous and actual state. If the stored marks are the
           same, just return *)
        let state = view##.state in

        (* Get the cursor position *)

        let root = state##.doc in
        Js.Opt.case (root##nodeAt (view##.state##.selection##._to))
          (fun () -> hide ())
          (fun node ->
             (* Check if we are editing a link *)
             match PM.O.get state##.schema##.marks "link" with
             | None -> ()
             | Some link_type ->
               let is_present = link_type##isInSet node##.marks in
               Js.Opt.case
                 is_present
                 (fun () -> hide ())
                 (fun mark ->
                    (* We are on a link we can edit the popsin *)

                    (* Get the node's bounding position *)
                    let position = root##resolve (view##.state##.selection##._to) in
                    let start = position##start Js.null
                    and end' = position##_end Js.null in

                    Tooltip.set_position
                      ~start
                      ~end'
                      view popin;

                    (* Extract the value from the attribute *)
                    let attrs = mark##.attrs in
                    let href_opt = PM.O.get attrs "href" in
                    let href' = Option.value href_opt ~default:Jstr.empty in

                    let a = El.a
                        ~at:At.[ href href' ]
                        [ El.txt href' ] in

                    let entry = build_field a
                        (fun new_value ->
                           PM.O.set attrs "href" new_value;

                           let mark' = state##.schema##mark_fromType link_type (Js.some attrs) in
                           (* Create a transaction which update the
                              mark with the new value *)
                           view##dispatch
                             state
                             ##.tr
                             ##(removeMark
                                  ~from:start
                                  ~to_:end'
                                  mark)
                             ##(addMark
                                  ~from:start
                                  ~to_:end'
                                  mark');
                           true

                        ) in


                    El.set_children popin
                      [ entry.field
                      ; entry.button ];

                 ))

    and destroy () = El.remove popin in

    object%js
      val update = Js.wrap_callback update
      val destroy= Js.wrap_callback destroy
    end

let plugin
  : PM.t -> PM.State.plugin Js.t
  = fun t ->
    let state = Jv.get (Jv.Id.to_jv t) "state" in

    let params = object%js
      val view = (fun view -> link_edit view)
    end in

    Jv.new' (Jv.get state "Plugin") [| Jv.Id.to_jv params |]
    |> Jv.Id.of_jv