James Moger
2013-11-14 e131717b489b5527e2b9853eaae8ad71ca6ea748
Display common repository documents as tabs on the docs page

Change-Id: I6935fa45598da847936748b474a3da47e94efaac
8 files modified
294 ■■■■ changed files
releases.moxie 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/MarkupProcessor.java 88 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocPage.html 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocPage.java 5 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocsPage.html 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocsPage.java 103 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/SummaryPage.java 2 ●●● patch | view | raw | blame | history
src/main/resources/gitblit.css 69 ●●●● patch | view | raw | blame | history
releases.moxie
@@ -43,6 +43,7 @@
    - Added setting to globally disable anonymous pushes in the receive pack
    - Added a normalized diffstat display to the commit, commitdiff, and compare pages
    - Added GO setting to automatically redirect all http requests to the secure https connector
    - Automatically display common repository root documents as tabs on the docs page
    dependencyChanges:
    - updated to Jetty 7.6.13
    - updated to JGit 3.1.0
@@ -55,6 +56,7 @@
    - { name: 'git.defaultAccessRestriction', defaultValue: 'PUSH' }
    - { name: 'git.mirrorPeriod', defaultValue: '30 mins' }
    - { name: 'web.commitMessageRenderer', defaultValue: 'plain' }
    - { name: 'web.documents', defaultValue: 'readme home index changelog contributing submitting_patches copying license notice authors' }
    - { name: 'web.showBranchGraph', defaultValue: 'true' }
    - { name: 'web.summaryShowReadme', defaultValue: 'false' }
    - { name: 'server.redirectToHttpsPort', defaultValue: 'true' }
