source: code/trunk/cmd/txt/html.ml@ 77

Last change on this file since 77 was 73, checked in by Izuru Yakumo, 8 months ago

Because sweet girls are the best, officially rebranding Logarion to Kosuzu

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 9.2 KB
Line 
1type templates_t = { header: string option; footer: string option }
2type t = { templates : templates_t; style : string }
3
4let ext = ".htm"
5let empty_templates = { header = None; footer = None }
6let default_opts = { templates = empty_templates; style = "" }
7
8let init kv =
9 let open Kosuzu in
10 let to_string key kv = match Store.KV.find key kv with
11 | fname -> Some (File_store.to_string fname)
12 | exception Not_found -> None in
13 let header = to_string "HTM-header" kv in
14 let footer = to_string "HTM-footer" kv in
15 let style = match to_string "HTM-style" kv with
16 | Some s -> Printf.sprintf "<style type=\"text/css\">%s</style>\n" s | None -> "" in
17 { templates = { header; footer}; style }
18
19let wrap conv htm text_title body =
20 let site_title = try Kosuzu.Store.KV.find "Title" conv.Conversion.kv with Not_found -> "" in
21 let replace x = let open Str in
22 global_replace (regexp "{{archive-title}}") site_title x
23 |> global_replace (regexp "{{text-title}}") text_title
24 in
25 let feed = try Kosuzu.Store.KV.find "HTM-feed" conv.Conversion.kv
26 with Not_found -> if Sys.file_exists (Filename.concat conv.Conversion.dir "feed.atom")
27 then "feed.atom" else "" in
28 let header = match htm.templates.header with
29 | Some x -> replace x
30 | None -> Printf.(sprintf "<a href='.'>%s</a>%s" site_title
31 (if feed <> "" then sprintf "<a href='%s' id='feed'>feed</a>" feed else ""))
32 in
33 let footer = match htm.templates.footer with None -> "" | Some x -> replace x in
34 Printf.sprintf "<!DOCTYPE HTML>\n<html>\n<head>\n<link rel=\"icon\" href=\"/favicon.ico\">\n<title>%s%s</title>\n%s\n%s\n<meta name=\"generator\" content=\"Kosuzu\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n%s%s%s</body>\n</html>"
35 text_title (if site_title <> "" then (" &bull; " ^ site_title) else "")
36 htm.style
37 (if feed <> "" then Printf.sprintf "<link rel='alternate' href='%s' type='application/atom+xml'>" feed else "")
38 header body footer
39
40let topic_link root topic =
41 let replaced_space = String.map (function ' '->'+' | x->x) in
42 "<a href='index." ^ root ^ ".htm#" ^ replaced_space topic ^ "'>"
43 ^ String.capitalize_ascii topic ^ "</a>"
44
45module HtmlConverter = struct
46 include Converter.Html
47 let uid_uri u a = Printf.sprintf "%s<a href='%s%s'>&lt;%s&gt;</a>" a u ext u
48 let angled_uri u a =
49 if try String.sub u 0 10 = "urn:txtid:" with Invalid_argument _ -> false
50 then angled_uri (String.(sub u 10 (length u - 10)) ^ ext) a else angled_uri u a
51end
52
53let page htm conversion text =
54 let open Kosuzu in
55 let open Text in
56 let module T = Parsers.Plain_text.Make (HtmlConverter) in
57 let sep_append ?(sep=", ") a x = match a,x with "",_ -> x | _, "" -> a | _ -> a ^ sep ^ x in
58 let opt_kv key value = if String.length value > 0
59 then "<dt>" ^ key ^ "<dd>" ^ value else "" in
60 let authors = Person.Set.to_string text.authors in
61 let header =
62 let time x = Printf.sprintf {|<time datetime="%s">%s</time>|}
63(Date.rfc_string x) (Date.pretty_date x) in
64 let topic_links x =
65 let to_linked t a =
66 let ts = Topic_set.of_string t in
67 sep_append a (List.fold_left (fun a t -> sep_append ~sep:" > " a (topic_link (List.hd ts) t)) "" ts) in
68 String_set.fold to_linked x "" in
69 let ref_links x =
70 let link l = HtmlConverter.uid_uri l "" in
71 String_set.fold (fun r a -> sep_append a (link r)) x "" in
72 let references, replies = let open Conversion in
73 let Rel.{ref_set; rep_set; _} = try Rel.Id_map.find text.id conversion.relations with Not_found -> Rel.empty in
74 ref_links ref_set, ref_links rep_set in
75 "<article><header><dl>"
76 ^ opt_kv "Title:" text.title
77 ^ opt_kv "Authors:" authors
78 ^ opt_kv "Date:" (time (Date.listing text.date))
79 ^ opt_kv "Series:" (str_set "series" text)
80 ^ opt_kv "Topics:" (topic_links (set "topics" text))
81 ^ opt_kv "Id:" text.id
82 ^ opt_kv "Refers:" (ref_links (set "references" text))
83 ^ opt_kv "In reply to:" (ref_links (set "in-reply-to" text))
84 ^ opt_kv "Referred by:" references
85 ^ opt_kv "Replies:" replies
86 ^ {|</dl></header><pre style="white-space:pre-wrap">|} in
87 wrap conversion htm text.title ((T.of_string text.body header) ^ "</pre></article>")
88
89let to_dated_links ?(limit) meta_list =
90 let meta_list = match limit with
91 | None -> meta_list
92 | Some limit->
93 let rec reduced acc i = function
94 | [] -> acc
95 | h::t -> if i < limit then reduced (h::acc) (i+1) t else acc in
96 List.rev @@ reduced [] 0 meta_list in
97 List.fold_left
98 (fun a m -> Printf.sprintf "%s <li> %s <a href=\"%s.htm\">%s</a>" a Kosuzu.(Date.(pretty_date (listing m.Text.date)))
99 (Kosuzu.Text.short_id m) m.Kosuzu.Text.title)
100 "" meta_list
101
102let date_index ?(limit) conv htm meta_list =
103 match limit with
104 | Some limit -> wrap conv htm "Index" (to_dated_links ~limit meta_list)
105 | None -> wrap conv htm "Index" (to_dated_links meta_list)
106
107let fold_topic_roots topic_roots =
108 let list_item root t = "<li>" ^ topic_link root t in
109 "<nav><h2>Main topics</h2>"
110 ^ List.fold_left (fun a x -> a ^ list_item x x) "<ul>" (List.rev topic_roots)
111 ^ "</ul></nav>"
112
113let fold_topics topic_map topic_roots metas =
114 let open Kosuzu in
115 let rec unordered_list root topic =
116 List.fold_left (fun a x -> a ^ list_item root x) "<ul>" topic
117 ^ "</ul>"
118 and sub_items root topic = match Topic_set.Map.find_opt topic topic_map with
119 | None -> ""
120 | Some (_, subtopics) -> unordered_list root (String_set.elements subtopics)
121and list_item root t =
122 let item =
123 if List.exists (fun x -> String_set.mem t (String_set.map Topic_set.topic (Text.set "topics" x))) metas
124 then topic_link root t else String.capitalize_ascii t in
125 "<ul><li>" ^ item ^ sub_items root t ^ "</ul>" in
126 "<nav><h2>Topics</h2>"
127 ^ List.fold_left (fun a x -> a ^ list_item x x) "<ul>" (List.rev topic_roots)
128 ^ "</ul></nav>"
129
130let text_item path meta =
131 let open Kosuzu in
132 "<time>" ^ Date.(pretty_date (listing meta.Text.date))
133 ^ {|</time> <a href="|} ^ path ^ Text.short_id meta ^ {|.htm">|} ^ meta.Text.title
134 ^ "</a><br>"
135
136let listing_index topic_map topic_roots path metas =
137 let rec item_group topics =
138 List.fold_left (fun acc topic -> acc ^ sub_groups topic ^ items topic) "" topics
139 and sub_groups topic = match Kosuzu.Topic_set.Map.find_opt topic topic_map with
140 | None -> ""
141 | Some (_, subtopics) -> item_group (Kosuzu.String_set.elements subtopics)
142and items topic =
143 let items =
144 let open Kosuzu in
145 List.fold_left
146 (fun a e ->
147 if String_set.mem topic (String_set.map (Kosuzu.Topic_set.topic) (Text.set "Topics" e))
148 then text_item path e ^ a else a) "" metas in
149 match items with
150 | "" -> ""
151 | x -> {|<h2 id="|} ^ topic ^ {|">|} ^ String.capitalize_ascii topic ^ "</h2>" ^ x in
152 "<nav><h1>Texts</h1>" ^ item_group topic_roots ^ "</nav>"
153
154let topic_main_index conv htm topic_roots metas =
155 wrap conv htm "Topics"
156 (fold_topic_roots topic_roots
157 ^ "<nav><h1>Latest</h1><ul>" ^ to_dated_links ~limit:10 metas
158 ^ {|</ul></nav><hr><a href="index.date.htm">More by date</a>|}
159^ let peers = try Kosuzu.Store.KV.find "Peers" conv.kv with Not_found -> "" in
160(if peers = "" then "" else
161 List.fold_left (fun a s -> Printf.sprintf {|%s<li><a href="%s">%s</a>|} a s s) "<h1>Peers</h1><ul>"
162 (Str.split (Str.regexp ";\n") (Kosuzu.Store.KV.find "Peers" conv.kv))
163 ^ "</ul>"))
164
165let topic_sub_index conv htm topic_map topic_root metas =
166 wrap conv htm topic_root
167 (fold_topics topic_map [topic_root] metas
168 ^ listing_index topic_map [topic_root] "" metas)
169
170let indices htm c =
171 let file name = Kosuzu.File_store.file (Filename.concat c.Conversion.dir name) in
172 let index_name = try Kosuzu.Store.KV.find "HTM-index" c.Conversion.kv with Not_found -> "index.html" in
173 if index_name <> "" then file index_name (topic_main_index c htm c.topic_roots c.texts);
174 file "index.date.htm" (date_index c htm c.texts);
175 List.iter
176 (fun root -> file ("index." ^ root ^ ".htm") (topic_sub_index c htm c.topics root c.texts))
177 c.topic_roots
178
179let converter kv =
180 let htm = init kv in
181 Conversion.{ ext; page = Some (page htm); indices = Some (indices htm) }
Note: See TracBrowser for help on using the repository browser.