open Batteries open Tyxml_js open Tyxml_js.Html5 open Lwt let langs = ["Toki-pona"; "English"] let dict_name = "toki.txt" let parse_dict s = String.nsplit ~by:"\n" s |> List.filter (fun l -> try Scanf.sscanf l " #" false with End_of_file | Scanf.Scan_failure _ -> true) |> List.map (String.nsplit ~by:"::" %> List.map String.strip) |> List.filter (fun l -> not (List.for_all String.is_empty l)) (** Reactive values *) (* Dictionnary *) let dict, set_dict = let initial_dict = match Dict_init.read dict_name with | None -> [] | Some contents -> parse_dict contents in React.S.create initial_dict (* Search pattern *) let search, set_search = React.S.create "" (* List of results *) let results : Html_types.div_content_fun Tyxml_js.Html5.elt list list list React.signal = React.S.l2 (fun dict search -> let len_search = String.length search in let search = String.lowercase search in let len = String.length search in let res_compare x y = let h (i, len) = if len = len_search then 0 else i+1 in compare (h x) (h y) in List.filter_map (fun l -> let matching = ref false in let l' = List.map (fun s -> String.Exceptionless.find (String.lowercase s) search |> Option.map_default (fun i -> matching := true; let s' = [pcdata (String.slice ~first:0 ~last:i s); b [pcdata (String.slice ~first:i ~last:(i + len) s)]; pcdata (String.slice ~first:(i + len) s)] in (s', Some (i, String.length s)) ) ([pcdata s], None) ) l in if !matching then Some ( List.split l' |> Tuple2.map2 (List.filter_map identity %> List.stable_sort res_compare %> List.hd) ) else None ) dict |> List.stable_sort (fun (_, x) (_, y) -> res_compare x y) |> List.map fst ) dict search (** Webpage html elements *) let input_box = input ~a:[a_input_type `Text; a_placeholder ""; ] () let search_box = div ~a:[a_class ["search"]] [ div ~a:[a_class ["container"]] [ form [input_box] ] ] let results_html = let open ReactiveData in let results' = React.S.map (fun res -> (div ~a:[a_class ["head"]] (List.map (fun s -> h2 [pcdata s]) langs)) :: (List.map (fun l -> div ~a:[a_class ["trad"]] ( List.mapi (fun i s -> let lang = try List.nth langs i with _ -> "" in div ~a:[a_class ["trad" ^ (string_of_int i)]] ((div ~a:[a_class ["trad_info"]] [pcdata lang]) :: s) ) l ) ) res) ) results in div ~a:[a_class ["container"]] [ R.Html5.div ~a:[a_class ["results"]] ( RList.from_signal results' ) ] let () = let main = Dom_html.getElementById "main" in let input_node = To_dom.of_input input_box in Lwt.async (fun _ -> Lwt_js_events.domContentLoaded () >>= fun _ -> main##appendChild (To_dom.of_node search_box) |> ignore; main##appendChild (To_dom.of_node results_html) |> ignore; return () ); Lwt.async (fun _ -> Lwt_js_events.limited_loop ~elapsed_time:0.2 Lwt_js_events.input input_node (fun _ _ -> let text = Js.to_string ((To_dom.of_input input_box)##.value) in set_search text; return () ) )