Skip to content

Commit

Permalink
Introduce title option
Browse files Browse the repository at this point in the history
  • Loading branch information
johansatge committed Feb 17, 2024
1 parent ed122f6 commit 339f12a
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 22 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ The following options are available:

| Option | Default value | Description |
| --- | --- | --- |
| `title` | _None_ | Title to display before the table of contents (supports Markdown) |
| `style` | `nestedList` | Table of contents style (can be `nestedList` or `inlineFirstLevel`) |
| `minLevel` | `0` | Include headings from the specified level |
| `maxLevel` | `0` | Include headings up to the specified level (`0` for no limit) |
Expand Down
37 changes: 28 additions & 9 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ if (isObsidian()) {
const codeblockId = 'table-of-contents'
const codeblockIdShort = 'toc'
const availableOptions = {
style: {
title: {
type: 'string',
default: '',
comment: 'Table of contents title',
},
style: {
type: 'value',
default: 'nestedList',
values: ['nestedList', 'inlineFirstLevel'],
comment: 'TOC style (nestedList|inlineFirstLevel)',
},
minLevel: {
type: 'number',
default: 0,
comment: 'Include headings from the specified level'
comment: 'Include headings from the specified level',
},
maxLevel: {
type: 'number',
Expand Down Expand Up @@ -117,12 +122,17 @@ class Renderer extends MarkdownRenderChild {
}

function getMarkdownFromHeadings(headings, options) {
const markdownHandlersByStyle = {
const styles = {
nestedList: getMarkdownNestedListFromHeadings,
inlineFirstLevel: getMarkdownInlineFirstLevelFromHeadings,
}
const markdown = markdownHandlersByStyle[options.style](headings, options)
return markdown || '_Table of contents: no headings found_'
let markdown = ''
if (options.title && options.title.length > 0) {
markdown += options.title + '\n'
}
const noHeadingMessage = '_Table of contents: no headings found_'
markdown += styles[options.style](headings, options) || noHeadingMessage
return markdown
}

function getMarkdownNestedListFromHeadings(headings, options) {
Expand All @@ -149,7 +159,9 @@ function getMarkdownInlineFirstLevelFromHeadings(headings, options) {

function getMarkdownHeading(heading, options) {
if (options.includeLinks) {
const cleaned = heading.heading.replaceAll('|', '-').replaceAll('[', '{').replaceAll(']', '}')
let cleaned = heading.heading
// Strip reserved wikilink characters
cleaned = cleaned.replaceAll('|', '-').replaceAll('[', '{').replaceAll(']', '}')
return `[[#${cleaned}]]`
}
return heading.heading
Expand All @@ -170,11 +182,15 @@ function parseOptionsFromSourceText(sourceText = '') {
}

function parseOptionFromSourceLine(line) {
const matches = line.match(/([a-zA-Z0-9._ ]+):([^#]+)/)
const matches = line.match(/([a-zA-Z0-9._ ]+):(.*)/)
if (line.startsWith('#') || !matches) return null
const possibleName = matches[1].trim()
const possibleValue = matches[2].trim()
const optionParams = availableOptions[possibleName]
let possibleValue = matches[2].trim()
if (!optionParams || optionParams.type !== 'string') {
// Strip comments from values except for strings (as string may contain markdown)
possibleValue = possibleValue.replace(/#[^#]*$/, '').trim()
}
const valueError = new Error(`Invalid value for \`${possibleName}\``)
if (optionParams && optionParams.type === 'number') {
const value = parseInt(possibleValue)
Expand All @@ -185,10 +201,13 @@ function parseOptionFromSourceLine(line) {
if (!['true', 'false'].includes(possibleValue)) throw valueError
return { name: possibleName, value: possibleValue === 'true' }
}
if (optionParams && optionParams.type === 'string') {
if (optionParams && optionParams.type === 'value') {
if (!optionParams.values.includes(possibleValue)) throw valueError
return { name: possibleName, value: possibleValue }
}
if (optionParams && optionParams.type === 'string') {
return { name: possibleName, value: possibleValue }
}
return null
}

Expand Down
18 changes: 17 additions & 1 deletion test/headings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const {
} = require('../main.js')

const testStandardHeadings = [
{ heading: 'Title [1] | level 1', level: 1 },
{ heading: 'Title [1] | level 1', level: 1 }, // With wiklink characters to be stripped
{ heading: 'Title 1 level 2', level: 2 },
{ heading: 'Title 1 level 3', level: 3 },
{ heading: 'Title 2 level 1', level: 1 },
Expand Down Expand Up @@ -91,6 +91,22 @@ describe('Headings', () => {
expect(md).toEqual(expectedMd)
})

test('Returns indented list with title', () => {
const options = parseOptionsFromSourceText('')
options.title = '# My TOC'
const md = getMarkdownFromHeadings(testStandardHeadings, options)
const expectedMd = sanitizeMd(`
# My TOC
- [[#Title {1} - level 1]]
- [[#Title 1 level 2]]
- [[#Title 1 level 3]]
- [[#Title 2 level 1]]
- [[#Title 3 level 1]]
- [[#Title 3 level 2]]
`)
expect(md).toEqual(expectedMd)
})

test('Returns flat first-level list with links', () => {
const options = parseOptionsFromSourceText('')
options.style = 'inlineFirstLevel'
Expand Down
27 changes: 15 additions & 12 deletions test/options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,32 @@ describe('Options', () => {
test('Returns default options if none are specified', () => {
const options = parseOptionsFromSourceText('')
expect(options).toEqual({
style: 'nestedList',
includeLinks: true,
minLevel: 0,
maxLevel: 0,
debugInConsole: false,
title: '',
style: 'nestedList',
includeLinks: true,
minLevel: 0,
maxLevel: 0,
debugInConsole: false,
})
})

test('Returns custom options if specified', () => {
const optionsText = `
style: inlineFirstLevel
title: # Some title
style: inlineFirstLevel # Some comment
minLevel: 1
maxLevel: 2
maxLevel: 2 # Some other comment
includeLinks: false
debugInConsole: true
`
const options = parseOptionsFromSourceText(optionsText)
expect(options).toEqual({
style: 'inlineFirstLevel',
includeLinks: false,
minLevel: 1,
maxLevel: 2,
debugInConsole: true,
title: '# Some title',
style: 'inlineFirstLevel',
includeLinks: false,
minLevel: 1,
maxLevel: 2,
debugInConsole: true,
})
})

Expand Down

0 comments on commit 339f12a

Please sign in to comment.