Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - Add table of content option and generation #75

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add toc_skip
  • Loading branch information
scandinave committed Sep 13, 2020
commit c99330083743fbf74fb10b2e07198736e7fd91f8
36 changes: 31 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,16 @@ Place the following comment `<!-- TOC -->` where you want the table of content b

Options for table of content are :

* `TOC_headings`: Set the table of content title ( default: 'Table of content' )
* `TOC_depth`: Skip n level for the table of content generation ( default: 0 )
* `toc_heading`: Set the TOC title ( default: 'Contents' )
* `toc_skip`: Skip n level for the TOC generation ( default: 0 )
* `toc_depth`: The depth of the table of content to generate. ( Default to 3 ). This take into account a possible toc_skip.
So if toc_skip = 1 and toc_depth = 3 then the generation will include levels 2, 3 and 4

If you want to skip the first level headings of your document, set level to 1.
scandinave marked this conversation as resolved.
Show resolved Hide resolved
With this, you can define a Document title that will not be included inside the table content.

If you want to customize the table of content style, use the id `#table-of-contents` to target the div that wrap the table of content

Table of content element will have class toc-depth-{n} where n is the level of the heading.
Example of custom style.
```
#table-of-contents {
Expand All @@ -143,8 +145,32 @@ Example of custom style.
text-align: center;
}

#table-of-contents li, table-of-contents h1 ul, table-of-contents h1 ul li{
list-style: outside none none !important;
#table-of-contents .toc-depth-1 {
margin-left: 1rem;
}

#table-of-contents .toc-depth-2 {
margin-left: 2rem;
}


#table-of-contents .toc-depth-3 {
margin-left: 3rem;
}


#table-of-contents .toc-depth-4 {
margin-left: 4rem;
}


#table-of-contents .toc-depth-5 {
margin-left: 5rem;
}


#table-of-contents .toc-depth-6 {
margin-left: 6rem;
}
```

Expand Down
17 changes: 12 additions & 5 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ export const defaultConfig: Config = {
left: '20mm',
},
},
toc_headings: 'Table of content',
toc_depth: 0,
toc_heading: 'Contents',
toc_skip: 0,
toc_depth: 3,
launch_options: {},
md_file_encoding: 'utf-8',
stylesheet_encoding: 'utf-8',
Expand Down Expand Up @@ -112,12 +113,18 @@ export interface Config {
port?: number;

/**
* The text to display on top of the table of content. Default to 'Table of content'
* The text to display on top of the table of content. Defaults to 'Content'
*/
toc_headings?: string;
toc_heading: string;

/**
* The starting depth from where to include headings inside the table of content. Default to 0
* The starting depth from where to include headings inside the table of content. Defaults to 0
*/
toc_skip: number;

/**
* The depth of the table of content to generate. Default to 3. This take into account a possible toc_skip.
* So if toc_skip = 1 and toc_depth = 3 then the generation will include levels 2, 3 and 4
*/
toc_depth: number;
}
50 changes: 17 additions & 33 deletions src/lib/get-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const getHtml = (md: string, config: Config) => {
(marked as any).use({ renderer: getHighlightRenderer(config.marked_options) });
(marked as any).use({ renderer: getHeadingRenderer(config).renderer });

const markedContent: string = marked(md);
const markedContent = marked(md);
let html = `<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
Expand All @@ -25,43 +25,27 @@ export const getHtml = (md: string, config: Config) => {
// generate table of content only if we have headings
// const { toc } = getHeadingRenderer(config);
if (html.includes('<!-- TOC -->') && toc.length !== 0) {
const ctx = [];
ctx.push(`<div id="table-of-contents"><h1>${config.toc_headings ?? 'Table of content'}</h1>\n<ul>`);
build(toc, config.toc_depth ?? 0, 0, ctx);
ctx.push('</ul></div>');
html = html.replace('<!-- TOC -->', ctx.join(''));
const generatedToc = [];
generatedToc.push(`<div id="table-of-contents"><h1>${config.toc_heading}</h1>\n<p>`);
build(toc, config.toc_skip, config.toc_depth, generatedToc);
generatedToc.push('</p></div>');
html = html.replace('<!-- TOC -->', generatedToc.join(''));
}

return html;
};

/**
* Build a tree with the calculated table of content
* @param coll The table of content data previously calculated
* @param startHeading The starting index for the generation
* @param level The starting level for the generation
* @param ctx A array that will handle generated table of content link
* Build the table of content
* @param toc The marked list of heading
* @param toc_skip Level to skip for the generation
* @param toc_depth The depth of the toc. Default to 3, increment by the toc_skip value
* @param generatedToc The array that will content the final toc data
*/
function build(coll: TableOfContent[], startHeading: number, level: number, ctx: any[]) {
if (startHeading >= coll.length || coll[startHeading].level <= level) {
return startHeading;
}

const node = coll[startHeading];
ctx.push(`<li><a href="#${node.anchor}">${node.text}</a>`);
startHeading++;
const childCtx: any[] = [];
startHeading = build(coll, startHeading, node.level, childCtx);
if (childCtx.length > 0) {
ctx.push('<ul>');
childCtx.forEach(function (idm) {
ctx.push(idm);
});
ctx.push('</ul>');
}

ctx.push('</li>');
startHeading = build(coll, startHeading, level, ctx);

return startHeading;
function build(toc: TableOfContent[], toc_skip: number, toc_depth: number, generatedToc: string[]) {
toc.forEach((node) => {
if (node.level > toc_skip && node.level <= toc_skip + toc_depth) {
generatedToc.push(`<a href="#${node.anchor}" class="toc-depth-${node.level}">${node.text}</a><br/>`);
}
});
}
18 changes: 16 additions & 2 deletions src/test/lib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,26 @@ test('getHtml should inject body classes', (t) => {
test('getHtml should return a valid html document with table of content', (t) => {
const html = getHtml('<!-- TOC --> \n # Foo', {
...defaultConfig,
toc_headings: 'TOC',
toc_heading: 'TOC',
}).replace(/\n/g, '');

t.regex(
html,
/.*<div id="table-of-contents"><h1>TOC<\/h1><ul><li><a href="#foo">Foo<\/a><\/li><\/ul><\/div>.*<h1 id="foo">Foo<\/h1>.*/,
/.*<div id="table-of-contents"><h1>TOC<\/h1><p><a href="#foo" class="toc-depth-1">Foo<\/a><br\/><\/p><\/div>.*<h1 id="foo">Foo<\/h1>.*/,
);
});

test('getHtml should return a valid html document with table of content taking skip and depth into account', (t) => {
const html = getHtml('<!-- TOC --> \n # Foo \n ## Depth2 \n ### Depth3 \n #### Depth4', {
...defaultConfig,
toc_heading: 'TOC',
toc_skip: 1,
toc_depth: 2,
}).replace(/\n/g, '');

t.regex(
html,
/.*<div id="table-of-contents"><h1>TOC<\/h1><p><a href="#depth2" clas{2}="toc-depth-2">Depth2<\/a><br\/><a href="#depth3" clas{2}="toc-depth-3">Depth3<\/a><br\/><\/p><\/div>.*<h1 id="fo{2}">Fo{2}<\/h1>.*/,
);
});

Expand Down