contrib/nitiwiki: extract link handling in its own module
[nit.git] / contrib / nitiwiki / src / wiki_html.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # HTML wiki rendering
16 module wiki_html
17
18 import wiki_links
19
20 redef class Nitiwiki
21
22 # Render HTML output looking for changes in the markdown sources.
23 redef fun render do
24 super
25 if not root_section.is_dirty and not force_render then return
26 var out_dir = expand_path(config.root_dir, config.out_dir)
27 out_dir.mkdir
28 copy_assets
29 root_section.add_child make_sitemap
30 root_section.render
31 end
32
33 # Copy the asset directory to the (public) output directory.
34 private fun copy_assets do
35 var src = expand_path(config.root_dir, config.assets_dir)
36 var out = expand_path(config.root_dir, config.out_dir)
37 if need_render(src, expand_path(out, config.assets_dir)) then
38 if src.file_exists then sys.system "cp -R {src} {out}"
39 end
40 end
41
42 # Build the wiki sitemap page.
43 private fun make_sitemap: WikiSitemap do
44 var sitemap = new WikiSitemap(self, "sitemap")
45 sitemap.is_dirty = true
46 return sitemap
47 end
48 end
49
50 redef class WikiEntry
51 # Get a `<a>` template link to `self`
52 fun tpl_link: Writable do
53 return "<a href=\"{url}\">{title}</a>"
54 end
55 end
56
57 redef class WikiSection
58
59 # Output directory (where to ouput the HTML pages for this section).
60 redef fun out_path: String do
61 if parent == null then
62 return wiki.config.out_dir
63 else
64 return wiki.expand_path(parent.out_path, name)
65 end
66 end
67
68 redef fun render do
69 if not is_dirty and not wiki.force_render then return
70 if is_new then
71 out_full_path.mkdir
72 else
73 sys.system "touch {out_full_path}"
74 end
75 if has_source then
76 wiki.message("Render section {out_path}", 1)
77 copy_files
78 end
79 var index = self.index
80 if index isa WikiSectionIndex then
81 wiki.message("Render auto-index for section {out_path}", 1)
82 index.is_dirty = true
83 add_child index
84 end
85 super
86 end
87
88 # Copy attached files from `src_path` to `out_path`.
89 private fun copy_files do
90 assert has_source
91 var dir = src_full_path.to_s
92 for name in dir.files do
93 if name == wiki.config_filename then continue
94 if name.has_suffix(".md") then continue
95 if has_child(name) then continue
96 var src = wiki.expand_path(dir, name)
97 var out = wiki.expand_path(out_full_path, name)
98 if not wiki.need_render(src, out) then continue
99 sys.system "cp -R {src} {out_full_path}"
100 end
101 end
102
103 redef fun tpl_link do return index.tpl_link
104
105 # Render the section hierarchy as a html tree.
106 #
107 # `limit` is used to specify the max-depth of the tree.
108 #
109 # The generated tree will be something like this:
110 #
111 # ~~~html
112 # <ul>
113 # <li>section 1</li>
114 # <li>section 2
115 # <ul>
116 # <li>section 2.1</li>
117 # <li>section 2.2</li>
118 # </ul>
119 # </li>
120 # </ul>
121 # ~~~
122 fun tpl_tree(limit: Int): Template do
123 return tpl_tree_intern(limit, 1)
124 end
125
126 # Build the template tree for this section recursively.
127 protected fun tpl_tree_intern(limit, count: Int): Template do
128 var out = new Template
129 var index = index
130 out.add "<li>"
131 out.add tpl_link
132 if (limit < 0 or count < limit) and
133 (children.length > 1 or (children.length == 1)) then
134 out.add " <ul>"
135 for child in children.values do
136 if child == index then continue
137 if child isa WikiArticle then
138 out.add "<li>"
139 out.add child.tpl_link
140 out.add "</li>"
141 else if child isa WikiSection and not child.is_hidden then
142 out.add child.tpl_tree_intern(limit, count + 1)
143 end
144 end
145 out.add " </ul>"
146 end
147 out.add "</li>"
148 return out
149 end
150 end
151
152 redef class WikiArticle
153
154 redef fun out_path: String do
155 if parent == null then
156 return wiki.expand_path(wiki.config.out_dir, "{name}.html")
157 else
158 return wiki.expand_path(parent.out_path, "{name}.html")
159 end
160 end
161
162 redef fun render do
163 super
164 if not is_dirty and not wiki.force_render then return
165 wiki.message("Render article {name}", 2)
166 var file = out_full_path
167 file.dirname.mkdir
168 tpl_page.write_to_file file
169 end
170
171
172 # Replace macros in the template by wiki data.
173 private fun tpl_page: TemplateString do
174 var tpl = wiki.load_template(template_file)
175 if tpl.has_macro("TOP_MENU") then
176 tpl.replace("TOP_MENU", tpl_menu)
177 end
178 if tpl.has_macro("HEADER") then
179 tpl.replace("HEADER", tpl_header)
180 end
181 if tpl.has_macro("BODY") then
182 tpl.replace("BODY", tpl_article)
183 end
184 if tpl.has_macro("FOOTER") then
185 tpl.replace("FOOTER", tpl_footer)
186 end
187 return tpl
188 end
189
190 # Generate the HTML header for this article.
191 fun tpl_header: Writable do
192 var file = header_file
193 if not wiki.has_template(file) then return ""
194 return wiki.load_template(file)
195 end
196
197 # Generate the HTML page for this article.
198 fun tpl_article: TplArticle do
199 var article = new TplArticle
200 article.body = content
201 article.breadcrumbs = new TplBreadcrumbs(self)
202 tpl_sidebar.blocks.add tpl_summary
203 article.sidebar = tpl_sidebar
204 return article
205 end
206
207 # Sidebar for this page.
208 var tpl_sidebar = new TplSidebar
209
210 # Generate the HTML summary for this article.
211 #
212 # Based on `headlines`
213 fun tpl_summary: Writable do
214 var headlines = self.headlines
215 var tpl = new Template
216 tpl.add "<ul class=\"summary list-unstyled\">"
217 var iter = headlines.iterator
218 while iter.is_ok do
219 var hl = iter.item
220 # parse title as markdown
221 var title = hl.title.md_to_html.to_s
222 title = title.substring(3, title.length - 8)
223 tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
224 iter.next
225 if iter.is_ok then
226 if iter.item.level > hl.level then
227 tpl.add "<ul class=\"list-unstyled\">"
228 else if iter.item.level < hl.level then
229 tpl.add "</li>"
230 tpl.add "</ul>"
231 end
232 else
233 tpl.add "</li>"
234 end
235 end
236 tpl.add "</ul>"
237 return tpl
238 end
239
240 # Generate the HTML menu for this article.
241 fun tpl_menu: Writable do
242 var file = menu_file
243 if not wiki.has_template(file) then return ""
244 var tpl = wiki.load_template(file)
245 if tpl.has_macro("MENUS") then
246 var items = new Template
247 for child in wiki.root_section.children.values do
248 if child isa WikiArticle and child.is_index then continue
249 if child isa WikiSection and child.is_hidden then continue
250 items.add "<li"
251 if self == child or self.breadcrumbs.has(child) then
252 items.add " class=\"active\""
253 end
254 items.add ">"
255 items.add child.tpl_link
256 items.add "</li>"
257 end
258 tpl.replace("MENUS", items)
259 end
260 return tpl
261 end
262
263 # Generate the HTML footer for this article.
264 fun tpl_footer: Writable do
265 var file = footer_file
266 if not wiki.has_template(file) then return ""
267 var tpl = wiki.load_template(file)
268 var time = new Tm.gmtime
269 if tpl.has_macro("YEAR") then
270 tpl.replace("YEAR", (time.year + 1900).to_s)
271 end
272 if tpl.has_macro("GEN_TIME") then
273 tpl.replace("GEN_TIME", time.to_s)
274 end
275 return tpl
276 end
277 end
278
279 # A `WikiArticle` that contains the sitemap tree.
280 class WikiSitemap
281 super WikiArticle
282
283 redef fun tpl_article do
284 var article = new TplArticle.with_title("Sitemap")
285 article.body = new TplPageTree(wiki.root_section, -1)
286 return article
287 end
288
289 redef var is_dirty = false
290 end
291
292 # A `WikiArticle` that contains the section index tree.
293 redef class WikiSectionIndex
294
295 redef var is_dirty = false
296
297 redef fun tpl_article do
298 var article = new TplArticle.with_title(section.title)
299 article.body = new TplPageTree(section, -1)
300 article.breadcrumbs = new TplBreadcrumbs(self)
301 return article
302 end
303 end
304
305 # Article HTML output.
306 class TplArticle
307 super Template
308
309 # Article title.
310 var title: nullable Writable = null
311
312 # Article HTML body.
313 var body: nullable Writable = null
314
315 # Sidebar of this article (if any).
316 var sidebar: nullable TplSidebar = null
317
318 # Breadcrumbs from wiki root to this article.
319 var breadcrumbs: nullable TplBreadcrumbs = null
320
321 # Init `self` with a `title`.
322 init with_title(title: Writable) do
323 self.title = title
324 end
325
326 redef fun rendering do
327 if sidebar != null then
328 add "<div class=\"col-sm-3 sidebar\">"
329 add sidebar.as(not null)
330 add "</div>"
331 add "<div class=\"col-sm-9 content\">"
332 else
333 add "<div class=\"col-sm-12 content\">"
334 end
335 if body != null then
336 add "<article>"
337 if breadcrumbs != null then
338 add breadcrumbs.as(not null)
339 end
340 if title != null then
341 add "<h1>"
342 add title.as(not null)
343 add "</h1>"
344 end
345 add body.as(not null)
346 add " </article>"
347 end
348 add "</div>"
349 end
350 end
351
352 # A collection of HTML blocks displayed on the side of a page.
353 class TplSidebar
354 super Template
355
356 # Blocks are `Stremable` pieces that will be rendered in the sidebar.
357 var blocks = new Array[Writable]
358
359 redef fun rendering do
360 for block in blocks do
361 add "<div class=\"sideblock\">"
362 add block
363 add "</div>"
364 end
365 end
366 end
367
368 # An HTML breadcrumbs that show the path from a `WikiArticle` to the `Nitiwiki` root.
369 class TplBreadcrumbs
370 super Template
371
372 # Bread crumb article.
373 var article: WikiArticle
374
375 redef fun rendering do
376 var path = article.breadcrumbs
377 if path.is_empty or path.length <= 2 and article.is_index then return
378 add "<ol class=\"breadcrumb\">"
379 for entry in path do
380 if entry == path.last then
381 add "<li class=\"active\">"
382 add entry.title
383 add "</li>"
384 else
385 if article.parent == entry and article.is_index then continue
386 add "<li>"
387 add entry.tpl_link
388 add "</li>"
389 end
390 end
391 add "</ol>"
392 end
393 end
394
395 # An HTML tree that show the section pages structure.
396 class TplPageTree
397 super Template
398
399 # Builds the page tree from `root`.
400 var root: WikiSection
401
402 # Limits the tree depth to `max_depth` levels.
403 var max_depth: Int
404
405 redef fun rendering do
406 add "<ul>"
407 add root.tpl_tree(-1)
408 add "</ul>"
409 end
410 end