Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

tocbot

Package Overview
Dependencies
Maintainers
1
Versions
116
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tocbot - npm Package Compare versions

Comparing version 1.0.0 to 1.0.1

docs/test/test.md

0

config.js

@@ -0,0 +0,0 @@ var paths = {

@@ -0,0 +0,0 @@ var gulp = require('gulp');

@@ -8,4 +8,5 @@ var gulp = require('gulp');

'docs-browserify',
'docs-browserify-test', // temp
'docs-browser-sync',
'docs-watch',
]);

2

gulp/tasks/docs-browser-sync.js

@@ -12,3 +12,3 @@ var gulp = require('gulp');

},
port: 4000,
port: 4100,
server: {

@@ -15,0 +15,0 @@ baseDir: paths.build,

@@ -0,0 +0,0 @@ var path = require('path');

var gulp = require('gulp');
gulp.task('build', [
gulp.task('docs-build', [
'docs-markdown',

@@ -5,0 +5,0 @@ 'docs-copy-files',

@@ -0,0 +0,0 @@ var gulp = require('gulp');

@@ -0,1 +1,2 @@

var path = require('path');
var gulp = require('gulp');

@@ -2,0 +3,0 @@ var ghPages = require('gulp-gh-pages');

@@ -11,3 +11,4 @@ var gulp = require('gulp');

var handleErrors = require('../util/handleErrors');
var paths = require('../../config').paths;
var config = require('../../config');
var paths = config.paths;

@@ -18,5 +19,3 @@ // Swig config.

loader: swig.loaders.fs(paths.src),
locals: {
paths: paths
}
locals: config
});

@@ -35,3 +34,3 @@

path.join('README.md'),
path.join('docs/**/*.md')
path.join(paths.docs, '**/*.md')
// path.join('*.md')

@@ -38,0 +37,0 @@ ])

@@ -0,0 +0,0 @@ var gulp = require('gulp');

@@ -14,2 +14,5 @@ var gulp = require('gulp');

.on('error', handleErrors);
// Watch task for test js
gulp.watch(path.join(paths.test + '**/*.js'), ['docs-browserify-test'])
.on('error', handleErrors);

@@ -16,0 +19,0 @@ // Watch tasks for html

@@ -0,0 +0,0 @@ var gulp = require('gulp');

var gulp = require('gulp');
var electronMocha = require('gulp-electron-mocha');
var mochatron = require('mochatron');
var paths = require('../../config').paths;
gulp.task('test', function() {
return gulp.src(paths.test, { read: false })
.pipe(electronMocha({
renderer: true,
}));
mochatron({
url: 'http://localhost:4100/test/test.html',
silent: true,
window: false
});
});

@@ -0,0 +0,0 @@ /* bundleLogger

@@ -0,0 +0,0 @@ var notify = require("gulp-notify");

@@ -0,0 +0,0 @@ /*

{
"name": "tocbot",
"version": "1.0.0",
"version": "1.0.1",
"description": "Generate a table of contents based on the heading structure of a html document.",

@@ -11,3 +11,3 @@ "main": "gulpfile.js",

"type": "git",
"url": "git+https://github.com/optimizely/tocbot.git"
"url": "git+https://github.com/tscanlin/tocbot.git"
},

@@ -28,11 +28,13 @@ "keywords": [

"bugs": {
"url": "https://github.com/optimizely/tocbot/issues"
"url": "https://github.com/tscanlin/tocbot/issues"
},
"homepage": "https://github.com/optimizely/tocbot#readme",
"homepage": "https://github.com/tscanlin/tocbot#readme",
"devDependencies": {
"babel-eslint": "^5.0.0",
"babel-runtime": "^6.3.19",
"browser-sync": "^2.10.1",
"browserify": "^12.0.1",
"chai": "^3.4.1",
"del": "^2.2.0",
"eslint": "^1.10.3",
"eslint": "^2.4.0",
"gulp": "^3.9.0",

@@ -52,2 +54,5 @@ "gulp-changed": "^1.3.0",

"js-yaml": "^3.4.6",
"mocha": "^2.3.4",
"mochatron": "^1.0.7",
"optimizely-oui": "github:optimizely/oui",
"require-dir": "^0.3.0",

@@ -60,5 +65,4 @@ "swig": "^1.4.2",

"dependencies": {
"optimizely-oui": "github:optimizely/oui",
"smooth-scroll": "cferdinandi/smooth-scroll"
}
}

@@ -6,4 +6,6 @@ ---

## Introduction
<h1 class="display--none"><a href="http://tscanlin.github.io/tocbot">Tocbot</a></h1>
<h2 id="introduction" class="hard flush">Introduction</h2>
tocbot is a small script to build a table of contents (TOC) from headings in an HTML document. tocbot works well with [Smooth Scroll](https://github.com/cferdinandi/smooth-scroll) and together they provide an experience very similar to [Tocify](http://gregfranko.com/jquery.tocify.js/).

@@ -14,5 +16,2 @@

### Checkout the [**Demo**](/)
## Get Started

@@ -25,6 +24,7 @@

Include the script at the bottom of the page before the closing body tag
Include the script at the bottom of the page before the closing body tag.
```html
<script src="http://optimizely.github.io/tocbot/js/tocbot.js"></script>
<script src="/js/tocbot.js"></script>
<script src="/js/smooth-scroll.js"></script>
```

@@ -34,3 +34,3 @@

Install it with npm
Install it with npm.

@@ -41,6 +41,13 @@ ```sh

And require it
And optionally.
```sh
npm install --save cferdinandi/smooth-scroll
```
Then require them.
```javascript
var tocbot = require('tocbot');
var smoothScroll = require('smooth-scroll');
```

@@ -51,6 +58,6 @@

CSS is used for expanding & collapsing groupings and some basic styling. The core of what's required is **only 24 lines unminified**.
CSS is used for expanding & collapsing groupings and some basic styling. The core of what's required is only 24 lines unminified.
```html
<link rel="stylesheet" href="http://optimizely.github.io/tocbot/css/style.css">
<link rel="stylesheet" href="http://tscanlin.github.io/tocbot/css/style.css">
```

@@ -91,3 +98,3 @@

There is no need for jQuery as this library uses **vanilla javascript**.
There is no need for jQuery as this library uses **vanilla javascript** and is only about 500 lines unminified.

@@ -134,4 +141,4 @@ There are no external dependencies for this script, besides [**Smooth Scroll**](https://github.com/cferdinandi/smooth-scroll) (which also has no dependencies) should you choose to include it.

activeLinkClass: 'is-active-link',
// Headings that match the excludeSelector will be skipped.
excludeSelector: '.skip-toc',
// Headings that match the ignoreSelector will be skipped.
ignoreSelector: '.skip-toc',
// Fixed position class to add to make sidebar fixed after scrolling down past the fixedSidebarOffset.

@@ -190,15 +197,10 @@ positionFixedClass: 'is-position-fixed',

## Running Tests
gulp test
## Roadmap
- Tests
- List size of js and css
- Split into smaller files (core, util/data, util/dom)
- Blog post to announce it!!!
- Eventually, a place to drop in markdown to preview it.
- React.js support.
- Debounce option.
- More tests
- Blog post to announce it
- Eventually, a place to drop in markdown to preview it
- Debounce option
- React.js support

@@ -214,4 +216,10 @@

Contributions and suggestions are welcome. Please feel free to open an issue if you run into a problem or have a feature request. I'll do my best to respond in a timely fashion.
Contributions and suggestions are welcome! Please feel free to open an issue if you run into a problem or have a feature request. I'll do my best to respond in a timely fashion.
If you want to open a pull request just fork the repo but please make sure all tests and lint pass first.
### Running Tests
`gulp test`
[//]: # (FAQ)

@@ -218,0 +226,0 @@

/**
* tocbot
* tocbot is similar to tocify (http://gregfranko.com/jquery.tocify.js/) (except its native w/ no need for jquery UI)
* This creates a toble of contents base on headings that allows users to easily jump to different sections.
* tocbot is similar to tocify (http://gregfranko.com/jquery.tocify.js/) (except its native w/ no need for jquery)
* This creates a toble of contents based on HTML headings which allows users to easily jump to different sections.
*
* @author Tim Scanlin (tim.scanlin@optimizely.com)
* @author Tim Scanlin
*/

@@ -21,3 +21,15 @@

var tocbot = {}; // Object for public APIs.
// Default options.
var defaultOptions = require('./default-options.js');
// Object to store current options.
var options = {};
// Object for public APIs.
var tocbot = {};
var BuildHtml = require('./build-html.js');
var ParseContent = require('./parse-content.js');
// Keep these variables at top scope once options are passed in.
var buildHtml;
var parseContent;
var doc = root.document;

@@ -27,6 +39,2 @@ var body = document.body;

// Helpers.
var forEach = [].forEach;
var reduce = [].reduce;
var some = [].some;
// From: https://github.com/Raynos/xtend

@@ -48,59 +56,2 @@ var hasOwnProperty = Object.prototype.hasOwnProperty;

// Default options.
var options = {};
var defaultOptions = {
// Where to render the table of contents.
tocSelector: '.js-toc',
// Where to grab the headings to build the table of contents.
contentSelector: '.js-content',
// Reference to smoothScroll
smoothScroll: undefined,
// smoothScroll Options
smoothScrollOptions: {
easing: 'easeInOutCubic',
offset: 0,
speed: 300, // animation duration.
updateURL: true,
},
// Which headings to grab inside of the contentSelector element.
headingsToSelect: [
'h1',
'h2',
'h3',
'h4',
'h5',
],
// Class to add to active links (the link corresponding to the top most heading on the page).
activeLinkClass: 'is-active-link',
// Headings that match the excludeSelector will be skipped.
excludeSelector: '.skip-toc',
// Fixed position class to add to make sidebar fixed after scrolling down past the fixedSidebarOffset.
positionFixedClass: 'is-position-fixed',
// fixedSidebarOffset can be any number but by default is set to auto which sets the fixedSidebarOffset to the sidebar element's offsetTop from the top of the document on init.
fixedSidebarOffset: 'auto',
// Main class to add to links.
linkClass: 'toc-link',
// Extra classes to add to links.
extraLinkClasses: 'color--base',
// Main class to add to lists.
listClass: 'toc-list',
// Extra classes to add to lists.
extraListClasses: 'soft--left',
// Headings offset between the headings and the top of the document (helps with weird rounding bugs that pop up).
headingsOffset: 2,
// Class that gets added when a list should be collapsed.
isCollapsedClass: 'is-collapsed',
// Class that gets added when a list should be able to be collapsed but isn't necessarily collpased.
collapsibleClass: 'collapsible',
// How many heading levels should not be collpased. For example, number 6 will show everything since there are only 6 heading levels and number 0 will collpase them all.
collapseDepth: 0,
// This is storing current state and not really a setting...
// TODO: Handle this better, maybe bring back a state object??
_currentlyHighlighting: true,
};
/**

@@ -111,9 +62,11 @@ * Destroy tocbot.

// Remove event listeners
doc.removeEventListener('scroll');
doc.removeEventListener('resize');
removeTocLinkListeners();
document.removeEventListener('scroll');
document.removeEventListener('resize');
if (buildHtml) {
document.removeEventListener('click', buildHtml.disableTocAnimation);
}
// Destroy smoothScroll if it exists.
if (options.smoothScroll) {
this.smoothScroll.destroy();
options.smoothScroll.destroy();
}

@@ -132,5 +85,2 @@ };

// Destroy it if it exists first.
tocbot.destroy();
// Merge defaults with user options.

@@ -142,28 +92,40 @@ // Set to options variable at the top.

// Pass options to these modules.
buildHtml = BuildHtml(options);
parseContent = ParseContent(options);
// For testing purposes.
this._buildHtml = buildHtml;
this._parseContent = parseContent;
// Destroy it if it exists first.
tocbot.destroy();
// Get headings array
var headingsArray = selectHeadings('body', options.headingsToSelect, {
excludeSelector: options.excludeSelector
});
var headingsArray = parseContent.selectHeadings(options.contentSelector, options.headingsToSelect);
// Build nested headings array.
var nestedHeadingsObj = nestHeadingsArray(headingsArray);
var nestedHeadingsObj = parseContent.nestHeadingsArray(headingsArray);
var nestedHeadings = nestedHeadingsObj.nest;
///////////////////////////////////////////////////////////////////////////////////////////////////
// USE CALLBACK TO BUILD NODE TREE IN MEMORY
// APPEND NODE TREE
// Render.
render(options.tocSelector, nestedHeadings);
buildHtml.render(options.tocSelector, nestedHeadings);
// Update Sidebar and bind listeners.
updateToc(headingsArray);
doc.addEventListener('scroll', function() {
updateToc(headingsArray);
buildHtml.updateToc(headingsArray);
document.addEventListener('scroll', function() {
buildHtml.updateToc(headingsArray);
});
doc.addEventListener('resize', function() {
updateToc(headingsArray);
document.addEventListener('resize', function() {
buildHtml.updateToc(headingsArray);
});
// Bind click listeners to disable animation.
document.addEventListener('click', buildHtml.disableTocAnimation);
// Initialize smoothscroll if it exists.
if (options.smoothScroll) {
this.smoothScroll = options.smoothScroll.init(options.smoothScrollOptions);
this.smoothScroll = options.smoothScroll.init(extend(options.smoothScrollOptions, {
callback: buildHtml.enableTocAnimation
}));
}

@@ -179,282 +141,9 @@

tocbot.destroy();
tocbot.init(customOptions);
tocbot.init(customOptions || this.options);
};
/**
* Select headings in content area, exclude any selector in options.excludeSelector
* @param {String} contentSelector
* @param {Array} headingsToSelect
* @param {Object} options
* @return {Array}
*/
function selectHeadings(contentSelector, headingsToSelect, options) {
if (options.excludeSelector) {
headingsToSelect = headingsToSelect.map(function(selector) {
return selector + ':not(' + options.excludeSelector + ')';
});
}
return doc.querySelector(contentSelector)
.querySelectorAll(headingsToSelect.join(', '));
}
// Make tocbot available globally.
root.tocbot = tocbot;
/**
* Nest headings array into nested arrays with 'children' property.
* @param {Array} headingsArray
* @param {Function} callback
* @return {Object}
*/
function nestHeadingsArray(headingsArray, callback) {
return reduce.call(headingsArray, function(prev, curr, index) {
var currentHeading = getHeadingObject(curr);
if (prev.lastItem) {
if (getHeadingLevel(curr) < getHeadingLevel(prev.lastItem)) {
// Allows going back up multiple levels.
prev.depth -= getHeadingLevel(prev.lastItem) - getHeadingLevel(curr);
}
if (getHeadingLevel(curr) > getHeadingLevel(prev.lastItem)) {
prev.depth++;
}
}
appendNodeAtDepth(currentHeading, prev.depth, prev.nest)
prev.lastItem = curr;
return prev;
}, {
lastItem: undefined,
depth: 0,
nest: [],
});
}
/**
* Append node at a specific depth in the nested array.
* @param {Object} node
* @param {Number} depth
* @param {Array} array
* @return {Array}
*/
function appendNodeAtDepth(node, depth, array) {
var counter = depth;
while(counter > 0) {
array = getLastItem(array).children;
counter--;
}
// console.log(depth, options.collapseDepth, depth < options.collapseDepth)
if (depth > options.collapseDepth) {
node.isCollapsed = true;
}
// collapsedClass: 'is-collapsed',
// collapseDepth: 1,
array.push(node);
return array;
}
/**
* Get the last item in an array and return a reference to it.
* @param {Array} array
* @return {Object}
*/
function getLastItem(array) {
return array[array.length - 1];
}
/**
* Get heading level for a heading dom node.
* @param {HTMLElement} heading
* @return {Number}
*/
function getHeadingLevel(heading) {
return +heading.nodeName.split('H').join('');
}
/**
* Get important properties from a heading element and store in a plain object.
* @param {HTMLElement} heading
* @return {Object}
*/
function getHeadingObject(heading) {
return {
id: heading.id,
children: [],
nodeName: heading.nodeName,
textContent: heading.textContent.trim(),
};
}
// HTML
/**
* Render nested heading array data into a given selector.
* @param {String} selector
* @param {Array} data
* @return {HTMLElement}
*/
function render(selector, data) {
var self = this;
var collapsed = false;
var container = createList(collapsed);
function createEl(d, container) {
var link = container.appendChild(createLink(d));
if (d.children.length) {
var list = createList(d.isCollapsed);
d.children.forEach(function(d) {
createEl(d, list);
});
link.appendChild(list);
}
}
data.forEach(function(d) {
createEl(d, container)
});
// Append the Elements that have been created;
return doc.querySelector(selector)
.appendChild(container);
}
function createLink(data) {
var item = document.createElement('li');
var a = document.createElement('a');
a.textContent = data.textContent;
if (options.smoothScroll !== undefined) {
a.setAttribute('data-scroll', '');
}
a.setAttribute('href', '#' + data.id);
a.setAttribute('class', options.linkClass
+ ' ' + 'node-name--' + data.nodeName
+ ' ' + options.extraLinkClasses);
item.appendChild(a);
return item;
}
function createList(isCollapsed) {
var list = document.createElement('ul');
var classes = options.listClass
+ ' ' + options.extraListClasses;
if (isCollapsed) {
classes += ' ' + options.collapsibleClass;
classes += ' ' + options.isCollapsedClass;
}
list.setAttribute('class', classes);
return list;
}
function updateFixedSidebarClass() {
var top = document.documentElement.scrollTop || body.scrollTop;
var tocEl = doc.querySelector(options.tocSelector);
if (options.fixedSidebarOffset === 'auto') {
options.fixedSidebarOffset = doc.querySelector(options.tocSelector).offsetTop;
}
if (top > options.fixedSidebarOffset) {
tocEl.classList.add(options.positionFixedClass);
} else {
tocEl.classList.remove(options.positionFixedClass);
}
}
// Update Toc
function updateToc(headingsArray) {
var top = document.documentElement.scrollTop || body.scrollTop;
// Add fixed class at offset;
updateFixedSidebarClass();
// Get the top most heading currently visible on the page so we know what to highlight.
var headings = headingsArray;
var topHeader;
// Using some instead of each so that we can escape early.
if (options._currentlyHighlighting
&& doc.querySelector(options.tocSelector) !== null
&& headings.length > 0) {
some.call(headings, function(heading, i) {
if (heading.offsetTop > top + options.headingsOffset) {
// Don't allow negative index value.
var index = (i === 0) ? i : i - 1;
topHeader = headings[index];
return true;
} else if (i === headings.length - 1) {
// This allows scrolling for the last heading on the page.
topHeader = headings[headings.length - 1];
return true;
}
});
// Remove the active class from the other tocLinks.
var tocLinks = doc.querySelector(options.tocSelector)
.querySelectorAll('.' + options.linkClass);
forEach.call(tocLinks, function(tocLink) {
tocLink.classList.remove(options.activeLinkClass);
});
// Add the active class to the active tocLink.
var activeTocLink = doc.querySelector(options.tocSelector)
.querySelector('.' + options.linkClass
+ '.node-name--' + topHeader.nodeName
+ '[href="#' + topHeader.id + '"]');
activeTocLink.classList.add(options.activeLinkClass);
var tocLists = doc.querySelector(options.tocSelector)
.querySelectorAll('.' + options.listClass + '.' + options.collapsibleClass);
// Collapse the other collapsible lists.
forEach.call(tocLists, function(list) {
list.classList.add(options.isCollapsedClass);
});
// Expand the active link's collapsible list and its sibling if applicable.
if (activeTocLink.nextSibling) {
activeTocLink.nextSibling.classList.remove(options.isCollapsedClass);
}
removeCollapsedFromParents(activeTocLink.parentNode.parentNode);
}
disableTocAnimation()
}
function removeCollapsedFromParents(element) {
// console.log(element, element.classList.contains(options.collapsibleClass))
if (element.classList.contains(options.collapsibleClass)) {
element.classList.remove(options.isCollapsedClass)
return removeCollapsedFromParents(element.parentNode.parentNode);
}
return element;
}
function disableTocAnimation() {
// Bind to tocLink clicks to temporarily disable highlighting
// while smoothScroll is animating.
var tocLinks = doc.querySelector(options.tocSelector)
.querySelectorAll('.' + options.linkClass);
if (tocLinks) {
forEach.call(tocLinks, function(tocLink) {
tocLink.addEventListener('click', function() {
options._currentlyHighlighting = false;
window.setTimeout(function() {
options._currentlyHighlighting = true;
}, options.smoothScrollOptions.speed);
});
});
}
}
function removeTocLinkListeners() {
// var tocLinks = doc.querySelector(options.tocSelector)
// .querySelectorAll('.' + options.linkClass);
// if (tocLinks) {
// forEach.call(tocLinks, function(tocLink) {
// // TODO: This event listener needs to get remove too!!
// tocLink.removeEventListener('click');
// });
// }
}
root.tocbot = tocbot;
window.tocbot = tocbot;
return tocbot;
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc