diff --git a/project/build.scala b/project/build.scala index a87a457..bcf125e 100644 --- a/project/build.scala +++ b/project/build.scala @@ -44,7 +44,8 @@ "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided", "org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"), "junit" % "junit" % "4.11" % "test", - "org.asciidoctor" % "asciidoctor-java-integration" % "0.1.4" + "org.asciidoctor" % "asciidoctor-java-integration" % "0.1.4", + "net.sourceforge.htmlcleaner" % "htmlcleaner" % "2.7" ), EclipseKeys.withSource := true, javacOptions in compile ++= Seq("-target", "6", "-source", "6"), diff --git a/src/main/scala/view/Asciidoc.scala b/src/main/scala/view/Asciidoc.scala index 978f49f..8d9d376 100644 --- a/src/main/scala/view/Asciidoc.scala +++ b/src/main/scala/view/Asciidoc.scala @@ -1,17 +1,14 @@ package view -import util.StringUtil -import util.ControlUtil._ -import util.Directory._ -import org.parboiled.common.StringUtils -import org.pegdown._ -import org.pegdown.ast._ -import org.pegdown.LinkRenderer.Rendering -import java.text.Normalizer -import java.util.Locale -import scala.collection.JavaConverters._ -import service.{ RequestCache, WikiService } -import org.asciidoctor.{ Asciidoctor, Attributes, AttributesBuilder, OptionsBuilder, SafeMode } +import org.asciidoctor.Asciidoctor +import org.asciidoctor.AttributesBuilder +import org.asciidoctor.OptionsBuilder +import org.asciidoctor.SafeMode +import org.htmlcleaner.HtmlCleaner +import org.htmlcleaner.HtmlNode +import org.htmlcleaner.SimpleHtmlSerializer +import org.htmlcleaner.TagNode +import org.htmlcleaner.TagNodeVisitor object Asciidoc { @@ -20,14 +17,41 @@ /** * Converts Markdown of Wiki pages to HTML. */ - def toHtml(asciidoc: String, repository: service.RepositoryService.RepositoryInfo, + def toHtml(asciidoc: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = { + val options = OptionsBuilder.options() options.safe(SafeMode.SECURE) val attributes = AttributesBuilder.attributes() attributes.showTitle(true) options.attributes(attributes.get()) - asciidoctor.render(asciidoc, options) + val rendered = asciidoctor.render(asciidoc, options) + + // this is always relative to the base dir of the repo, as we currently only render README files. + val relativeUrlPrefix = s"${helpers.url(repository)}/blob/${branch}/" + prefixRelativeUrls(rendered, relativeUrlPrefix) } + + def prefixRelativeUrls(html: String, urlPrefix: String): String = { + val cleaner = new HtmlCleaner() + val node = cleaner.clean(html) + node.traverse(new TagNodeVisitor() { + override def visit(tagNode: TagNode, htmlNode: HtmlNode): Boolean = { + htmlNode match { + case tag: TagNode if tag.getName == "a" => + Option(tag.getAttributeByName("href")) foreach { href => + if (!href.startsWith("/") && !href.startsWith("http://") && !href.startsWith("https://")) { + tag.addAttribute("href", s"${urlPrefix}${href}") + } + } + case _ => + } + // continue traversal + true + } + }) + new SimpleHtmlSerializer(cleaner.getProperties()).getAsString(node) + } + } diff --git a/src/main/scala/view/helpers.scala b/src/main/scala/view/helpers.scala index b60b4b3..4c7ad5b 100644 --- a/src/main/scala/view/helpers.scala +++ b/src/main/scala/view/helpers.scala @@ -27,12 +27,12 @@ def plural(count: Int, singular: String, plural: String = ""): String = if(count == 1) singular else if(plural.isEmpty) singular + "s" else plural - private[this] val renderersBySuffix: Seq[(String, (String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = + private[this] val renderersBySuffix: Seq[(String, (String, String, service.RepositoryService.RepositoryInfo, Boolean, Boolean, app.Context) => Html)] = Seq( - ".md" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".markdown" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".adoc" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, repository, enableWikiLink, enableRefsLink)(context)), - ".asciidoc" -> ((fileContent, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, repository, enableWikiLink, enableRefsLink)(context)) + ".md" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".markdown" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => markdown(fileContent, repository, enableWikiLink, enableRefsLink)(context)), + ".adoc" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)), + ".asciidoc" -> ((fileContent, branch, repository, enableWikiLink, enableRefsLink, context) => asciidoc(fileContent, branch, repository, enableWikiLink, enableRefsLink)(context)) ) def renderableSuffixes: Seq[String] = renderersBySuffix.map(_._1) @@ -44,13 +44,13 @@ enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink)) - def renderMarkup(fileName: String, fileContent: String, + def renderMarkup(fileName: String, fileContent: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = { val fileNameLower = fileName.toLowerCase renderersBySuffix.find { case (suffix, _) => fileNameLower.endsWith(suffix) } match { - case Some((_, handler)) => handler(fileContent, repository, enableWikiLink, enableRefsLink, context) + case Some((_, handler)) => handler(fileContent, branch, repository, enableWikiLink, enableRefsLink, context) case None => Html( s"${ fileContent.split("(\\r\\n)|\\n").map(xml.Utility.escape(_)).mkString("
") @@ -59,9 +59,9 @@ } } - def asciidoc(value: String, repository: service.RepositoryService.RepositoryInfo, + def asciidoc(value: String, branch: String, repository: service.RepositoryService.RepositoryInfo, enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = - Html(Asciidoc.toHtml(value, repository, enableWikiLink, enableRefsLink)) + Html(Asciidoc.toHtml(value, branch, repository, enableWikiLink, enableRefsLink)) /** * Returns <img> which displays the avatar icon for the given user name. diff --git a/src/main/twirl/repo/files.scala.html b/src/main/twirl/repo/files.scala.html index 470021f..5362b14 100644 --- a/src/main/twirl/repo/files.scala.html +++ b/src/main/twirl/repo/files.scala.html @@ -80,7 +80,7 @@ @readme.map { case(file, content) =>
@file.name
-
@renderMarkup(file.name, content, repository, false, false)
+
@renderMarkup(file.name, content, branch, repository, false, false)
} }