Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow @ref in index; introduce @link #313

Merged
merged 4 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions core/src/main/scala/com/lightbend/paradox/markdown/Directive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,24 @@ object RefDirective {
*/
class LinkException(message: String) extends RuntimeException(message)

def isRefDirective(node: DirectiveNode): Boolean = {
node.format == DirectiveNode.Format.Inline && (node.name == "ref" || node.name == "ref:")
}

}

case class LinkDirective(page: Page, pathExists: String => Boolean, convertPath: String => String, variables: Map[String, String])
extends InlineDirective("link", "link:") with SourceDirective {

def render(node: DirectiveNode, visitor: Visitor, printer: Printer): Unit =
new ExpLinkNodeExtended(node.label, resolvedSource(node, page), node.contentsNode, node.attributes).accept(visitor)

}

object LinkDirective {
def isLinkDirective(node: DirectiveNode): Boolean = {
node.format == DirectiveNode.Format.Inline && (node.name == "link" || node.name == "link:")
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright © 2015 - 2019 Lightbend, Inc. <http://www.lightbend.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.lightbend.paradox.markdown

import org.pegdown.ast.{ DirectiveAttributes, ExpLinkNode, Node }

class ExpLinkNodeExtended(url: String, title: String, child: Node, val attributes: DirectiveAttributes) extends ExpLinkNode(url, title, child)
12 changes: 12 additions & 0 deletions core/src/main/scala/com/lightbend/paradox/markdown/Index.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ package com.lightbend.paradox.markdown
import com.lightbend.paradox.tree.Tree
import com.lightbend.paradox.tree.Tree.Forest
import java.io.File

import org.pegdown.ast.DirectiveNode.Source
import org.pegdown.ast._

import scala.annotation.tailrec
import scala.collection.JavaConverters._

Expand Down Expand Up @@ -120,6 +123,15 @@ object Index {
private def linkRef(node: Node, level: Int): Option[Ref] = {
node match {
case link: ExpLinkNode => Some(Ref(level, link.url, link.getChildren.get(0), group = None, Nil))
case ref: DirectiveNode if RefDirective.isRefDirective(ref) =>
ref.source match {
case source: Source.Direct =>
Some(Ref(level, source.value, new TextNode(ref.label), group = None, Nil))
case source: Source.Ref =>
Some(Ref(level, source.value, new TextNode(ref.label), group = None, Nil))
case other =>
sys.error(s"unexpected Source type: ${ref.source}")
}
case other => other.getChildren.asScala.toList match {
// only check first children
case first :: _ => linkRef(first, level)
Expand Down
21 changes: 20 additions & 1 deletion core/src/main/scala/com/lightbend/paradox/markdown/Writer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.lightbend.paradox.markdown

import com.lightbend.paradox.tree.Tree.Location
import org.parboiled.common.StringUtils
import org.pegdown.FastEncoder.encode
import org.pegdown.plugins.ToHtmlSerializerPlugin
import org.pegdown.ast._
import org.pegdown.{ LinkRenderer, Printer, ToHtmlSerializer, VerbatimSerializer }
Expand Down Expand Up @@ -125,6 +127,7 @@ object Writer {

def defaultDirectives: Seq[Context => Directive] = Seq(
context => RefDirective(context.location.tree.label, context.paths, context.pageMappings, context.properties),
context => LinkDirective(context.location.tree.label, context.paths, context.pageMappings, context.properties),
context => ExtRefDirective(context.location.tree.label, context.properties),
context => ScaladocDirective(context.location.tree.label, context.properties),
context => JavadocDirective(context.location.tree.label, context.properties),
Expand Down Expand Up @@ -160,7 +163,23 @@ object Writer {
super.render(node, interpolatedUrl(url) getOrElse url, title, alt)

override def render(node: ExpLinkNode, text: String): LinkRenderer.Rendering = {
super.render(new ExpLinkNode(node.title, substituteVarsInString(node.url, context.properties), node.getChildren.get(0)), text)
val title = node.title
val url = substituteVarsInString(node.url, context.properties)
node match {
case n: ExpLinkNodeExtended =>
val rendering: LinkRenderer.Rendering = new LinkRenderer.Rendering(url, text)
val r2 =
if (StringUtils.isEmpty(title)) rendering
else rendering.withAttribute("title", encode(title))
if (n.attributes.value("open", "same") == "new") {
r2
.withAttribute("target", "_blank")
.withAttribute("rel", "noopener noreferrer")
} else r2

case _ =>
super.render(new ExpLinkNode(title, url, node.getChildren.get(0)), text)
}
}

override def render(node: RefLinkNode, url: String, title: String, text: String): LinkRenderer.Rendering = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* @ref[`@@include`](includes.md)
* @ref[`@@@index`](organizing-pages.md#index-container)
* @ref[`@javadoc`](linking.md#javadoc)
* @ref[`@link`](linking.md#ref-link)
* @ref[`@@@note`](callouts.md#note-callout)
* @ref[`@ref`](linking.md#ref-link)
* @ref[`@scaladoc`](linking.md#scaladoc)
Expand Down
8 changes: 8 additions & 0 deletions docs/src/main/paradox/directives/linking.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Linking
-------

#### External links

External links can be created with the default markdown syntax `[text](url)`. Additionally Paradox introduces `@link:` which accepts the `open=new` attribute to make the link open in a new browser tab (it adds `target="_blank" rel="noopener noreferrer"` to the anchor tag).

```
See the @link:[Paradox GitHub repo](https://github.com/lightbend/paradox) { open=new } for more information.
```

#### @ref link

Paradox extensions are designed so the resulting Markdown is Github friendly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,66 @@ class IndexSpec extends MarkdownBaseSpec {
""")
}

it should "create toc" in {
indexed(
"a.md" -> """
|# A
|@@@ index
|* @ref:[Big B](b.md)
|@@@
""",
"b.md" -> """
|# B
|## B2
""") shouldEqual index(
"""
|- a.html
| - b.html
| - #b2
""")
}

it should "create page tree with refs" in {
indexed(
"a.md" -> """
|# A
|@@@ index
|* @ref:[b](b.md)
|* @ref:[c](c.md)
|@@@
|## A2
|### A3
""",
"b.md" -> """
|# B
|@@@ index
|* @ref[d](d.md)
|@@@
|## B2
""",
"c.md" -> """
|# C
|## C2
""",
"d.md" -> """
|# D
|## D2
|### D3
""") shouldEqual index(
"""
|- a.html
| - #a2
| - #a3
| - b.html
| - #b2
| - d.html
| - #d2
| - #d3
| - c.html
| - #c2
""")
}

it should "create page tree" in {
indexed(
"a.md" -> """
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright © 2015 - 2019 Lightbend, Inc. <http://www.lightbend.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.lightbend.paradox.markdown

class LinkDirectiveSpec extends MarkdownBaseSpec {

private implicit val context = writerContextWithProperties("page.variable" -> "https://page")

def testMarkdown(text: String, pagePath: String = "page.md", testPath: String = "test.md"): Map[String, String] = markdownPages(
testPath -> text
)

def testHtml(text: String, pagePath: String = "page.html", testPath: String = "test.html"): Map[String, String] = {
htmlPages(testPath -> text)
}

"Link directive" should "create links" in {
testMarkdown("@link[External page](https://domain.com/page.html)") shouldEqual testHtml("""<p><a href="https://domain.com/page.html" title="External page">External page</a></p>""")
}

it should "support 'link:' as an alternative name" in {
testMarkdown("@link:[External page](https://domain.com/page.html)") shouldEqual testHtml("""<p><a href="https://domain.com/page.html" title="External page">External page</a></p>""")
}

it should "handle anchored links correctly" in {
testMarkdown("@link:[External page](https://domain.com/page.html#anchor)") shouldEqual testHtml("""<p><a href="https://domain.com/page.html#anchor" title="External page">External page</a></p>""")
}

it should "retain whitespace before or after" in {
testMarkdown("This @link:[Page](external.pdf) is linked.") shouldEqual
testHtml("""<p>This <a href="external.pdf" title="Page">Page</a> is linked.</p>""")
}

it should "use the `open` attributes" in {
testMarkdown("This @link:[Page](page.pdf) { open=new } is linked.") shouldEqual
testHtml("""<p>This <a href="page.pdf" title="Page" target="_blank" rel="noopener noreferrer">Page</a> is linked.</p>""")
}

it should "support referenced links with implicit key" in {
testMarkdown(
"""This @link:[SBT] { .ref a=1 } is linked.
|
| [SBT]: https://scala-sbt.org
""".stripMargin) shouldEqual testHtml("""<p>This <a href="https://scala-sbt.org" title="SBT">SBT</a> is linked.</p>""")
}

it should "support referenced links with empty key" in {
testMarkdown(
"""This @link:[SBT][] is linked.
|
| [SBT]: https://scala-sbt.org
""".stripMargin) shouldEqual testHtml("""<p>This <a href="https://scala-sbt.org" title="SBT">SBT</a> is linked.</p>""")
}

it should "support referenced links with defined key" in {
testMarkdown(
"""This @link:[Page][123] { .ref a=1 } is linked.
|
| [123]: https://scala-sbt.org
""".stripMargin) shouldEqual testHtml("""<p>This <a href="https://scala-sbt.org" title="Page">Page</a> is linked.</p>""")
}

it should "throw link exceptions for invalid reference keys" in {
the[RefDirective.LinkException] thrownBy {
markdown("@ref[Page][123]")
} should have message "Undefined reference key [123] in [test.html]"
}

it should "support variables in link paths" in {
testMarkdown("@link[Page]($page.variable$.html)") shouldEqual testHtml("""<p><a href="https://page.html" title="Page">Page</a></p>""")
}

}