package quill

  1. Overview
  2. Docs

Source file model_dom.ml

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
open Quill_markdown
open Brr_ext

let log fmt =
  Printf.ksprintf
    (fun s -> Brr.Console.(log [ Jstr.v ("[model_dom] " ^ s) ]))
    fmt

let parse_dom (root : El.t) : Quill_markdown.block list =
  match El.find_first_by_selector (Jstr.v "#editor") ~root with
  | None ->
      log "Could not find editor element";
      []
  | Some editor_div ->
      Quill_markdown.reset_ids ();
      let content = Jstr.to_string (El.text_content editor_div) in
      log "Parsing content: %s" content;
      Quill_markdown.document_of_md (String.trim content)

let rec text_length_inline inline : int =
  match inline.inline_content with
  | Run s -> String.length s
  | Code_span s -> 2 + String.length s
  | Emph inner -> 2 + text_length_inline inner
  | Strong inner -> 4 + text_length_inline inner
  | Seq items ->
      List.fold_left (fun acc i -> acc + text_length_inline i) 0 items
  | Break _ -> 1
  | Image { alt; src } -> 2 + text_length_inline alt + 3 + String.length src + 1
  | Link { text; href } ->
      1 + text_length_inline text + 3 + String.length href + 1
  | Raw_html html -> String.length html

and text_length_block block : int =
  match block.content with
  | Paragraph inline -> text_length_inline inline + 1 (* inline + "\n" *)
  | Heading (level, inline) ->
      level + 1 + text_length_inline inline + 1 (* "# " + inline + "\n" *)
  | Codeblock { code; _ } ->
      4 + String.length code + 4 + 1 (* "```\n" + code + "\n```" + "\n" *)
  | Blank_line () -> 1 (* "\n" *)
  | Blocks bs -> List.fold_left (fun acc b -> acc + text_length_block b) 0 bs
  | Thematic_break -> 4 (* "---\n" *)
  | Block_quote blocks ->
      List.fold_left (fun acc b -> acc + 2 + text_length_block b) 0 blocks
  | List (_, _, items) ->
      List.fold_left
        (fun acc item ->
          acc
          + List.fold_left (fun acc b -> acc + 2 + text_length_block b) 0 item)
        0 items
  | Html_block html -> String.length html + 1
  | Link_reference_definition _ ->
      0 (* These are typically not visible in rendered output *)

let rec find_in_blocks (blocks : block list) (offset : int) :
    (string * int) option =
  let rec loop blocks cumulative =
    match blocks with
    | [] -> None
    | b :: rest ->
        let len = text_length_block b in
        if offset < cumulative + len then find_in_block b (offset - cumulative)
        else loop rest (cumulative + len)
  in
  loop blocks 0

and find_in_block (block : block) (offset : int) : (string * int) option =
  match block.content with
  | Paragraph inline -> find_in_inline inline offset
  | Heading (level, inline) ->
      let syntax_len = level + 1 in
      (* "# " *)
      if offset < syntax_len then None
      else find_in_inline inline (offset - syntax_len)
  | Codeblock { code; _ } ->
      let syntax_start_len = 4 in
      (* "```\n" *)
      let code_len = String.length code in
      if offset >= syntax_start_len && offset <= syntax_start_len + code_len
      then Some ("block", block.id)
      else None
  | Blank_line () -> if offset = 0 then Some ("block", block.id) else None
  | Blocks bs -> find_in_blocks bs offset
  | Thematic_break -> if offset <= 3 then Some ("block", block.id) else None
  | Block_quote blocks -> find_in_blocks blocks offset
  | List (_, _, items) ->
      let rec find_in_items items offset =
        match items with
        | [] -> None
        | item :: rest -> (
            match find_in_blocks item offset with
            | Some result -> Some result
            | None ->
                let item_len =
                  List.fold_left (fun acc b -> acc + text_length_block b) 0 item
                in
                find_in_items rest (offset - item_len))
      in
      find_in_items items offset
  | Html_block _ -> Some ("block", block.id)
  | Link_reference_definition _ -> None

and find_in_inline (inline : inline) (offset : int) : (string * int) option =
  match inline.inline_content with
  | Run s ->
      if offset <= String.length s then Some ("inline", inline.id) else None
  | Code_span s ->
      let syntax_len = 1 in
      (* "`" *)
      let content_len = String.length s in
      if offset >= syntax_len && offset <= syntax_len + content_len then
        Some ("inline", inline.id)
      else None
  | Emph inner ->
      let syntax_len = 1 in
      (* "*" *)
      let inner_len = text_length_inline inner in
      if offset >= syntax_len && offset <= syntax_len + inner_len then
        find_in_inline inner (offset - syntax_len)
      else None
  | Strong inner ->
      let syntax_len = 2 in
      (* "**" *)
      let inner_len = text_length_inline inner in
      if offset >= syntax_len && offset <= syntax_len + inner_len then
        find_in_inline inner (offset - syntax_len)
      else None
  | Seq items ->
      let rec loop items cumulative =
        match items with
        | [] -> None
        | i :: rest ->
            let len = text_length_inline i in
            if offset <= cumulative + len then
              find_in_inline i (offset - cumulative)
            else loop rest (cumulative + len)
      in
      loop items 0
  | Break _ -> if offset = 0 then Some ("inline", inline.id) else None
  | Image { alt; _ } -> find_in_inline alt offset
  | Link { text; _ } -> find_in_inline text offset
  | Raw_html html ->
      if offset <= String.length html then Some ("inline", inline.id) else None
OCaml

Innovation. Community. Security.