Skip to content
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
47 changes: 29 additions & 18 deletions bootstrap-toc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/*!
* Bootstrap Table of Contents v<%= version %> (http://afeld.github.io/bootstrap-toc/)
* Copyright 2015 Aidan Feldman
* Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */
* Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md)
*
* Modified by Ircama, 2016, adding the following options:
* - headings
* - class
Copy link
Owner

Choose a reason for hiding this comment

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

Mind removing this? If all contributions were added to the top of the file, it would get very large very fast 😏 Easier to point people to

https://github.com/afeld/bootstrap-toc/search?q=-author%3Aafeld&type=Issues&utf8=%E2%9C%93

since that list keeps itself up-to-date.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, sure!

*/
(function() {
'use strict';

Expand Down Expand Up @@ -56,20 +61,20 @@
return $childList;
},

generateNavEl: function(anchor, text) {
var $a = $('<a></a>');
generateNavEl: function(anchor, text, navLevel, hClass) {
var $a = $('<a class=' + hClass + navLevel + '></a>');
$a.attr('href', '#' + anchor);
$a.text(text);
var $li = $('<li></li>');
$li.append($a);
return $li;
},

generateNavItem: function(headingEl) {
generateNavItem: function(headingEl, navLevel, hClass) {
var anchor = this.generateAnchor(headingEl);
var $heading = $(headingEl);
var text = $heading.data('toc-text') || $heading.text();
return this.generateNavEl(anchor, text);
return this.generateNavEl(anchor, text, navLevel, hClass);
},

// Find the first heading level (`<h1>`, then `<h2>`, etc.) that has more than one element. Defaults to 1 (for `<h1>`).
Expand All @@ -85,30 +90,34 @@
},

// returns the elements for the top level, and the next below it
getHeadings: function($scope, topLevel) {
var topSelector = 'h' + topLevel;

var secondaryLevel = topLevel + 1;
var secondarySelector = 'h' + secondaryLevel;

return this.findOrFilter($scope, topSelector + ',' + secondarySelector);
getHeadings: function($scope, topLevel, headingList) {
if (headingList === undefined) {
var topSelector = 'h' + topLevel;

var secondaryLevel = topLevel + 1;
var secondarySelector = 'h' + secondaryLevel;

return this.findOrFilter($scope, topSelector + ',' + secondarySelector);
} else {
return this.findOrFilter($scope, headingList);
}
},

getNavLevel: function(el) {
return parseInt(el.tagName.charAt(1), 10);
},

populateNav: function($topContext, topLevel, $headings) {
populateNav: function($topContext, topLevel, $headings, hClass) {
var $context = $topContext;
var $prevNav;

var helpers = this;
$headings.each(function(i, el) {
var $newNav = helpers.generateNavItem(el);
var navLevel = helpers.getNavLevel(el);
var $newNav = helpers.generateNavItem(el, navLevel, hClass);

// determine the proper $context
if (navLevel === topLevel) {
if (navLevel <= topLevel) {
// use top level
$context = $topContext;
} else if ($prevNav && $context === $topContext) {
Expand Down Expand Up @@ -137,16 +146,18 @@
},

// accepts a jQuery object, or an options object
init: function(opts) {
init: function(opts, conf) {
opts = this.helpers.parseOps(opts);
var headingList = conf === undefined ? undefined : conf.headings;
var hClass = conf === undefined || conf.class === undefined || conf.class == '' ? 'toc-nav-h' : conf.class;

// ensure that the data attribute is in place for styling
opts.$nav.attr('data-toggle', 'toc');

var $topContext = this.helpers.createChildNavList(opts.$nav);
var topLevel = this.helpers.getTopLevel(opts.$scope);
var $headings = this.helpers.getHeadings(opts.$scope, topLevel);
this.helpers.populateNav($topContext, topLevel, $headings);
var $headings = this.helpers.getHeadings(opts.$scope, topLevel, headingList);
this.helpers.populateNav($topContext, topLevel, $headings, hClass);
}
};

Expand Down
49 changes: 30 additions & 19 deletions dist/bootstrap-toc.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/*!
* Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/)
* Bootstrap Table of Contents v<%= version %> (http://afeld.github.io/bootstrap-toc/)
* Copyright 2015 Aidan Feldman
* Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */
* Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md)
*
* Modified by Ircama, 2016, adding the following options:
* - headings
* - class
*/
(function() {
'use strict';

Expand Down Expand Up @@ -56,20 +61,20 @@
return $childList;
},

generateNavEl: function(anchor, text) {
var $a = $('<a></a>');
generateNavEl: function(anchor, text, navLevel, hClass) {
var $a = $('<a class=' + hClass + navLevel + '></a>');
$a.attr('href', '#' + anchor);
$a.text(text);
var $li = $('<li></li>');
$li.append($a);
return $li;
},

generateNavItem: function(headingEl) {
generateNavItem: function(headingEl, navLevel, hClass) {
var anchor = this.generateAnchor(headingEl);
var $heading = $(headingEl);
var text = $heading.data('toc-text') || $heading.text();
return this.generateNavEl(anchor, text);
return this.generateNavEl(anchor, text, navLevel, hClass);
},

// Find the first heading level (`<h1>`, then `<h2>`, etc.) that has more than one element. Defaults to 1 (for `<h1>`).
Expand All @@ -85,30 +90,34 @@
},

// returns the elements for the top level, and the next below it
getHeadings: function($scope, topLevel) {
var topSelector = 'h' + topLevel;

var secondaryLevel = topLevel + 1;
var secondarySelector = 'h' + secondaryLevel;

return this.findOrFilter($scope, topSelector + ',' + secondarySelector);
getHeadings: function($scope, topLevel, headingList) {
if (headingList === undefined) {
var topSelector = 'h' + topLevel;

var secondaryLevel = topLevel + 1;
var secondarySelector = 'h' + secondaryLevel;

return this.findOrFilter($scope, topSelector + ',' + secondarySelector);
} else {
return this.findOrFilter($scope, headingList);
}
},

getNavLevel: function(el) {
return parseInt(el.tagName.charAt(1), 10);
},

populateNav: function($topContext, topLevel, $headings) {
populateNav: function($topContext, topLevel, $headings, hClass) {
var $context = $topContext;
var $prevNav;

var helpers = this;
$headings.each(function(i, el) {
var $newNav = helpers.generateNavItem(el);
var navLevel = helpers.getNavLevel(el);
var $newNav = helpers.generateNavItem(el, navLevel, hClass);

// determine the proper $context
if (navLevel === topLevel) {
if (navLevel <= topLevel) {
// use top level
$context = $topContext;
} else if ($prevNav && $context === $topContext) {
Expand Down Expand Up @@ -137,16 +146,18 @@
},

// accepts a jQuery object, or an options object
init: function(opts) {
init: function(opts, conf) {
opts = this.helpers.parseOps(opts);
var headingList = conf === undefined ? undefined : conf.headings;
var hClass = conf === undefined || conf.class === undefined || conf.class == '' ? 'toc-nav-h' : conf.class;

// ensure that the data attribute is in place for styling
opts.$nav.attr('data-toggle', 'toc');

var $topContext = this.helpers.createChildNavList(opts.$nav);
var topLevel = this.helpers.getTopLevel(opts.$scope);
var $headings = this.helpers.getHeadings(opts.$scope, topLevel);
this.helpers.populateNav($topContext, topLevel, $headings);
var $headings = this.helpers.getHeadings(opts.$scope, topLevel, headingList);
this.helpers.populateNav($topContext, topLevel, $headings, hClass);
}
};

Expand Down
27 changes: 24 additions & 3 deletions dist/bootstrap-toc.min.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
/*!
* Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/)
* Copyright 2015 Aidan Feldman
* Bootstrap Table of Contents v0.4.2 (http://afeld.github.io/bootstrap-toc/)
* Copyright 2015 Aidan Feldman, modified by Ircama, 2016
* Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */
!function(){"use strict";window.Toc={helpers:{findOrFilter:function(e,t){var r=e.find(t);return e.filter(t).add(r).filter(":not([data-toc-skip])")},generateUniqueIdBase:function(e){var t=$(e).text(),r=t.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g,"-");return r||e.tagName.toLowerCase()},generateUniqueId:function(e){for(var t=this.generateUniqueIdBase(e),r=0;;r++){var n=t;if(r>0&&(n+="-"+r),!document.getElementById(n))return n}},generateAnchor:function(e){if(e.id)return e.id;var t=this.generateUniqueId(e);return e.id=t,t},createNavList:function(){return $('<ul class="nav"></ul>')},createChildNavList:function(e){var t=this.createNavList();return e.append(t),t},generateNavEl:function(e,t){var r=$("<a></a>");r.attr("href","#"+e),r.text(t);var n=$("<li></li>");return n.append(r),n},generateNavItem:function(e){var t=this.generateAnchor(e),r=$(e),n=r.data("toc-text")||r.text();return this.generateNavEl(t,n)},getTopLevel:function(e){for(var t=1;t<=6;t++){var r=this.findOrFilter(e,"h"+t);if(r.length>1)return t}return 1},getHeadings:function(e,t){var r="h"+t,n=t+1,a="h"+n;return this.findOrFilter(e,r+","+a)},getNavLevel:function(e){return parseInt(e.tagName.charAt(1),10)},populateNav:function(e,t,r){var n,a=e,i=this;r.each(function(r,o){var s=i.generateNavItem(o),u=i.getNavLevel(o);u===t?a=e:n&&a===e&&(a=i.createChildNavList(n)),a.append(s),n=s})},parseOps:function(e){var t;return t=e.jquery?{$nav:e}:e,t.$scope=t.$scope||$(document.body),t}},init:function(e){e=this.helpers.parseOps(e),e.$nav.attr("data-toggle","toc");var t=this.helpers.createChildNavList(e.$nav),r=this.helpers.getTopLevel(e.$scope),n=this.helpers.getHeadings(e.$scope,r);this.helpers.populateNav(t,r,n)}},$(function(){$('nav[data-toggle="toc"]').each(function(e,t){var r=$(t);Toc.init(r)})})}();
!function(){"use strict"
window.Toc={helpers:{findOrFilter:function(e,t){var r=e.find(t)
return e.filter(t).add(r).filter(":not([data-toc-skip])")},generateUniqueIdBase:function(e){var t=$(e).text(),r=t.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g,"-")
return r||e.tagName.toLowerCase()},generateUniqueId:function(e){for(var t=this.generateUniqueIdBase(e),r=0;;r++){var a=t
if(r>0&&(a+="-"+r),!document.getElementById(a))return a}},generateAnchor:function(e){if(e.id)return e.id
var t=this.generateUniqueId(e)
return e.id=t,t},createNavList:function(){return $('<ul class="nav"></ul>')},createChildNavList:function(e){var t=this.createNavList()
return e.append(t),t},generateNavEl:function(e,t,r,a){var n=$("<a class="+a+r+"></a>")
n.attr("href","#"+e),n.text(t)
var i=$("<li></li>")
return i.append(n),i},generateNavItem:function(e,t,r){var a=this.generateAnchor(e),n=$(e),i=n.data("toc-text")||n.text()
return this.generateNavEl(a,i,t,r)},getTopLevel:function(e){for(var t=1;6>=t;t++){var r=this.findOrFilter(e,"h"+t)
if(r.length>1)return t}return 1},getHeadings:function(e,t,r){if(void 0===r){var a="h"+t,n=t+1,i="h"+n
return this.findOrFilter(e,a+","+i)}return this.findOrFilter(e,r)},getNavLevel:function(e){return parseInt(e.tagName.charAt(1),10)},populateNav:function(e,t,r,a){var n,i=e,s=this
r.each(function(r,o){var c=s.getNavLevel(o),v=s.generateNavItem(o,c,a)
t>=c?i=e:n&&i===e&&(i=s.createChildNavList(n)),i.append(v),n=v})},parseOps:function(e){var t
return t=e.jquery?{$nav:e}:e,t.$scope=t.$scope||$(document.body),t}},init:function(e,t){e=this.helpers.parseOps(e)
var r=void 0===t?void 0:t.headings,a=void 0===t||void 0===t["class"]||""==t["class"]?"toc-nav-h":t["class"]
e.$nav.attr("data-toggle","toc")
var n=this.helpers.createChildNavList(e.$nav),i=this.helpers.getTopLevel(e.$scope),s=this.helpers.getHeadings(e.$scope,i,r)
this.helpers.populateNav(n,i,s,a)}},$(function(){$('nav[data-toggle="toc"]').each(function(e,t){var r=$(t)
Toc.init(r)})})}()
66 changes: 66 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,72 @@ nav[data-toggle='toc'] {
}
```

## Additional options

By default, the plugin finds the first heading level (`<h1>`, then `<h2>`, etc.) that has more than one element and defaults to 1 (for `<h1>`). The identified level becomes the top heading and the plugin supports a single nesting level associated to it, including the subsequent heading only. E.g., if the identified top level is `<h1>`, the nested level will be `<h2>`, which is the next below it; if the top level is `<h2>`, the nested one will be `<h3>`. No additional level to the nested one and no previous level to the top one will be shown.
Copy link
Owner

Choose a reason for hiding this comment

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

This is great, thanks for adding!

Copy link
Contributor Author

@Ircama Ircama Oct 24, 2016

Choose a reason for hiding this comment

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

I added it as it took me sometimes to understand that, if my top level was h2, only this level and h3 could be represented, while h1 within the middle of the document and also h4 did not show up.


The plugin allows options to customize this behavior:

* `headings` (which can be a string): if set to a list of headers (in the form *hN,hM,...* where N, M, ... are levels, e.g., `h1,h2,h3`), all of them are searched instead of the first declared one and of its subsequent heading. Example: `headings: 'h1,h2,h3,h4,h5,h6'` (in this case, all of them are searched and not only `<h2>` and `<h3>` in case `<h2>` is the top level).
Copy link
Owner

Choose a reason for hiding this comment

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

What's the use case here? Do you have an example page where you would want to use this? What would the desired outcome be? Also, does this differ from

$scope: $('h1,h2,h3,h4,h5,h6')

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi, I generally agree about your comments and you can add new commits to this branch. This PR was more than discuss than considering an immediate merge.

What's the use case here? Do you have an example page where you would want to use this? What would the desired outcome be?

Check this page as an example:

https://ircama.github.io/osm-carto-tutorials/tile-server-ubuntu/

Your plugin is running there.

In fact, at the end of the HTML:

  $(function() {
    var navSelector = '#toc';
    var $myNav = $(navSelector);
    Toc.init($myNav, { headings: 'h1,h2,h3,h4,h5,h6' } );
    $('body').scrollspy({
      target: navSelector,
      offset: 220
    });
  });

In the page, you can see that my top level is 2 and that I generally use one sublevel, but there are some paragraphs with an additional sublevel 4:

https://ircama.github.io/osm-carto-tutorials/tile-server-ubuntu/#install-mapnik-library-from-package

  • [h2] Install Mapnik library
    • [h3] Install Mapnik library from package
    • [h3] Alternatively, install Mapnik from sources
      • [h4] Install Boost from package
      • [h4] Alternatively, install the latest version of Boost from source
      • [h4] Install HarfBuzz from source
      • [h4] Build the Mapnik library from source

You see that h4 items are present (this is thanks to { headings: 'h1,h2,h3,h4,h5,h6' }), are jointly expanded with h3 (and there is no way with this drop to do differently) and they are indented and represented with smaller font size (thanks to the auto-class naming and to the CSS).

Also, does this differ from $scope: $('h1,h2,h3,h4,h5,h6')?

Could you please make an example of this?

Copy link
Owner

Choose a reason for hiding this comment

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

Let's deal with the "depth" feature request separately: #37

* `class`: (which can be a string): if set to a string, its value is used to customize the CSS class that the plugin sets for each description, concatenating it with the related header level. If not set, the default class heading will be `toc-nav-h`. Example: by default, a top level description (`<h1>`) has class`toc-nav-h1`, a second level description (`<h2>`) has class `toc-nav-h2`, etc. If the option `class` is set to e.g. `my-class`, a top level description (`<h1>`) will have class `my-class-h1`, a second level description (`<h2>`) will have class `my-class-h2`, etc. This feature allows adding CSS attributes to customize the descriptions according its header level.
Copy link
Owner

Choose a reason for hiding this comment

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

While I like the adding of classes to different levels to make styling easier, the customization of said class seems overkill...

Copy link
Owner

Choose a reason for hiding this comment

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

Also, what's the behavior when the top-level heading in the sidebar corresponds to an <h2>? My assumption is that users would care more about the level in the table of contents than what h* it corresponds to, so I would make it more clear by using toc-nav-level-1 and toc-nav-level-2 or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I like the adding of classes to different levels to make styling easier, the customization of said class seems overkill...

Maybe it is indead an overkill if no one is going to use the same root in other places. You can suggest a code rework or do it yourself if wanted.

Also, what's the behavior when the top-level heading in the sidebar corresponds to an <h2>? My assumption is that users would care more about the level in the table of contents than what h* it corresponds to, so I would make it more clear by using toc-nav-level-1 and toc-nav-level-2 or something like that.

If you have top level h2, h1 in the middle of the document will be always shown when setting headings: 'h1,h2,h3,h4,h5,h6', but not shown if leaving this option out.

so I would make it more clear by using toc-nav-level-1 and toc-nav-level-2 or something like that.

To make sure I got your point, would you change toc-nav-h to toc-nav-level-h?

Copy link
Owner

Choose a reason for hiding this comment

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

Still not sure I understand the use case here. Feel free to open an issue if you are still interested.


Example of usage:

```javascript
Toc.init($myNav, {
headings: 'h1,h2,h3,h4,h5,h6',
class: 'my-class'
});
```
Copy link
Owner

Choose a reason for hiding this comment

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

Let's use the existing options object—no need to make a second one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. Would you please suggest a code rework? Thanks

Copy link
Owner

Choose a reason for hiding this comment

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

Toc.init({
  $nav: $myNav,
  headings: 'h1,h2,h3,h4,h5,h6',
  class: 'my-class'
});


Example of CSS to customize the behavior of the plugin where the target documentation is supposed to mostly exploit `<h2>` as top level and `<h3>` as neighboring one (supposing to set `headings: 'h1,h2,h3,h4,h5,h6'` and not to change the default `class` option):

```css
/*
* Customizations of Bootstrap Table of Contents v0.4.2
*/
nav[data-toggle='toc'] .nav .nav > li > a {
padding-bottom: 3px !important;
}
.toc-nav-h1 {
text-decoration: navy dotted underline;
font-size: 15px !important;
padding-left: 15px !important;
}
.toc-nav-h4 {
font-size: 11px !important;
padding-left: 40px !important;
}
.toc-nav-h5 {
font-size: 11px !important;
padding-left: 46px !important;
}
.toc-nav-h6 {
font-size: 11px !important;
padding-left: 52px !important;
}
```

The following example includes an additional setting for scrollspy (both `target` and `offset` options are used:

```html
<script>
try {
$(function() {
var navSelector = '#toc';
var $myNav = $(navSelector);
Toc.init($myNav, { headings: 'h1,h2,h3,h4,h5,h6' } );
$('body').scrollspy({
target: navSelector,
offset: 220
});
});
}
catch(e) {
}
Copy link
Owner

Choose a reason for hiding this comment

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

Mind removing the try/catch? Using it like this would swallow any errors, and therefore is definitely not something I want the documentation to encourage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, right!

</script>
```
Copy link
Owner

Choose a reason for hiding this comment

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

Thinking it would make more sense to throw an example like this in a sandbox so that it can be interactive, and tweakable by the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that would be great, I did not have time to setup it.


## See also

This plugin was heavily inspired by:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bootstrap-toc",
"version": "0.4.1",
"version": "0.4.2",
"private": true,
"scripts": {
"test": "gulp test"
Expand Down