Skip to content

Tab-group switching for snippets #95

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

Closed
wants to merge 4 commits into from
Closed
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
43 changes: 43 additions & 0 deletions core/src/main/java/org/pegdown/ast/VerbatimGroupNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright © 2015 - 2016 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 org.pegdown.ast;

public class VerbatimGroupNode extends VerbatimNode {
private final String group;

public VerbatimGroupNode(String text) {
this(text, "", "");
}

public VerbatimGroupNode(String text, String type) {
this(text, type, "");
}

public VerbatimGroupNode(String text, String type, String group) {
super(text, type);
this.group = group;
}

@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}

public String getGroup() {
return group;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ case class SnipDirective(page: Page, variables: Map[String, String])
} else new File(page.file.getParentFile, source)
val text = Snippet(file, labels)
val lang = Option(node.attributes.value("type")).getOrElse(Snippet.language(file))
new VerbatimNode(text, lang).accept(visitor)
val group = Option(node.attributes.value("group")).getOrElse("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should use the tab name for a group if the group is not explicitly defined. This would allow easier feature use for the documentation writers making docs less verbose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tab name doesn't seem too easy to reach at this point, but defaulting to the lang seems reasonable, too - applied to #107

new VerbatimGroupNode(text, lang, group).accept(visitor)
} catch {
case e: FileNotFoundException =>
throw new SnipDirective.LinkException(s"Unknown snippet [${e.getMessage}] referenced from [${page.path}]")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
package com.lightbend.paradox.markdown

import org.parboiled.common.StringUtils
import org.pegdown.ast.VerbatimNode
import org.pegdown.ast.{ VerbatimNode, VerbatimGroupNode }
import org.pegdown.{ Printer, VerbatimSerializer }

/**
* Add markup around verbatim blocks.
*/
abstract class StyledVerbatimSerializer extends VerbatimSerializer {

def printPreAttributes(printer: Printer): Unit
def printPreAttributes(printer: Printer, nodeGroup: String = ""): Unit

def printCodeAttributes(printer: Printer, nodeType: String): Unit

Expand All @@ -34,7 +34,10 @@ abstract class StyledVerbatimSerializer extends VerbatimSerializer {

printer.print("<pre")
if (!StringUtils.isEmpty(node.getType)) {
printPreAttributes(printer)
node match {
case vgn: VerbatimGroupNode => printPreAttributes(printer, vgn.getGroup)
case vn: VerbatimNode => printPreAttributes(printer)
}
}
printer.print(">")

Expand Down Expand Up @@ -66,7 +69,13 @@ abstract class StyledVerbatimSerializer extends VerbatimSerializer {
* Add prettify markup around verbatim blocks.
*/
object PrettifyVerbatimSerializer extends StyledVerbatimSerializer {
override def printPreAttributes(printer: Printer): Unit = printClass(printer, "prettyprint")
override def printPreAttributes(printer: Printer, nodeGroup: String): Unit = {
nodeGroup match {
case "" => printClass(printer, "prettyprint")
case g => printClass(printer, "prettyprint tab-group-" + g)
}
}

override def printCodeAttributes(printer: Printer, nodeType: String): Unit = nodeType match {
case "text" | "nocode" => printClass(printer, "nocode")
case _ => printClass(printer, s"language-$nodeType")
Expand Down
39 changes: 39 additions & 0 deletions docs/src/main/paradox/features/snippet-inclusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,45 @@ should not be highlighted set `type=text` in the directive's attribute section:
@@snip [example.log](example.log) { #example-log type=text }
```

#### tab switching

It is possible to associate multiple snippets under the same "tag". If some tab of a snippet is switched by the user, all tabs associated with the selected one will be switched as well. To associate snippet tabs under some tag, set the `group` field of the snippet:

@@@vars
```markdown
First-java
: @@snip [example-first.java](../../resources/tab-switching/examples.java) { #java_first group=java }
$empty$
First-scala
: @@snip [example-first.scala](../../resources/tab-switching/examples.scala) { #scala_first group=scala }
$empty$
Some separator.
$empty$
Second-java
: @@snip [example-second.java](../../resources/tab-switching/examples.java) { #java_second group=java }
$empty$
Second-scala
: @@snip [example-second.scala](../../resources/tab-switching/examples.scala) { #scala_second group=scala }
```
@@@

The result will be rendered like this (try to switch tabs):

First-java
: @@snip [example-first.java](../../resources/tab-switching/examples.java) { #java_first group=java }

First-scala
: @@snip [example-first.scala](../../resources/tab-switching/examples.scala) { #scala_first group=scala }

Some separator.

Second-java
: @@snip [example-second.java](../../resources/tab-switching/examples.java) { #java_second group=java }

Second-scala
: @@snip [example-second.scala](../../resources/tab-switching/examples.scala) { #scala_second group=scala }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIC there is not way to provide grouping for tabs which contain inline text (not a snippet).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, would be good to have an approach that supports anything in the tab, rather than relying on the group attribute in snippets.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the current tab grouping allow different types of groups? For example, Java/Scala and sbt/Maven/Gradle on the same page.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed tabs with inline text are still not supported, but switching arbitrary text is now available in #107.

Different types of groups on the same page should be no problem.



### snip.*.base_dir

In order to specify your snippet source paths off certain base directories you can define placeholders
Expand Down
15 changes: 15 additions & 0 deletions docs/src/main/resources/tab-switching/examples.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// #java_first
class JavaFirst {
public static void main(String[] args) {
System.out.println("Hello java!");
}
}
// #java_first

// #java_second
class JavaSecond {
public static void main(String[] args) {
System.out.println("Hello java!");
}
}
// #java_second
11 changes: 11 additions & 0 deletions docs/src/main/resources/tab-switching/examples.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// #scala_first
object ScalaFirst extends App {
println("Hello scala!")
}
// #scala_first

// #scala_second
object ScalaSecond extends App {
println("Hello scala!")
}
// #scala_second
2 changes: 2 additions & 0 deletions plugin/src/sbt-test/paradox/snippets/expected/group.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<pre class="prettyprint tab-group-a"><code class="language-scala">private class SomethingElse</code></pre>
<pre class="prettyprint tab-group-b"><code class="language-scala">import scala.util.Try</code></pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@@ snip [Group A](../../test/scala/Multiple.scala) { #multiple-something-else group=a }

@@ snip [Group B](../../test/scala/Multiple.scala) { #parseint-imports group=b }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tests if the generated HTML is correct, but will the group=b snippet be hidden by default? I have tried it on a project and it looks like that both snippets are always visible.

Also if there are no tabbed snippets in the page, then a user will have no way of switching between the two. I think we should add a hover box or other interactive control that would allow switching between the alternatives.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a selectbox for switching in #107

Actually selecting tabs/alternatives is done in javascript, so isn't easy to incorporate in a test...

1 change: 1 addition & 0 deletions plugin/src/sbt-test/paradox/snippets/test
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ $ must-mirror target/paradox/site/main/multiple.html expected/multiple.html
$ must-mirror target/paradox/site/main/some-xml.html expected/some-xml.html
$ must-mirror target/paradox/site/main/nocode.html expected/nocode.html
$ must-mirror target/paradox/site/main/configured-bases.html expected/configured-bases.html
$ must-mirror target/paradox/site/main/group.html expected/group.html
104 changes: 97 additions & 7 deletions themes/generic/src/main/assets/js/page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,56 @@
$(function() {

// Tabbed code samples

var tabGroupClass = "tab-group";
var tabGroupCookie = "tabGroupsPref";
var cookieTg = getCookie(tabGroupCookie);
var cookieTgList = [];
if(cookieTg != "")
cookieTgList = JSON.parse(cookieTg);

// http://www.w3schools.com/js/js_cookies.asp
function setCookie(cname,cvalue,exdays) {
if(!exdays) exdays = 365;
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

// http://www.w3schools.com/js/js_cookies.asp
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}

function arrayToJson(arr) {
return JSON.stringify(arr);
}

// http://stackoverflow.com/questions/12551635/jquery-remove-duplicates-from-an-array-of-strings/12551709#12551709
function addToList(arr, elem) {
function unique(list) {
var result = [];
$.each(list, function(i, e) {
if ($.inArray(e, result) == -1) result.push(e);
});
return result;
}
arr.unshift(elem);
return unique(arr);
}

$("dl").has("dd > pre").each(function() {
var dl = $(this);
Expand All @@ -18,20 +68,60 @@ $(function() {
dd.addClass("has-note");
}
});
var current = dts.first().addClass("first").addClass("current");

var current;
for(var i = 0; i < cookieTgList.length && !current; i++) {
dts.each(function() {
var dt = $(this);
var pre = dt.next("dd").find("pre");
if(pre.hasClass(cookieTgList[i]))
current = dt.addClass("current");
});
}

if(!current)
current = dts.first().addClass("current");
var currentContent = current.next("dd").addClass("current").show();
dts.first().addClass("first");
dts.last().addClass("last");
dl.css("height", current.height() + currentContent.height());
});

$("dl.tabbed dt a").click(function(e){
e.preventDefault();
var current = $(this).parent("dt");
var dl = current.parent("dl");
dl.find(".current").removeClass("current").next("dd").removeClass("current").hide();
current.addClass("current");
var currentContent = current.next("dd").addClass("current").show();
dl.css("height", current.height() + currentContent.height());
var currentDt = $(this).parent("dt");
var currentDl = currentDt.parent("dl");
var currentClasses = currentDt.next("dd").find("pre").attr("class").split(' ');
var currentGroup;
var regex = new RegExp("^" + tabGroupClass + "-.*");
for(var i = 0; i < currentClasses.length && !currentGroup; i++) {
if(regex.test(currentClasses[i])) {
currentGroup = currentClasses[i];
cookieTgList = addToList(cookieTgList, currentGroup);
setCookie(tabGroupCookie, arrayToJson(cookieTgList));
}
}

if(!currentGroup) {
currentDl.find(".current").removeClass("current").next("dd").removeClass("current").hide();
currentDt.addClass("current");
var currentContent = currentDt.next("dd").addClass("current").show();
currentDl.css("height", currentDt.height() + currentContent.height());
} else {
$("dl").has("dd > pre").each(function() {
var dl = $(this);
dl.find("dt").each(function() {
var dt = $(this);
var pre = dt.next("dd").find("pre");
if(pre.hasClass(currentGroup)) {
dl.find(".current").removeClass("current").next("dd").removeClass("current").hide();
dt.addClass("current");
var currentContent = dt.next("dd").addClass("current").show();
dl.css("height", dt.height() + currentContent.height());
}
});
});
}
});

});