Action

Table of Contents

Posted by @fffergal, Last update 6 months ago

UPDATES

6 months ago

  • If you don’t use a placeholder, the table of contents will be inserted before the first non-title header.
  • Helpful error messages that make the Action easier to use.
  • Checking header levels to make sure nested lists in the table of contents are possible.

Insert a table of contents based on Markdown headers.

Put the placeholder <!– start toc –><!– end toc –> where you want the table of contents to appear.

You can run it multiple times and it will update the table of contents.

If you don’t use a placeholder, the table of contents will be inserted before the first non-title header.

This is similar to the {{TOC}} feature if you are using the MultiMarkdown renderer (the default), except it will put your table of contents in the Draft, not just previews.

Steps

  • script

    // See online documentation for examples
    // https://docs.getdrafts.com/docs/actions/scripting
    
    const addToc = content => {
    	const headerLines = content.split('\n').filter(line => /^#+\s+[^\s]+/.test(line))
    	if (headerLines.length == 0) {
    		alert("No Markdown headers (e.g. ## Header) found")
    		return
    	}
    	const tocLines = []
    	let lastLevel = 0
    	for (let i = 0; i < headerLines.length; i++) {
    		const splitMatch = headerLines[i].match(/^(#+)\s+(.*)$/)
    		// - 2 assumes the first header is h2
    		const level = splitMatch[1].length - 2
    		if (level < 0) {
    			alert("h1 headers (#) are not compatible with Table of Contents. Please use h2 headers (##) and below.")
    			return null
    		}
    		if (level > lastLevel + 1) {
    			alert("Cannot make a table of contents if header levels are skipped, e.g. ## followed by ####")
    			return
    		}
    		lastLevel = level
    		const headerText = splitMatch[2]
    		tocLines.push("    ".repeat(level) + "* " + headerText)
    	}
    	if (tocLines[0][0] != "*") {
    		alert("The first header must be a h2 (##)")
    		return
    	}
    
    	let startMatch = content.match(/<!-- toc start -->/)
    	let endMatch = content.match(/<!-- toc end -->/)
    	if (startMatch == null && endMatch == null) {
    		const firstHeaderMatch = content.match(/^(#+)\s+(.*)$/m)
    		content = `${content.substring(0, firstHeaderMatch.index)}<!-- toc start --><!-- toc end -->\n\n${content.substring(firstHeaderMatch.index)}`
    		startMatch = content.match(/<!-- toc start -->/)
    		endMatch = content.match(/<!-- toc end -->/)
    	}
    	if (startMatch == null || endMatch == null) {
    		alert("You need both <!-- toc start --> and <!-- toc end --> in your draft to place a table of contents")
    		return
    	}
    	if (endMatch.index < startMatch.index) {
    		alert("<!-- toc start --> must come before <!-- toc end -->")
    		return
    	}
    	const startPos = startMatch.index + startMatch[0].length
    	const endPos = endMatch.index
    	return `${content.substring(0, startPos)}\n${tocLines.join("\n")}\n${content.substring(endPos)}`
    }
    
    const newBody = addToc(draft.lines.slice(1).join("\n"))
    if (newBody != null) {
    	draft.content = `${draft.lines[0]}\n${newBody}`
    	draft.update()
    }

Options

  • After Success Default
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.