src/main/java/com/gitblit/wicket/MarkupProcessor.java
@@ -21,7 +21,10 @@
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
@@ -82,6 +85,21 @@
        return list;
    }
    public List<String> getAllExtensions() {
        List<String> list = getMarkupExtensions();
        list.add("txt");
        list.add("TXT");
        return list;
    }
    private List<String> getRoots() {
        return settings.getStrings(Keys.web.documents);
    }
    private String [] getEncodings() {
        return settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);
    }
    private MarkupSyntax determineSyntax(String documentPath) {
        String ext = StringUtils.getFileExtension(documentPath).toLowerCase();
        if (StringUtils.isEmpty(ext)) {
@@ -105,33 +123,67 @@
        return MarkupSyntax.PLAIN;
    }
    public MarkupDocument parseReadme(Repository r, String repositoryName, String commitId) {
        String readme = null;
        RevCommit commit = JGitUtils.getCommit(r, commitId);
        List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);
    public boolean hasRootDocs(Repository r) {
        List<String> roots = getRoots();
        List<String> extensions = getAllExtensions();
        List<PathModel> paths = JGitUtils.getFilesInPath(r, null, null);
        for (PathModel path : paths) {
            if (!path.isTree()) {
                String name = path.name.toLowerCase();
                if (name.equals("readme") || name.equals("readme.txt")) {
                    readme = path.name;
                    break;
                } else if (name.startsWith("readme.")) {
                    String ext = StringUtils.getFileExtension(name).toLowerCase();
                    if (getMarkupExtensions().contains(ext)) {
                        readme = path.name;
                        break;
                String ext = StringUtils.getFileExtension(path.name).toLowerCase();
                String name = StringUtils.stripFileExtension(path.name).toLowerCase();
                if (roots.contains(name)) {
                    if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
        if (!StringUtils.isEmpty(readme)) {
            String [] encodings = settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);
            String markup = JGitUtils.getStringContent(r, commit.getTree(), readme, encodings);
            return parse(repositoryName, commitId, readme, markup);
    public List<MarkupDocument> getRootDocs(Repository r, String repositoryName, String commitId) {
        List<String> roots = getRoots();
        List<MarkupDocument> list = getDocs(r, repositoryName, commitId, roots);
        return list;
    }
    public MarkupDocument getReadme(Repository r, String repositoryName, String commitId) {
        List<MarkupDocument> list = getDocs(r, repositoryName, commitId, Arrays.asList("readme"));
        if (list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }
        return null;
    private List<MarkupDocument> getDocs(Repository r, String repositoryName, String commitId, List<String> names) {
        List<String> extensions = getAllExtensions();
        String [] encodings = getEncodings();
        Map<String, MarkupDocument> map = new HashMap<String, MarkupDocument>();
        RevCommit commit = JGitUtils.getCommit(r, commitId);
        List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);
        for (PathModel path : paths) {
            if (!path.isTree()) {
                String ext = StringUtils.getFileExtension(path.name).toLowerCase();
                String name = StringUtils.stripFileExtension(path.name).toLowerCase();
                if (names.contains(name)) {
                    if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {
                        String markup = JGitUtils.getStringContent(r, commit.getTree(), path.name, encodings);
                        MarkupDocument doc = parse(repositoryName, commitId, path.name, markup);
                        map.put(name, doc);
                    }
                }
            }
        }
        // return document list in requested order
        List<MarkupDocument> list = new ArrayList<MarkupDocument>();
        for (String name : names) {
            if (map.containsKey(name)) {
                list.add(map.get(name));
            }
        }
        return list;
    }
    public MarkupDocument parse(String repositoryName, String commitId, String documentPath, String markupText) {
src/main/java/com/gitblit/wicket/pages/DocPage.html
@@ -7,8 +7,8 @@
<body>
<wicket:extend>
        <!-- doc nav links -->    
        <div class="page_nav2">
            <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a>  | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a>
        <div style="float: right;" class="page_nav2">
            <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a>  | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
        </div>    
    
        <!--  document content -->
src/main/java/com/gitblit/wicket/pages/DocPage.java
@@ -20,7 +20,6 @@
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -54,7 +53,7 @@
        if (StringUtils.isEmpty(markupText)) {
            String name = StringUtils.stripFileExtension(path);
            List<String> docExtensions = processor.getMarkupExtensions();
            List<String> docExtensions = processor.getAllExtensions();
            for (String ext : docExtensions) {
                String checkName = name + "." + ext;
                markupText = JGitUtils.getStringContent(r, commit.getTree(), checkName, encodings);
@@ -73,8 +72,6 @@
                WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
        add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
                repositoryName, objectId, documentPath)));
        add(new BookmarkablePageLink<Void>("headLink", DocPage.class,
                WicketUtils.newPathParameter(repositoryName, Constants.HEAD, documentPath)));
        MarkupDocument markupDoc = processor.parse(repositoryName, getBestCommitId(commit), documentPath, markupText);
        add(new Label("content", markupDoc.html).setEscapeModelStrings(false));
src/main/java/com/gitblit/wicket/pages/DocsPage.html
@@ -7,23 +7,30 @@
<body>
<wicket:extend>
<div wicket:id="docs"></div>
<div class="docs" wicket:id="docs"></div>
<wicket:fragment wicket:id="indexFragment">
<wicket:fragment wicket:id="tabsFragment">
    <ul class="nav nav-tabs">
        <li class="active"><a data-toggle="tab" href="#home"><wicket:message key="gb.home">[home]</wicket:message></a></li>
        <li wicket:id="tabTitle">
            <a data-toggle="tab" wicket:id="link"><span wicket:id="label">[label]</span></a>
        </li>
        <li><a data-toggle="tab" href="#pages"><wicket:message key="gb.pages">[pages]</wicket:message></a></li>
    </ul>
    <div class="tab-content">
        <div id="home" class="tab-pane active">
            <div class="markdown" wicket:id="index"></div>
        <div wicket:id="tabContent" class="tab-pane">
            <!-- doc nav links -->
            <div style="float: right;" class="docnav">
                <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a>  | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a>
            </div>
            <div class="content" wicket:id="content"></div>
        </div>
        <div id="pages" wicket:id="documents" class="tab-pane"></div>
        <div id="pages" class="tab-pane">
            <div style="padding-top: 5px;" wicket:id="documents"></div>
        </div>
    </div>
</wicket:fragment>
<wicket:fragment wicket:id="noIndexFragment">
    <div style="margin-top:5px;" class="header"><i class="icon-book" style="vertical-align: middle;"></i> <b><span wicket:id="header">[header]</span></b></div>
    <div wicket:id="documents"></div>
</wicket:fragment>
src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -15,13 +15,14 @@
 */
package com.gitblit.wicket.pages;
import java.util.Arrays;
import java.util.List;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
@@ -38,6 +39,7 @@
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
import com.gitblit.wicket.MarkupProcessor.MarkupSyntax;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.LinkPanel;
@@ -51,45 +53,76 @@
        Repository r = getRepository();
        RevCommit head = JGitUtils.getCommit(r, null);
        List<String> extensions = processor.getMarkupExtensions();
        final String commitId = getBestCommitId(head);
        List<String> extensions = processor.getAllExtensions();
        List<PathModel> paths = JGitUtils.getDocuments(r, extensions);
        String doc = null;
        String markup = null;
        String html = null;
        List<String> roots = Arrays.asList("home");
        // try to find a custom index/root page
        for (PathModel path : paths) {
            String name = path.name.toLowerCase();
            name = StringUtils.stripFileExtension(name);
            if (roots.contains(name)) {
                doc = path.name;
                break;
            }
        }
        if (!StringUtils.isEmpty(doc)) {
            // load the document
            String [] encodings = GitBlit.getEncodings();
            markup = JGitUtils.getStringContent(r, head.getTree(), doc, encodings);
            // parse document
            MarkupDocument markupDoc = processor.parse(repositoryName, getBestCommitId(head), doc, markup);
            html = markupDoc.html;
        }
        List<MarkupDocument> roots = processor.getRootDocs(r, repositoryName, commitId);
        Fragment fragment = null;
        if (StringUtils.isEmpty(html)) {
            // no custom index/root, use the standard document list
        if (roots.isEmpty()) {
            // no identified root documents just show the standard document list
            fragment = new Fragment("docs", "noIndexFragment", this);
            fragment.add(new Label("header", getString("gb.docs")));
        } else {
            // custom index/root, use tabbed ui of index/root and document list
            fragment = new Fragment("docs", "indexFragment", this);
            Component content = new Label("index", html).setEscapeModelStrings(false);
            fragment.add(content);
            // root documents, use tabbed ui of index/root and document list
            fragment = new Fragment("docs", "tabsFragment", this);
            ListDataProvider<MarkupDocument> docDp = new ListDataProvider<MarkupDocument>(roots);
            // tab titles
            DataView<MarkupDocument> tabTitles = new DataView<MarkupDocument>("tabTitle", docDp) {
                private static final long serialVersionUID = 1L;
                int counter;
                @Override
                public void populateItem(final Item<MarkupDocument> item) {
                    MarkupDocument doc = item.getModelObject();
                    String file = StringUtils.getLastPathElement(doc.documentPath);
                    file = StringUtils.stripFileExtension(file);
                    String name = file.replace('_', ' ').replace('-',  ' ');
                    ExternalLink link = new ExternalLink("link", "#" + file);
                    link.add(new Label("label", name.toUpperCase()).setRenderBodyOnly(true));
                    item.add(link);
                    if (counter == 0) {
                        counter++;
                        item.add(new SimpleAttributeModifier("class", "active"));
                    }
                }
            };
            fragment.add(tabTitles);
            // tab content
            DataView<MarkupDocument> tabsView = new DataView<MarkupDocument>("tabContent", docDp) {
                private static final long serialVersionUID = 1L;
                int counter;
                @Override
                public void populateItem(final Item<MarkupDocument> item) {
                    MarkupDocument doc = item.getModelObject();
                    // document page links
                    item.add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
                            WicketUtils.newPathParameter(repositoryName, commitId, doc.documentPath)));
                    item.add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
                            WicketUtils.newPathParameter(repositoryName, commitId, doc.documentPath)));
                    item.add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
                            repositoryName, commitId, doc.documentPath)));
                    // document content
                    String file = StringUtils.getLastPathElement(doc.documentPath);
                    file = StringUtils.stripFileExtension(file);
                    Component content = new Label("content", doc.html)
                        .setEscapeModelStrings(false);
                    if (!MarkupSyntax.PLAIN.equals(doc.syntax)) {
                        content.add(new SimpleAttributeModifier("class", "markdown"));
                    }
                    item.add(content);
                    item.add(new SimpleAttributeModifier("id", file));
                    if (counter == 0) {
                        counter++;
                        item.add(new SimpleAttributeModifier("class", "tab-pane active"));
                    }
                }
            };
            fragment.add(tabsView);
        }
        // document list
src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -142,7 +142,7 @@
            // show a readme on the summary page
            RevCommit head = JGitUtils.getCommit(r, null);
            MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
            MarkupDocument markupDoc = processor.parseReadme(r, repositoryName, getBestCommitId(head));
            MarkupDocument markupDoc = processor.getReadme(r, repositoryName, getBestCommitId(head));
            if (markupDoc == null || markupDoc.markup == null) {
                add(new Label("readme").setVisible(false));
            } else {
src/main/resources/gitblit.css
@@ -103,6 +103,16 @@
    margin-bottom: -1px;
}
ul.nav {
    border-color: #ccc;
}
.nav-tabs > .active > a,
.nav-tabs > .active > a:hover {
  border: 1px solid #ccc;
  border-bottom-color: transparent;
}
.navbar .active a {
    background-color: transparent !important;
    outline: 0;
@@ -1507,8 +1517,57 @@
li.L7,
li.L9 { background: #fafafa !important; }
div.markdown {
    max-width: 900px;
div.docs {
    max-width: 880px;
}
div.docs ul.nav {
    margin-bottom: 0px !important;
}
div.docs div.docnav {
    display: inline-block;
    padding: 6px 5px 6px 5px;
    border-left: 1px solid #ccc;
    border-right: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
    border-radius: 0px 0px 3px 3px;
    background-color: #ECF1F4;
    color: #666;
    text-align: left;
    margin-top: -10px;
}
div.docs .content {
    margin-top: 10px;
}
div.docs div.markdown {
    margin-top: 10px;
}
div.markdown {
    line-height: 1.4;
}
div.markdown h1 {
    padding: 0px 0px 4px;
    border-bottom: 1px solid rgb(221, 221, 221);
    margin: 4px 0px 8px;
}
div.markdown h2 {
    padding: 4px 0px;
    border-bottom: 1px solid rgb(238, 238, 238);
    margin: 4px 0px 8px;
}
div.markdown h3 {
    padding: 8px 0px 4px;
}
div.markdown li {
    line-height: 1.4;
}
div.markdown pre {
@@ -1533,11 +1592,7 @@
    background-color: rgb(250, 250, 250);
    border: 1px solid rgb(221, 221, 221);
    border-radius: 3px;
    padding: 0 0.2em;
}
div.markdown a {
    text-decoration: underline;
    padding: 0 0.4em;
}
div.markdown table {