From a7317acec01cde855a9f9f3d2da3dcc49d89aa86 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 25 Oct 2013 08:39:56 -0400
Subject: [PATCH] Support for intra-Markdown linking using [[WikiLinks]] syntax (issue-324)

---
 src/main/java/com/gitblit/wicket/pages/DocsPage.java       |   69 ++++++++++++++---
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.java |   31 +++++++
 src/main/java/com/gitblit/wicket/pages/MarkdownPage.java   |   32 +++++++-
 src/main/java/com/gitblit/wicket/pages/SummaryPage.java    |    4 
 src/main/resources/gitblit.css                             |   17 ++--
 releases.moxie                                             |    1 
 src/main/java/com/gitblit/utils/MarkdownUtils.java         |   14 +++
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties  |    3 
 src/main/java/com/gitblit/wicket/pages/DocsPage.html       |   25 +++++-
 9 files changed, 163 insertions(+), 33 deletions(-)

diff --git a/releases.moxie b/releases.moxie
index b2968a1..e9ec642 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -31,6 +31,7 @@
 	- 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:
diff --git a/src/main/java/com/gitblit/utils/MarkdownUtils.java b/src/main/java/com/gitblit/utils/MarkdownUtils.java
index f9c07fb..1595f65 100644
--- a/src/main/java/com/gitblit/utils/MarkdownUtils.java
+++ b/src/main/java/com/gitblit/utils/MarkdownUtils.java
@@ -22,6 +22,7 @@
 import java.io.StringWriter;
 
 import org.apache.commons.io.IOUtils;
+import org.pegdown.LinkRenderer;
 import org.pegdown.PegDownProcessor;
 
 /**
@@ -55,8 +56,19 @@
 	 * @throws java.text.ParseException
 	 */
 	public static String transformMarkdown(String markdown) {
+		return transformMarkdown(markdown, null);
+	}
+
+	/**
+	 * Returns the html version of the markdown source text.
+	 *
+	 * @param markdown
+	 * @return html version of markdown text
+	 * @throws java.text.ParseException
+	 */
+	public static String transformMarkdown(String markdown, LinkRenderer linkRenderer) {
 		PegDownProcessor pd = new PegDownProcessor(ALL);
-		String html = pd.markdownToHtml(markdown);
+		String html = pd.markdownToHtml(markdown, linkRenderer == null ? new LinkRenderer() : linkRenderer);
 		return html;
 	}
 
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index 4b19f9b..526093a 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -503,4 +503,5 @@
 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
diff --git a/src/main/java/com/gitblit/wicket/pages/DocsPage.html b/src/main/java/com/gitblit/wicket/pages/DocsPage.html
index ad93000..7f1e64e 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/DocsPage.html
@@ -6,10 +6,26 @@
 
 <body>
 <wicket:extend>
-	
-	<!-- header -->
+
+<div wicket:id="docs"></div>
+
+<wicket:fragment wicket:id="indexFragment">
+	<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><a data-toggle="tab" href="#pages"><wicket:message key="gb.pages">[pages]</wicket:message></a></li>
+	</ul>
+	<div class="tab-content">
+		<div id="home" wicket:id="index" class="tab-pane active"></div>
+		<div id="pages" wicket:id="documents" class="tab-pane"></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>
+
+<wicket:fragment wicket:id="documentsFragment">
 	<!-- documents -->	
 	<table style="width:100%" class="pretty">
 		<tr wicket:id="document">
@@ -22,7 +38,8 @@
 				</span>	
 			</td>
 		</tr>
-	</table>	
+	</table>
+</wicket:fragment>
 </wicket:extend>	
 </body>
 </html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/DocsPage.java b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
index eea9595..58471ef 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -15,21 +15,27 @@
  */
 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.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
 
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.models.PathModel;
 import com.gitblit.utils.ByteFormat;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
@@ -42,14 +48,51 @@
 		super(params);
 
 		Repository r = getRepository();
+		RevCommit head = JGitUtils.getCommit(r, null);
 		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
 		List<PathModel> paths = JGitUtils.getDocuments(r, extensions);
 
