-
-
Notifications
You must be signed in to change notification settings - Fork 131
Automatically generate table of contents in text #2213
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
base: master
Are you sure you want to change the base?
Changes from all commits
82acffa
0f6ef6d
239ef2c
d8078ab
742d1df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { SKIP, visit } from 'unist-util-visit' | ||
import { extractHeadings } from './toc' | ||
|
||
export default function remarkToc () { | ||
return function transformer (tree) { | ||
const headings = extractHeadings(tree) | ||
|
||
visit(tree, 'paragraph', (node, index, parent) => { | ||
if ( | ||
node.children?.length === 1 && | ||
node.children[0].type === 'text' && | ||
node.children[0].value.trim() === '{:toc}' | ||
) { | ||
parent.children.splice(index, 1, buildToc(headings)) | ||
return [SKIP, index] | ||
} | ||
}) | ||
} | ||
} | ||
|
||
function buildToc (headings) { | ||
const root = { type: 'list', ordered: false, spread: false, children: [] } | ||
const stack = [{ depth: 0, node: root }] // holds the current chain of parents | ||
|
||
for (const { heading, slug, depth } of headings) { | ||
// walk up the stack to find the parent of the current heading | ||
while (stack.length && depth <= stack[stack.length - 1].depth) { | ||
stack.pop() | ||
} | ||
let parent = stack[stack.length - 1].node | ||
|
||
// if the parent is a li, gets its child ul | ||
if (parent.type === 'listItem') { | ||
let ul = parent.children.find(c => c.type === 'list') | ||
if (!ul) { | ||
ul = { type: 'list', ordered: false, spread: false, children: [] } | ||
parent.children.push(ul) | ||
} | ||
parent = ul | ||
} | ||
|
||
// build the li from the current heading | ||
const listItem = { | ||
type: 'listItem', | ||
spread: false, | ||
children: [{ | ||
type: 'paragraph', | ||
children: [{ | ||
type: 'link', | ||
url: `#${slug}`, | ||
children: [{ type: 'text', value: heading }] | ||
}] | ||
}] | ||
} | ||
|
||
parent.children.push(listItem) | ||
stack.push({ depth, node: listItem }) | ||
} | ||
|
||
return root | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { fromMarkdown } from 'mdast-util-from-markdown' | ||
import { visit } from 'unist-util-visit' | ||
import { toString } from 'mdast-util-to-string' | ||
import { slug } from 'github-slugger' | ||
|
||
export function extractHeadings (markdownOrTree) { | ||
const tree = typeof markdownOrTree === 'string' | ||
? fromMarkdown(markdownOrTree) | ||
: markdownOrTree | ||
|
||
const headings = [] | ||
|
||
visit(tree, 'heading', node => { | ||
const str = toString(node) | ||
headings.push({ | ||
heading: str, | ||
slug: slug(str.replace(/[^\w\-\s]+/gi, '')), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note for the future, unrelated to review: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, will leave this as is for now |
||
depth: node.depth | ||
}) | ||
}) | ||
|
||
return headings | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that we don't give IDs to nodes inside comments, only on full posts:
stacker.news/components/text.js
Line 116 in 4620160
Because of this, the table of contents won't work if used in comments. I personally think that the ToC doesn't make that much sense in comments, so we can just disable
{:toc}
for them withtopLevel
awareness. What do you think? ^^Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, it wouldn't make sense to use Toc in comments, so I changed it so that remarkToc is only processed for topLevel items