Skip to content
This repository has been archived by the owner on Jan 14, 2022. It is now read-only.

Commit

Permalink
First commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mitsunori Kubota committed Jan 16, 2015
0 parents commit 9a7ee33
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
93 changes: 93 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# metalsmith-autotoc

> A metalsmith plugin to generate table of contents of a document.
This plugin generate table of contents object to document's metadata.

## Installation

```bash
$ npm install metalsmith-autotoc
```

## Example

Source file `src/index.html`:

```html
---
autotoc: true
template: 'layout.eco'
---
<h2>aaa</h2>

<p>paragraph</p>

<h2>bbb</h2>

<p>paragraph</p>

<h2>ccc</h2>

<p>paragraph</p>
```

Template file `templates/layout.eco`:

```eco
<ul>
<% for tocItem in @toc: %>
<li><a href="#<%= tocItem.id %>"><%= tocItem.text %></a></li>
<% end %>
</ul>
<%- @contents %>
```

Build file `build.js`:

```javascript
var metalsmith = require('metalsmith');
var autotoc = require('metalsmith-autotoc');
var templates = require('metalsmith-templates');

metalsmith(__dirname)
.source('./src')
.destination('./dest')
.use(autotoc({selector: 'h2, h3, h4'}))
.use(templates({
engine: 'eco',
directory: './templates'
}))
.build();
```

Results file `dest/index.html`:

```
<ul>
<li><a href="#aaa">aaa</a></li>
<li><a href="#bbb">bbb</a></li>
<li><a href="#ccc">ccc</a></li>
</ul>
<h2 id="aaa">aaa</h2>
<p>paragraph</p>
<h2 id="bbb">bbb</h2>
<p>paragraph</p>
<h2 id="ccc">ccc</h2>
<p>paragraph</p>
```

## License

MIT
144 changes: 144 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@

var jsdom = require('jsdom');
var async = require('async');
var slug = require('slug');

var TocItem = function() {
TocItem.prototype.init.apply(this, arguments);
};

TocItem.prototype = {
/**
* @param {Object} [params]
* @param {String} [params.id]
* @param {String} [params.text]
*/
init: function(params) {
params = params || {};
this.id = params.id || '';
this.text = params.text || '';
this.children = [];
this.parent = null;
},
/**
* @param {TocItem} tocItem
*/
add: function(tocItem) {
if (tocItem.parent) {
throw 'tocItem.parent exists';
}
tocItem.parent = this;
this.children.push(tocItem);
},

toJSON: function() {
return {
id: this.id,
text: this.text,
children: this.children
};
}
};

module.exports = function(options) {
options = options || {};
options.selector = options.selector || 'h3, h4, h5';

function generateId(header) {
if (!header.id) {
return slug(header.innerHTML);
} else {
return header.id;
}
}

function getRootLevel(headers) {
return headers.map(function(header) {
return header.level;
}).sort()[0] - 1 || 1;
}

function buildTocItems(headers) {
if (headers.length == 0) {
return [];
}

var root = new TocItem();
var toc = root;

headers = headers.map(function(header) {
return {
id: header.id,
text: header.innerHTML,
level: parseInt(header.tagName.match(/^h([123456])$/i)[1], 10)
};
});

var lastLevel = getRootLevel(headers);

headers.forEach(function(header) {
var id = header.id;
var text = header.text;
var level = header.level;

while (level != 1 + lastLevel) {
if (level < 1 + lastLevel) {
toc = toc.parent;
lastLevel--;
} else if (level > 1 + lastLevel) {
var emptyToc = new TocItem();
toc.add(emptyToc);
toc = emptyToc;
lastLevel++;
}
}

var newToc = new TocItem({
text: text,
id: id
});

toc.add(newToc);
toc = newToc;
lastLevel = level;
});

return root.children;
}

return function(files, metalsmith, done) {
var fileList = Object.keys(files).map(function(path) {
return files[path]
});

async.each(fileList, function(file, done) {
if (!file.autotoc) {
done();
} else {
var contents = file.contents.toString('utf8');
jsdom.env({
html: '<html><body>' + contents + '</body></html>',
feature: {QuerySelector: true},
done: function(error, window) {
if (error) {
throw error;
}

var headers = Array.prototype.slice.call(
window.document.querySelectorAll(file.autotocSelector || options.selector || 'h3, h4')
).map(function(header) {
header.id = generateId(header);
return header;
});

file.contents = new Buffer(window.document.body.innerHTML);
file.toc = buildTocItems(headers);
done();
}
});
}
}, function() {
done();
});
};
};
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "metalsmith-autotoc",
"version": "0.1.0-dev",
"description": "A metalsmith plugin to generate table of contents of a document.",
"repository": "git@github.com:anatoo/metalsmith-autotoc.git",
"main": "index.js",
"scripts": {
"test": "./node_modules/.bin/mocha test"
},
"author": "Mitsunori KUBOTA <anatoo.jp@gmail.com> (http://anatoo.jp)",
"license": "MIT",
"dependencies": {
"async": "^0.9.0",
"jsdom": "^2.0.0",
"slug": "^0.8.0"
},
"devDependencies": {
"metalsmith": "^1.0.1",
"assert-dir-equal": "^1.0.1",
"mocha": "^2.1.0",
"metalsmith-templates": "^0.6.0",
"eco": "^1.1.0-rc-3"
}
}
22 changes: 22 additions & 0 deletions test/fixtures/basic/build/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<ul>

<li><a href="#aaa">aaa</a></li>

<li><a href="#bbb">bbb</a></li>

<li><a href="#ccc">ccc</a></li>

</ul>

<h2 id="aaa">aaa</h2>

<p>paragraph</p>

<h2 id="bbb">bbb</h2>

<p>paragraph</p>

<h2 id="ccc">ccc</h2>

<p>paragraph</p>

22 changes: 22 additions & 0 deletions test/fixtures/basic/expected/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<ul>

<li><a href="#aaa">aaa</a></li>

<li><a href="#bbb">bbb</a></li>

<li><a href="#ccc">ccc</a></li>

</ul>

<h2 id="aaa">aaa</h2>

<p>paragraph</p>

<h2 id="bbb">bbb</h2>

<p>paragraph</p>

<h2 id="ccc">ccc</h2>

<p>paragraph</p>

16 changes: 16 additions & 0 deletions test/fixtures/basic/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: 'hoge'
autotoc: true
template: 'layout.eco'
---
<h2>aaa</h2>

<p>paragraph</p>

<h2>bbb</h2>

<p>paragraph</p>

<h2>ccc</h2>

<p>paragraph</p>
7 changes: 7 additions & 0 deletions test/fixtures/basic/templates/layout.eco
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<ul>
<% for tocItem in @toc: %>
<li><a href="#<%= tocItem.id %>"><%= tocItem.text %></a></li>
<% end %>
</ul>

<%- @contents %>
23 changes: 23 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
var assert = require('assert');
var equal = require('assert-dir-equal');
var metalsmith = require('metalsmith');
var templates = require('metalsmith-templates');
var autotoc = require('..');

describe('metalsmith-autotoc', function() {
it('should generate table of contents', function(done) {
metalsmith(__dirname + '/fixtures/basic')
.use(autotoc({selector: 'h2'}))
.use(templates({
engine: 'eco',
directory: './templates'
}))
.build(function(error) {
if (error) {
throw error;
}
equal('test/fixtures/basic/expected', 'test/fixtures/basic/build');
done();
});
});
});

0 comments on commit 9a7ee33

Please sign in to comment.