+		String doc = null;
+		String markdown = 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();
+			if (name.indexOf('.') > -1) {
+				name = name.substring(0, name.lastIndexOf('.'));
+			}
+			if (roots.contains(name)) {
+				doc = path.name;
+				break;
+			}
+		}
+
+		if (!StringUtils.isEmpty(doc)) {
+			// load the document
+			String [] encodings = GitBlit.getEncodings();
+			markdown = JGitUtils.getStringContent(r, head.getTree(), doc, encodings);
+			html = MarkdownUtils.transformMarkdown(markdown, getLinkRenderer());
+		}
+
+		Fragment fragment = null;
+		if (StringUtils.isEmpty(html)) {
+			// no custom index/root, use 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);
+		}
+
+		// document list
+		final String id = getBestCommitId(head);
 		final ByteFormat byteFormat = new ByteFormat();
-
-		add(new Label("header", getString("gb.docs")));
-
-		// documents list
+		Fragment docs = new Fragment("documents", "documentsFragment", this);
 		ListDataProvider<PathModel> pathsDp = new ListDataProvider<PathModel>(paths);
 		DataView<PathModel> pathsView = new DataView<PathModel>("document", pathsDp) {
 			private static final long serialVersionUID = 1L;
@@ -60,23 +103,25 @@
 				PathModel entry = item.getModelObject();
 				item.add(WicketUtils.newImage("docIcon", "file_world_16x16.png"));
 				item.add(new Label("docSize", byteFormat.format(entry.size)));
-				item.add(new LinkPanel("docName", "list", entry.name, BlobPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				item.add(new LinkPanel("docName", "list", entry.name, MarkdownPage.class, WicketUtils
+						.newPathParameter(repositoryName, id, entry.path)));
 
 				// links
-				item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				item.add(new BookmarkablePageLink<Void>("view", MarkdownPage.class, WicketUtils
+						.newPathParameter(repositoryName, id, entry.path)));
 				item.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+						.newPathParameter(repositoryName, id, entry.path)));
 				item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+						.newPathParameter(repositoryName, id, entry.path)));
 				item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+						.newPathParameter(repositoryName, id, entry.path)));
 				WicketUtils.setAlternatingBackground(item, counter);
 				counter++;
 			}
 		};
-		add(pathsView);
+		docs.add(pathsView);
+		fragment.add(docs);
+		add(fragment);
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
index 188a5b4..e0c85cf 100644
--- a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
@@ -16,6 +16,7 @@
 package com.gitblit.wicket.pages;
 
 import java.text.MessageFormat;
+import java.util.List;
 
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
@@ -25,6 +26,7 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 
 import com.gitblit.GitBlit;
+import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
@@ -38,11 +40,32 @@
 	public MarkdownPage(PageParameters params) {
 		super(params);
 
-		final String markdownPath = WicketUtils.getPath(params);
+		final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");
 
 		Repository r = getRepository();
 		RevCommit commit = JGitUtils.getCommit(r, objectId);
 		String [] encodings = GitBlit.getEncodings();
+		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
+
+		// Read raw markdown content and transform it to html
+		String markdownPath = path;
+		String markdownText = JGitUtils.getStringContent(r, commit.getTree(), path, encodings);
+		if (StringUtils.isEmpty(markdownText)) {
+			String name = path;
+			if (path.indexOf('.') > -1) {
+				name = path.substring(0, path.lastIndexOf('.'));
+			}
+
+			for (String ext : extensions) {
+				String checkName = name + "." + ext;
+				markdownText = JGitUtils.getStringContent(r, commit.getTree(), checkName, encodings);
+				if (!StringUtils.isEmpty(markdownText)) {
+					// found it
+					markdownPath = path;
+					break;
+				}
+			}
+		}
 
 		// markdown page links
 		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
@@ -54,13 +77,14 @@
 		add(new BookmarkablePageLink<Void>("headLink", MarkdownPage.class,
 				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, markdownPath)));
 
-		// Read raw markdown content and transform it to html
-		String markdownText = JGitUtils.getStringContent(r, commit.getTree(), markdownPath, encodings);
 		String htmlText;
 		try {
-			htmlText = MarkdownUtils.transformMarkdown(markdownText);
+			htmlText = MarkdownUtils.transformMarkdown(markdownText, getLinkRenderer());
 		} catch (Exception e) {
 			logger.error("failed to transform markdown", e);
+			if (markdownText == null) {
+				markdownText = String.format("Markdown document <b>%1$s</b> not found in <em>%2$s</em>", markdownPath, repositoryName);
+			}
 			markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
 			htmlText = StringUtils.breakLinesForHtml(markdownText);
 		}
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
index 2df0a0e..3b1d296 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -16,6 +16,8 @@
 package com.gitblit.wicket.pages;
 
 import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -43,6 +45,8 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.pegdown.LinkRenderer;
