nitiwiki: macro ROOT_URL is now relative
[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 import markdown::decorators
20
21 redef class Nitiwiki
22
23 # Render HTML output looking for changes in the markdown sources.
24 redef fun render do
25 super
26 if not root_section.is_dirty and not force_render then return
27 var out_dir = expand_path(config.root_dir, config.out_dir)
28 out_dir.mkdir
29 copy_assets
30 root_section.add_child make_sitemap
31 root_section.render
32 end
33
34 # Copy the asset directory to the (public) output directory.
35 private fun copy_assets do
36 var src = expand_path(config.root_dir, config.assets_dir)
37 var out = expand_path(config.root_dir, config.out_dir)
38 if need_render(src, expand_path(out, config.assets_dir)) then
39 if src.file_exists then sys.system "cp -R {src} {out}"
40 end
41 end
42
43 # Build the wiki sitemap page.
44 private fun make_sitemap: WikiSitemap do
45 var sitemap = new WikiSitemap(self, "sitemap")
46 sitemap.is_dirty = true
47 return sitemap
48 end
49
50 # Markdown processor used for inline element such as titles in TOC.
51 private var inline_processor: MarkdownProcessor is lazy do
52 var proc = new MarkdownProcessor
53 proc.emitter.decorator = new InlineDecorator
54 return proc
55 end
56
57 # Inline markdown (remove h1, p, ... elements).
58 private fun inline_md(md: Writable): Writable do
59 return inline_processor.process(md.write_to_string)
60 end
61 end
62
63 redef class WikiEntry
64 # Get a `<a>` template link to `self`
65 fun tpl_link(context: WikiEntry): Writable do
66 return "<a href=\"{href_from(context)}\">{title}</a>"
67 end
68 end
69
70 redef class WikiSection
71
72 # Output directory (where to ouput the HTML pages for this section).
73 redef fun out_path: String do
74 if parent == null then
75 return wiki.config.out_dir
76 else
77 return wiki.expand_path(parent.out_path, name)
78 end
79 end
80
81 redef fun render do
82 if not is_dirty and not wiki.force_render then return
83 if is_new then
84 out_full_path.mkdir
85 else
86 sys.system "touch {out_full_path}"
87 end
88 if has_source then
89 wiki.message("Render section {out_path}", 1)
90 copy_files
91 end
92 var index = self.index
93 if index isa WikiSectionIndex then
94 wiki.message("Render auto-index for section {out_path}", 1)
95 index.is_dirty = true
96 add_child index
97 end
98 super
99 end
100
101 # Copy attached files from `src_path` to `out_path`.
102 private fun copy_files do
103 assert has_source
104 var dir = src_full_path.to_s
105 for name in dir.files do
106 if name == wiki.config_filename then continue
107 if name.has_suffix(".md") then continue
108 if has_child(name) then continue
109 var src = wiki.expand_path(dir, name)
110 var out = wiki.expand_path(out_full_path, name)
111 if not wiki.need_render(src, out) then continue
112 sys.system "cp -R {src} {out_full_path}"
113 end
114 end
115
116 redef fun tpl_link(context) do return index.tpl_link(context)
117
118 # Render the section hierarchy as a html tree.
119 #
120 # `limit` is used to specify the max-depth of the tree.
121 #
122 # The generated tree will be something like this:
123 #
124 # ~~~html
125 # <ul>
126 # <li>section 1</li>
127 # <li>section 2
128 # <ul>
129 # <li>section 2.1</li>
130 # <li>section 2.2</li>
131 # </ul>
132 # </li>
133 # </ul>
134 # ~~~
135 fun tpl_tree(limit: Int): Template do
136 return tpl_tree_intern(limit, 1, self)
137 end
138
139 # Build the template tree for this section recursively.
140 protected fun tpl_tree_intern(limit, count: Int, context: WikiEntry): Template do
141 var out = new Template
142 var index = index
143 out.add "<li>"
144 out.add tpl_link(context)
145 if (limit < 0 or count < limit) and
146 (children.length > 1 or (children.length == 1)) then
147 out.add " <ul>"
148 for child in children.values do
149 if child == index then continue
150 if child isa WikiArticle then
151 out.add "<li>"
152 out.add child.tpl_link(context)
153 out.add "</li>"
154 else if child isa WikiSection and not child.is_hidden then
155 out.add child.tpl_tree_intern(limit, count + 1, context)
156 end
157 end
158 out.add " </ul>"
159 end
160 out.add "</li>"
161 return out
162 end
163 end
164
165 redef class WikiArticle
166
167 redef fun out_path: String do
168 if parent == null then
169 return wiki.expand_path(wiki.config.out_dir, "{name}.html")
170 else
171 return wiki.expand_path(parent.out_path, "{name}.html")
172 end
173 end
174
175 redef fun render do
176 super
177 if not is_dirty and not wiki.force_render then return
178 wiki.message("Render article {name}", 2)
179 var file = out_full_path
180 file.dirname.mkdir
181 tpl_page.write_to_file file
182 end
183
184
185 # Load a template and resolve page-related macros
186 fun load_template(template_file: String): TemplateString do
187 var tpl = wiki.load_template(template_file)
188 if tpl.has_macro("ROOT_URL") then
189 var root_dir = href.dirname.relpath("")
190 # Avoid issues if the macro is just followed by a `/` (as with url prefix)
191 if root_dir == "" then root_dir = "."
192 tpl.replace("ROOT_URL", root_dir)
193 end
194 return tpl
195 end
196
197 # Replace macros in the template by wiki data.
198 private fun tpl_page: TemplateString do
199 var tpl = load_template(template_file)
200 if tpl.has_macro("TOP_MENU") then
201 tpl.replace("TOP_MENU", tpl_menu)
202 end
203 if tpl.has_macro("HEADER") then
204 tpl.replace("HEADER", tpl_header)
205 end
206 if tpl.has_macro("BODY") then
207 tpl.replace("BODY", tpl_article)
208 end
209 if tpl.has_macro("FOOTER") then
210 tpl.replace("FOOTER", tpl_footer)
211 end
212 return tpl
213 end
214
215 # Generate the HTML header for this article.
216 fun tpl_header: Writable do
217 var file = header_file
218 if not wiki.has_template(file) then return ""
219 return load_template(file)
220 end
221
222 # Generate the HTML page for this article.
223 fun tpl_article: TplArticle do
224 var article = new TplArticle
225 article.body = content
226 if wiki.config.auto_breadcrumbs then
227 article.breadcrumbs = new TplBreadcrumbs(self)
228 end
229 article.sidebar = tpl_sidebar
230 article.sidebar_pos = wiki.config.sidebar
231 return article
232 end
233
234 # Sidebar for this page.
235 var tpl_sidebar: TplSidebar is lazy do
236 var res = new TplSidebar
237 if wiki.config.auto_summary then
238 res.blocks.add tpl_summary
239 end
240 res.blocks.add_all sidebar.blocks
241 return res
242 end
243
244 # Generate the HTML summary for this article.
245 #
246 # Based on `headlines`
247 fun tpl_summary: Writable do
248 var headlines = self.headlines
249 var tpl = new Template
250 tpl.add "<ul class=\"summary list-unstyled\">"
251 var iter = headlines.iterator
252 while iter.is_ok do
253 var hl = iter.item
254 # parse title as markdown
255 var title = wiki.inline_md(hl.title)
256 tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
257 iter.next
258 if iter.is_ok then
259 if iter.item.level > hl.level then
260 tpl.add "<ul class=\"list-unstyled\">"
261 else if iter.item.level < hl.level then
262 tpl.add "</li>"
263 tpl.add "</ul>"
264 end
265 else
266 tpl.add "</li>"
267 end
268 end
269 tpl.add "</ul>"
270 return tpl
271 end
272
273 # Generate the HTML menu for this article.
274 fun tpl_menu: Writable do
275 var file = menu_file
276 if not wiki.has_template(file) then return ""
277 var tpl = load_template(file)
278 if tpl.has_macro("MENUS") then
279 var items = new Template
280 for child in wiki.root_section.children.values do
281 if child isa WikiArticle and child.is_index then continue
282 if child isa WikiSection and child.is_hidden then continue
283 items.add "<li"
284 if self == child or self.breadcrumbs.has(child) then
285 items.add " class=\"active\""
286 end
287 items.add ">"
288 items.add child.tpl_link(self)
289 items.add "</li>"
290 end
291 tpl.replace("MENUS", items)
292 end
293 return tpl
294 end
295
296 # Generate the HTML footer for this article.
297 fun tpl_footer: Writable do
298 var file = footer_file
299 if not wiki.has_template(file) then return ""
300 var tpl = load_template(file)
301 var time = new Tm.gmtime
302 if tpl.has_macro("YEAR") then
303 tpl.replace("YEAR", (time.year + 1900).to_s)
304 end
305 if tpl.has_macro("GEN_TIME") then
306 tpl.replace("GEN_TIME", time.to_s)
307 end
308 if tpl.has_macro("LAST_CHANGES") then
309 var url = "{wiki.config.last_changes}{src_path or else ""}"
310 tpl.replace("LAST_CHANGES", url)
311 end
312 if tpl.has_macro("EDIT") then
313 var url = "{wiki.config.edit}{src_path or else ""}"
314 tpl.replace("EDIT", url)
315 end
316 return tpl
317 end
318 end
319
320 # A `WikiArticle` that contains the sitemap tree.
321 class WikiSitemap
322 super WikiArticle
323
324 redef fun tpl_article do
325 var article = new TplArticle.with_title("Sitemap")
326 article.body = new TplPageTree(wiki.root_section, -1)
327 return article
328 end
329
330 redef var is_dirty = false
331 end
332
333 # A `WikiArticle` that contains the section index tree.
334 redef class WikiSectionIndex
335
336 redef var is_dirty = false
337
338 redef fun tpl_article do
339 var article = new TplArticle.with_title(section.title)
340 article.body = new TplPageTree(section, -1)
341 article.breadcrumbs = new TplBreadcrumbs(self)
342 return article
343 end
344 end
345
346 # Article HTML output.
347 class TplArticle
348 super Template
349
350 # Article title.
351 var title: nullable Writable = null
352
353 # Article HTML body.
354 var body: nullable Writable = null
355
356 # Sidebar of this article (if any).
357 var sidebar: nullable TplSidebar = null
358
359 # Position of the sidebar.
360 #
361 # See `WikiConfig::sidebar`.
362 var sidebar_pos: String = "left"
363
364 # Breadcrumbs from wiki root to this article.
365 var breadcrumbs: nullable TplBreadcrumbs = null
366
367 # Init `self` with a `title`.
368 init with_title(title: Writable) do
369 self.title = title
370 end
371
372 redef fun rendering do
373 if sidebar_pos == "left" then render_sidebar
374 if sidebar == null then
375 add "<div class=\"col-sm-12 content\">"
376 else
377 add "<div class=\"col-sm-9 content\">"
378 end
379 if body != null then
380 add "<article>"
381 if breadcrumbs != null then
382 add breadcrumbs.as(not null)
383 end
384 if title != null then
385 add "<h1>"
386 add title.as(not null)
387 add "</h1>"
388 end
389 add body.as(not null)
390 add " </article>"
391 end
392 add "</div>"
393 if sidebar_pos == "right" then render_sidebar
394 end
395
396 private fun render_sidebar do
397 if sidebar == null then return
398 add "<div class=\"col-sm-3 sidebar\">"
399 add sidebar.as(not null)
400 add "</div>"
401 end
402 end
403
404 # A collection of HTML blocks displayed on the side of a page.
405 class TplSidebar
406 super Template
407
408 # Blocks are `Stremable` pieces that will be rendered in the sidebar.
409 var blocks = new Array[Writable]
410
411 redef fun rendering do
412 for block in blocks do
413 add "<nav class=\"sideblock\">"
414 add block
415 add "</nav>"
416 end
417 end
418 end
419
420 # An HTML breadcrumbs that show the path from a `WikiArticle` to the `Nitiwiki` root.
421 class TplBreadcrumbs
422 super Template
423
424 # Bread crumb article.
425 var article: WikiArticle
426
427 redef fun rendering do
428 var path = article.breadcrumbs
429 if path.is_empty or path.length <= 2 and article.is_index then return
430 add "<ol class=\"breadcrumb\">"
431 for entry in path do
432 if entry == path.last then
433 add "<li class=\"active\">"
434 add entry.title
435 add "</li>"
436 else
437 if article.parent == entry and article.is_index then continue
438 add "<li>"
439 add entry.tpl_link(article)
440 add "</li>"
441 end
442 end
443 add "</ol>"
444 end
445 end
446
447 # An HTML tree that show the section pages structure.
448 class TplPageTree
449 super Template
450
451 # Builds the page tree from `root`.
452 var root: WikiSection
453
454 # Limits the tree depth to `max_depth` levels.
455 var max_depth: Int
456
457 redef fun rendering do
458 add "<ul>"
459 add root.tpl_tree(-1)
460 add "</ul>"
461 end
462 end