]> source.dussan.org Git - gitblit.git/commitdiff
Support for intra-Markdown linking using [[WikiLinks]] syntax (issue-324)
authorJames Moger <james.moger@gitblit.com>
Fri, 25 Oct 2013 03:44:11 +0000 (23:44 -0400)
committerJames Moger <james.moger@gitblit.com>
Fri, 25 Oct 2013 12:39:56 +0000 (08:39 -0400)
All WikiLinks must be specified relative to the root of the repository.
The displayed link text is stripped to just the document name. Spaces in
the document name are replaced with '-' characters; this is consistent with
wiki syntax and Github.

Change-Id: Id3fb1b5441352d9bacc4993a5b85882db113693b

releases.moxie
src/main/java/com/gitblit/utils/MarkdownUtils.java
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
src/main/java/com/gitblit/wicket/pages/DocsPage.html
src/main/java/com/gitblit/wicket/pages/DocsPage.java
src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
src/main/java/com/gitblit/wicket/pages/SummaryPage.java
src/main/resources/gitblit.css

index b2968a11d585c1bc86ed0a805db133a6d81b98c1..e9ec642dff10306a56b9cf7e1cc23461f7589c3c 100644 (file)
@@ -31,6 +31,7 @@ r20: {
        - Added option to render Markdown commit messages (issue-203)
        - Added setting to control creating a repository as --shared on Unix servers (issue-263)
        - Added raw links to the commit, commitdiff, and compare pages (issue-319)
+       - Support intradocument linking in Markdown content using [[WikiLinks]] syntax (issue-324)
        - Added setting to globally disable anonymous pushes in the receive pack
        - Added a normalized diffstat display to the commit, commitdiff, and compare pages
     dependencyChanges:
index f9c07fbc50ed523e590ee8f8b4a9c773f70872eb..1595f6545b06a862d817aa3fb2108a7b269ce9d1 100644 (file)
@@ -22,6 +22,7 @@ import java.io.Reader;
 import java.io.StringWriter;\r
 \r
 import org.apache.commons.io.IOUtils;\r
+import org.pegdown.LinkRenderer;\r
 import org.pegdown.PegDownProcessor;\r
 \r
 /**\r
@@ -55,8 +56,19 @@ public class MarkdownUtils {
         * @throws java.text.ParseException\r
         */\r
        public static String transformMarkdown(String markdown) {\r
+               return transformMarkdown(markdown, null);\r
+       }\r
+\r
+       /**\r
+        * Returns the html version of the markdown source text.\r
+        *\r
+        * @param markdown\r
+        * @return html version of markdown text\r
+        * @throws java.text.ParseException\r
+        */\r
+       public static String transformMarkdown(String markdown, LinkRenderer linkRenderer) {\r
                PegDownProcessor pd = new PegDownProcessor(ALL);\r
-               String html = pd.markdownToHtml(markdown);\r
+               String html = pd.markdownToHtml(markdown, linkRenderer == null ? new LinkRenderer() : linkRenderer);\r
                return html;\r
        }\r
 \r
index 4b19f9b28e6604393b687faea3e6bf40a2aeb291..526093ab99651ee52c8500210da007b145781629 100644 (file)
@@ -503,4 +503,5 @@ gb.todaysActivityNone = today / none
 gb.noActivityToday = there has been no activity today
 gb.anonymousUser= anonymous
 gb.commitMessageRenderer = commit message renderer
-gb.diffStat = {0} insertions & {1} deletions
\ No newline at end of file
+gb.diffStat = {0} insertions & {1} deletions
+gb.home = home
\ No newline at end of file
index ad93000c1a5d421f781b82da04dd4444019ef070..7f1e64e077fbefd8efcaa4749a1f2dbb863461e9 100644 (file)
@@ -6,10 +6,26 @@
 \r
 <body>\r
 <wicket:extend>\r
-       \r
-       <!-- header -->\r
+\r
+<div wicket:id="docs"></div>\r
+\r
+<wicket:fragment wicket:id="indexFragment">\r
+       <ul class="nav nav-tabs">\r
+               <li class="active"><a data-toggle="tab" href="#home"><wicket:message key="gb.home">[home]</wicket:message></a></li>\r
+               <li><a data-toggle="tab" href="#pages"><wicket:message key="gb.pages">[pages]</wicket:message></a></li>\r
+       </ul>\r
+       <div class="tab-content">\r
+               <div id="home" wicket:id="index" class="tab-pane active"></div>\r
+               <div id="pages" wicket:id="documents" class="tab-pane"></div>\r
+       </div>\r
+</wicket:fragment>\r
+\r
+<wicket:fragment wicket:id="noIndexFragment">\r
        <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>\r
-       \r
+       <div wicket:id="documents"></div>\r
+</wicket:fragment>\r
+\r
+<wicket:fragment wicket:id="documentsFragment">\r
        <!-- documents -->      \r
        <table style="width:100%" class="pretty">\r
                <tr wicket:id="document">\r
@@ -22,7 +38,8 @@
                                </span> \r
                        </td>\r
                </tr>\r
-       </table>        \r
+       </table>\r
+</wicket:fragment>\r
 </wicket:extend>       \r
 </body>\r
 </html>
\ No newline at end of file
index eea9595c34023a1f7748784bc1059166c29264ad..58471ef9e3b61f4a801c73dfed36718d3220c558 100644 (file)
  */\r
 package com.gitblit.wicket.pages;\r
 \r
+import java.util.Arrays;\r
 import java.util.List;\r
 \r
+import org.apache.wicket.Component;\r
 import org.apache.wicket.PageParameters;\r
 import org.apache.wicket.markup.html.basic.Label;\r
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;\r
+import org.apache.wicket.markup.html.panel.Fragment;\r
 import org.apache.wicket.markup.repeater.Item;\r
 import org.apache.wicket.markup.repeater.data.DataView;\r
 import org.apache.wicket.markup.repeater.data.ListDataProvider;\r
 import org.eclipse.jgit.lib.Repository;\r
+import org.eclipse.jgit.revwalk.RevCommit;\r
 \r
 import com.gitblit.GitBlit;\r
 import com.gitblit.Keys;\r
 import com.gitblit.models.PathModel;\r
 import com.gitblit.utils.ByteFormat;\r
 import com.gitblit.utils.JGitUtils;\r
+import com.gitblit.utils.MarkdownUtils;\r
+import com.gitblit.utils.StringUtils;\r
 import com.gitblit.wicket.CacheControl;\r
 import com.gitblit.wicket.CacheControl.LastModified;\r
 import com.gitblit.wicket.WicketUtils;\r
@@ -42,14 +48,51 @@ public class DocsPage extends RepositoryPage {
                super(params);\r
 \r
                Repository r = getRepository();\r
+               RevCommit head = JGitUtils.getCommit(r, null);\r
                List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);\r
                List<PathModel> paths = JGitUtils.getDocuments(r, extensions);\r
 \r
-               final ByteFormat byteFormat = new ByteFormat();\r
+               String doc = null;\r
+               String markdown = null;\r
+               String html = null;\r
+\r
+               List<String> roots = Arrays.asList("home");\r
 \r
-               add(new Label("header", getString("gb.docs")));\r
+               // try to find a custom index/root page\r
+               for (PathModel path : paths) {\r
+                       String name = path.name.toLowerCase();\r
+                       if (name.indexOf('.') > -1) {\r
+                               name = name.substring(0, name.lastIndexOf('.'));\r
+                       }\r
+                       if (roots.contains(name)) {\r
+                               doc = path.name;\r
+                               break;\r
+                       }\r
+               }\r
 \r
-               // documents list\r
+               if (!StringUtils.isEmpty(doc)) {\r
+                       // load the document\r
+                       String [] encodings = GitBlit.getEncodings();\r
+                       markdown = JGitUtils.getStringContent(r, head.getTree(), doc, encodings);\r
+                       html = MarkdownUtils.transformMarkdown(markdown, getLinkRenderer());\r
+               }\r
+\r
+               Fragment fragment = null;\r
+               if (StringUtils.isEmpty(html)) {\r
+                       // no custom index/root, use the standard document list\r
+                       fragment = new Fragment("docs", "noIndexFragment", this);\r
+                       fragment.add(new Label("header", getString("gb.docs")));\r
+               } else {\r
+                       // custom index/root, use tabbed ui of index/root and document list\r
+                       fragment = new Fragment("docs", "indexFragment", this);\r
+                       Component content = new Label("index", html).setEscapeModelStrings(false);\r
+                       fragment.add(content);\r
+               }\r
+\r
+               // document list\r
+               final String id = getBestCommitId(head);\r
+               final ByteFormat byteFormat = new ByteFormat();\r
+               Fragment docs = new Fragment("documents", "documentsFragment", this);\r
                ListDataProvider<PathModel> pathsDp = new ListDataProvider<PathModel>(paths);\r
                DataView<PathModel> pathsView = new DataView<PathModel>("document", pathsDp) {\r
                        private static final long serialVersionUID = 1L;\r
@@ -60,23 +103,25 @@ public class DocsPage extends RepositoryPage {
                                PathModel entry = item.getModelObject();\r
                                item.add(WicketUtils.newImage("docIcon", "file_world_16x16.png"));\r
                                item.add(new Label("docSize", byteFormat.format(entry.size)));\r
-                               item.add(new LinkPanel("docName", "list", entry.name, BlobPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                               item.add(new LinkPanel("docName", "list", entry.name, MarkdownPage.class, WicketUtils\r
+                                               .newPathParameter(repositoryName, id, entry.path)));\r
 \r
                                // links\r
-                               item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                               item.add(new BookmarkablePageLink<Void>("view", MarkdownPage.class, WicketUtils\r
+                                               .newPathParameter(repositoryName, id, entry.path)));\r
                                item.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                                               .newPathParameter(repositoryName, id, entry.path)));\r
                                item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                                               .newPathParameter(repositoryName, id, entry.path)));\r
                                item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                                               .newPathParameter(repositoryName, id, entry.path)));\r
                                WicketUtils.setAlternatingBackground(item, counter);\r
                                counter++;\r
                        }\r
                };\r
