rehype-toc
Advanced tools
Comparing version
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const get_inner_text_1 = require("./get-inner-text"); | ||
const options_1 = require("./options"); | ||
/** | ||
@@ -8,10 +9,10 @@ * Creates an `<ol>` element containing the table of contents. | ||
function createTOC(headings, options) { | ||
let levels = []; | ||
let currentLevel = { | ||
depth: 0, | ||
headingNumber: 0, | ||
list: createList(undefined, 1, options), | ||
list: undefined, | ||
}; | ||
let levels = [currentLevel]; | ||
for (let heading of headings) { | ||
let headingNumber = options.headings.indexOf(heading.tagName) + 1; | ||
let headingNumber = parseInt(heading.tagName.slice(-1), 10); | ||
if (headingNumber > currentLevel.headingNumber) { | ||
@@ -26,4 +27,6 @@ // This is a higher heading number, so start a new level | ||
// Add the new list to the previous level's list | ||
let lastItem = currentLevel.list.children.slice(-1)[0]; | ||
lastItem && lastItem.children.push(level.list); | ||
if (currentLevel.list) { | ||
let lastItem = currentLevel.list.children.slice(-1)[0]; | ||
lastItem.children.push(level.list); | ||
} | ||
levels.push(level); | ||
@@ -34,10 +37,14 @@ currentLevel = level; | ||
if (headingNumber < currentLevel.headingNumber) { | ||
// This is a lower heading number, so we need to go up to the corresponding previous level | ||
do { | ||
levels.pop(); | ||
currentLevel = levels.slice(-1)[0]; | ||
if (currentLevel.headingNumber === headingNumber) { | ||
// This is a lower heading number, so we need to go up to a previous level | ||
for (let i = levels.length - 2; i >= 0; i--) { | ||
let level = levels[i]; | ||
if (level.headingNumber === headingNumber) { | ||
// We found the previous level that matches this heading | ||
levels = levels.slice(0, i + 1); | ||
currentLevel = level; | ||
break; | ||
} | ||
} while (levels.length > 1); | ||
} | ||
// If headings are in an incorrect order, then we may need to adjust the headingNumber | ||
currentLevel.headingNumber = Math.min(currentLevel.headingNumber, headingNumber); | ||
} | ||
@@ -50,4 +57,8 @@ // This heading is the same level as the previous heading, | ||
} | ||
let toc = levels.pop().list; | ||
return toc; | ||
if (levels.length === 0) { | ||
return createList(undefined, 1, options); | ||
} | ||
else { | ||
return levels[0].list; | ||
} | ||
} | ||
@@ -63,3 +74,3 @@ exports.createTOC = createTOC; | ||
properties: { | ||
class: `${options.cssClasses.list} ${options.cssClasses.list}-${depth}`, | ||
class: options_1.buildClass(options.cssClasses.list, depth), | ||
}, | ||
@@ -72,5 +83,5 @@ children: [], | ||
} | ||
if (depth === 1) { | ||
if (depth === 1 && options.cssClasses.toc) { | ||
// This is the top-level table of contents list | ||
list.properties.class = options.cssClasses.toc + " " + list.properties.class; | ||
list.properties.class = options.cssClasses.toc + " " + (list.properties.class || ""); | ||
} | ||
@@ -87,3 +98,3 @@ return list; | ||
properties: { | ||
class: `${options.cssClasses.listItem} ${options.cssClasses.listItem}-${heading.tagName}`, | ||
class: options_1.buildClass(options.cssClasses.listItem, heading.tagName), | ||
}, | ||
@@ -95,4 +106,4 @@ children: [ | ||
properties: { | ||
class: options_1.buildClass(options.cssClasses.link, heading.tagName), | ||
href: `#${heading.properties.id || ""}`, | ||
class: `${options.cssClasses.link} ${options.cssClasses.link}-${heading.tagName}`, | ||
}, | ||
@@ -99,0 +110,0 @@ children: [ |
import { toc } from "./rehype-toc"; | ||
export { Options } from "./options"; | ||
export { Options, PartialOptions } from "./options"; | ||
export * from "./types"; | ||
export { toc }; | ||
export default toc; |
@@ -66,1 +66,5 @@ import { Node } from "unist"; | ||
export declare function applyDefaults(config?: PartialOptions): Options; | ||
/** | ||
* Builds a CSS class string from the given user-defined class name | ||
*/ | ||
export declare function buildClass(name: string, suffix: string | number): string | undefined; |
@@ -11,6 +11,6 @@ "use strict"; | ||
cssClasses: { | ||
toc: cssClasses.toc || "toc", | ||
list: cssClasses.list || "toc-level", | ||
listItem: cssClasses.listItem || "toc-item", | ||
link: cssClasses.link || "toc-link", | ||
toc: cssClasses.toc === undefined ? "toc" : cssClasses.toc, | ||
list: cssClasses.list === undefined ? "toc-level" : cssClasses.list, | ||
listItem: cssClasses.listItem === undefined ? "toc-item" : cssClasses.listItem, | ||
link: cssClasses.link === undefined ? "toc-link" : cssClasses.link, | ||
}, | ||
@@ -21,2 +21,15 @@ customizeTOC: config.customizeTOC || ((toc) => toc), | ||
exports.applyDefaults = applyDefaults; | ||
/** | ||
* Builds a CSS class string from the given user-defined class name | ||
*/ | ||
function buildClass(name, suffix) { | ||
if (name) { | ||
let cssClass = name; | ||
if (suffix) { | ||
cssClass += ` ${name}-${suffix}`; | ||
} | ||
return cssClass; | ||
} | ||
} | ||
exports.buildClass = buildClass; | ||
//# sourceMappingURL=options.js.map |
@@ -36,5 +36,2 @@ import { Node } from "unist"; | ||
tagName: "ol" | "ul"; | ||
properties: { | ||
class: string; | ||
}; | ||
children: ListItemNode[]; | ||
@@ -48,6 +45,3 @@ } | ||
tagName: "li"; | ||
properties: { | ||
class: string; | ||
}; | ||
children: Array<HtmlElementNode | TextNode>; | ||
children: Node[]; | ||
} |
{ | ||
"name": "rehype-toc", | ||
"version": "1.0.0-alpha.1", | ||
"version": "1.0.0", | ||
"description": "A rehype plugin that adds a table of contents (TOC) to the page", | ||
@@ -59,2 +59,3 @@ "keywords": [ | ||
"rehype-parse": "^6.0.0", | ||
"rehype-slug": "^2.0.3", | ||
"rehype-stringify": "^6.0.0", | ||
@@ -61,0 +62,0 @@ "shx": "^0.3.2", |
200
README.md
# Table of Contents plugin for rehype | ||
A rehype plugin that adds a table of contents (TOC) to the page | ||
A [rehype](https://github.com/rehypejs/rehype) plugin that adds a table of contents (TOC) to the page | ||
@@ -15,13 +15,165 @@ [](https://travis-ci.com/JS-DevTools/rehype-toc) | ||
Features | ||
-------------------------- | ||
- Adds an `<ol>` list outlining all headings on the page | ||
- Combine with [rehype-slug](https://github.com/rehypejs/rehype-slug) to create links to each heading | ||
- Ignores headings outside of `<main>` if it exists | ||
- You can customize which headings are included (defaults to `<h1>` - `<h6>`) | ||
- You can customize the CSS classes on every TOC element | ||
- Hooks give you complete customization of the generated HTML | ||
Example | ||
-------------------------- | ||
**input.html**<br> | ||
Here's the original HTML file. There are three levels of headings (`<h1>` - `<h3>`), and none of them have IDs. | ||
```html | ||
<html> | ||
<body> | ||
<h1>Apple Pie Recipe</h1> | ||
<p>This is the world's best apple pie recipe...</p> | ||
<div> | ||
<h2>Filling</h2> | ||
<p>The filling is the best part...</p> | ||
<h3>Preparing the apples</h3> | ||
<p>Cut the apples into 1/4 inch slices...</p> | ||
<h3>Preparing the spice mix</h3> | ||
<p>In a mixing bowl, combine sugar, cinnamon...</p> | ||
</div> | ||
<div> | ||
<h2>Crust</h2> | ||
<p>How to make the perfect flaky crust...</p> | ||
<h3>Preparing the dough</h3> | ||
<p>Combine flour, sugar, salt...</p> | ||
<h3>The criss-cross top</h3> | ||
<p>Cut the top crust into 1/2 inch strips...</p> | ||
</div> | ||
</body> | ||
</html> | ||
``` | ||
**example.js**<br> | ||
This script reads the `input.html` file above writes the results to `output.html` (shown below). The script uses [unified](https://unified.js.org/), [rehype-parse](https://github.com/rehypejs/rehype/tree/master/packages/rehype-parse), [rehype-slug](https://github.com/rehypejs/rehype-slug), and [rehype-stringify](https://github.com/rehypejs/rehype/tree/master/packages/rehype-stringify). | ||
```javascript | ||
import toc from "rehype-toc"; | ||
const unified = require("unified"); | ||
const parse = require("rehype-parse"); | ||
const slug = require("rehype-slug"); | ||
const toc = require("rehype-toc"); | ||
const stringify = require("rehype-stringify"); | ||
const fs = require("fs"); | ||
// TODO: Add a usage example here | ||
async function example() { | ||
// Create a Rehype processor with the TOC plugin | ||
const processor = unified() | ||
.use(parse) | ||
.use(slug) | ||
.use(toc) | ||
.use(stringify); | ||
// Read the original HTML file | ||
let inputHTML = await fs.promises.readFile("input.html"); | ||
// Process the HTML, adding heading IDs and Table of Contents | ||
let outputHTML = await processor.process(inputHTML); | ||
// Save the new HTML | ||
await fs.promises.writeFile("output.html", outputHTML); | ||
} | ||
``` | ||
**output.html**<br> | ||
Here's the HTML that gets created by the above script. Notice that a table of contents has been added at the top of the `<body>`, with links to each of the headings on the page. The headings also now have IDs, thanks to [rehype-slug](https://github.com/rehypejs/rehype-slug). | ||
```html | ||
<html> | ||
<body> | ||
<ol class="toc toc-level toc-level-1"> | ||
<li class="toc-item toc-item-h1"> | ||
<a class="toc-link toc-link-h1" href="#apple-pie-recipe"> | ||
Apple Pie Recipe | ||
</a> | ||
<ol class="toc-level toc-level-2"> | ||
<li class="toc-item toc-item-h2"> | ||
<a class="toc-link toc-link-h2" href="#filling"> | ||
Filling | ||
</a> | ||
<ol class="toc-level toc-level-3"> | ||
<li class="toc-item toc-item-h3"> | ||
<a class="toc-link toc-link-h3" href="#preparing-the-apples"> | ||
Preparing the apples | ||
</a> | ||
</li> | ||
<li class="toc-item toc-item-h3"> | ||
<a class="toc-link toc-link-h3" href="#preparing-the-spice-mix"> | ||
Preparing the spice mix | ||
</a> | ||
</li> | ||
</ol> | ||
</li> | ||
<li class="toc-item toc-item-h2"> | ||
<a class="toc-link toc-link-h2" href="#crust"> | ||
Crust | ||
</a> | ||
<ol class="toc-level toc-level-3"> | ||
<li class="toc-item toc-item-h3"> | ||
<a class="toc-link toc-link-h3" href="#preparing-the-dough"> | ||
Preparing the dough | ||
</a> | ||
</li> | ||
<li class="toc-item toc-item-h3"> | ||
<a class="toc-link toc-link-h3" href="#the-criss-cross-top"> | ||
The criss-cross top | ||
</a> | ||
</li> | ||
</ol> | ||
</li> | ||
</ol> | ||
</li> | ||
</ol> | ||
<h1 id="apple-pie-recipe">Apple Pie Recipe</h1> | ||
<p>This is the world's best apple pie recipe...</p> | ||
<div> | ||
<h2 id="filling">Filling</h2> | ||
<p>The filling is the best part...</p> | ||
<h3 id="preparing-the-apples">Preparing the apples</h3> | ||
<p>Cut the apples into 1/4 inch slices...</p> | ||
<h3 id="preparing-the-spice-mix">Preparing the spice mix</h3> | ||
<p>In a mixing bowl, combine sugar, cinnamon...</p> | ||
</div> | ||
<div> | ||
<h2 id="crust">Crust</h2> | ||
<p>How to make the perfect flaky crust...</p> | ||
<h3 id="preparing-the-dough">Preparing the dough</h3> | ||
<p>Combine flour, sugar, salt...</p> | ||
<h3 id="the-criss-cross-top">The criss-cross top</h3> | ||
<p>Cut the top crust into 1/2 inch strips...</p> | ||
</div> | ||
</body> | ||
</html> | ||
``` | ||
Installation | ||
@@ -35,10 +187,50 @@ -------------------------- | ||
You'll probably want to install [unified](https://unified.js.org/), [rehype-parse](https://github.com/rehypejs/rehype/tree/master/packages/rehype-parse), [rehype-stringify](https://github.com/rehypejs/rehype/tree/master/packages/rehype-stringify), and [rehype-slug](https://github.com/rehypejs/rehype-slug) as well. | ||
```bash | ||
npm install unified rehype-parse rehype-stringify rehype-slug | ||
``` | ||
Usage | ||
-------------------------- | ||
TODO: Document the library's API and CLI usage | ||
Using the Rehype TOC plugin requires an understanding of how to use Unified and Rehype. [Here is an excelleng guide](https://unified.js.org/using-unified.html) to learn the basics. | ||
The Rehype TOC plugin works just like any other Rehype plugin. Pass it to [the `.use()` method](https://github.com/unifiedjs/unified#processoruseplugin-options), optionally with an [options object](#options). | ||
```javascript | ||
const unified = require("unified"); | ||
const toc = require("rehype-toc"); | ||
// Use the Rehype TOC plugin with its default options | ||
unified().use(toc); | ||
// Use the Rehype TOC plugin with custom options | ||
unified().use(toc, { | ||
headings: ["h1", "h2"], // Only include <h1> and <h2> headings in the TOC | ||
cssClasses: { | ||
toc: "page-outline", // Change the CSS class for the TOC | ||
link: "page-link", // Change the CSS class for links in the TOC | ||
} | ||
}); | ||
``` | ||
Options | ||
-------------------------- | ||
Rehype Rehype TOC supports the following options: | ||
|Option |Type |Default |Description | ||
|:---------------------|:-------------------|:----------------------|:----------------------------------------- | ||
|`headings` |array of strings |h1, h2, h3, h4, h5, h6 |The HTML heading tags to include in the table of contents | ||
|`cssClasses.toc` |string |toc |The CSS class name for the top-level `<ol>` element that contains the whole table of contents. | ||
|`cssClasses.list` |string |toc-level |The CSS class name for all `<ol>` elements in the table of contents, including the top-level one. | ||
|`cssClasses.listItem` |string |toc-item |The CSS class name for all `<li>` elements in the table of contents. | ||
|`cssClasses.link` |string |toc-link |The CSS class name for all `<a>` elements in the table of contents. | ||
|`customizeTOC` |function |no-op |Allows you to customize the table of contents before it is added to the page.<br><br>The function receives the TOC node tree and can modify it in any way you want. Or you can return a new node tree to use instead. Or return `false` to prevent the the TOC from being added to the page. | ||
Contributing | ||
@@ -45,0 +237,0 @@ -------------------------- |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
36464
33.92%463
4.99%0
-100%268
252.63%22
4.76%