Code tabs widget

Hi all,

Currently I use Hugo for my blog and it’s awesome.

I’m looking to use Hugo to build a documentation portal.

One thing I would need is the ability to have a code switcher to display code snippets in different languages.
Like so http://cl.ly/image/2F1t011H1R0k/Screen%20Shot%202015-03-27%20at%2018.16.05.png

And it would be awesome to be able to write that in markdown.

Do you think I could write a custom theme for Hugo that provides this widget?

I would have done this as nested shortcodes with a set of highlight shortcodes to do the syntax highlighting.

One could imagine one could do some tweaks to the Hugo core to do these “shortcode widgets” more elegantly, but I don’t see it right yet.

Ok thanks for the quick reply! I’ll look into what you suggested with shortcodes.

Thanks

As I said: The nested shortcodes will work … but It would be cool if someone would think about a higher level component / widget type … to make this more “package friendly”.

I also need this feature for the documentation of my project. Could you point me to an example how to create a code tab with nested shortcodes with hugo?

I implemented this with nested shortcodes as suggested. An outer shortcode tabs has children tab which contain code blocks:

 {{% tabs %}}
   {{% tab "Java" %}}
     ```java
        System.out.println("hello tabs");
     ```
   {{% /tab %}}

   {{% tab "JavaScript" %}}
     ```javascript
       console.log("hello tabs");
     ```
   {{% /tab %}}
 {{% tabs %}}

In my case I’m using bootstrap nav-tabs. That requires a certain HTML structure and IDs that wouldn’t work for the shortcode structure that’s easy to use.

What I did was dump the contents into the DOM and then use JavaScript to fix up the structure, and used some onclick handling to make the correct tab active.

Here’s the tabs shortcode:

<div class='code-tabs'>
  <ul class="nav nav-tabs"></ul>
  <div class="tab-content">{{ .Inner }}</div>
</div>

Here’s the (nested) tab shortcode:

<div class="tab-pane" title="{{ .Get 0 }}">
  {{ .Inner }}
</div>

And here’s the (admittedly crude) JavaScript:

$('.tab-content').find('.tab-pane').each(function(idx, item) {
  var navTabs = $(this).closest('.code-tabs').find('.nav-tabs'),
      title = $(this).attr('title');
  navTabs.append('<li><a href="#">'+title+'</a></li');
});

$('.code-tabs ul.nav-tabs').each(function() {
  $(this).find("li:first").addClass('active');
})

$('.code-tabs .tab-content').each(function() {
  $(this).find("div:first").addClass('active');
});

$('.nav-tabs a').click(function(e){
  e.preventDefault();
  var tab = $(this).parent(),
      tabIndex = tab.index(),
      tabPanel = $(this).closest('.code-tabs'),
      tabPane = tabPanel.find('.tab-pane').eq(tabIndex);
  tabPanel.find('.active').removeClass('active');
  tab.addClass('active');
  tabPane.addClass('active');
});

(I’m using highlight.js for code formatting; haven’t tried it with pygments.)

Paired with some CSS, you can get a nice tabbed code block layout:

7 Likes

I spent a lot of time to do it.
Hugo doesn’t support it, although Hugo provided an example, but it is about how to use json, yaml or toml via transform.Remarshal. It doesn’t suit me because I needed other languages.
So what do you need for implementation:

  1. shortcode ‘tabs’
  2. shortcode ‘tab’
  3. css
  4. js

TABS:

 <div class='code-tabs'>
  <ul class="nav nav-tabs"></ul>
  <div class="tab-content">{{ .Inner }}</div>
</div>

TAB:

<div class="tab-pane" title="{{ .Get 0 }}">
 {{ .Inner }}
</div>

CSS

#body .nav-tabs {
position: unset;
width: unset;
}

.tab-content > .tab-pane {
display: none;
}

.tab-pane {
padding: 3px 0px;
}

.tab-pane {
padding: 3px 0px;
}

.tab-content > .active {
display: block;
}

.nav-tabs {
color: #0f0f0f;
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
}

.nav-tabs > li {
color: #0f0f0f;
float: left;
}

.nav-tabs > li > a {
display: block;
padding: 5px 10px;
font-size: 14px;
}

pre {
display: block;
padding: 9.5px;
margin: 10px 0px 10px;
line-height: 1;
}

.code-tabs {
margin: 10px 0px 0px 00px;
}

.nav-tabs > li.active > a, .nav-tabs > li > a:hover {
color: #000 !important;
background: transparent;
}

JS

    $(document).ready(function () {

    $('.tab-content').find('.tab-pane').each(function (idx, item) {
        var navTabs = $(this).closest('.code-tabs').find('.nav-tabs'),
            title = $(this).attr('title');
        navTabs.append('<li><a href="#">' + title + '</a></li');
    });

    updateCurrentTab()


    $('.nav-tabs a').click(function (e) {
        e.preventDefault();
        var tab = $(this).parent(),
                  tabIndex = tab.index(),
                  tabPanel = $(this).closest('.code-tabs'),
                  tabPane = tabPanel.find('.tab-pane').eq(tabIndex);
        tabPanel.find('.active').removeClass('active');
        tab.addClass('active');
        tabPane.addClass('active');

        // Store the number of config language selected in users' localStorage
         if(window.localStorage) {
            window.localStorage.setItem("configLangPref", tabIndex + 1)
         }

         // After click update here not only selected part of code but others
         updateCurrentTab()

    });

    function updateCurrentTab() {
        var holder = '.nav-tabs a'

        // By default current tab number is 1
        var tabNumber = 1

        // Get saved tab number
        if (window.localStorage.getItem('configLangPref')) {
           tabNumber = window.localStorage.getItem('configLangPref')
        }

        // Remove 'active' code to avoid multiple examples of code
        $('.nav-tabs a').closest('.code-tabs').find('.active').removeClass('active');

        // Set 'active' state to current li(language) and div(code) by tabNumber
        $('.code-tabs ul.nav-tabs').find("li:nth-of-type(" + tabNumber + ")" ).addClass('active');
        $('.code-tabs .tab-content').find("div:nth-of-type(" + tabNumber + ")").addClass('active');

    }
});

I hope it will help you
4 Likes