From 667ef680c198a7c66b3f2bdbc20e12f6ba6b9cde Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 20 Sep 2015 03:51:46 +0900 Subject: [PATCH 01/10] Switch markdown processor to markedj from pegdown --- project/build.scala | 3 +- .../scala/gitbucket/core/view/Markdown.scala | 473 +++++++++--------- 2 files changed, 239 insertions(+), 237 deletions(-) diff --git a/project/build.scala b/project/build.scala index 6249a92f9..9a19a679a 100644 --- a/project/build.scala +++ b/project/build.scala @@ -50,7 +50,8 @@ object MyBuild extends Build { "org.json4s" %% "json4s-jackson" % "3.2.11", "jp.sf.amateras" %% "scalatra-forms" % "0.1.0", "commons-io" % "commons-io" % "2.4", - "org.pegdown" % "pegdown" % "1.5.0", +// "org.pegdown" % "pegdown" % "1.5.0", + "io.github.gitbucket" % "markedj" % "1.0.0", "org.apache.commons" % "commons-compress" % "1.9", "org.apache.commons" % "commons-email" % "1.3.3", "org.apache.httpcomponents" % "httpclient" % "4.3.6", diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 3a80b8f36..ebdf97698 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -7,10 +7,11 @@ import java.util.regex.Pattern import gitbucket.core.controller.Context import gitbucket.core.service.{RepositoryService, RequestCache, WikiService} import gitbucket.core.util.StringUtil -import org.parboiled.common.StringUtils -import org.pegdown.LinkRenderer.Rendering -import org.pegdown._ -import org.pegdown.ast._ +import io.github.gitbucket.markedj._ +//import org.parboiled.common.StringUtils +//import org.pegdown.LinkRenderer.Rendering +//import org.pegdown._ +//import org.pegdown.ast._ import scala.collection.JavaConverters._ @@ -35,241 +36,241 @@ object Markdown { enableTaskList: Boolean = false, hasWritePermission: Boolean = false, pages: List[String] = Nil)(implicit context: Context): String = { - - // escape issue id - val s = if(enableRefsLink){ - markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2") - } else markdown - - // escape task list - val source = if(enableTaskList){ - GitBucketHtmlSerializer.escapeTaskList(s) - } else s - - val rootNode = new PegDownProcessor( - Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | - Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML | Extensions.STRIKETHROUGH - ).parseMarkdown(source.toCharArray) - - new GitBucketHtmlSerializer( - markdown, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, - hasWritePermission, pages - ).toHtml(rootNode) + Marked.marked(markdown, new Options()) +// // escape issue id +// val s = if(enableRefsLink){ +// markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2") +// } else markdown +// +// // escape task list +// val source = if(enableTaskList){ +// GitBucketHtmlSerializer.escapeTaskList(s) +// } else s +// +// val rootNode = new PegDownProcessor( +// Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | +// Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML | Extensions.STRIKETHROUGH +// ).parseMarkdown(source.toCharArray) +// +// new GitBucketHtmlSerializer( +// markdown, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, +// hasWritePermission, pages +// ).toHtml(rootNode) } } -class GitBucketLinkRender( - context: Context, - repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, - pages: List[String]) extends LinkRenderer with WikiService { - - override def render(node: WikiLinkNode): Rendering = { - if(enableWikiLink){ - try { - val text = node.getText - val (label, page) = if(text.contains('|')){ - val i = text.indexOf('|') - (text.substring(0, i), text.substring(i + 1)) - } else { - (text, text) - } - - val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) - - if(pages.contains(page)){ - new Rendering(url, label) - } else { - new Rendering(url, label).withAttribute("class", "absent") - } - } catch { - case e: java.io.UnsupportedEncodingException => throw new IllegalStateException - } - } else { - super.render(node) - } - } -} - -class GitBucketVerbatimSerializer extends VerbatimSerializer { - def serialize(node: VerbatimNode, printer: Printer): Unit = { - printer.println.print("") - var text: String = node.getText - while (text.charAt(0) == '\n') { - printer.print("
") - text = text.substring(1) - } - printer.printEncoded(text) - printer.print("") - } -} - -class GitBucketHtmlSerializer( - markdown: String, - repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, - enableRefsLink: Boolean, - enableAnchor: Boolean, - enableTaskList: Boolean, - hasWritePermission: Boolean, - pages: List[String] - )(implicit val context: Context) extends ToHtmlSerializer( - new GitBucketLinkRender(context, repository, enableWikiLink, pages), - Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava - ) with LinkConverter with RequestCache { - - override protected def printImageTag(rendering: LinkRenderer.Rendering): Unit = { - printer.print("") - .print("\"").printEncoded(rendering.text).print("\"/") - } - - override protected def printLink(rendering: LinkRenderer.Rendering): Unit = { - printer.print('<').print('a') - printAttribute("href", fixUrl(rendering.href)) - for (attr <- rendering.attributes.asScala) { - printAttribute(attr.name, attr.value) - } - printer.print('>').print(rendering.text).print("") - } - - private def fixUrl(url: String, isImage: Boolean = false): String = { - if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){ - url - } else if(url.startsWith("#")){ - ("#" + GitBucketHtmlSerializer.generateAnchorName(url.substring(1))) - } else if(!enableWikiLink){ - if(context.currentPath.contains("/blob/")){ - url + (if(isImage) "?raw=true" else "") - } else if(context.currentPath.contains("/tree/")){ - val paths = context.currentPath.split("/") - val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") - } else { - val paths = context.currentPath.split("/") - val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") - } - } else { - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url - } - } - - private def printAttribute(name: String, value: String): Unit = { - printer.print(' ').print(name).print('=').print('"').print(value).print('"') - } - - private def printHeaderTag(node: HeaderNode): Unit = { - val tag = s"h${node.getLevel}" - val child = node.getChildren.asScala.headOption - val anchorName = child match { - case Some(x: AnchorLinkNode) => x.getName - case Some(x: TextNode) => x.getText - case _ => GitBucketHtmlSerializer.generateAnchorName(extractText(node)) // TODO - } - - printer.print(s"""<$tag class="markdown-head">""") - if(enableAnchor){ - printer.print(s"""""") - printer.print(s"""""") - } - child match { - case Some(x: AnchorLinkNode) => printer.print(x.getText) - case _ => visitChildren(node) - } - printer.print(s"") - } - - private def extractText(node: Node): String = { - val sb = new StringBuilder() - node.getChildren.asScala.map { - case x: TextNode => sb.append(x.getText) - case x: Node => sb.append(extractText(x)) - } - sb.toString() - } - - override def visit(node: HeaderNode): Unit = { - printHeaderTag(node) - } - - override def visit(node: TextNode): Unit = { - // convert commit id and username to link. - val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText - - // convert task list to checkbox. - val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission) else t - - if (abbreviations.isEmpty) { - printer.print(text) - } else { - printWithAbbreviations(text) - } - } - - override def visit(node: VerbatimNode) { - val printer = new Printer() - val serializer = verbatimSerializers.get(VerbatimSerializer.DEFAULT) - serializer.serialize(node, printer) - val html = printer.getString - - // convert commit id and username to link. - val t = if(enableRefsLink) convertRefsLinks(html, repository, "issue:", escapeHtml = false) else html - - this.printer.print(t) - } - - override def visit(node: BulletListNode): Unit = { - if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { - printer.println().print("""") - } else { - printIndentedTag(node, "ul") - } - } - - override def visit(node: ListItemNode): Unit = { - if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { - printer.println() - printer.print("""
  • """) - visitChildren(node) - printer.print("
  • ") - } else { - printer.println() - printTag(node, "li") - } - } - - override def visit(node: ExpLinkNode) { - printLink(linkRenderer.render(node, printLinkChildrenToString(node))) - } - - def printLinkChildrenToString(node: SuperNode) = { - val priorPrinter = printer - printer = new Printer() - visitLinkChildren(node) - val result = printer.getString() - printer = priorPrinter - result - } - - def visitLinkChildren(node: SuperNode) { - import scala.collection.JavaConversions._ - node.getChildren.foreach(child => child match { - case node: ExpImageNode => visitLinkChild(node) - case node: SuperNode => visitLinkChildren(node) - case _ => child.accept(this) - }) - } - - def visitLinkChild(node: ExpImageNode) { - printer.print("\"").printEncoded(printChildrenToString(node)).print("\"/") - } -} +//class GitBucketLinkRender( +// context: Context, +// repository: RepositoryService.RepositoryInfo, +// enableWikiLink: Boolean, +// pages: List[String]) extends LinkRenderer with WikiService { +// +// override def render(node: WikiLinkNode): Rendering = { +// if(enableWikiLink){ +// try { +// val text = node.getText +// val (label, page) = if(text.contains('|')){ +// val i = text.indexOf('|') +// (text.substring(0, i), text.substring(i + 1)) +// } else { +// (text, text) +// } +// +// val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) +// +// if(pages.contains(page)){ +// new Rendering(url, label) +// } else { +// new Rendering(url, label).withAttribute("class", "absent") +// } +// } catch { +// case e: java.io.UnsupportedEncodingException => throw new IllegalStateException +// } +// } else { +// super.render(node) +// } +// } +//} +// +//class GitBucketVerbatimSerializer extends VerbatimSerializer { +// def serialize(node: VerbatimNode, printer: Printer): Unit = { +// printer.println.print("") +// var text: String = node.getText +// while (text.charAt(0) == '\n') { +// printer.print("
    ") +// text = text.substring(1) +// } +// printer.printEncoded(text) +// printer.print("") +// } +//} +// +//class GitBucketHtmlSerializer( +// markdown: String, +// repository: RepositoryService.RepositoryInfo, +// enableWikiLink: Boolean, +// enableRefsLink: Boolean, +// enableAnchor: Boolean, +// enableTaskList: Boolean, +// hasWritePermission: Boolean, +// pages: List[String] +// )(implicit val context: Context) extends ToHtmlSerializer( +// new GitBucketLinkRender(context, repository, enableWikiLink, pages), +// Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava +// ) with LinkConverter with RequestCache { +// +// override protected def printImageTag(rendering: LinkRenderer.Rendering): Unit = { +// printer.print("") +// .print("\"").printEncoded(rendering.text).print("\"/") +// } +// +// override protected def printLink(rendering: LinkRenderer.Rendering): Unit = { +// printer.print('<').print('a') +// printAttribute("href", fixUrl(rendering.href)) +// for (attr <- rendering.attributes.asScala) { +// printAttribute(attr.name, attr.value) +// } +// printer.print('>').print(rendering.text).print("") +// } +// +// private def fixUrl(url: String, isImage: Boolean = false): String = { +// if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){ +// url +// } else if(url.startsWith("#")){ +// ("#" + GitBucketHtmlSerializer.generateAnchorName(url.substring(1))) +// } else if(!enableWikiLink){ +// if(context.currentPath.contains("/blob/")){ +// url + (if(isImage) "?raw=true" else "") +// } else if(context.currentPath.contains("/tree/")){ +// val paths = context.currentPath.split("/") +// val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch +// repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") +// } else { +// val paths = context.currentPath.split("/") +// val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch +// repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") +// } +// } else { +// repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url +// } +// } +// +// private def printAttribute(name: String, value: String): Unit = { +// printer.print(' ').print(name).print('=').print('"').print(value).print('"') +// } +// +// private def printHeaderTag(node: HeaderNode): Unit = { +// val tag = s"h${node.getLevel}" +// val child = node.getChildren.asScala.headOption +// val anchorName = child match { +// case Some(x: AnchorLinkNode) => x.getName +// case Some(x: TextNode) => x.getText +// case _ => GitBucketHtmlSerializer.generateAnchorName(extractText(node)) // TODO +// } +// +// printer.print(s"""<$tag class="markdown-head">""") +// if(enableAnchor){ +// printer.print(s"""""") +// printer.print(s"""""") +// } +// child match { +// case Some(x: AnchorLinkNode) => printer.print(x.getText) +// case _ => visitChildren(node) +// } +// printer.print(s"") +// } +// +// private def extractText(node: Node): String = { +// val sb = new StringBuilder() +// node.getChildren.asScala.map { +// case x: TextNode => sb.append(x.getText) +// case x: Node => sb.append(extractText(x)) +// } +// sb.toString() +// } +// +// override def visit(node: HeaderNode): Unit = { +// printHeaderTag(node) +// } +// +// override def visit(node: TextNode): Unit = { +// // convert commit id and username to link. +// val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText +// +// // convert task list to checkbox. +// val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission) else t +// +// if (abbreviations.isEmpty) { +// printer.print(text) +// } else { +// printWithAbbreviations(text) +// } +// } +// +// override def visit(node: VerbatimNode) { +// val printer = new Printer() +// val serializer = verbatimSerializers.get(VerbatimSerializer.DEFAULT) +// serializer.serialize(node, printer) +// val html = printer.getString +// +// // convert commit id and username to link. +// val t = if(enableRefsLink) convertRefsLinks(html, repository, "issue:", escapeHtml = false) else html +// +// this.printer.print(t) +// } +// +// override def visit(node: BulletListNode): Unit = { +// if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { +// printer.println().print("""
      """).indent(+2) +// visitChildren(node) +// printer.indent(-2).println().print("
    ") +// } else { +// printIndentedTag(node, "ul") +// } +// } +// +// override def visit(node: ListItemNode): Unit = { +// if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { +// printer.println() +// printer.print("""
  • """) +// visitChildren(node) +// printer.print("
  • ") +// } else { +// printer.println() +// printTag(node, "li") +// } +// } +// +// override def visit(node: ExpLinkNode) { +// printLink(linkRenderer.render(node, printLinkChildrenToString(node))) +// } +// +// def printLinkChildrenToString(node: SuperNode) = { +// val priorPrinter = printer +// printer = new Printer() +// visitLinkChildren(node) +// val result = printer.getString() +// printer = priorPrinter +// result +// } +// +// def visitLinkChildren(node: SuperNode) { +// import scala.collection.JavaConversions._ +// node.getChildren.foreach(child => child match { +// case node: ExpImageNode => visitLinkChild(node) +// case node: SuperNode => visitLinkChildren(node) +// case _ => child.accept(this) +// }) +// } +// +// def visitLinkChild(node: ExpImageNode) { +// printer.print("\"").printEncoded(printChildrenToString(node)).print("\"/") +// } +//} object GitBucketHtmlSerializer { From 8f7c5fc9226282525d7388d53b5619a0ad131d71 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 20 Sep 2015 04:45:46 +0900 Subject: [PATCH 02/10] Fix code and ordered list style --- project/build.scala | 5 ++- .../scala/gitbucket/core/view/Markdown.scala | 45 ++++++++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/project/build.scala b/project/build.scala index 9a19a679a..3e10eab93 100644 --- a/project/build.scala +++ b/project/build.scala @@ -38,7 +38,8 @@ object MyBuild extends Build { scalaVersion := ScalaVersion, resolvers ++= Seq( Classpaths.typesafeReleases, - "amateras-repo" at "http://amateras.sourceforge.jp/mvn/" + "amateras-repo" at "http://amateras.sourceforge.jp/mvn/", + "amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/" ), scalacOptions := Seq("-deprecation", "-language:postfixOps"), libraryDependencies ++= Seq( @@ -51,7 +52,7 @@ object MyBuild extends Build { "jp.sf.amateras" %% "scalatra-forms" % "0.1.0", "commons-io" % "commons-io" % "2.4", // "org.pegdown" % "pegdown" % "1.5.0", - "io.github.gitbucket" % "markedj" % "1.0.0", + "io.github.gitbucket" % "markedj" % "1.0.1-SNAPSHOT", "org.apache.commons" % "commons-compress" % "1.9", "org.apache.commons" % "commons-email" % "1.3.3", "org.apache.httpcomponents" % "httpclient" % "4.3.6", diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index ebdf97698..aefdef5f7 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -1,19 +1,14 @@ package gitbucket.core.view import java.text.Normalizer -import java.util.Locale +import java.util.{Optional, Locale} import java.util.regex.Pattern import gitbucket.core.controller.Context import gitbucket.core.service.{RepositoryService, RequestCache, WikiService} import gitbucket.core.util.StringUtil import io.github.gitbucket.markedj._ -//import org.parboiled.common.StringUtils -//import org.pegdown.LinkRenderer.Rendering -//import org.pegdown._ -//import org.pegdown.ast._ - -import scala.collection.JavaConverters._ +import io.github.gitbucket.markedj.Utils._ object Markdown { @@ -36,16 +31,19 @@ object Markdown { enableTaskList: Boolean = false, hasWritePermission: Boolean = false, pages: List[String] = Nil)(implicit context: Context): String = { - Marked.marked(markdown, new Options()) -// // escape issue id -// val s = if(enableRefsLink){ -// markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2") -// } else markdown -// -// // escape task list -// val source = if(enableTaskList){ -// GitBucketHtmlSerializer.escapeTaskList(s) -// } else s + // escape issue id + val s = if(enableRefsLink){ + markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2") + } else markdown + + // escape task list + val source = if(enableTaskList){ + GitBucketHtmlSerializer.escapeTaskList(s) + } else s + + val options = new Options() + Marked.marked(source, options, new GitBucketMarkedRenderer(options)) + // // val rootNode = new PegDownProcessor( // Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | @@ -59,6 +57,19 @@ object Markdown { } } +class GitBucketMarkedRenderer(options: Options) extends Renderer(options) { + + override def code(code: String, lang: Optional[String], escaped: Boolean): String = { + "
    " +
    +      (if(escaped) code else escape(code, true)) + "
    " + } + + override def text(text: String): String = { + text + } + +} + //class GitBucketLinkRender( // context: Context, // repository: RepositoryService.RepositoryInfo, From 1ea1e74a0c9d6aa3e7b99e8f2d87083fd4aa50dd Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 20 Sep 2015 12:49:49 +0900 Subject: [PATCH 03/10] Fix links --- .../scala/gitbucket/core/view/Markdown.scala | 146 ++++++++---------- 1 file changed, 61 insertions(+), 85 deletions(-) diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index aefdef5f7..5d274f6c2 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -42,22 +42,15 @@ object Markdown { } else s val options = new Options() - Marked.marked(source, options, new GitBucketMarkedRenderer(options)) - -// -// val rootNode = new PegDownProcessor( -// Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | -// Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML | Extensions.STRIKETHROUGH -// ).parseMarkdown(source.toCharArray) -// -// new GitBucketHtmlSerializer( -// markdown, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, -// hasWritePermission, pages -// ).toHtml(rootNode) + val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission, pages) + Marked.marked(source, options, renderer) } } -class GitBucketMarkedRenderer(options: Options) extends Renderer(options) { +class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean, + pages: List[String]) + (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { override def code(code: String, lang: Optional[String], escaped: Boolean): String = { "
    " +
    @@ -65,7 +58,61 @@ class GitBucketMarkedRenderer(options: Options) extends Renderer(options) {
       }
     
       override def text(text: String): String = {
    -    text
    +    // convert commit id and username to link.
    +    val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "issue:") else text
    +
    +    // convert task list to checkbox.
    +    val t2 = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t1, hasWritePermission) else t1
    +
    +    t2
    +  }
    +
    +  override def image(href: String, title: Optional[String], text: String): String = {
    +    super.image(fixUrl(href, true), title, text);
    +  }
    +
    +  override def nolink(text: String): String = {
    +    if(enableWikiLink && text.startsWith("[") && text.endsWith("]")){
    +      val link = text.replaceAll("(^\\[\\[|\\]\\]$)", "")
    +
    +      val (label, page) = if(link.contains('|')){
    +        val i = link.indexOf('|')
    +        (link.substring(0, i), link.substring(i + 1))
    +      } else {
    +        (link, link)
    +      }
    +
    +      val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page)
    +      if(pages.contains(page)){
    +        "" + escape(page) + ""
    +      } else {
    +        "" + escape(page) + ""
    +      }
    +    } else {
    +      escape(text)
    +    }
    +  }
    +
    +  private def fixUrl(url: String, isImage: Boolean = false): String = {
    +    if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){
    +      url
    +    } else if(url.startsWith("#")){
    +      ("#" + GitBucketHtmlSerializer.generateAnchorName(url.substring(1)))
    +    } else if(!enableWikiLink){
    +      if(context.currentPath.contains("/blob/")){
    +        url + (if(isImage) "?raw=true" else "")
    +      } else if(context.currentPath.contains("/tree/")){
    +        val paths = context.currentPath.split("/")
    +        val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch
    +        repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
    +      } else {
    +        val paths = context.currentPath.split("/")
    +        val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch
    +        repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
    +      }
    +    } else {
    +      repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
    +    }
       }
     
     }
    @@ -76,50 +123,6 @@ class GitBucketMarkedRenderer(options: Options) extends Renderer(options) {
     //    enableWikiLink: Boolean,
     //    pages: List[String]) extends LinkRenderer with WikiService {
     //
    -//  override def render(node: WikiLinkNode): Rendering = {
    -//    if(enableWikiLink){
    -//      try {
    -//        val text = node.getText
    -//        val (label, page) = if(text.contains('|')){
    -//          val i = text.indexOf('|')
    -//          (text.substring(0, i), text.substring(i + 1))
    -//        } else {
    -//          (text, text)
    -//        }
    -//
    -//        val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page)
    -//
    -//        if(pages.contains(page)){
    -//          new Rendering(url, label)
    -//        } else {
    -//          new Rendering(url, label).withAttribute("class", "absent")
    -//        }
    -//      } catch {
    -//        case e: java.io.UnsupportedEncodingException => throw new IllegalStateException
    -//      }
    -//    } else {
    -//      super.render(node)
    -//    }
    -//  }
    -//}
    -//
    -//class GitBucketVerbatimSerializer extends VerbatimSerializer {
    -//  def serialize(node: VerbatimNode, printer: Printer): Unit = {
    -//    printer.println.print("")
    -//    var text: String = node.getText
    -//    while (text.charAt(0) == '\n') {
    -//      printer.print("
    ") -// text = text.substring(1) -// } -// printer.printEncoded(text) -// printer.print("
    ") -// } -//} -// //class GitBucketHtmlSerializer( // markdown: String, // repository: RepositoryService.RepositoryInfo, @@ -134,11 +137,6 @@ class GitBucketMarkedRenderer(options: Options) extends Renderer(options) { // Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava // ) with LinkConverter with RequestCache { // -// override protected def printImageTag(rendering: LinkRenderer.Rendering): Unit = { -// printer.print("") -// .print("\"").printEncoded(rendering.text).print("\"/") -// } -// // override protected def printLink(rendering: LinkRenderer.Rendering): Unit = { // printer.print('<').print('a') // printAttribute("href", fixUrl(rendering.href)) @@ -148,28 +146,6 @@ class GitBucketMarkedRenderer(options: Options) extends Renderer(options) { // printer.print('>').print(rendering.text).print("") // } // -// private def fixUrl(url: String, isImage: Boolean = false): String = { -// if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){ -// url -// } else if(url.startsWith("#")){ -// ("#" + GitBucketHtmlSerializer.generateAnchorName(url.substring(1))) -// } else if(!enableWikiLink){ -// if(context.currentPath.contains("/blob/")){ -// url + (if(isImage) "?raw=true" else "") -// } else if(context.currentPath.contains("/tree/")){ -// val paths = context.currentPath.split("/") -// val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch -// repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") -// } else { -// val paths = context.currentPath.split("/") -// val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch -// repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") -// } -// } else { -// repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url -// } -// } -// // private def printAttribute(name: String, value: String): Unit = { // printer.print(' ').print(name).print('=').print('"').print(value).print('"') // } From a0f684cfdf3e5b602f5257fe10cbbe5152fb5453 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 20 Sep 2015 14:39:20 +0900 Subject: [PATCH 04/10] Bump markedj to 1.0.1 --- project/build.scala | 3 +- .../scala/gitbucket/core/view/Markdown.scala | 49 ------------------- 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/project/build.scala b/project/build.scala index 3e10eab93..f8e111efe 100644 --- a/project/build.scala +++ b/project/build.scala @@ -51,8 +51,7 @@ object MyBuild extends Build { "org.json4s" %% "json4s-jackson" % "3.2.11", "jp.sf.amateras" %% "scalatra-forms" % "0.1.0", "commons-io" % "commons-io" % "2.4", -// "org.pegdown" % "pegdown" % "1.5.0", - "io.github.gitbucket" % "markedj" % "1.0.1-SNAPSHOT", + "io.github.gitbucket" % "markedj" % "1.0.1", "org.apache.commons" % "commons-compress" % "1.9", "org.apache.commons" % "commons-email" % "1.3.3", "org.apache.httpcomponents" % "httpclient" % "4.3.6", diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 5d274f6c2..06ba37cde 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -117,12 +117,6 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re } -//class GitBucketLinkRender( -// context: Context, -// repository: RepositoryService.RepositoryInfo, -// enableWikiLink: Boolean, -// pages: List[String]) extends LinkRenderer with WikiService { -// //class GitBucketHtmlSerializer( // markdown: String, // repository: RepositoryService.RepositoryInfo, @@ -146,10 +140,6 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re // printer.print('>').print(rendering.text).print("") // } // -// private def printAttribute(name: String, value: String): Unit = { -// printer.print(' ').print(name).print('=').print('"').print(value).print('"') -// } -// // private def printHeaderTag(node: HeaderNode): Unit = { // val tag = s"h${node.getLevel}" // val child = node.getChildren.asScala.headOption @@ -171,19 +161,6 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re // printer.print(s"") // } // -// private def extractText(node: Node): String = { -// val sb = new StringBuilder() -// node.getChildren.asScala.map { -// case x: TextNode => sb.append(x.getText) -// case x: Node => sb.append(extractText(x)) -// } -// sb.toString() -// } -// -// override def visit(node: HeaderNode): Unit = { -// printHeaderTag(node) -// } -// // override def visit(node: TextNode): Unit = { // // convert commit id and username to link. // val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText @@ -231,32 +208,6 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re // printTag(node, "li") // } // } -// -// override def visit(node: ExpLinkNode) { -// printLink(linkRenderer.render(node, printLinkChildrenToString(node))) -// } -// -// def printLinkChildrenToString(node: SuperNode) = { -// val priorPrinter = printer -// printer = new Printer() -// visitLinkChildren(node) -// val result = printer.getString() -// printer = priorPrinter -// result -// } -// -// def visitLinkChildren(node: SuperNode) { -// import scala.collection.JavaConversions._ -// node.getChildren.foreach(child => child match { -// case node: ExpImageNode => visitLinkChild(node) -// case node: SuperNode => visitLinkChildren(node) -// case _ => child.accept(this) -// }) -// } -// -// def visitLinkChild(node: ExpImageNode) { -// printer.print("\"").printEncoded(printChildrenToString(node)).print("\"/") -// } //} object GitBucketHtmlSerializer { From a359624f018dca6fa008cc82f634c5a0994ac95c Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 21 Sep 2015 10:48:44 +0900 Subject: [PATCH 05/10] Restore task-list support --- .../scala/gitbucket/core/view/Markdown.scala | 65 ++++++++----------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 06ba37cde..03e1eff8a 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -57,6 +57,29 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re (if(escaped) code else escape(code, true)) + "" } + override def list(body: String, ordered: Boolean): String = { + var listType: String = null + if (ordered) { + listType = "ol" + } + else { + listType = "ul" + } + if(body.contains("""class="task-list-item-checkbox"""")){ + return "<" + listType + " class=\"task-list\">\n" + body + "\n" + } else { + return "<" + listType + ">\n" + body + "\n" + } + } + + override def listitem(text: String): String = { + if(text.contains("""class="task-list-item-checkbox" """)){ + return "
  • " + text + "
  • " + } else { + return "
  • " + text + "
  • " + } + } + override def text(text: String): String = { // convert commit id and username to link. val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "issue:") else text @@ -72,7 +95,7 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re } override def nolink(text: String): String = { - if(enableWikiLink && text.startsWith("[") && text.endsWith("]")){ + if(enableWikiLink && text.startsWith("[[") && text.endsWith("]]")){ val link = text.replaceAll("(^\\[\\[|\\]\\]$)", "") val (label, page) = if(link.contains('|')){ @@ -84,9 +107,9 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) if(pages.contains(page)){ - "" + escape(page) + "" + "" + escape(label) + "" } else { - "" + escape(page) + "" + "" + escape(label) + "" } } else { escape(text) @@ -161,20 +184,6 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re // printer.print(s"") // } // -// override def visit(node: TextNode): Unit = { -// // convert commit id and username to link. -// val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText -// -// // convert task list to checkbox. -// val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission) else t -// -// if (abbreviations.isEmpty) { -// printer.print(text) -// } else { -// printWithAbbreviations(text) -// } -// } -// // override def visit(node: VerbatimNode) { // val printer = new Printer() // val serializer = verbatimSerializers.get(VerbatimSerializer.DEFAULT) @@ -186,28 +195,6 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re // // this.printer.print(t) // } -// -// override def visit(node: BulletListNode): Unit = { -// if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { -// printer.println().print("""
      """).indent(+2) -// visitChildren(node) -// printer.indent(-2).println().print("
    ") -// } else { -// printIndentedTag(node, "ul") -// } -// } -// -// override def visit(node: ListItemNode): Unit = { -// if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) { -// printer.println() -// printer.print("""
  • """) -// visitChildren(node) -// printer.print("
  • ") -// } else { -// printer.println() -// printTag(node, "li") -// } -// } //} object GitBucketHtmlSerializer { From 353784c23eed61c087663ced5f6b8d7324e218b9 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 21 Sep 2015 11:01:40 +0900 Subject: [PATCH 06/10] Restore header anchor --- .../scala/gitbucket/core/view/Markdown.scala | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 03e1eff8a..5f8a16993 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -42,16 +42,29 @@ object Markdown { } else s val options = new Options() - val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission, pages) + val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages) Marked.marked(source, options, renderer) } } class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, enableRefsLink: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean, + enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean, pages: List[String]) (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { + override def heading(text: String, level: Int, raw: String): String = { + val out = new StringBuilder() + out.append("") + out.append(text) + if(enableAnchor){ + val anchorName = GitBucketHtmlSerializer.generateAnchorName(text.replaceAll("<.*>", "")) + out.append("") + out.append("") + } + out.append("\n") + out.toString() + } + override def code(code: String, lang: Optional[String], escaped: Boolean): String = { "
    " +
           (if(escaped) code else escape(code, true)) + "
    " @@ -74,9 +87,9 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re override def listitem(text: String): String = { if(text.contains("""class="task-list-item-checkbox" """)){ - return "
  • " + text + "
  • " + return "
  • " + text + "
  • \n" } else { - return "
  • " + text + "
  • " + return "
  • " + text + "
  • \n" } } @@ -163,27 +176,6 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re // printer.print('>').print(rendering.text).print("") // } // -// private def printHeaderTag(node: HeaderNode): Unit = { -// val tag = s"h${node.getLevel}" -// val child = node.getChildren.asScala.headOption -// val anchorName = child match { -// case Some(x: AnchorLinkNode) => x.getName -// case Some(x: TextNode) => x.getText -// case _ => GitBucketHtmlSerializer.generateAnchorName(extractText(node)) // TODO -// } -// -// printer.print(s"""<$tag class="markdown-head">""") -// if(enableAnchor){ -// printer.print(s"""""") -// printer.print(s"""""") -// } -// child match { -// case Some(x: AnchorLinkNode) => printer.print(x.getText) -// case _ => visitChildren(node) -// } -// printer.print(s"") -// } -// // override def visit(node: VerbatimNode) { // val printer = new Printer() // val serializer = verbatimSerializers.get(VerbatimSerializer.DEFAULT) From 84c3bc4ad48147e6d1f2b39ab37aefe486441e43 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 21 Sep 2015 11:24:59 +0900 Subject: [PATCH 07/10] Refactoring --- .../scala/gitbucket/core/view/Markdown.scala | 94 ++++++------------- 1 file changed, 30 insertions(+), 64 deletions(-) diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 5f8a16993..9be7ae97e 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -2,10 +2,9 @@ package gitbucket.core.view import java.text.Normalizer import java.util.{Optional, Locale} -import java.util.regex.Pattern import gitbucket.core.controller.Context -import gitbucket.core.service.{RepositoryService, RequestCache, WikiService} +import gitbucket.core.service.{RepositoryService, RequestCache} import gitbucket.core.util.StringUtil import io.github.gitbucket.markedj._ import io.github.gitbucket.markedj.Utils._ @@ -38,13 +37,19 @@ object Markdown { // escape task list val source = if(enableTaskList){ - GitBucketHtmlSerializer.escapeTaskList(s) + s.replaceAll("""(?m)^( *)- \[([x| ])\] """, "$1* task:$2: ") +// escapeTaskList(s) } else s val options = new Options() val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages) Marked.marked(source, options, renderer) } + +// private def escapeTaskList(text: String): String = { +// Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ") +// } + } class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo, @@ -53,14 +58,17 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { override def heading(text: String, level: Int, raw: String): String = { + val id = generateAnchorName(text) val out = new StringBuilder() - out.append("") - out.append(text) + + out.append("") + if(enableAnchor){ - val anchorName = GitBucketHtmlSerializer.generateAnchorName(text.replaceAll("<.*>", "")) - out.append("") - out.append("") + out.append("") + out.append("") } + + out.append(text) out.append("\n") out.toString() } @@ -98,13 +106,17 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "issue:") else text // convert task list to checkbox. - val t2 = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t1, hasWritePermission) else t1 + val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 t2 } + override def link(href: String, title: Optional[String], text: String): String = { + super.link(fixUrl(href, true), title, text) + } + override def image(href: String, title: Optional[String], text: String): String = { - super.image(fixUrl(href, true), title, text); + super.image(fixUrl(href, true), title, text) } override def nolink(text: String): String = { @@ -133,7 +145,7 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){ url } else if(url.startsWith("#")){ - ("#" + GitBucketHtmlSerializer.generateAnchorName(url.substring(1))) + ("#" + generateAnchorName(url.substring(1))) } else if(!enableWikiLink){ if(context.currentPath.contains("/blob/")){ url + (if(isImage) "?raw=true" else "") @@ -151,62 +163,16 @@ class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.Re } } -} - -//class GitBucketHtmlSerializer( -// markdown: String, -// repository: RepositoryService.RepositoryInfo, -// enableWikiLink: Boolean, -// enableRefsLink: Boolean, -// enableAnchor: Boolean, -// enableTaskList: Boolean, -// hasWritePermission: Boolean, -// pages: List[String] -// )(implicit val context: Context) extends ToHtmlSerializer( -// new GitBucketLinkRender(context, repository, enableWikiLink, pages), -// Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava -// ) with LinkConverter with RequestCache { -// -// override protected def printLink(rendering: LinkRenderer.Rendering): Unit = { -// printer.print('<').print('a') -// printAttribute("href", fixUrl(rendering.href)) -// for (attr <- rendering.attributes.asScala) { -// printAttribute(attr.name, attr.value) -// } -// printer.print('>').print(rendering.text).print("") -// } -// -// override def visit(node: VerbatimNode) { -// val printer = new Printer() -// val serializer = verbatimSerializers.get(VerbatimSerializer.DEFAULT) -// serializer.serialize(node, printer) -// val html = printer.getString -// -// // convert commit id and username to link. -// val t = if(enableRefsLink) convertRefsLinks(html, repository, "issue:", escapeHtml = false) else html -// -// this.printer.print(t) -// } -//} - -object GitBucketHtmlSerializer { - - private val Whitespace = "[\\s]".r - - def generateAnchorName(text: String): String = { - val noWhitespace = Whitespace.replaceAllIn(text, "-") - val normalized = Normalizer.normalize(noWhitespace, Normalizer.Form.NFD) - val noSpecialChars = StringUtil.urlEncode(normalized) - noSpecialChars.toLowerCase(Locale.ENGLISH) + private def generateAnchorName(text: String): String = { + val normalized = Normalizer.normalize(text.replaceAll("<.*>", "").replaceAll("[\\s]", "-"), Normalizer.Form.NFD) + val encoded = StringUtil.urlEncode(normalized) + encoded.toLowerCase(Locale.ENGLISH) } - def escapeTaskList(text: String): String = { - Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ") - } - - def convertCheckBox(text: String, hasWritePermission: Boolean): String = { + private def convertCheckBox(text: String, hasWritePermission: Boolean): String = { val disabled = if (hasWritePermission) "" else "disabled" text.replaceAll("task:x:", """") - .replaceAll("task: :", """") + .replaceAll("task: :", """") } + } From e1c155d09d2f4da95a33476fe4e68d2b3e7526d7 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 21 Sep 2015 12:08:16 +0900 Subject: [PATCH 08/10] Fix Markdown testcase --- .../scala/gitbucket/core/view/Markdown.scala | 228 +++++++++--------- ...erializerSpec.scala => MarkdownSpec.scala} | 4 +- 2 files changed, 118 insertions(+), 114 deletions(-) rename src/test/scala/gitbucket/core/view/{GitBucketHtmlSerializerSpec.scala => MarkdownSpec.scala} (95%) diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 9be7ae97e..2e126eb6c 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -1,6 +1,7 @@ package gitbucket.core.view import java.text.Normalizer +import java.util.regex.Pattern import java.util.{Optional, Locale} import gitbucket.core.controller.Context @@ -19,7 +20,7 @@ object Markdown { * @param enableRefsLink if true then issue reference (e.g. #123) is rendered as link * @param enableAnchor if true then anchor for headline is generated * @param enableTaskList if true then task list syntax is available - * @param hasWritePermission + * @param hasWritePermission true if user has writable to ths given repository * @param pages the list of existing Wiki pages */ def toHtml(markdown: String, @@ -37,8 +38,7 @@ object Markdown { // escape task list val source = if(enableTaskList){ - s.replaceAll("""(?m)^( *)- \[([x| ])\] """, "$1* task:$2: ") -// escapeTaskList(s) + escapeTaskList(s) } else s val options = new Options() @@ -46,133 +46,137 @@ object Markdown { Marked.marked(source, options, renderer) } -// private def escapeTaskList(text: String): String = { -// Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ") -// } + /** + * Extends markedj Renderer for GitBucket + */ + class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean, + pages: List[String]) + (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { -} + override def heading(text: String, level: Int, raw: String): String = { + val id = generateAnchorName(text) + val out = new StringBuilder() -class GitBucketMarkedRenderer(options: Options, repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean, enableTaskList: Boolean, hasWritePermission: Boolean, - pages: List[String]) - (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { + out.append("") - override def heading(text: String, level: Int, raw: String): String = { - val id = generateAnchorName(text) - val out = new StringBuilder() - - out.append("") - - if(enableAnchor){ - out.append("") - out.append("") - } - - out.append(text) - out.append("\n") - out.toString() - } - - override def code(code: String, lang: Optional[String], escaped: Boolean): String = { - "
    " +
    -      (if(escaped) code else escape(code, true)) + "
    " - } - - override def list(body: String, ordered: Boolean): String = { - var listType: String = null - if (ordered) { - listType = "ol" - } - else { - listType = "ul" - } - if(body.contains("""class="task-list-item-checkbox"""")){ - return "<" + listType + " class=\"task-list\">\n" + body + "\n" - } else { - return "<" + listType + ">\n" + body + "\n" - } - } - - override def listitem(text: String): String = { - if(text.contains("""class="task-list-item-checkbox" """)){ - return "
  • " + text + "
  • \n" - } else { - return "
  • " + text + "
  • \n" - } - } - - override def text(text: String): String = { - // convert commit id and username to link. - val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "issue:") else text - - // convert task list to checkbox. - val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 - - t2 - } - - override def link(href: String, title: Optional[String], text: String): String = { - super.link(fixUrl(href, true), title, text) - } - - override def image(href: String, title: Optional[String], text: String): String = { - super.image(fixUrl(href, true), title, text) - } - - override def nolink(text: String): String = { - if(enableWikiLink && text.startsWith("[[") && text.endsWith("]]")){ - val link = text.replaceAll("(^\\[\\[|\\]\\]$)", "") - - val (label, page) = if(link.contains('|')){ - val i = link.indexOf('|') - (link.substring(0, i), link.substring(i + 1)) - } else { - (link, link) + if(enableAnchor){ + out.append("") + out.append("") } - val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) - if(pages.contains(page)){ - "" + escape(label) + "" - } else { - "" + escape(label) + "" - } - } else { - escape(text) + out.append(text) + out.append("
    \n") + out.toString() } + + override def code(code: String, lang: Optional[String], escaped: Boolean): String = { + "
    " +
    +        (if(escaped) code else escape(code, true)) + "
    " + } + + override def list(body: String, ordered: Boolean): String = { + var listType: String = null + if (ordered) { + listType = "ol" + } + else { + listType = "ul" + } + if(body.contains("""class="task-list-item-checkbox"""")){ + return "<" + listType + " class=\"task-list\">\n" + body + "\n" + } else { + return "<" + listType + ">\n" + body + "\n" + } + } + + override def listitem(text: String): String = { + if(text.contains("""class="task-list-item-checkbox" """)){ + return "
  • " + text + "
  • \n" + } else { + return "
  • " + text + "
  • \n" + } + } + + override def text(text: String): String = { + // convert commit id and username to link. + val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "issue:") else text + + // convert task list to checkbox. + val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 + + t2 + } + + override def link(href: String, title: Optional[String], text: String): String = { + super.link(fixUrl(href, true), title, text) + } + + override def image(href: String, title: Optional[String], text: String): String = { + super.image(fixUrl(href, true), title, text) + } + + override def nolink(text: String): String = { + if(enableWikiLink && text.startsWith("[[") && text.endsWith("]]")){ + val link = text.replaceAll("(^\\[\\[|\\]\\]$)", "") + + val (label, page) = if(link.contains('|')){ + val i = link.indexOf('|') + (link.substring(0, i), link.substring(i + 1)) + } else { + (link, link) + } + + val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) + if(pages.contains(page)){ + "" + escape(label) + "" + } else { + "" + escape(label) + "" + } + } else { + escape(text) + } + } + + private def fixUrl(url: String, isImage: Boolean = false): String = { + if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){ + url + } else if(url.startsWith("#")){ + ("#" + generateAnchorName(url.substring(1))) + } else if(!enableWikiLink){ + if(context.currentPath.contains("/blob/")){ + url + (if(isImage) "?raw=true" else "") + } else if(context.currentPath.contains("/tree/")){ + val paths = context.currentPath.split("/") + val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch + repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") + } else { + val paths = context.currentPath.split("/") + val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch + repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") + } + } else { + repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url + } + } + } - private def fixUrl(url: String, isImage: Boolean = false): String = { - if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){ - url - } else if(url.startsWith("#")){ - ("#" + generateAnchorName(url.substring(1))) - } else if(!enableWikiLink){ - if(context.currentPath.contains("/blob/")){ - url + (if(isImage) "?raw=true" else "") - } else if(context.currentPath.contains("/tree/")){ - val paths = context.currentPath.split("/") - val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") - } else { - val paths = context.currentPath.split("/") - val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") - } - } else { - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url - } + def escapeTaskList(text: String): String = { + Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ") } - private def generateAnchorName(text: String): String = { + def generateAnchorName(text: String): String = { val normalized = Normalizer.normalize(text.replaceAll("<.*>", "").replaceAll("[\\s]", "-"), Normalizer.Form.NFD) val encoded = StringUtil.urlEncode(normalized) encoded.toLowerCase(Locale.ENGLISH) } - private def convertCheckBox(text: String, hasWritePermission: Boolean): String = { + def convertCheckBox(text: String, hasWritePermission: Boolean): String = { val disabled = if (hasWritePermission) "" else "disabled" text.replaceAll("task:x:", """") .replaceAll("task: :", """") } } + diff --git a/src/test/scala/gitbucket/core/view/GitBucketHtmlSerializerSpec.scala b/src/test/scala/gitbucket/core/view/MarkdownSpec.scala similarity index 95% rename from src/test/scala/gitbucket/core/view/GitBucketHtmlSerializerSpec.scala rename to src/test/scala/gitbucket/core/view/MarkdownSpec.scala index 82fb3d958..a2ec2e9c4 100644 --- a/src/test/scala/gitbucket/core/view/GitBucketHtmlSerializerSpec.scala +++ b/src/test/scala/gitbucket/core/view/MarkdownSpec.scala @@ -2,9 +2,9 @@ package gitbucket.core.view import org.specs2.mutable._ -class GitBucketHtmlSerializerSpec extends Specification { +class MarkdownSpec extends Specification { - import GitBucketHtmlSerializer._ + import Markdown._ "generateAnchorName" should { "convert whitespace characters to hyphens" in { From 2402a3ac723d5c719e29a4ab8a19a2d83a2d7329 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 22 Sep 2015 03:06:22 +0900 Subject: [PATCH 09/10] Enable task list after update issue --- src/main/scala/gitbucket/core/controller/IssuesController.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 8f2bfafb1..1bee1bf3d 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -233,7 +233,7 @@ trait IssuesControllerBase extends ControllerBase { org.json4s.jackson.Serialization.write( Map("title" -> x.title, "content" -> Markdown.toHtml(x.content getOrElse "No description given.", - repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName)) + repository, false, true, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName)) )) } } else Unauthorized From 50dc205ef7feb4d31f8f55060c9ebb74e7383b2c Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 22 Sep 2015 03:26:57 +0900 Subject: [PATCH 10/10] Use JDK8 for Travis build --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6caeabf9c..b85cad5d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,5 @@ language: scala sudo: false script: - sbt test +jdk: + - oraclejdk8