+import org.pegdown.ast.WikiLinkNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -644,6 +648,33 @@
 		return isOwner;
 	}
 
+	/**
+	 * Returns a Pegdown/Markdown link renderer which renders WikiLinks.
+	 *
+	 * @return a link renderer
+	 */
+	protected LinkRenderer getLinkRenderer() {
+		RevCommit head = JGitUtils.getCommit(r, "HEAD");
+		final String id = getBestCommitId(head);
+		LinkRenderer renderer = new LinkRenderer() {
+			@Override
+			public Rendering render(WikiLinkNode node) {
+				try {
+					String path = URLEncoder.encode(node.getText().replace(' ', '-'), "UTF-8");
+					String name = node.getText();
+					if (name.indexOf('/') > -1) {
+						name = name.substring(name.lastIndexOf('/') + 1);
+					}
+					String url = urlFor(MarkdownPage.class, WicketUtils.newPathParameter(repositoryName, id, path)).toString();
+					return new Rendering(url, name);
+				} catch (UnsupportedEncodingException e) {
+					throw new IllegalStateException();
+				}
+			}
+		};
+		return renderer;
+	}
+
 	private class SearchForm extends SessionlessForm<Void> implements Serializable {
 		private static final long serialVersionUID = 1L;
 
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
index 0a13837..827e079 100644
--- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -167,7 +167,7 @@
 				String [] encodings = GitBlit.getEncodings();
 				markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);
 				if (isMarkdown) {
-					htmlText = MarkdownUtils.transformMarkdown(markdownText);
+					htmlText = MarkdownUtils.transformMarkdown(markdownText, getLinkRenderer());
 				} else {
 					htmlText = MarkdownUtils.transformPlainText(markdownText);
 				}
@@ -181,7 +181,7 @@
 		if (StringUtils.isEmpty(htmlText)) {
 			add(new Label("readme").setVisible(false));
 		} else {
-			Fragment fragment = new Fragment("readme", isMarkdown ? "markdownPanel" : "plaintextPanel");
+			Fragment fragment = new Fragment("readme", isMarkdown ? "markdownPanel" : "plaintextPanel", this);
 			fragment.add(new Label("readmeFile", readme));
 			// Add the html to the page
 			Component content = new Label("readmeContent", htmlText).setEscapeModelStrings(false);
diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css
index 23f4312..53113ac 100644
--- a/src/main/resources/gitblit.css
+++ b/src/main/resources/gitblit.css
@@ -490,7 +490,6 @@
 	border:0px;
 	padding: 0;
 	line-height: 1.35em;
-	vertical-align:top;
 }
 
 table {
@@ -821,7 +820,6 @@
 	padding: 3px;
 	border: 1px solid #ddd;
 	border-bottom: 0;
-	border-radius: 3px 3px 0 0;
 	font-weight: bold;
 	font-family: Helvetica,arial,freesans,clean,sans-serif;
 }
@@ -839,9 +837,6 @@
 	margin:0 0 2px;
 	padding:7px 14px;	
 	border:1px solid #ddd;
-	border-radius: 3px;
-	-webkit-border-radius:3px;
-	-moz-border-radius:3px;border-radius:3px;
 }
 
 div.header a, div.commitHeader a {
@@ -1512,9 +1507,13 @@
 li.L7,
 li.L9 { background: #fafafa !important; }
 
+div.markdown {
+	max-width: 900px;
+}
+
 div.markdown pre {
-    background-color: #F5F5F5;
-    border: 1px solid rgba(0, 0, 0, 0.15);
+	background-color: rgb(251, 251, 251);
+	border: 1px solid rgb(221, 221, 221);
     border-radius: 4px 4px 4px 4px;
     display: block;
     font-size: 12px;
@@ -1531,8 +1530,8 @@
 }
 
 div.markdown code {
-	background-color: #ffffe0;
-    border: 1px solid orange;
+	background-color: rgb(251, 251, 251);
+	border: 1px solid rgb(221, 221, 221);
     border-radius: 3px;
     padding: 0 0.2em;
 }

--
Gitblit v1.9.1