lib/markdown: merge processor and emitter
[nit.git] / src / doc / html_templates / html_templates.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 # Introduces templates that compose the documentation HTML rendering.
16 module html_templates
17
18 import html_model
19 import html::bootstrap
20 import doc_phases::doc_structure
21 import doc_phases::doc_hierarchies
22 import doc_phases::doc_graphs
23 import doc_phases::doc_intros_redefs
24 import doc_phases::doc_lin
25 import doc_phases::doc_readme
26 intrude import doc_down
27
28 # Renders the page as HTML.
29 redef class DocPage
30 super Template
31
32 # Page url.
33 var html_url: String is writable, noinit
34
35 # Directory where css, js and other assets can be found.
36 var shareurl: String is writable, noinit
37
38 # Attributes of the body tag element.
39 var body_attrs = new Array[TagAttribute]
40
41 # Top menu template if any.
42 var topmenu: DocTopMenu is writable, noinit
43
44 # Sidebar template if any.
45 var sidebar: nullable DocSideBar = null is writable
46
47 # Footer content if any.
48 var footer: nullable Writable = null is writable
49
50 # JS scripts to append at the end of the body
51 var scripts = new Array[TplScript]
52
53 # Renders the html `<head>`.
54 private fun render_head do
55 var css = (self.shareurl / "css").html_escape
56 var vendors = (self.shareurl / "vendors").html_escape
57
58 addn "<!DOCTYPE html>"
59 addn "<head>"
60 addn " <meta charset='utf-8'/>"
61 addn " <!--link rel='stylesheet' href='{css}/Nitdoc.UI.css' type='text/css'/-->"
62 addn " <link rel='stylesheet' href='{vendors}/bootstrap/css/bootstrap.min.css'/>"
63 addn " <link rel='stylesheet' href='{css}/nitdoc.bootstrap.css'/>"
64 addn " <link rel='stylesheet' href='{css}/nitdoc.css'/>"
65 addn " <link rel='stylesheet' href='{css}/Nitdoc.QuickSearch.css'/>"
66 addn " <link rel='stylesheet' href='{css}/Nitdoc.ModalBox.css'/>"
67 addn " <link rel='stylesheet' href='{css}/Nitdoc.GitHub.css'/>"
68 addn " <title>{title.html_escape}</title>"
69 addn "</head>"
70 add "<body"
71 for attr in body_attrs do add attr
72 addn ">"
73 end
74
75 # Renders the footer and content.
76 private fun render_content do
77 add root
78 if footer != null then
79 addn "<div class='well footer'>"
80 add footer.as(not null)
81 addn "</div>"
82 end
83 end
84
85 # Render JS scripts
86 private fun render_footer do
87 var vendors = (self.shareurl / "vendors").html_escape
88 var js = (self.shareurl / "js").html_escape
89
90 addn "<script src='{vendors}/jquery/jquery-1.11.1.min.js'></script>"
91 addn "<script src='{vendors}/jquery/jquery-ui-1.10.4.custom.min.js'></script>"
92 addn "<script src='{vendors}/bootstrap/js/bootstrap.min.js'></script>"
93 addn "<script src='{js}/lib/utils.js'></script>"
94 addn "<script src='{js}/plugins/filtering.js'></script>"
95 addn "<script src='quicksearch-list.js'></script>"
96 addn "<script src='{js}/plugins/quicksearch.js'></script>"
97 for script in scripts do add script
98 addn """<script>
99 $(function () {
100 $("[data-toggle='tooltip']").tooltip();
101 $("[data-toggle='popover']").popover();
102 });
103 </script>"""
104 addn "</body>"
105 addn "</html>"
106 end
107
108 # Render the whole page
109 redef fun rendering do
110 render_head
111 addn "<div class='container-fluid'>"
112 addn " <div class='row'>"
113 add topmenu
114 addn " </div>"
115 addn " <div class='row' id='content'>"
116 var sidebar = self.sidebar
117 if sidebar != null then
118 addn "<div class='col col-xs-3 col-lg-2'>"
119 add sidebar
120 addn "</div>"
121 addn "<div class='col col-xs-9 col-lg-10' data-spy='scroll' data-target='.summary'>"
122 render_content
123 addn "</div>"
124 else
125 addn "<div class='col col-xs-12'>"
126 render_content
127 addn "</div>"
128 end
129 addn " </div>"
130 addn "</div>"
131 render_footer
132 end
133
134 # Render table of content for this page.
135 fun html_toc: UnorderedList do
136 var lst = new UnorderedList
137 lst.css_classes.add "nav"
138 for child in root.children do
139 child.render_toc_item(lst)
140 end
141 return lst
142 end
143 end
144
145 # Top menu bar template.
146 #
147 # FIXME should be a Bootstrap component template
148 # At this moment, the topmenu structure stills to specific to Nitdoc to use the
149 # generic component.
150 class DocTopMenu
151 super UnorderedList
152
153 # Brand link to display in first position of the top menu.
154 #
155 # This is where you want to put your logo.
156 var brand: nullable Writable is noinit, writable
157
158 # Active menu item.
159 #
160 # Depends on the current page, this allows to hilighted the current item.
161 #
162 # FIXME should be using Boostrap breadcrumbs component.
163 # This will still like this to avoid diff and be changed in further fixes
164 # when we will modify the output.
165 var active_item: nullable ListItem is noinit, writable
166
167 redef fun rendering do
168 addn "<nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>"
169 addn " <div class='container-fluid'>"
170 addn " <div class='navbar-header'>"
171 add " <button type='button' class='navbar-toggle' "
172 addn " data-toggle='collapse' data-target='#topmenu-collapse'>"
173 addn " <span class='sr-only'>Toggle menu</span>"
174 addn " <span class='icon-bar'></span>"
175 addn " <span class='icon-bar'></span>"
176 addn " <span class='icon-bar'></span>"
177 addn " </button>"
178 if brand != null then
179 add "<span class='navbar-brand'>"
180 add brand.write_to_string
181 add "</span>"
182 end
183 addn " </div>"
184 addn " <div class='collapse navbar-collapse' id='topmenu-collapse'>"
185 addn " <ul class='nav navbar-nav'>"
186 for item in items do
187 if item == active_item then item.css_classes.add "active"
188 add item.write_to_string
189 end
190 addn " </ul>"
191 addn " </div>"
192 addn " </div>"
193 addn "</nav>"
194 end
195 end
196
197 # Nitdoc sidebar template.
198 class DocSideBar
199 super Template
200
201 # Sidebar contains `DocSideBox`.
202 var boxes = new Array[DocSideBox]
203
204 redef fun rendering do
205 if boxes.is_empty then return
206 addn "<div id='sidebar'>"
207 for box in boxes do add box
208 addn "</div>"
209 end
210 end
211
212 # Something that can be put in a DocSideBar.
213 class DocSideBox
214 super Template
215
216 # Box HTML id, used for Bootstrap collapsing feature.
217 #
218 # Use `html_title.to_cmangle` by default.
219 var id: String is lazy do return title.write_to_string.to_cmangle
220
221 # Title of the box to display.
222 var title: Writable
223
224 # Content to display in the box.
225 var content: Writable
226
227 # Is the box opened by default?
228 #
229 # Otherwise, the user will have to clic on the title to display the content.
230 #
231 # Default is `true`.
232 var is_open = true is writable
233
234 redef fun rendering do
235 var open = ""
236 if is_open then open = "in"
237 addn "<div class='panel'>"
238 addn " <div class='panel-heading'>"
239 add " <a data-toggle='collapse' data-parent='#sidebar'"
240 add " data-target='#box_{id}' href='#'>"
241 add title
242 addn " </a>"
243 addn " </div>"
244 addn " <div id='box_{id}' class='summary panel-body collapse {open}'>"
245 add content
246 addn " </div>"
247 addn "</div>"
248 end
249 end
250
251 redef class DocComposite
252 super Template
253
254 # HTML anchor id
255 var html_id: String is writable, lazy do return id
256
257 # Title to display if any.
258 #
259 # This title can be decorated with HTML.
260 var html_title: nullable Writable is writable, lazy do return title
261
262 # Subtitle to display if any.
263 var html_subtitle: nullable Writable is noinit, writable
264
265 # Render the element title and subtitle.
266 private fun render_title do
267 if html_title != null then
268 var header = new Header(hlvl, html_title.write_to_string)
269 header.css_classes.add "signature"
270 addn header
271 end
272 if html_subtitle != null then
273 addn "<div class='info subtitle'>"
274 addn html_subtitle.write_to_string
275 addn "</div>"
276 end
277 end
278
279 # Render the element body.
280 private fun render_body do
281 for child in children do addn child.write_to_string
282 end
283
284 redef fun rendering do
285 if is_hidden then return
286 render_title
287 render_body
288 end
289
290 # Level <hX> for HTML heading.
291 private fun hlvl: Int do return depth
292
293 # A short, undecorated title that goes in the table of contents.
294 #
295 # By default, returns `html_title.to_s`, subclasses should redefine it.
296 var html_toc_title: nullable String is lazy, writable do
297 if html_title == null then return toc_title
298 return html_title.write_to_string
299 end
300
301 # Render this element in a table of contents.
302 private fun render_toc_item(lst: UnorderedList) do
303 if is_toc_hidden or html_toc_title == null then return
304
305 var content = new Template
306 content.add new Link("#{html_id}", html_toc_title.to_s)
307 if not children.is_empty then
308 var sublst = new UnorderedList
309 sublst.css_classes.add "nav"
310 for child in children do
311 child.render_toc_item(sublst)
312 end
313 content.add sublst
314 end
315 lst.add_li new ListItem(content)
316 end
317
318 # ID used in HTML tab labels.
319 #
320 # We sanitize it for Boostrap JS panels that do not like ":" and "." in ids.
321 var html_tab_id: String is lazy do
322 var id = html_id.replace(":", "")
323 id = id.replace(".", "")
324 return "{id}-tab"
325 end
326 end
327
328 redef class DocRoot
329 redef fun rendering do
330 for child in children do addn child.write_to_string
331 end
332 end
333
334 redef class DocSection
335 super BSComponent
336
337 redef fun rendering do
338 if is_hidden then
339 addn "<a id=\"{html_id}\"></a>"
340 return
341 end
342 addn "<section{render_css_classes} id=\"{html_id}\">"
343 render_title
344 render_body
345 addn "</section>"
346 end
347 end
348
349 redef class DocArticle
350 super BSComponent
351
352 redef fun rendering do
353 if is_hidden then return
354 addn "<article{render_css_classes} id=\"{html_id}\">"
355 render_title
356 render_body
357 addn "</article>"
358 end
359 end
360
361 redef class TabbedGroup
362 redef fun render_body do
363 var tabs = new DocTabs("{html_id}.tabs", "")
364 for child in children do
365 if child.is_hidden then continue
366 var title = child.html_toc_title or else child.toc_title or else ""
367 tabs.add_panel new DocTabPanel(child.html_tab_id, title, child)
368 end
369 addn tabs
370 end
371 end
372
373 redef class PanelGroup
374 redef var html_title = null
375 redef var toc_title is lazy do return title or else ""
376 redef var is_toc_hidden = true
377 end
378
379 redef class HomeArticle
380 redef var html_title = "Overview"
381
382 # HTML content to display on the home page.
383 #
384 # This attribute is set by the `doc_render` phase who knows the context.
385 var content: nullable String is noinit, writable
386
387 redef fun render_body do
388 var content = self.content
389 if content != null then add content
390 super
391 end
392 end
393
394 redef class IndexArticle
395 redef var html_title = "Index"
396
397 redef fun render_body do
398 addn "<div class='container-fluid'>"
399 addn " <div class='row'>"
400 render_list("Modules", mmodules)
401 render_list("Classes", mclasses)
402 render_list("Properties", mprops)
403 addn "</div>"
404 addn "</div>"
405 end
406
407 # Displays a list from the content of `mentities`.
408 private fun render_list(title: String, mentities: Array[MEntity]) do
409 if mentities.is_empty then return
410 addn "<div class='col-xs-4'>"
411 addn new Header(3, title)
412 var lst = new UnorderedList
413 for mentity in mentities do
414 if mentity isa MProperty then
415 var tpl = new Template
416 tpl.add mentity.intro.html_link
417 tpl.add " ("
418 tpl.add mentity.intro.mclassdef.mclass.html_link
419 tpl.add ")"
420 lst.add_li new ListItem(tpl)
421 else
422 lst.add_li new ListItem(mentity.html_link)
423 end
424 end
425 addn lst
426 addn "</div>"
427 end
428 end
429
430 redef class MEntityComposite
431 redef var html_title is lazy do return mentity.nitdoc_name
432 end
433
434 redef class MEntitySection
435 redef var html_title is lazy do return mentity.html_name
436 redef var html_subtitle is lazy do return mentity.html_declaration
437 end
438
439 redef class ConcernSection
440 redef var html_title is lazy do return "in {mentity.nitdoc_name}"
441 end
442
443 redef class IntroArticle
444 redef var html_title = null
445
446 # Link to source to display if any.
447 var html_source_link: nullable Writable is noinit, writable
448
449 redef fun render_body do
450 var tabs = new DocTabs("{html_id}.tabs", "")
451 var comment = mentity.html_documentation
452 if mentity isa MPackage then
453 comment = mentity.html_synopsis
454 end
455 if comment != null then
456 tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
457 end
458 for child in children do
459 if child.is_hidden then continue
460 var title = child.html_toc_title or else child.toc_title or else ""
461 tabs.add_panel new DocTabPanel(child.html_tab_id, title, child)
462 end
463 var lnk = html_source_link
464 if lnk != null then
465 tabs.drop_list.items.add new ListItem(lnk)
466 end
467 addn tabs
468 end
469 end
470
471 redef class ConcernsArticle
472 redef var html_title = "Concerns"
473 redef fun render_body do add concerns.html_list
474 end
475
476 redef class DefinitionListArticle
477 redef var html_title is lazy do
478 var title = new Template
479 title.add mentity.html_icon
480 title.add mentity.html_link
481 return title
482 end
483
484 redef var html_subtitle is lazy do return mentity.html_namespace
485 redef var html_toc_title is lazy do return mentity.html_name
486 end
487
488 redef class DefinitionArticle
489 redef var html_title is lazy do return mentity.html_name
490 redef var html_subtitle is lazy do return mentity.html_declaration
491
492 # Does `self` display only it's title and no body?
493 #
494 # FIXME diff hack
495 var is_no_body: Bool = false is writable
496
497 # Does `self` display only the short content as definition?
498 #
499 # FIXME diff hack
500 var is_short_comment: Bool = false is writable
501
502 # Link to source to display if any.
503 var html_source_link: nullable Writable is noinit, writable
504
505 redef fun render_body do
506 var tabs = new DocTabs("{html_id}.tabs", "")
507 if not is_no_body then
508 var comment
509 if is_short_comment or mentity isa MPackage then
510 comment = mentity.html_synopsis
511 else
512 comment = mentity.html_documentation
513 end
514 if comment != null then
515 tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
516 end
517 end
518 for child in children do
519 if child.is_hidden then continue
520 var title = child.html_toc_title or else child.toc_title or else ""
521 tabs.add_panel new DocTabPanel(child.html_tab_id, title, child)
522 end
523 var lnk = html_source_link
524 if lnk != null then
525 tabs.drop_list.items.add new ListItem(lnk)
526 end
527 addn tabs
528 end
529 end
530
531 redef class MEntitiesListArticle
532 redef fun render_body do
533 var lst = new UnorderedList
534 lst.css_classes.add "list-unstyled list-definition"
535 for mentity in mentities do
536 lst.add_li mentity.html_list_item
537 end
538 add lst
539 end
540 end
541
542 redef class DefinitionLinArticle
543 redef fun render_body do
544 var lst = new UnorderedList
545 lst.css_classes.add "list-unstyled list-labeled"
546 for mentity in mentities do
547 if not mentity isa MPropDef then continue # TODO handle all mentities
548 var tpl = new Template
549 tpl.add mentity.mclassdef.html_namespace
550 var comment = mentity.mclassdef.html_synopsis
551 if comment != null then
552 tpl.add ": "
553 tpl.add comment
554 end
555 var li = new ListItem(tpl)
556 li.css_classes.add "signature"
557 lst.add_li li
558 end
559 add lst
560 end
561 end
562
563 redef class GraphArticle
564 redef var html_title = null
565
566 # Graph in SVG with clickable map.
567 #
568 # This attribute is set by the `doc_render` phase who knows the context.
569 var svg: nullable String = null is writable
570
571 redef fun render_body do
572 addn "<div class=\"text-center\">"
573 var svg = self.svg
574 if svg != null then add svg
575 addn "</div>"
576 end
577 end
578
579 redef class ReadmeSection
580 redef var html_id is lazy do
581 return markdown_processor.decorator.strip_id(html_title.as(not null).to_s)
582 end
583
584 redef var html_title is lazy do
585 return markdown_processor.process(title.as(not null))
586 end
587 end
588
589 redef class ReadmeArticle
590 redef var html_id = ""
591 redef var html_title = null
592 redef var is_toc_hidden = true
593
594 redef fun render_body do
595 add markdown_processor.process(md.trim.write_to_string)
596 end
597 end
598
599 redef class DocumentationArticle
600 redef var html_title is lazy do
601 var synopsis = mentity.html_synopsis
602 if synopsis == null then return mentity.html_link
603 return "{mentity.html_link.write_to_string} &ndash; {synopsis.write_to_string}"
604 end
605
606 redef var html_subtitle is lazy do return null
607 redef var html_toc_title is lazy do return mentity.html_name
608 redef var is_toc_hidden is lazy do return depth > 3
609
610 redef fun render_body do
611 var tabs = new DocTabs("{html_id}.tabs", "")
612 var comment = mentity.html_comment
613 if comment != null then
614 tabs.add_panel new DocTabPanel("{html_tab_id}-comment", "Comment", comment)
615 end
616 for child in children do
617 if child.is_hidden then continue
618 var title = child.html_toc_title or else child.toc_title or else ""
619 tabs.add_panel new DocTabPanel(child.html_tab_id, title, child)
620 end
621 addn tabs
622 end
623 end