nitdoc: move page url into `NitdocPage`
[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 init do end
161
162 # Add a content between `<li>` tags
163 fun add_item(content: Streamable, is_active: Bool) do
164 var tpl = new Template
165 tpl.add "<li"
166 if is_active then
167 tpl.add " class='active'"
168 end
169 tpl.add ">"
170 tpl.add content
171 tpl.add "</li>"
172 add_raw(tpl)
173 end
174
175 # Add a raw content to the menu
176 fun add_raw(content: Streamable) do
177 elts.add content
178 end
179
180 redef fun rendering do
181 if brand == null and elts.is_empty then return
182 add "<nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>"
183 add " <div class='container-fluid'>"
184 add " <div class='navbar-header'>"
185 add " <button type='button' class='navbar-toggle' "
186 add " data-toggle='collapse' data-target='#topmenu-collapse'>"
187 add " <span class='sr-only'>Toggle menu</span>"
188 add " <span class='icon-bar'></span>"
189 add " <span class='icon-bar'></span>"
190 add " <span class='icon-bar'></span>"
191 add " </button>"
192 if brand != null then add brand.as(not null)
193 add " </div>"
194 add " <div class='collapse navbar-collapse' id='topmenu-collapse'>"
195 if not elts.is_empty then
196 add "<ul class='nav navbar-nav'>"
197 for elt in elts do add elt
198 add "</ul>"
199 end
200 add " </div>"
201 add " </div>"
202 add "</nav>"
203 end
204 end
205
206 # A sidebar template
207 class TplSidebar
208 super Template
209
210 # Sidebar contains sidebar element templates called boxes
211 var boxes = new Array[TplSidebarElt]
212
213 # Sort boxes by order priority
214 private fun order_boxes do
215 var sorter = new OrderComparator
216 sorter.sort(boxes)
217 end
218
219 redef fun rendering do
220 if boxes.is_empty then return
221 order_boxes
222 add "<div id='sidebar'>"
223 for box in boxes do add box
224 add "</div>"
225 end
226 end
227
228 # Comparator used to sort boxes by order
229 private class OrderComparator
230 super Comparator[TplSidebarElt]
231
232 redef fun compare(a, b) do
233 if a.order < b.order then return -1
234 if a.order > b.order then return 1
235 return 0
236 end
237 end
238
239 # Something that can be put in the sidebar
240 class TplSidebarElt
241 super Template
242
243 # Order of the box in the sidebar
244 var order: Int = 1
245
246 init with_order(order: Int) do self.order = order
247 end
248
249 # Agenericbox that can be added to sidebar
250 class TplSideBox
251 super TplSidebarElt
252
253 # Title of the box to display
254 # Title is also a placeholder for the collapse link
255 var title: String
256
257 # Box HTML id
258 # equals to `title.to_cmangle` by default
259 # Used for collapsing
260 var id: String
261
262 # Content to display in the box
263 # box will not be rendered if the content is null
264 var content: nullable Streamable writable
265
266 # Is the box opened by default
267 # otherwise, the user will have to clic on the title to display the content
268 var is_open writable = false
269
270 init(title: String) do
271 self.title = title
272 self.id = title.to_cmangle
273 end
274
275 init with_content(title: String, content: Streamable) do
276 init(title)
277 self.content = content
278 end
279
280 redef fun rendering do
281 if content == null then return
282 var open = ""
283 if is_open then open = "in"
284 add "<div class='panel'>"
285 add " <div class='panel-heading'>"
286 add " <a data-toggle='collapse' data-parent='#sidebar' data-target='#box_{id}' href='#'>"
287 add title
288 add " </a>"
289 add " </div>"
290 add " <div id='box_{id}' class='panel-body collapse {open}'>"
291 add content.as(not null)
292 add " </div>"
293 add "</div>"
294 end
295 end
296
297 # Something that can go on a summary template
298 class TplSummaryElt
299 super Template
300
301 # Add an element to the summary
302 fun add_child(child: TplSummaryElt) is abstract
303 end
304
305 # A summary that can go on the sidebar
306 # If the page contains a sidebar, the summary is automatically placed
307 # on top of the sidebarauto-generated
308 # summary contains anchors to all sections displayed in the page
309 class TplSummary
310 super TplSidebarElt
311 super TplSummaryElt
312
313 # Summary elements to display
314 var children = new Array[TplSummaryElt]
315
316 redef fun add_child(child) do children.add child
317
318 redef fun rendering do
319 if children.is_empty then return
320 add "<div class='panel'>"
321 add " <div class='panel-heading'>"
322 add " <a data-toggle='collapse' data-parent='#sidebar' data-target='#box-sum' href='#'>"
323 add "Summary"
324 add " </a>"
325 add " </div>"
326 add " <div id='box-sum' class='summary collapse in'>"
327 add " <ul class='nav'>"
328 for entry in children do add entry
329 add " </ul>"
330 add " </div>"
331 add "</div>"
332 end
333 end
334
335 # A summary entry
336 class TplSummaryEntry
337 super TplSummaryElt
338
339 # Text to display
340 var text: Streamable
341
342 # Children of this entry
343 # Will be displayed as a tree
344 var children = new Array[TplSummaryElt]
345
346 init(text: Streamable) do self.text = text
347
348 redef fun add_child(child) do children.add child
349
350 redef fun rendering do
351 add "<li>"
352 add text
353 if not children.is_empty then
354 add "<ul class='nav'>"
355 for entry in children do add entry
356 add "</ul>"
357 end
358 add "</li>"
359 end
360 end
361
362 # Something that can go in a section
363 # Sections are automatically collected to populate the menu
364 class TplSectionElt
365 super Template
366
367 # HTML anchor id
368 var id: String
369
370 # Title to display if any
371 # if both `title` and `summary_title` are null then
372 # the section will not appear in the summary
373 var title: nullable Streamable writable
374
375 # Subtitle to display if any
376 var subtitle: nullable Streamable writable
377
378 # Title that appear in the summary
379 # if null use `title` instead
380 var summary_title: nullable String writable
381
382 # CSS classes to apply on the section element
383 var css_classes = new Array[String]
384
385 # CSS classes to apply on the title heading element
386 var title_classes = new Array[String]
387
388 # Parent article/section if any
389 var parent: nullable TplSectionElt
390
391 init(id: String) do self.id = id
392
393 init with_title(id: String, title: Streamable) do
394 init(id)
395 self.title = title
396 end
397
398 # Level <hX> for HTML heading
399 protected fun hlvl: Int do
400 if parent == null then return 1
401 return parent.hlvl + 1
402 end
403
404 # Elements contained by this section
405 var children = new Array[TplSectionElt]
406
407 # Add an element in this section
408 fun add_child(child: TplSectionElt) do
409 child.parent = self
410 children.add child
411 end
412
413 # Is the section empty (no content at all)
414 fun is_empty: Bool do return children.is_empty
415
416 # Render this section in the summary
417 fun render_summary(parent: TplSummaryElt) do
418 if is_empty then return
419 var title = summary_title
420 if title == null and self.title != null then title = self.title.write_to_string
421 if title == null then return
422 var lnk = new TplLink("#{id}", title)
423 var entry = new TplSummaryEntry(lnk)
424 for child in children do
425 child.render_summary(entry)
426 end
427 parent.add_child entry
428 end
429 end
430
431 # A HTML <section> element
432 class TplSection
433 super TplSectionElt
434
435 redef fun rendering do
436 add "<section id='{id}' class='{css_classes.join(" ")}'>"
437 if title != null then
438 var lvl = hlvl
439 if lvl == 2 then title_classes.add "well well-sm"
440 add "<h{lvl} class='{title_classes.join(" ")}'>"
441 add title.as(not null)
442 add "</h{lvl}>"
443 end
444 if subtitle != null then
445 add "<div class='info subtitle'>"
446 add subtitle.as(not null)
447 add "</div>"
448 end
449 for child in children do
450 add child
451 end
452 add "</section>"
453 end
454 end
455
456 # A page article that can go in a section
457 class TplArticle
458 super TplSectionElt
459
460 # Content for this article
461 var content: nullable Streamable writable = null
462 var source_link: nullable Streamable writable = null
463
464 init with_content(id: String, title: Streamable, content: Streamable) do
465 with_title(id, title)
466 self.content = content
467 end
468
469 redef fun render_summary(parent) do
470 if is_empty then return
471 var title = summary_title
472 if title == null and self.title != null then title = self.title.write_to_string
473 if title == null then return
474 var lnk = new TplLink("#{id}", title)
475 parent.add_child new TplSummaryEntry(lnk)
476 end
477
478 redef fun rendering do
479 if is_empty then return
480 add "<article id='{id}' class='{css_classes.join(" ")}'>"
481 if source_link != null then
482 add "<div class='source-link'>"
483 add source_link.as(not null)
484 add "</div>"
485 end
486 if title != null then
487 var lvl = hlvl
488 if lvl == 2 then title_classes.add "well well-sm"
489 add "<h{lvl} class='{title_classes.join(" ")}'>"
490 add title.as(not null)
491 add "</h{lvl}>"
492 end
493 if subtitle != null then
494 add "<div class='info subtitle'>"
495 add subtitle.as(not null)
496 add "</div>"
497 end
498 if content != null then
499 add content.as(not null)
500 end
501 for child in children do
502 add child
503 end
504 add """</article>"""
505 end
506
507 redef fun is_empty: Bool do
508 return title == null and subtitle == null and content == null
509 end
510 end
511
512 # A module / class / prop definition
513 class TplDefinition
514 super Template
515
516 # Comment to display
517 var comment: nullable Streamable = null is writable
518
519 # Namespace for this definition
520 var namespace: nullable Streamable = null is writable
521
522 # Location link to display
523 var location: nullable Streamable = null is writable
524
525 private fun render_info do
526 add "<div class='info text-right'>"
527 if namespace != null then
528 if comment == null then
529 add "<span class=\"noComment\">no comment for </span>"
530 end
531 add namespace.as(not null)
532 end
533 if location != null then
534 add " "
535 add location.as(not null)
536 end
537 add "</div>"
538 end
539
540 private fun render_comment do
541 if comment != null then add comment.as(not null)
542 end
543
544 redef fun rendering do
545 add "<div class='definition'>"
546 render_comment
547 render_info
548 add "</div>"
549 end
550 end
551
552 # Class definition
553 class TplClassDefinition
554 super TplDefinition
555
556 var intros = new Array[TplListElt]
557 var redefs = new Array[TplListElt]
558
559 init do end
560
561 redef fun rendering do
562 add "<div class='definition'>"
563 render_comment
564 render_info
565 render_list("Introduces", intros)
566 render_list("Redefines", redefs)
567 add "</div>"
568 end
569
570 private fun render_list(name: String, elts: Array[TplListElt]) do
571 if elts.is_empty then return
572 add "<h5>{name}</h5>"
573 add "<ul class='list-unstyled list-definition'>"
574 for elt in elts do add elt
575 add "</ul>"
576 end
577 end
578
579 # Layout for Search page
580 class TplSearchPage
581 super TplSectionElt
582
583 var modules = new Array[Streamable]
584 var classes = new Array[Streamable]
585 var props = new Array[Streamable]
586
587 redef fun rendering do
588 var title = self.title
589 if title != null then add "<h1>{title}</h1>"
590 add "<div class='container-fluid'>"
591 add " <div class='row'>"
592 if not modules.is_empty then
593 add "<div class='col-xs-4'>"
594 add "<h3>Modules</h3>"
595 add "<ul>"
596 for m in modules do
597 add "<li>"
598 add m
599 add "</li>"
600 end
601 add "</ul>"
602 add "</div>"
603 end
604 if not classes.is_empty then
605 add "<div class='col-xs-4'>"
606 add "<h3>Classes</h3>"
607 add "<ul>"
608 for c in classes do
609 add "<li>"
610 add c
611 add "</li>"
612 end
613 add "</ul>"
614 add "</div>"
615 end
616 if not props.is_empty then
617 add "<div class='col-xs-4'>"
618 add "<h3>Properties</h3>"
619 add "<ul>"
620 for p in props do
621 add "<li>"
622 add p
623 add "</li>"
624 end
625 add "</ul>"
626 add "</div>"
627 end
628 add " </div>"
629 add "</div>"
630 end
631 end
632
633 #####################
634 # Basiv HTML elements
635 #####################
636
637 # A html link <a>
638 class TplLink
639 super Template
640
641 # Link href
642 var href: String writable
643
644 # Text to display in the link
645 var text: String writable
646
647 # Optional title
648 var title: nullable String writable
649
650 init(href, text: String) do
651 self.href = href
652 self.text = text
653 end
654
655 init with_title(href, text, title: String) do
656 init(href, text)
657 self.title = title
658 end
659
660 redef fun rendering do
661 add "<a href=\""
662 add href
663 add "\""
664 if title != null then
665 add " title=\""
666 add title.as(not null)
667 add "\""
668 end
669 add ">"
670 add text
671 add "</a>"
672 end
673 end
674
675 # A <ul> list
676 class TplList
677 super TplListElt
678
679 # Elements contained in this list
680 # can be <li> or <ul> elements
681 var elts = new Array[TplListElt]
682
683 # CSS classes of the <ul> element
684 var css_classes = new Array[String]
685
686 # Add content wrapped in a <li> element
687 fun add_li(content: Streamable) do elts.add new TplListItem.with_content(content)
688
689 init do end
690
691 init with_classes(classes: Array[String]) do self.css_classes = classes
692
693 fun is_empty: Bool do return elts.is_empty
694
695 redef fun rendering do
696 if elts.is_empty then return
697 add "<ul class='{css_classes.join(" ")}'>"
698 for elt in elts do add elt
699 add "</ul>"
700 end
701 end
702
703 # Something that can be added to a TplList
704 class TplListElt
705 super Template
706 end
707
708 # A list item <li>
709 class TplListItem
710 super TplListElt
711
712 # Content of the list item
713 var content = new Template
714
715 # CSS classes of the <li> element
716 var css_classes = new Array[String]
717
718 init do end
719
720 init with_content(content: Streamable) do append(content)
721
722 init with_classes(content: Streamable, classes: Array[String]) do
723 with_content(content)
724 css_classes = classes
725 end
726
727 # Append `content` to the item
728 # similar to `self.content.add`
729 fun append(content: Streamable) do self.content.add content
730
731 redef fun rendering do
732 add "<li class='"
733 for cls in css_classes do add " {cls}"
734 add "'>"
735 add content
736 add "</li>"
737 end
738 end
739
740 # A label with a text content
741 class TplLabel
742 super Template
743
744 # Content of the label if any
745 var content: nullable Streamable
746
747 # CSS classes of the <span> element
748 var css_classes = new Array[String]
749
750 init do end
751 init with_content(content: Streamable) do self.content = content
752 init with_classes(classes: Array[String]) do self.css_classes = classes
753
754 redef fun rendering do
755 add "<span class='label {css_classes.join(" ")}'>"
756 if content != null then add content.as(not null)
757 add "</span>"
758 end
759 end
760
761 # A label with an icon
762 class TplIcon
763 super TplLabel
764
765 # Bootsrap icon name
766 # see: http://getbootstrap.com/components/#glyphicons
767 var icon: String
768
769 init with_icon(icon: String) do self.icon = icon
770
771 redef fun rendering do
772 add "<span class='glyphicon glyphicon-{icon} {css_classes.join(" ")}'>"
773 if content != null then add content.as(not null)
774 add "</span>"
775 end
776 end
777
778 # A HTML tag attribute
779 # `<tag attr="value">`
780 class TagAttribute
781 super Template
782
783 var name: String
784 var value: nullable String
785
786 init(name: String, value: nullable String) do
787 self.name = name
788 self.value = value
789 end
790
791 redef fun rendering do
792 var value = self.value
793 if value == null then
794 add(" {name}")
795 else
796 add(" {name}=\"{value}\"")
797 end
798 end
799 end
800
801 # Javacript template
802 class TplScript
803 super Template
804
805 var attrs = new Array[TagAttribute]
806 var content: nullable Streamable = null is writable
807
808 init do
809 attrs.add(new TagAttribute("type", "text/javascript"))
810 end
811
812 protected fun render_content do
813 if content != null then add content.as(not null)
814 end
815
816 redef fun rendering do
817 add "<script"
818 for attr in attrs do add attr
819 add ">"
820 render_content
821 add "</script>"
822 end
823 end
824
825 # JS script for Piwik Tracker
826 class TplPiwikScript
827 super TplScript
828
829 var tracker_url: String
830 var site_id: String
831
832 init(tracker_url, site_id: String) do
833 super
834 self.tracker_url = tracker_url
835 self.site_id = site_id
836 end
837
838 redef fun render_content do
839 add "<!-- Piwik -->"
840 add "var _paq = _paq || [];"
841 add " _paq.push([\"trackPageView\"]);"
842 add " _paq.push([\"enableLinkTracking\"]);"
843 add "(function() \{"
844 add " var u=((\"https:\" == document.location.protocol) ? \"https\" : \"http\") + \"://{tracker_url}\";"
845 add " _paq.push([\"setTrackerUrl\", u+\"piwik.php\"]);"
846 add " _paq.push([\"setSiteId\", \"{site_id}\"]);"
847 add " var d=document, g=d.createElement(\"script\"), s=d.getElementsByTagName(\"script\")[0]; g.type=\"text/javascript\";"
848 add " g.defer=true; g.async=true; g.src=u+\"piwik.js\"; s.parentNode.insertBefore(g,s);"
849 add "\})();"
850 end
851 end
852