Markdown: recognize special note, promo, and aside blocks
Extend the supported markdown syntax with a special aside format
useful for documentation writing.
*** note
Pay special attention here!
You may have to watch out for crazy developers.
***
A note block starts with *** on its own line and ends with *** on its
own line. Everything inside of the block is read as Markdown and
reprocessed through a recursive call whose AST is inlined.
Three kinds of notes are recognized, which are given different CSS:
*** note
Some sort of a warning you should watch out for.
***
*** promo
Look here this may interest you.
***
*** aside
The author felt you should read this here, but really
it's just a distraction and maybe should be omitted or
moved to another page buried under a link.
***
Change-Id: Icbfd27d3afd7d0137e37b7d5e821938c70207cf1
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/DivNode.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/DivNode.java
new file mode 100644
index 0000000..a84a041
--- /dev/null
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/DivNode.java
@@ -0,0 +1,42 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// 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.google.gitiles.doc;
+
+import org.pegdown.ast.Node;
+import org.pegdown.ast.ParaNode;
+import org.pegdown.ast.SuperNode;
+
+import java.util.List;
+
+/** Block note to render as {@code <div class="{clazz}">}. */
+public class DivNode extends SuperNode {
+ private final String style;
+
+ DivNode(String style, List<Node> list) {
+ super(list.size() == 1 && list.get(0) instanceof ParaNode
+ ? ((ParaNode) list.get(0)).getChildren()
+ : list);
+ this.style = style;
+ }
+
+ public String getStyleName() {
+ return style;
+ }
+
+ @Override
+ public void accept(org.pegdown.ast.Visitor visitor) {
+ ((Visitor) visitor).visit(this);
+ }
+}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
index b79bf4d..43c589f 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
@@ -17,15 +17,19 @@
import com.google.gitiles.GitilesView;
import org.parboiled.Rule;
+import org.parboiled.support.StringBuilderVar;
import org.pegdown.Parser;
import org.pegdown.ParsingTimeoutException;
import org.pegdown.PegDownProcessor;
+import org.pegdown.ast.Node;
import org.pegdown.ast.RootNode;
import org.pegdown.plugins.BlockPluginParser;
import org.pegdown.plugins.PegDownPlugins;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.List;
+
/**
* Additional markdown extensions known to Gitiles.
* <p>
@@ -66,13 +70,18 @@
return new PegDownProcessor(MD_OPTIONS, plugins);
}
+ private PegDownProcessor parser;
+
GitilesMarkdown() {
super(MD_OPTIONS, 2000L, DefaultParseRunnerProvider);
}
@Override
public Rule[] blockPluginRules() {
- return new Rule[]{ toc() };
+ return new Rule[]{
+ note(),
+ toc(),
+ };
}
public Rule toc() {
@@ -80,4 +89,32 @@
string("[TOC]"),
push(new TocNode()));
}
+
+ public Rule note() {
+ StringBuilderVar body = new StringBuilderVar();
+ return NodeSequence(
+ string("***"), Sp(), typeOfNote(), Newline(),
+ oneOrMore(
+ testNot(string("***"), Newline()),
+ Line(body)),
+ string("***"), Newline(),
+ push(new DivNode(popAsString(), parse(body))));
+ }
+
+ public Rule typeOfNote() {
+ return firstOf(
+ sequence(string("note"), push(match())),
+ sequence(string("promo"), push(match())),
+ sequence(string("aside"), push(match())));
+ }
+
+ public List<Node> parse(StringBuilderVar body) {
+ // The pegdown code doesn't provide enough visibility to directly
+ // use its existing parsing rules. Recurse manually for inner text
+ // parsing within a block.
+ if (parser == null) {
+ parser = newParser();
+ }
+ return parser.parseMarkdown(body.getChars()).getChildren();
+ }
}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java
index a3feb6c..1086bab 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java
@@ -100,6 +100,13 @@
}
@Override
+ public void visit(DivNode node) {
+ html.open("div").attribute("class", node.getStyleName());
+ visitChildren(node);
+ html.close("div");
+ }
+
+ @Override
public void visit(HeaderNode node) {
String tag = "h" + node.getLevel();
html.open(tag);
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java
index b8cf64e..19a68cc 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java
@@ -15,5 +15,6 @@
package com.google.gitiles.doc;
public interface Visitor extends org.pegdown.ast.Visitor {
+ void visit(DivNode node);
void visit(TocNode node);
}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
index e047f59..c324786 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
@@ -164,4 +164,33 @@
}
th {
background-color: #f5f5f5;
-}
\ No newline at end of file
+}
+
+.note, .promo, .aside {
+ border: 1px solid;
+ border-radius: 4px;
+ margin: 10px 0;
+ padding: 10px;
+}
+.note {
+ background: #fffbe4;
+ border-color: #f8f6e6;
+}
+.promo {
+ background: #f6f9ff;
+ border-color: #eff2f9;
+}
+.aside {
+ background: #f9f9f9;
+ border-color: #f2f2f2;
+}
+.note p:first-child,
+.promo p:first-child,
+.aside p:first-child {
+ margin-top: 0;
+}
+.note p:last-child,
+.promo p:last-child,
+.aside p:last-child {
+ margin-bottom: 0;
+}