nitdoc: Escape headers.
[nit.git] / src / doc / doc_templates.nit
index 8d0186b..c4729a5 100644 (file)
 module doc_templates
 
 import template
+import json::static
 
 # A documentation page
 class TplPage
        super Template
 
        # Page title in HTML header
-       var title: String writable
+       var title: String is writable, noinit
+
+       # Page url
+       var url: String is writable, noinit
 
        # Directory where css, js and other assets can be found
-       var shareurl: String writable
+       var shareurl: String is writable, noinit
 
        # Attributes of the body tag element
        var body_attrs = new Array[TagAttribute]
 
        # Top menu template if any
-       var topmenu: TplTopMenu writable
+       var topmenu: TplTopMenu is writable, noinit
 
        # Sidebar template if any
-       var sidebar: nullable TplSidebar writable
+       var sidebar: nullable TplSidebar = null is writable
 
        # Content of the page in form a TplSection
        var sections = new Array[TplSection]
 
        # Footer content if any
-       var footer: nullable Streamable writable
+       var footer: nullable Streamable = null is writable
 
        # JS scripts to append at the end of the body
        var scripts = new Array[TplScript]
 
-       init do end
-
        # Add a section to this page
        fun add_section(section: TplSection) do
                sections.add section
@@ -55,16 +57,19 @@ class TplPage
 
        # Render the html header
        private fun render_head do
+               var css = (self.shareurl / "css").html_escape
+               var vendors = (self.shareurl / "vendors").html_escape
+
                add "<!DOCTYPE html>"
                add "<head>"
                add " <meta charset='utf-8'/>"
-               add " <!--link rel='stylesheet' href='{shareurl}/css/Nitdoc.UI.css' type='text/css'/-->"
-               add " <link rel='stylesheet' href='{shareurl}/vendors/bootstrap/css/bootstrap.min.css'/>"
-               add " <link rel='stylesheet' href='{shareurl}/css/nitdoc.bootstrap.css'/>"
-               add " <link rel='stylesheet' href='{shareurl}/css/nitdoc.css'/>"
-               add " <link rel='stylesheet' href='{shareurl}/css/Nitdoc.QuickSearch.css'/>"
-               add " <link rel='stylesheet' href='{shareurl}/css/Nitdoc.ModalBox.css'/>"
-               add " <link rel='stylesheet' href='{shareurl}/css/Nitdoc.GitHub.css'/>"
+               add " <!--link rel='stylesheet' href='{css}/Nitdoc.UI.css' type='text/css'/-->"
+               add " <link rel='stylesheet' href='{vendors}/bootstrap/css/bootstrap.min.css'/>"
+               add " <link rel='stylesheet' href='{css}/nitdoc.bootstrap.css'/>"
+               add " <link rel='stylesheet' href='{css}/nitdoc.css'/>"
+               add " <link rel='stylesheet' href='{css}/Nitdoc.QuickSearch.css'/>"
+               add " <link rel='stylesheet' href='{css}/Nitdoc.ModalBox.css'/>"
+               add " <link rel='stylesheet' href='{css}/Nitdoc.GitHub.css'/>"
                add " <title>{title}</title>"
                add "</head>"
                add "<body"
@@ -102,10 +107,13 @@ class TplPage
 
        # Render JS scripts
        private fun render_footer do
