516b17e21878b7bfd64a442c6b18431b18387420
[nit.git] / src / doc / doc_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 # HTML templates used by Nitdoc to generate API documentation
16 # Pages are assembled using `Template`
17 module doc_templates
18
19 import template
20
21 # A documentation page
22 class TplPage
23 super Template
24
25 # Page title in HTML header
26 var title: String is writable, noinit
27
28 # Page url
29 var url: String is writable, noinit
30
31 # Directory where css, js and other assets can be found
32 var shareurl: String is writable, noinit
33
34 # Attributes of the body tag element
35 var body_attrs = new Array[TagAttribute]
36
37 # Top menu template if any
38 var topmenu: TplTopMenu is writable, noinit
39
40 # Sidebar template if any
41 var sidebar: nullable TplSidebar = null is writable
42
43 # Content of the page in form a TplSection
44 var sections = new Array[TplSection]
45
46 # Footer content if any
47 var footer: nullable Streamable = null is writable
48
49 # JS scripts to append at the end of the body
50 var scripts = new Array[TplScript]
51
52 init do end
53
54 # Add a section to this page
55 fun add_section(section: TplSection) do
56 sections.add section
57 end
58
59 # Render the html header
60 private fun render_head do
61 add "<!DOCTYPE html>"
62 add "<head>"
63 add " <meta charset='utf-8'/>"
64 add " <!--link rel='stylesheet' href='{shareurl}/css/Nitdoc.UI.css' type='text/css'/-->"
65 add " <link rel='stylesheet' href='{shareurl}/vendors/bootstrap/css/bootstrap.min.css'/>"
66 add " <link rel='stylesheet' href='{shareurl}/css/nitdoc.bootstrap.css'/>"
67 add " <link rel='stylesheet' href='{shareurl}/css/nitdoc.css'/>"
68 add " <link rel='stylesheet' href='{shareurl}/css/Nitdoc.QuickSearch.css'/>"
69 add " <link rel='stylesheet' href='{shareurl}/css/Nitdoc.ModalBox.css'/>"
70 add " <link rel='stylesheet' href='{shareurl}/css/Nitdoc.GitHub.css'/>"
71 add " <title>{title}</title>"
72 add "</head>"
73 add "<body"
74 for attr in body_attrs do add attr
75 add ">"
76 end
77
78 # Render the topmenu template
79 private fun render_topmenu do
80 add " <div class='row'>"
81 add topmenu
82 add " </div>"
83 end
84
85 # Render the sidebar
86 # Sidebar is automatically populated with a summary of all sections
87 fun render_sidebar do
88 if sidebar == null then return
89 var summary = new TplSummary.with_order(0)
90 for section in sections do
91 section.render_summary summary
92 end
93 sidebar.boxes.add summary
94 add sidebar.as(not null)
95 end
96 # Render the footer and content
97 private fun render_content do
98 for section in sections do add section
99 if footer != null then
100 add "<div class='well footer'>"
101 add footer.as(not null)
102 add "</div>"
103 end
104 end
105
106 # Render JS scripts
107 private fun render_footer do
108 add "<script src='{shareurl}/vendors/jquery/jquery-1.11.1.min.js'></script>"
109 add "<script src='{shareurl}/vendors/jquery/jquery-ui-1.10.4.custom.min.js'></script>"
110 add "<script src='{shareurl}/vendors/bootstrap/js/bootstrap.min.js'></script>"
111 add "<script data-main='{shareurl}/js/nitdoc' src='{shareurl}/js/lib/require.js'></script>"
112 for script in scripts do add script
113 add """<script>
114 $(function () {
115 $("[data-toggle='tooltip']").tooltip();
116 $("[data-toggle='popover']").popover();
117 });
118 </script>"""
119 add "</body>"
120 add "</html>"
121 end
122
123 # Render the whole page
124 redef fun rendering do
125 render_head
126 add "<div class='container-fluid'>"
127 render_topmenu
128 add " <div class='row' id='content'>"
129 if sidebar != null then
130 add "<div class='col col-xs-3 col-lg-2'>"
131 render_sidebar
132 add "</div>"
133 add "<div class='col col-xs-9 col-lg-10' data-spy='scroll' data-target='.summary'>"
134 render_content
135 add "</div>"
136 else
137 add "<div class='col col-xs-12'>"
138 render_content
139 add "</div>"
140 end
141 add " </div>"
142 add "</div>"
143 render_footer
144 end
145 end
146
147 #########################
148 # general layout elements
149 #########################
150
151 # Top menu bar template
152 class TplTopMenu
153 super Template
154
155 # Brand link to display in first position of the top menu
156 private var brand: nullable Streamable = null is writable
157 # Elements of the topmenu
158 private var elts = new Array[Streamable]
159
160 # The page url where the top menu is displayed.
161 #
162 # Used to select the active link.
163 private var current_url: String
164
165 init(current_url: String) do
166 self.current_url = current_url
167 end
168
169 # Add a new link to the menu.
170 fun add_link(content: TplLink) do
171 var is_active = content.href == current_url
172 add_item(content, is_active)
173 end
174
175 # Add a content between `<li>` tags
176 fun add_item(content: Streamable, is_active: Bool) do
177 var tpl = new Template
178 tpl.add "<li"
179 if is_active then
180 tpl.add " class='active'"
181 end
182 tpl.add ">"
183 tpl.add content
184 tpl.add "</li>"
185 add_raw(tpl)
186 end
187
188 # Add a raw content to the menu
189 fun add_raw(content: Streamable) do
190 elts.add content
191 end
192
193 redef fun rendering do
194 if brand == null and elts.is_empty then return
195 add "<nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>"
196 add " <div class='container-fluid'>"
197 add " <div class='navbar-header'>"
198 add " <button type='button' class='navbar-toggle' "
199 add " data-toggle='collapse' data-target='#topmenu-collapse'>"
200 add " <span class='sr-only'>Toggle menu</span>"
201 add " <span class='icon-bar'></span>"
202 add " <span class='icon-bar'></span>"
203 add " <span class='icon-bar'></span>"
204 add " </button>"
205 if brand != null then add brand.as(not null)
206 add " </div>"
207 add " <div class='collapse navbar-collapse' id='topmenu-collapse'>"
208 if not elts.is_empty then
209 add "<ul class='nav navbar-nav'>"
210 for elt in elts do add elt
211 add "</ul>"
212 end
213 add " </div>"
214 add " </div>"
215 add "</nav>"
216 end
217 end
218
219 # A sidebar template
220 class TplSidebar
221 super Template
222
223 # Sidebar contains sidebar element templates called boxes
224 var boxes = new Array[TplSidebarElt]
225
226 # Sort boxes by order priority
227 private fun order_boxes do
228 var sorter = new OrderComparator
229 sorter.sort(boxes)
230 end
231
232 redef fun rendering do
233 if boxes.is_empty then return
234 order_boxes
235 add "<div id='sidebar'>"
236 for box in boxes do add box
237 add "</div>"
238 end
239 end
240
241 # Comparator used to sort boxes by order
242 private class OrderComparator
243 super Comparator
244
245 redef type COMPARED: TplSidebarElt
246
247 redef fun compare(a, b) do
248 if a.order < b.order then return -1
249 if a.order > b.order then return 1
250 return 0
251 end
252 end
253
254 # Something that can be put in the sidebar
255 class TplSidebarElt
256 super Template
257
258 # Order of the box in the sidebar
259 var order: Int = 1
260
261 init with_order(order: Int) do self.order = order
262 end
263
264 # Agenericbox that can be added to sidebar
265 class TplSideBox
266 super TplSidebarElt
267
268 # Title of the box to display
269 # Title is also a placeholder for the collapse link
270 var title: String
271
272 # Box HTML id
273 # equals to `title.to_cmangle` by default
274 # Used for collapsing
275 var id: String
276
277 # Content to display in the box
278 # box will not be rendered if the content is null
279 var content: nullable Streamable is writable
280
281 # Is the box opened by default
282 # otherwise, the user will have to clic on the title to display the content
283 var is_open = false is writable
284
285 init(title: String) do
286 self.title = title
287 self.id = title.to_cmangle
288 end
289
290 init with_content(title: String, content: Streamable) do
291 init(title)
292 self.content = content
293 end
294
295 redef fun rendering do
296 if content == null then return
297 var open = ""
298 if is_open then open = "in"
299 add "<div class='panel'>"
300 add " <div class='panel-heading'>"
301 add " <a data-toggle='collapse' data-parent='#sidebar' data-target='#box_{id}' href='#'>"
302 add title
303 add " </a>"
304 add " </div>"
305 add " <div id='box_{id}' class='panel-body collapse {open}'>"
306 add content.as(not null)
307 add " </div>"
308 add "</div>"
309 end
310 end
311
312 # Something that can go on a summary template
313 class TplSummaryElt
314 super Template
315
316 # Add an element to the summary
317 fun add_child(child: TplSummaryElt) is abstract
318 end
319
320 # A summary that can go on the sidebar
321 # If the page contains a sidebar, the summary is automatically placed
322 # on top of the sidebarauto-generated
323 # summary contains anchors to all sections displayed in the page
324 class TplSummary
325 super TplSidebarElt
326 super TplSummaryElt
327
328 # Summary elements to display
329 var children = new Array[TplSummaryElt]
330
331 redef fun add_child(child) do children.add child
332
333 redef fun rendering do
334 if children.is_empty then return
335 add "<div class='panel'>"
336 add " <div class='panel-heading'>"
337 add " <a data-toggle='collapse' data-parent='#sidebar' data-target='#box-sum' href='#'>"
338 add "Summary"
339 add " </a>"
340 add " </div>"
341 add " <div id='box-sum' class='summary collapse in'>"
342 add " <ul class='nav'>"
343 for entry in children do add entry
344 add " </ul>"
345 add " </div>"
346 add "</div>"
347 end
348 end
349
350 # A summary entry
351 class TplSummaryEntry
352 super TplSummaryElt
353
354 # Text to display
355 var text: Streamable
356
357 # Children of this entry
358 # Will be displayed as a tree
359 var children = new Array[TplSummaryElt]
360
361 init(text: Streamable) do self.text = text
362
363 redef fun add_child(child) do children.add child
364
365 redef fun rendering do
366 add "<li>"
367 add text
368 if not children.is_empty then
369 add "<ul class='nav'>"
370 for entry in children do add entry
371 add "</ul>"
372 end
373 add "</li>"
374 end
375 end
376
377 # Something that can go in a section
378 # Sections are automatically collected to populate the menu
379 class TplSectionElt
380 super Template
381
382 # HTML anchor id
383 var id: String
384
385 # Title to display if any
386 # if both `title` and `summary_title` are null then
387 # the section will not appear in the summary
388 var title: nullable Streamable is writable
389
390 # Subtitle to display if any
391 var subtitle: nullable Streamable is writable
392
393 # Title that appear in the summary
394 # if null use `title` instead
395 var summary_title: nullable String is writable
396
397 # CSS classes to apply on the section element
398 var css_classes = new Array[String]
399
400 # CSS classes to apply on the title heading element
401 var title_classes = new Array[String]
402
403 # Parent article/section if any
404 var parent: nullable TplSectionElt
405
406 init(id: String) do self.id = id
407
408 init with_title(id: String, title: Streamable) do
409 init(id)
410 self.title = title
411 end
412
413 # Level <hX> for HTML heading
414 protected fun hlvl: Int do
415 if parent == null then return 1
416 return parent.hlvl + 1
417 end
418
419 # Elements contained by this section
420 var children = new Array[TplSectionElt]
421
422 # Add an element in this section
423 fun add_child(child: TplSectionElt) do
424 child.parent = self
425 children.add child
426 end
427
428 # Is the section empty (no content at all)
429 fun is_empty: Bool do return children.is_empty
430
431 # Render this section in the summary
432 fun render_summary(parent: TplSummaryElt) do
433 if is_empty then return
434 var title = summary_title
435 if title == null and self.title != null then title = self.title.write_to_string
436 if title == null then return
437 var lnk = new TplLink("#{id}", title)
438 var entry = new TplSummaryEntry(lnk)
439 for child in children do
440 child.render_summary(entry)
441 end
442 parent.add_child entry
443 end
444 end
445
446 # A HTML <section> element
447 class TplSection
448 super TplSectionElt
449
450 redef fun rendering do
451 add "<section id='{id}' class='{css_classes.join(" ")}'>"
452 if title != null then
453 var lvl = hlvl
454 if lvl == 2 then title_classes.add "well well-sm"
455 add "<h{lvl} class='{title_classes.join(" ")}'>"
456 add title.as(not null)
457 add "</h{lvl}>"
458 end
459 if subtitle != null then
460 add "<div class='info subtitle'>"
461 add subtitle.as(not null)
462 add "</div>"
463 end
464 for child in children do
465 add child
466 end
467 add "</section>"
468 end
469 end
470
471 # A page article that can go in a section
472 class TplArticle
473 super TplSectionElt
474
475 # Content for this article
476 var content: nullable Streamable = null is writable
477 var source_link: nullable Streamable = null is writable
478
479 init with_content(id: String, title: Streamable, content: Streamable) do
480 with_title(id, title)
481 self.content = content
482 end
483
484 redef fun render_summary(parent) do
485 if is_empty then return
486 var title = summary_title
487 if title == null and self.title != null then title = self.title.write_to_string
488 if title == null then return
489 var lnk = new TplLink("#{id}", title)
490 parent.add_child new TplSummaryEntry(lnk)
491 end
492
493 redef fun rendering do
494 if is_empty then return
495 add "<article id='{id}' class='{css_classes.join(" ")}'>"
496 if source_link != null then
497 add "<div class='source-link'>"
498 add source_link.as(not null)
499 add "</div>"
500 end
501 if title != null then
502 var lvl = hlvl
503 if lvl == 2 then title_classes.add "well well-sm"
504 add "<h{lvl} class='{title_classes.join(" ")}'>"
505 add title.as(not null)
506 add "</h{lvl}>"
507 end
508 if subtitle != null then
509 add "<div class='info subtitle'>"
510 add subtitle.as(not null)
511 add "</div>"
512 end
513 if content != null then
514 add content.as(not null)
515 end
516 for child in children do
517 add child
518 end
519 add """</article>"""
520 end
521
522 redef fun is_empty: Bool do
523 return title == null and subtitle == null and content == null and children.is_empty
524 end
525 end
526
527 # A module / class / prop definition
528 class TplDefinition
529 super Template
530
531 # Comment to display
532 var comment: nullable Streamable = null is writable
533
534 # Namespace for this definition
535 var namespace: nullable Streamable = null is writable
536
537 # Location link to display
538 var location: nullable Streamable = null is writable
539
540 private fun render_info do
541 add "<div class='info text-right'>"
542 if namespace != null then
543 if comment == null then
544 add "<span class=\"noComment\">no comment for </span>"
545 end
546 add namespace.as(not null)
547 end
548 if location != null then
549 add " "
550 add location.as(not null)
551 end
552 add "</div>"
553 end
554
555 private fun render_comment do
556 if comment != null then add comment.as(not null)
557 end
558
559 redef fun rendering do
560 add "<div class='definition'>"
561 render_comment
562 render_info
563 add "</div>"
564 end
565 end
566
567 # Class definition
568 class TplClassDefinition
569 super TplDefinition
570
571 var intros = new Array[TplListElt]
572 var redefs = new Array[TplListElt]
573
574 init do end
575
576 redef fun rendering do
577 add "<div class='definition'>"
578 render_comment
579 render_info
580 render_list("Introduces", intros)
581 render_list("Redefines", redefs)
582 add "</div>"
583 end
584
585 private fun render_list(name: String, elts: Array[TplListElt]) do
586 if elts.is_empty then return
587 add "<h5>{name}</h5>"
588 add "<ul class='list-unstyled list-definition'>"
589 for elt in elts do add elt
590 add "</ul>"
591 end
592 end
593
594 # Layout for Search page
595 class TplSearchPage
596 super TplSectionElt
597
598 var modules = new Array[Streamable]
599 var classes = new Array[Streamable]
600 var props = new Array[Streamable]
601
602 redef fun rendering do
603 var title = self.title
604 if title != null then add "<h1>{title}</h1>"
605 add "<div class='container-fluid'>"
606 add " <div class='row'>"
607 if not modules.is_empty then
608 add "<div class='col-xs-4'>"
609 add "<h3>Modules</h3>"
610 add "<ul>"
611 for m in modules do
612 add "<li>"
613 add m
614 add "</li>"
615 end
616 add "</ul>"
617 add "</div>"
618 end
619 if not classes.is_empty then
620 add "<div class='col-xs-4'>"
621 add "<h3>Classes</h3>"
622 add "<ul>"
623 for c in classes do
624 add "<li>"
625 add c
626 add "</li>"
627 end
628 add "</ul>"
629 add "</div>"
630 end
631 if not props.is_empty then
632 add "<div class='col-xs-4'>"
633 add "<h3>Properties</h3>"
634 add "<ul>"
635 for p in props do
636 add "<li>"
637 add p
638 add "</li>"
639 end
640 add "</ul>"
641 add "</div>"
642 end
643 add " </div>"
644 add "</div>"
645 end
646 end
647
648 #####################
649 # Basiv HTML elements
650 #####################
651
652 # A html link <a>
653 class TplLink
654 super Template
655
656 # Link href
657 var href: String is writable
658
659 # Text to display in the link
660 var text: Streamable is writable
661
662 # Optional title
663 var title: nullable String is writable
664
665 init(href, text: String) do
666 self.href = href
667 self.text = text
668 end
669
670 init with_title(href, text, title: String) do
671 init(href, text)
672 self.title = title
673 end
674
675 redef fun rendering do
676 add "<a href=\""
677 add href
678 add "\""
679 if title != null then
680 add " title=\""
681 add title.as(not null)
682 add "\""
683 end
684 add ">"
685 add text
686 add "</a>"
687 end
688 end
689
690 # A <ul> list
691 class TplList
692 super TplListElt
693
694 # Elements contained in this list
695 # can be <li> or <ul> elements
696 var elts = new Array[TplListElt]
697
698 # CSS classes of the <ul> element
699 var css_classes = new Array[String]
700
701 # Add content wrapped in a <li> element
702 fun add_li(item: TplListItem) do elts.add item
703
704 init do end
705
706 init with_classes(classes: Array[String]) do self.css_classes = classes
707
708 fun is_empty: Bool do return elts.is_empty
709
710 redef fun rendering do
711 if elts.is_empty then return
712 add "<ul class='{css_classes.join(" ")}'>"
713 for elt in elts do add elt
714 add "</ul>"
715 end
716 end
717
718 # Something that can be added to a TplList
719 class TplListElt
720 super Template
721 end
722
723 # A list item <li>
724 class TplListItem
725 super TplListElt
726
727 # Content of the list item
728 var content = new Template
729
730 # CSS classes of the <li> element
731 var css_classes = new Array[String]
732
733 init do end
734
735 init with_content(content: Streamable) do append(content)
736
737 init with_classes(content: Streamable, classes: Array[String]) do
738 with_content(content)
739 css_classes = classes
740 end
741
742 # Append `content` to the item
743 # similar to `self.content.add`
744 fun append(content: Streamable) do self.content.add content
745
746 redef fun rendering do
747 add "<li class='{css_classes.join(" ")}'>"
748 add content
749 add "</li>"
750 end
751 end
752
753 # A Bootstrap tab component that contains `TplTabPanel`.
754 class TplTab
755 super Template
756
757 # Panels contained in the tab.
758 var panels = new Array[TplTabPanel]
759
760 # Add a new panel.
761 fun add_panel(panel: TplTabPanel) do panels.add panel
762
763 # CSS classes of the tab component.
764 var css_classes = new Array[String]
765
766 redef fun rendering do
767 add "<div class='tab-content'>"
768 for panel in panels do add panel
769 add "</div>"
770 end
771 end
772
773 # A panel that goes in a `TplTab`.
774 class TplTabPanel
775 super Template
776
777 # CSS classes of the pane element.
778 var css_classes = new Array[String]
779
780 # The panel id.
781 #
782 # Used to show/hide panel.
783 var id: String
784
785 # The panel name.
786 #
787 # Displayed in the tab header or in the pointing link.
788 var name: Streamable
789
790 # Is the panel visible by default?
791 var is_active = false is writable
792
793 # Body of the panel
794 var content: nullable Streamable = null is writable
795
796 # Get a link pointing to this panel.
797 fun tpl_link_to: Streamable do
798 var lnk = new Template
799 lnk.add "<a data-target='#{id}' data-toggle='pill'>"
800 lnk.add name
801 lnk.add "</a>"
802 return lnk
803 end
804
805 redef fun rendering do
806 add "<div class='tab-pane {css_classes.join(" ")}"
807 if is_active then add "active"
808 add "' id='{id}'>"
809 if content != null then add content.as(not null)
810 add "</div>"
811 end
812 end
813
814 # A label with a text content
815 class TplLabel
816 super Template
817
818 # Content of the label if any
819 var content: nullable Streamable = null is writable
820
821 # CSS classes of the <span> element
822 var css_classes = new Array[String]
823
824 init with_content(content: Streamable) do self.content = content
825 init with_classes(classes: Array[String]) do self.css_classes = classes
826
827 redef fun rendering do
828 add "<span class='label {css_classes.join(" ")}'>"
829 if content != null then add content.as(not null)
830 add "</span>"
831 end
832 end
833
834 # A label with an icon
835 class TplIcon
836 super TplLabel
837
838 # Bootsrap icon name
839 # see: http://getbootstrap.com/components/#glyphicons
840 var icon: String
841
842 init with_icon(icon: String) do self.icon = icon
843
844 redef fun rendering do
845 add "<span class='glyphicon glyphicon-{icon} {css_classes.join(" ")}'>"
846 if content != null then add content.as(not null)
847 add "</span>"
848 end
849 end
850
851 # A HTML tag attribute
852 # `<tag attr="value">`
853 class TagAttribute
854 super Template
855
856 var name: String
857 var value: nullable String
858
859 init(name: String, value: nullable String) do
860 self.name = name
861 self.value = value
862 end
863
864 redef fun rendering do
865 var value = self.value
866 if value == null then
867 add(" {name}")
868 else
869 add(" {name}=\"{value}\"")
870 end
871 end
872 end
873
874 # Javacript template
875 class TplScript
876 super Template
877
878 var attrs = new Array[TagAttribute]
879 var content: nullable Streamable = null is writable
880
881 init do
882 attrs.add(new TagAttribute("type", "text/javascript"))
883 end
884
885 protected fun render_content do
886 if content != null then add content.as(not null)
887 end
888
889 redef fun rendering do
890 add "<script"
891 for attr in attrs do add attr
892 add ">"
893 render_content
894 add "</script>"
895 end
896 end
897
898 # JS script for Piwik Tracker
899 class TplPiwikScript
900 super TplScript
901
902 var tracker_url: String
903 var site_id: String
904
905 init(tracker_url, site_id: String) do
906 super
907 self.tracker_url = tracker_url
908 self.site_id = site_id
909 end
910
911 redef fun render_content do
912 add "<!-- Piwik -->"
913 add "var _paq = _paq || [];"
914 add " _paq.push([\"trackPageView\"]);"
915 add " _paq.push([\"enableLinkTracking\"]);"
916 add "(function() \{"
917 add " var u=((\"https:\" == document.location.protocol) ? \"https\" : \"http\") + \"://{tracker_url}\";"
918 add " _paq.push([\"setTrackerUrl\", u+\"piwik.php\"]);"
919 add " _paq.push([\"setSiteId\", \"{site_id}\"]);"
920 add " var d=document, g=d.createElement(\"script\"), s=d.getElementsByTagName(\"script\")[0]; g.type=\"text/javascript\";"
921 add " g.defer=true; g.async=true; g.src=u+\"piwik.js\"; s.parentNode.insertBefore(g,s);"
922 add "\})();"
923 end
924 end
925