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 f507350
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 23 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ 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 |
| `minLevel` | `0` | Include headings from the specified level (`0` for no limit) |
| `maxLevel` | `0` | Include headings up to the specified level (`0` for no limit) |
| `includeLinks` | `true` | Make headings clickable |
| `debugInConsole` | `false` | Print debug info in Obsidian console |
Expand Down
38 changes: 29 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: '',
},
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 @@ -69,7 +74,8 @@ function onInsertTocWithDocs(editor) {
let markdown = ['```' + codeblockId]
Object.keys(availableOptions).forEach((optionName) => {
const option = availableOptions[optionName]
markdown.push(`${optionName}: ${option.default} # ${option.comment}`)
const comment = option.comment.length > 0 ? ` # ${option.comment}` : ''
markdown.push(`${optionName}: ${option.default}${comment}`)
})
markdown.push('```')
editor.replaceRange(markdown.join('\n'), editor.getCursor())
Expand Down Expand Up @@ -121,8 +127,13 @@ function getMarkdownFromHeadings(headings, options) {
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 += markdownHandlersByStyle[options.style](headings, options) || noHeadingMessage
return markdown
}

function getMarkdownNestedListFromHeadings(headings, options) {
Expand All @@ -149,7 +160,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 +183,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 +202,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 f507350

Please sign in to comment.