-               add "<script src='{shareurl}/vendors/jquery/jquery-1.11.1.min.js'></script>"
-               add "<script src='{shareurl}/vendors/jquery/jquery-ui-1.10.4.custom.min.js'></script>"
-               add "<script src='{shareurl}/vendors/bootstrap/js/bootstrap.min.js'></script>"
-               add "<script data-main='{shareurl}/js/nitdoc' src='{shareurl}/js/lib/require.js'</script>"
+               var vendors = (self.shareurl / "vendors").html_escape
+               var js = (self.shareurl / "js").html_escape
+
+               add "<script src='{vendors}/jquery/jquery-1.11.1.min.js'></script>"
+               add "<script src='{vendors}/jquery/jquery-ui-1.10.4.custom.min.js'></script>"
+               add "<script src='{vendors}/bootstrap/js/bootstrap.min.js'></script>"
+               add "<script data-main='{js}/nitdoc' src='{js}/lib/require.js'></script>"
                for script in scripts do add script
                add """<script>
                        $(function () {
@@ -149,18 +157,31 @@ end
 class TplTopMenu
        super Template
 
+       # Brand link to display in first position of the top menu
+       private var brand: nullable Streamable = null is writable
        # Elements of the topmenu
        private var elts = new Array[Streamable]
 
-       # Add a new link to the menu
-       fun add_link(href, name: String, is_active: Bool) do
+       # The page url where the top menu is displayed.
+       #
+       # Used to select the active link.
+       private var current_url: String
+
+       # Add a new link to the menu.
+       fun add_link(content: TplLink) do
+               var is_active = content.href == current_url
+               add_item(content, is_active)
+       end
+
+       # Add a content between `<li>` tags
+       fun add_item(content: Streamable, is_active: Bool) do
                var tpl = new Template
                tpl.add "<li"
                if is_active then
                        tpl.add " class='active'"
                end
                tpl.add ">"
-               tpl.add new TplLink(href, name)
+               tpl.add content
                tpl.add "</li>"
                add_raw(tpl)
        end
@@ -171,13 +192,26 @@ class TplTopMenu
        end
 
        redef fun rendering do
+               if brand == null and elts.is_empty then return
                add "<nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>"
                add " <div class='container-fluid'>"
+               add "  <div class='navbar-header'>"
+               add "   <button type='button' class='navbar-toggle' "
+               add "       data-toggle='collapse' data-target='#topmenu-collapse'>"
+               add "    <span class='sr-only'>Toggle menu</span>"
+               add "    <span class='icon-bar'></span>"
+               add "    <span class='icon-bar'></span>"
+               add "    <span class='icon-bar'></span>"
+               add "   </button>"
+               if brand != null then add brand.as(not null)
+               add "  </div>"
+               add "  <div class='collapse navbar-collapse' id='topmenu-collapse'>"
                if not elts.is_empty then
                        add "<ul class='nav navbar-nav'>"
-                       for elt in elts do add(elt)
+                       for elt in elts do add elt
                        add "</ul>"
                end
+               add "  </div>"
                add " </div>"
                add "</nav>"
        end
@@ -207,7 +241,9 @@ end
 
 # Comparator used to sort boxes by order
 private class OrderComparator
-       super Comparator[TplSidebarElt]
+       super Comparator
+
+       redef type COMPARED: TplSidebarElt
 
        redef fun compare(a, b) do
                if a.order < b.order then return -1
@@ -237,18 +273,17 @@ class TplSideBox
        # Box HTML id
        # equals to `title.to_cmangle` by default
        # Used for collapsing
-       var id: String
+       var id: String is noinit
 
        # Content to display in the box
        # box will not be rendered if the content is null
-       var content: nullable Streamable writable
+       var content: nullable Streamable = null is writable
 
        # Is the box opened by default
        # otherwise, the user will have to clic on the title to display the content
-       var is_open writable = false
+       var is_open = false is writable
 
-       init(title: String) do
-               self.title = title
+       init do
                self.id = title.to_cmangle
        end
 
@@ -323,8 +358,6 @@ class TplSummaryEntry
        # Will be displayed as a tree
        var children = new Array[TplSummaryElt]
 
-       init(text: Streamable) do self.text = text
-
        redef fun add_child(child) do children.add child
 
        redef fun rendering do
@@ -350,19 +383,23 @@ class TplSectionElt
        # Title to display if any
        # if both `title` and `summary_title` are null then
        # the section will not appear in the summary
-       var title: nullable Streamable writable
+       var title: nullable Streamable = null is writable
 
        # Subtitle to display if any
-       var subtitle: nullable Streamable writable
+       var subtitle: nullable Streamable = null is writable
 
        # Title that appear in the summary
        # if null use `title` instead
-       var summary_title: nullable String writable
+       var summary_title: nullable String = null is writable
 
-       # Parent section of this section if any
-       var parent: nullable TplSection
+       # CSS classes to apply on the section element
+       var css_classes = new Array[String]
+
+       # CSS classes to apply on the title heading element
+       var title_classes = new Array[String]
 
-       init(id: String) do self.id = id
+       # Parent article/section if any
+       var parent: nullable TplSectionElt = null
 
        init with_title(id: String, title: Streamable) do
                init(id)
@@ -375,17 +412,6 @@ class TplSectionElt
                return parent.hlvl + 1
        end
 
-       # Render this section in the summary
-       protected fun render_summary(parent: TplSummaryElt) is abstract
-
-       # Is the section empty (no content at all)
-       fun is_empty: Bool is abstract
-end
-
-# A HTML <section> element
-class TplSection
-       super TplSectionElt
-
        # Elements contained by this section
        var children = new Array[TplSectionElt]
 
@@ -395,9 +421,11 @@ class TplSection
                children.add child
        end
 
-       redef fun is_empty: Bool do return children.is_empty
+       # Is the section empty (no content at all)
+       fun is_empty: Bool do return children.is_empty
 
-       redef fun render_summary(parent) do
+       # Render this section in the summary
+       fun render_summary(parent: TplSummaryElt) do
                if is_empty then return
                var title = summary_title
                if title == null and self.title != null then title = self.title.write_to_string
@@ -409,13 +437,18 @@ class TplSection
                end
                parent.add_child entry
        end
+end
+
+# A HTML <section> element
+class TplSection
+       super TplSectionElt
 
        redef fun rendering do
-               if is_empty then return
-               add "<section id='{id}'>"
+               add "<section id='{id}' class='{css_classes.join(" ")}'>"
                if title != null then
                        var lvl = hlvl
-                       add "<h{lvl}>"
+                       if lvl == 2 then title_classes.add "well well-sm"
+                       add "<h{lvl} class='{title_classes.join(" ")}'>"
                        add title.as(not null)
                        add "</h{lvl}>"
                end
@@ -436,10 +469,13 @@ class TplArticle
        super TplSectionElt
 
        # Content for this article
-       var content: nullable Streamable writable = null
+       var content: nullable Streamable = null is writable
+       var source_link: nullable Streamable = null is writable
 
-       # CSS classes to apply on the article title heading element
-       var title_classes = new Array[String]
+       init with_content(id: String, title: Streamable, content: Streamable) do
+               with_title(id, title)
+               self.content = content
+       end
 
        redef fun render_summary(parent) do
                if is_empty then return
@@ -451,9 +487,16 @@ class TplArticle
        end
 
        redef fun rendering do
-               add "<article id='{id}'>"
+               if is_empty then return
+               add "<article id='{id}' class='{css_classes.join(" ")}'>"
+               if source_link != null then
+                       add "<div class='source-link'>"
+                       add source_link.as(not null)
+                       add "</div>"
+               end
                if title != null then
                        var lvl = hlvl
+                       if lvl == 2 then title_classes.add "well well-sm"
                        add "<h{lvl} class='{title_classes.join(" ")}'>"
                        add title.as(not null)
                        add "</h{lvl}>"
@@ -466,10 +509,15 @@ class TplArticle
                if content != null then
                        add content.as(not null)
                end
+               for child in children do
+                       add child
+               end
                add """</article>"""
        end
 
-       redef fun is_empty: Bool do return content == null
+       redef fun is_empty: Bool do
+               return title == null and subtitle == null and content == null and children.is_empty
+       end
 end
 
 # A module / class / prop definition
@@ -477,22 +525,22 @@ class TplDefinition
        super Template
 
        # Comment to display
-       var comment: nullable Streamable writable
+       var comment: nullable Streamable = null is writable
 
        # Namespace for this definition
-       var namespace: Streamable writable
+       var namespace: nullable Streamable = null is writable
 
        # Location link to display
-       var location: nullable Streamable writable
-
-       init do end
+       var location: nullable Streamable = null is writable
 
        private fun render_info do
                add "<div class='info text-right'>"
-               if comment == null then
-                       add "<span class=\"noComment\">no comment for </span>"
+               if namespace != null then
+                       if comment == null then
+                               add "<span class=\"noComment\">no comment for </span>"
+                       end
+                       add namespace.as(not null)
                end
-               add namespace
                if location != null then
                        add " "
                        add location.as(not null)
@@ -532,7 +580,7 @@ class TplClassDefinition
 
        private fun render_list(name: String, elts: Array[TplListElt]) do
                if elts.is_empty then return
-               add "<h5>{name}</h5>"
+               add "<h5>{name.html_escape}</h5>"
                add "<ul class='list-unstyled list-definition'>"
                for elt in elts do add elt
                add "</ul>"
@@ -549,7 +597,7 @@ class TplSearchPage
 
        redef fun rendering do
                var title = self.title
-               if title != null then add "<h1>{title}</h1>"
+               if title != null then add "<h1>{title.to_s.html_escape}</h1>"
                add "<div class='container-fluid'>"
                add " <div class='row'>"
                if not modules.is_empty then
@@ -602,18 +650,13 @@ class TplLink
        super Template
 
        # Link href
-       var href: String writable
+       var href: String is writable
 
        # Text to display in the link
-       var text: String writable
+       var text: Streamable is writable
 
        # Optional title
-       var title: nullable String writable
-
-       init(href, text: String) do
-               self.href = href
-               self.text = text
-       end
+       var title: nullable String = null is writable
 
        init with_title(href, text, title: String) do
                init(href, text)
@@ -647,12 +690,14 @@ class TplList
        var css_classes = new Array[String]
 
        # Add content wrapped in a <li> element
-       fun add_li(content: Streamable) do elts.add new TplListItem.with_content(content)
+       fun add_li(item: TplListItem) do elts.add item
 
        init do end
 
        init with_classes(classes: Array[String]) do self.css_classes = classes
 
+       fun is_empty: Bool do return elts.is_empty
+
        redef fun rendering do
                if elts.is_empty then return
                add "<ul class='{css_classes.join(" ")}'>"
@@ -690,25 +735,83 @@ class TplListItem
        fun append(content: Streamable) do self.content.add content
 
        redef fun rendering do
-               add "<li class='"
-               for cls in css_classes do add " {cls}"
-               add "'>"
+               add "<li class='{css_classes.join(" ")}'>"
                add content
                add "</li>"
        end
 end
 
+# A Bootstrap tab component that contains `TplTabPanel`.
+class TplTab
+       super Template
+
+       # Panels contained in the tab.
+       var panels = new Array[TplTabPanel]
+
+       # Add a new panel.
+       fun add_panel(panel: TplTabPanel) do panels.add panel
+
+       # CSS classes of the tab component.
+       var css_classes = new Array[String]
+
+       redef fun rendering do
+               add "<div class='tab-content'>"
+               for panel in panels do add panel
+               add "</div>"
+       end
+end
+
+# A panel that goes in a `TplTab`.
+class TplTabPanel
+       super Template
+
+       # CSS classes of the pane element.
+       var css_classes = new Array[String]
+
+       # The panel id.
+       #
+       # Used to show/hide panel.
+       var id: String is noinit
+
+       # The panel name.
+       #
+       # Displayed in the tab header or in the pointing link.
+       var name: Streamable
+
+       # Is the panel visible by default?
+       var is_active = false is writable
+
+       # Body of the panel
+       var content: nullable Streamable = null is writable
+
+       # Get a link pointing to this panel.
+       fun tpl_link_to: Streamable do
+               var lnk = new Template
+               lnk.add "<a data-target='#{id}' data-toggle='pill'>"
+               lnk.add name
+               lnk.add "</a>"
+               return lnk
+       end
+
+       redef fun rendering do
+               add "<div class='tab-pane {css_classes.join(" ")}"
+               if is_active then add "active"
+               add "' id='{id}'>"
+               if content != null then add content.as(not null)
+               add "</div>"
+       end
+end
+
 # A label with a text content
 class TplLabel
        super Template
 
        # Content of the label if any
-       var content: nullable Streamable
+       var content: nullable Streamable = null is writable
 
        # CSS classes of the <span> element
        var css_classes = new Array[String]
 
-       init do end
        init with_content(content: Streamable) do self.content = content
        init with_classes(classes: Array[String]) do self.css_classes = classes
 
@@ -744,11 +847,6 @@ class TagAttribute
        var name: String
        var value: nullable String
 
-       init(name: String, value: nullable String) do
-               self.name = name
-               self.value = value
-       end
-
        redef fun rendering do
                var value = self.value
                if value == null then
@@ -764,7 +862,7 @@ class TplScript
        super Template
 
        var attrs = new Array[TagAttribute]
-       var content: nullable Streamable writable
+       var content: nullable Streamable = null is writable
 
        init do
                attrs.add(new TagAttribute("type", "text/javascript"))
@@ -790,21 +888,20 @@ class TplPiwikScript
        var tracker_url: String
        var site_id: String
 
-       init(tracker_url, site_id: String) do
-               super
-               self.tracker_url = tracker_url
-               self.site_id = site_id
-       end
-
        redef fun render_content do
+               var site_id = self.site_id.to_json
+               var tracker_url = self.tracker_url.trim
+               if tracker_url.chars.last != '/' then tracker_url += "/"
+               tracker_url = "://{tracker_url}".to_json
+
                add "<!-- Piwik -->"
                add "var _paq = _paq || [];"
                add " _paq.push([\"trackPageView\"]);"
                add " _paq.push([\"enableLinkTracking\"]);"
                add "(function() \{"
-               add " var u=((\"https:\" == document.location.protocol) ? \"https\" : \"http\") + \"://{tracker_url}\";"
+               add " var u=((\"https:\" == document.location.protocol) ? \"https\" : \"http\") + {tracker_url};"
                add " _paq.push([\"setTrackerUrl\", u+\"piwik.php\"]);"
-               add " _paq.push([\"setSiteId\", \"{site_id}\"]);"
+               add " _paq.push([\"setSiteId\", {site_id}]);"
                add " var d=document, g=d.createElement(\"script\"), s=d.getElementsByTagName(\"script\")[0]; g.type=\"text/javascript\";"
                add " g.defer=true; g.async=true; g.src=u+\"piwik.js\"; s.parentNode.insertBefore(g,s);"
                add "\})();"