-               add(pathsView);\r
+               docs.add(pathsView);\r
+               fragment.add(docs);\r
+               add(fragment);\r
        }\r
 \r
        @Override\r
index 188a5b4b5ebeb8f4898e80ec11c224e1e40b0e74..e0c85cf59ad5af7d52ad0d4ff9eccebc3144c08e 100644 (file)
@@ -16,6 +16,7 @@
 package com.gitblit.wicket.pages;\r
 \r
 import java.text.MessageFormat;\r
+import java.util.List;\r
 \r
 import org.apache.wicket.PageParameters;\r
 import org.apache.wicket.markup.html.basic.Label;\r
@@ -25,6 +26,7 @@ import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;\r
 \r
 import com.gitblit.GitBlit;\r
+import com.gitblit.Keys;\r
 import com.gitblit.utils.JGitUtils;\r
 import com.gitblit.utils.MarkdownUtils;\r
 import com.gitblit.utils.StringUtils;\r
@@ -38,11 +40,32 @@ public class MarkdownPage extends RepositoryPage {
        public MarkdownPage(PageParameters params) {\r
                super(params);\r
 \r
-               final String markdownPath = WicketUtils.getPath(params);\r
+               final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");\r
 \r
                Repository r = getRepository();\r
                RevCommit commit = JGitUtils.getCommit(r, objectId);\r
                String [] encodings = GitBlit.getEncodings();\r
+               List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);\r
+\r
+               // Read raw markdown content and transform it to html\r
+               String markdownPath = path;\r
+               String markdownText = JGitUtils.getStringContent(r, commit.getTree(), path, encodings);\r
+               if (StringUtils.isEmpty(markdownText)) {\r
+                       String name = path;\r
+                       if (path.indexOf('.') > -1) {\r
+                               name = path.substring(0, path.lastIndexOf('.'));\r
+                       }\r
+\r
+                       for (String ext : extensions) {\r
+                               String checkName = name + "." + ext;\r
+                               markdownText = JGitUtils.getStringContent(r, commit.getTree(), checkName, encodings);\r
+                               if (!StringUtils.isEmpty(markdownText)) {\r
+                                       // found it\r
+                                       markdownPath = path;\r
+                                       break;\r
+                               }\r
+                       }\r
+               }\r
 \r
                // markdown page links\r
                add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,\r
@@ -54,13 +77,14 @@ public class MarkdownPage extends RepositoryPage {
                add(new BookmarkablePageLink<Void>("headLink", MarkdownPage.class,\r
                                WicketUtils.newPathParameter(repositoryName, Constants.HEAD, markdownPath)));\r
 \r
-               // Read raw markdown content and transform it to html\r
-               String markdownText = JGitUtils.getStringContent(r, commit.getTree(), markdownPath, encodings);\r
                String htmlText;\r
                try {\r
-                       htmlText = MarkdownUtils.transformMarkdown(markdownText);\r
+                       htmlText = MarkdownUtils.transformMarkdown(markdownText, getLinkRenderer());\r
                } catch (Exception e) {\r
                        logger.error("failed to transform markdown", e);\r
+                       if (markdownText == null) {\r
+                               markdownText = String.format("Markdown document <b>%1$s</b> not found in <em>%2$s</em>", markdownPath, repositoryName);\r
+                       }\r
                        markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);\r
                        htmlText = StringUtils.breakLinesForHtml(markdownText);\r
                }\r
index 2df0a0e0b11294158a675324ee8f47a60d6e99e0..3b1d296b11d92844585e6a6bdc1a1bf627f197cb 100644 (file)
@@ -16,6 +16,8 @@
 package com.gitblit.wicket.pages;\r
 \r
 import java.io.Serializable;\r
+import java.io.UnsupportedEncodingException;\r
+import java.net.URLEncoder;\r
 import java.text.MessageFormat;\r
 import java.util.ArrayList;\r
 import java.util.Arrays;\r
@@ -43,6 +45,8 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.lib.PersonIdent;\r
 import org.eclipse.jgit.lib.Repository;\r
 import org.eclipse.jgit.revwalk.RevCommit;\r
+import org.pegdown.LinkRenderer;\r
+import org.pegdown.ast.WikiLinkNode;\r
 import org.slf4j.Logger;\r
 import org.slf4j.LoggerFactory;\r
 \r
@@ -644,6 +648,33 @@ public abstract class RepositoryPage extends RootPage {
                return isOwner;\r
        }\r
 \r
+       /**\r
+        * Returns a Pegdown/Markdown link renderer which renders WikiLinks.\r
+        *\r
+        * @return a link renderer\r
+        */\r
+       protected LinkRenderer getLinkRenderer() {\r
+               RevCommit head = JGitUtils.getCommit(r, "HEAD");\r
+               final String id = getBestCommitId(head);\r
+               LinkRenderer renderer = new LinkRenderer() {\r
+                       @Override\r
+                       public Rendering render(WikiLinkNode node) {\r
+                               try {\r
+                                       String path = URLEncoder.encode(node.getText().replace(' ', '-'), "UTF-8");\r
+                                       String name = node.getText();\r
+                                       if (name.indexOf('/') > -1) {\r
+                                               name = name.substring(name.lastIndexOf('/') + 1);\r
+                                       }\r
+                                       String url = urlFor(MarkdownPage.class, WicketUtils.newPathParameter(repositoryName, id, path)).toString();\r
+                                       return new Rendering(url, name);\r
+                               } catch (UnsupportedEncodingException e) {\r
+                                       throw new IllegalStateException();\r
+                               }\r
+                       }\r
+               };\r
+               return renderer;\r
+       }\r
+\r
        private class SearchForm extends SessionlessForm<Void> implements Serializable {\r
                private static final long serialVersionUID = 1L;\r
 \r
index 0a13837171185b7297212c0b093f8a758203630b..827e079965ccf665c03890fafbfee5be448e56e0 100644 (file)
@@ -167,7 +167,7 @@ public class SummaryPage extends RepositoryPage {
                                String [] encodings = GitBlit.getEncodings();\r
                                markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);\r
                                if (isMarkdown) {\r
-                                       htmlText = MarkdownUtils.transformMarkdown(markdownText);\r
+                                       htmlText = MarkdownUtils.transformMarkdown(markdownText, getLinkRenderer());\r
                                } else {\r
                                        htmlText = MarkdownUtils.transformPlainText(markdownText);\r
                                }\r
@@ -181,7 +181,7 @@ public class SummaryPage extends RepositoryPage {
                if (StringUtils.isEmpty(htmlText)) {\r
                        add(new Label("readme").setVisible(false));\r
                } else {\r
-                       Fragment fragment = new Fragment("readme", isMarkdown ? "markdownPanel" : "plaintextPanel");\r
+                       Fragment fragment = new Fragment("readme", isMarkdown ? "markdownPanel" : "plaintextPanel", this);\r
                        fragment.add(new Label("readmeFile", readme));\r
                        // Add the html to the page\r
                        Component content = new Label("readmeContent", htmlText).setEscapeModelStrings(false);\r
index 23f4312ee671154dfcd9a426209be09672bcfa48..53113acc4bcd64589ab278150c7aaa5ad4b6d3db 100644 (file)
@@ -490,7 +490,6 @@ pre, code, pre.prettyprint, pre.plainprint {
        border:0px;\r
        padding: 0;\r
        line-height: 1.35em;\r
-       vertical-align:top;\r
 }\r
 \r
 table {\r
@@ -821,7 +820,6 @@ div.header {
        padding: 3px;\r
        border: 1px solid #ddd;\r
        border-bottom: 0;\r
-       border-radius: 3px 3px 0 0;\r
        font-weight: bold;\r
        font-family: Helvetica,arial,freesans,clean,sans-serif;\r
 }\r
@@ -839,9 +837,6 @@ div.commitHeader {
        margin:0 0 2px;\r
        padding:7px 14px;       \r
        border:1px solid #ddd;\r
-       border-radius: 3px;\r
-       -webkit-border-radius:3px;\r
-       -moz-border-radius:3px;border-radius:3px;\r
 }\r
 \r
 div.header a, div.commitHeader a {\r
@@ -1512,9 +1507,13 @@ li.L5,
 li.L7,\r
 li.L9 { background: #fafafa !important; }\r
 \r
+div.markdown {\r
+       max-width: 900px;\r
+}\r
+\r
 div.markdown pre {\r
-    background-color: #F5F5F5;\r
-    border: 1px solid rgba(0, 0, 0, 0.15);\r
+       background-color: rgb(251, 251, 251);\r
+       border: 1px solid rgb(221, 221, 221);\r
     border-radius: 4px 4px 4px 4px;\r
     display: block;\r
     font-size: 12px;\r
@@ -1531,8 +1530,8 @@ div.markdown pre code {
 }\r
 \r
 div.markdown code {\r
-       background-color: #ffffe0;\r
-    border: 1px solid orange;\r
+       background-color: rgb(251, 251, 251);\r
+       border: 1px solid rgb(221, 221, 221);\r
     border-radius: 3px;\r
     padding: 0 0.2em;\r
 }\r