vault backup: 2025-04-11 14:41:26
This commit is contained in:
3
.obsidian/community-plugins.json
vendored
3
.obsidian/community-plugins.json
vendored
@ -6,5 +6,6 @@
|
|||||||
"file-explorer-note-count",
|
"file-explorer-note-count",
|
||||||
"obsidian-git",
|
"obsidian-git",
|
||||||
"better-export-pdf",
|
"better-export-pdf",
|
||||||
"hidden-folder-obsidian"
|
"hidden-folder-obsidian",
|
||||||
|
"automatic-table-of-contents"
|
||||||
]
|
]
|
9
.obsidian/hotkeys.json
vendored
9
.obsidian/hotkeys.json
vendored
@ -136,5 +136,14 @@
|
|||||||
],
|
],
|
||||||
"key": "["
|
"key": "["
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"automatic-table-of-contents:insert-automatic-table-of-contents-docs": [
|
||||||
|
{
|
||||||
|
"modifiers": [
|
||||||
|
"Mod",
|
||||||
|
"Shift"
|
||||||
|
],
|
||||||
|
"key": "D"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
17
.obsidian/plugins/obsidian-automatic-table-of-contents-main/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
17
.obsidian/plugins/obsidian-automatic-table-of-contents-main/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help improve the plugin
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: johansatge
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Hello! Please follow the checklist below when filling a bug report_
|
||||||
|
|
||||||
|
- [ ] Check in the readme if the bug is linked to a documented limitation
|
||||||
|
- [ ] Describe what the bug is
|
||||||
|
- [ ] Check if the bug can be reproduced in the _Obsidian Sandbox_ vault, or with other plugins disabled
|
||||||
|
- [ ] Paste a Markdown block that helps reproducing the described issue
|
||||||
|
- [ ] Upload a screenshot with the rendered result if relevant
|
||||||
|
- [ ] Describe the environment (OS, Obsidian version, installed plugins)
|
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this plugin
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: johansatge
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Hello! Please follow the checklist below when filling a feature request_
|
||||||
|
|
||||||
|
- [ ] Check in the readme if your feature request is linked to a documented limitation
|
||||||
|
- [ ] Is your feature request related to a problem?
|
||||||
|
- [ ] Describe the solution you'd like
|
||||||
|
- [ ] Provide a Markdown block that helps describing your request, if relevant
|
27
.obsidian/plugins/obsidian-automatic-table-of-contents-main/.github/workflows/test.yml
vendored
Normal file
27
.obsidian/plugins/obsidian-automatic-table-of-contents-main/.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
name: test
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [18.x]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: Cache node_modules
|
||||||
|
id: cache-node-modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
key: ${{ runner.os }}-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
path: node_modules
|
||||||
|
- name: Install dependencies
|
||||||
|
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||||
|
run: npm ci
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
2
.obsidian/plugins/obsidian-automatic-table-of-contents-main/.gitignore
vendored
Normal file
2
.obsidian/plugins/obsidian-automatic-table-of-contents-main/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# https://github.com/pjeby/hot-reload
|
||||||
|
.hotreload
|
21
.obsidian/plugins/obsidian-automatic-table-of-contents-main/CONTRIBUTING.md
vendored
Normal file
21
.obsidian/plugins/obsidian-automatic-table-of-contents-main/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
First off, thanks for taking the time to contribute! :tada:
|
||||||
|
|
||||||
|
## Bugs and questions
|
||||||
|
|
||||||
|
Report bugs, ask questions, and request features using [GitHub Issues](https://github.com/johansatge/obsidian-automatic-table-of-contents/issues).
|
||||||
|
|
||||||
|
When submitting a bug report, do not hesitate to be as exhaustive as possible, by adding:
|
||||||
|
|
||||||
|
* A quick summary of the bug
|
||||||
|
* The expected and actual behavior
|
||||||
|
* The platform you are using (operating system, Obsidian version...)
|
||||||
|
* A Markdown sample
|
||||||
|
* Any other information that you think would be useful
|
||||||
|
|
||||||
|
## Working on the project
|
||||||
|
|
||||||
|
When contributing to this repository, please create an issue first, so the change you wish to make can be discussed with the other maintainers.
|
||||||
|
|
||||||
|
When you submit code changes, your submissions are understood to be under the same license that covers the project. Feel free to contact the maintainers if that's a concern.
|
9
.obsidian/plugins/obsidian-automatic-table-of-contents-main/LICENSE
vendored
Normal file
9
.obsidian/plugins/obsidian-automatic-table-of-contents-main/LICENSE
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright © 2023 Johan Satgé
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
118
.obsidian/plugins/obsidian-automatic-table-of-contents-main/README.md
vendored
Normal file
118
.obsidian/plugins/obsidian-automatic-table-of-contents-main/README.md
vendored
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# Obsidian Automatic Table Of Contents
|
||||||
|
|
||||||
|
[](https://obsidian.md/plugins?search=automatic%20table%20of%20contents)
|
||||||
|
[](https://github.com/johansatge/obsidian-automatic-table-of-contents/releases)
|
||||||
|
[](https://github.com/johansatge/obsidian-automatic-table-of-contents/actions)
|
||||||
|
|
||||||
|
> An Obsidian plugin to create a table of contents in a note, that updates itself when the note changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [From Obsidian (easiest)](#from-obsidian-easiest)
|
||||||
|
- [From git](#from-git)
|
||||||
|
- [From source](#from-source)
|
||||||
|
- [Usage and options](#usage-and-options)
|
||||||
|
- [Limitations and known bugs](#limitations-and-known-bugs)
|
||||||
|
- [Publish a new version](#publish-a-new-version)
|
||||||
|
- [Changelog](#changelog)
|
||||||
|
- [License](#license)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### From Obsidian (easiest)
|
||||||
|
|
||||||
|
Install the plugin from the [Community plugins](https://obsidian.md/plugins?search=automatic%20table%20of%20contents) section in the app settings.
|
||||||
|
|
||||||
|
### From git
|
||||||
|
|
||||||
|
Clone the plugin in your `.obsidian/plugins` directory:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cd /path/to/your/vault/.obsidian/plugins
|
||||||
|
git clone git@github.com:johansatge/obsidian-automatic-table-of-contents.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### From source
|
||||||
|
|
||||||
|
Download the [latest release](https://github.com/johansatge/obsidian-automatic-table-of-contents/releases) and unzip it in the `.obsidian/plugins/automatic-table-of-contents` directory.
|
||||||
|
|
||||||
|
## Usage and options
|
||||||
|
|
||||||
|
Insert a codeblock with the `table-of-contents` (or its short version `toc`) syntax.
|
||||||
|
|
||||||
|
````
|
||||||
|
```table-of-contents
|
||||||
|
option1: value1
|
||||||
|
option2: value2
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
Alternatively, two commands are available in the command palette:
|
||||||
|
|
||||||
|
- Insert table of contents
|
||||||
|
- Insert table of contents (with available options)
|
||||||
|
|
||||||
|
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`, `nestedOrderedList` or `inlineFirstLevel`) |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
|
## Limitations and known bugs
|
||||||
|
|
||||||
|
- The table of contents may not be generated correctly if the document doesn't implement a correct titles hierarchy (level 1 title then level 3 without a level 2 in between, for instance)
|
||||||
|
- HTML & markdown that may be in the document headings are stripped when `includeLinks: true` (see [#24](https://github.com/johansatge/obsidian-automatic-table-of-contents/issues/24) & [#27](https://github.com/johansatge/obsidian-automatic-table-of-contents/issues/27))
|
||||||
|
- LaTeX equations are not rendered correctly when `includeLinks: true` (see [#13](https://github.com/johansatge/obsidian-automatic-table-of-contents/issues/13))
|
||||||
|
- Links might not behave correctly if the same title is present several times on the page (see [#57](https://github.com/johansatge/obsidian-automatic-table-of-contents/issues/57))
|
||||||
|
|
||||||
|
## Publish a new version
|
||||||
|
|
||||||
|
- Push a commit with the new version number as message with:
|
||||||
|
- The relevant changelog in `README.md`
|
||||||
|
- The new version number in `manifest.json`
|
||||||
|
- Tag the commit with the version number
|
||||||
|
- Publish a [new GitHub release](https://github.com/johansatge/obsidian-automatic-table-of-contents/releases/new) with:
|
||||||
|
- The version number as title
|
||||||
|
- The changelog from `README.md` as description
|
||||||
|
- `main.js` and `manifest.json` as attachments
|
||||||
|
- _Set as the latest release_ checked
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
This project uses [semver](http://semver.org/).
|
||||||
|
|
||||||
|
| Version | Date | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `1.5.1` | 2025-01-18 | Rename command for readability (fix #53) |
|
||||||
|
| `1.5.0` | 2024-11-24 | Add `hideWhenEmpty` option (fix #51) |
|
||||||
|
| `1.4.0` | 2024-05-19 | Add `nestedOrderedList` style ([@bjtho08](https://github.com/bjtho08)) (fix #41) |
|
||||||
|
| `1.3.3` | 2024-05-16 | Compute the right min level when `style:inlineFirstLevel` (fix #39) |
|
||||||
|
| `1.3.2` | 2024-02-18 | Harden headings stripping |
|
||||||
|
| `1.3.1` | 2024-02-18 | Strip headings for readability (fix #24, #27) |
|
||||||
|
| `1.3.0` | 2024-02-17 | Introduce `title` option (fix #5, #32) |
|
||||||
|
| `1.2.0` | 2024-01-19 | Introduce `toc` shorthand trigger (fix #19) |
|
||||||
|
| `1.1.0` | 2024-01-03 | Introduce `minLevel` option ([@ras0q](https://github.com/ras0q)) (fix #11) |
|
||||||
|
| `1.0.6` | 2023-11-02 | Escape special characters (fix #3) |
|
||||||
|
| `1.0.5` | 2023-11-01 | Fix plugin activation on mobile (fix #17) |
|
||||||
|
| `1.0.4` | 2023-10-31 | Support pages with no first level headings (fix #6) |
|
||||||
|
| `1.0.3` | 2023-09-30 | Fix readme |
|
||||||
|
| `1.0.2` | 2023-09-25 | Fix output sometimes displaying `undefined` headings |
|
||||||
|
| `1.0.1` | 2023-09-09 | Fix reference to global `App` instance |
|
||||||
|
| `1.0.0` | 2023-08-27 | Initial version |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is released under the [MIT License](LICENSE).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Bug reports and feature requests are welcome! More details in the [contribution guidelines](CONTRIBUTING.md).
|
BIN
.obsidian/plugins/obsidian-automatic-table-of-contents-main/images/command-palette.jpg
vendored
Normal file
BIN
.obsidian/plugins/obsidian-automatic-table-of-contents-main/images/command-palette.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
.obsidian/plugins/obsidian-automatic-table-of-contents-main/images/demo.gif
vendored
Normal file
BIN
.obsidian/plugins/obsidian-automatic-table-of-contents-main/images/demo.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
BIN
.obsidian/plugins/obsidian-automatic-table-of-contents-main/images/demo.mp4
vendored
Normal file
BIN
.obsidian/plugins/obsidian-automatic-table-of-contents-main/images/demo.mp4
vendored
Normal file
Binary file not shown.
301
.obsidian/plugins/obsidian-automatic-table-of-contents-main/main.js
vendored
Normal file
301
.obsidian/plugins/obsidian-automatic-table-of-contents-main/main.js
vendored
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
let Plugin = class {}
|
||||||
|
let MarkdownRenderer = {}
|
||||||
|
let MarkdownRenderChild = class {}
|
||||||
|
let htmlToMarkdown = (html) => html
|
||||||
|
|
||||||
|
if (isObsidian()) {
|
||||||
|
const obsidian = require('obsidian')
|
||||||
|
Plugin = obsidian.Plugin
|
||||||
|
MarkdownRenderer = obsidian.MarkdownRenderer
|
||||||
|
MarkdownRenderChild = obsidian.MarkdownRenderChild
|
||||||
|
htmlToMarkdown = obsidian.htmlToMarkdown
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeblockId = 'table-of-contents'
|
||||||
|
const codeblockIdShort = 'toc'
|
||||||
|
const availableOptions = {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
comment: '',
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: 'value',
|
||||||
|
default: 'nestedList',
|
||||||
|
values: ['nestedList', 'nestedOrderedList', 'inlineFirstLevel'],
|
||||||
|
comment: 'TOC style (nestedList|nestedOrderedList|inlineFirstLevel)',
|
||||||
|
},
|
||||||
|
minLevel: {
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
comment: 'Include headings from the specified level',
|
||||||
|
},
|
||||||
|
maxLevel: {
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
comment: 'Include headings up to the specified level',
|
||||||
|
},
|
||||||
|
includeLinks: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
comment: 'Make headings clickable',
|
||||||
|
},
|
||||||
|
hideWhenEmpty: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
comment: 'Hide TOC if no headings are found'
|
||||||
|
},
|
||||||
|
debugInConsole: {
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
comment: 'Print debug info in Obsidian console',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObsidianAutomaticTableOfContents extends Plugin {
|
||||||
|
async onload() {
|
||||||
|
const handler = (sourceText, element, context) => {
|
||||||
|
context.addChild(new Renderer(this.app, element, context.sourcePath, sourceText))
|
||||||
|
}
|
||||||
|
this.registerMarkdownCodeBlockProcessor(codeblockId, handler)
|
||||||
|
this.registerMarkdownCodeBlockProcessor(codeblockIdShort, handler)
|
||||||
|
this.addCommand({
|
||||||
|
id: 'insert-automatic-table-of-contents',
|
||||||
|
name: 'Insert table of contents',
|
||||||
|
editorCallback: onInsertToc,
|
||||||
|
})
|
||||||
|
this.addCommand({
|
||||||
|
id: 'insert-automatic-table-of-contents-docs',
|
||||||
|
name: 'Insert table of contents (with available options)',
|
||||||
|
editorCallback: onInsertTocWithDocs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInsertToc(editor) {
|
||||||
|
const markdown = '```' + codeblockId + '\n```'
|
||||||
|
editor.replaceRange(markdown, editor.getCursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInsertTocWithDocs(editor) {
|
||||||
|
let markdown = ['```' + codeblockId]
|
||||||
|
Object.keys(availableOptions).forEach((optionName) => {
|
||||||
|
const option = availableOptions[optionName]
|
||||||
|
const comment = option.comment.length > 0 ? ` # ${option.comment}` : ''
|
||||||
|
markdown.push(`${optionName}: ${option.default}${comment}`)
|
||||||
|
})
|
||||||
|
markdown.push('```')
|
||||||
|
editor.replaceRange(markdown.join('\n'), editor.getCursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
class Renderer extends MarkdownRenderChild {
|
||||||
|
constructor(app, element, sourcePath, sourceText) {
|
||||||
|
super(element)
|
||||||
|
this.app = app
|
||||||
|
this.element = element
|
||||||
|
this.sourcePath = sourcePath
|
||||||
|
this.sourceText = sourceText
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render on load
|
||||||
|
onload() {
|
||||||
|
this.render()
|
||||||
|
this.registerEvent(this.app.metadataCache.on('changed', this.onMetadataChange.bind(this)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render on file change
|
||||||
|
onMetadataChange() {
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
try {
|
||||||
|
const options = parseOptionsFromSourceText(this.sourceText)
|
||||||
|
if (options.debugInConsole) debug('Options', options)
|
||||||
|
|
||||||
|
const metadata = this.app.metadataCache.getCache(this.sourcePath)
|
||||||
|
const headings = metadata && metadata.headings ? metadata.headings : []
|
||||||
|
if (options.debugInConsole) debug('Headings', headings)
|
||||||
|
|
||||||
|
const markdown = getMarkdownFromHeadings(headings, options)
|
||||||
|
if (options.debugInConsole) debug('Markdown', markdown)
|
||||||
|
|
||||||
|
this.element.empty()
|
||||||
|
MarkdownRenderer.renderMarkdown(markdown, this.element, this.sourcePath, this)
|
||||||
|
} catch(error) {
|
||||||
|
const readableError = `_💥 Could not render table of contents (${error.message})_`
|
||||||
|
MarkdownRenderer.renderMarkdown(readableError, this.element, this.sourcePath, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkdownFromHeadings(headings, options) {
|
||||||
|
const markdownHandlersByStyle = {
|
||||||
|
nestedList: getMarkdownNestedListFromHeadings,
|
||||||
|
nestedOrderedList: getMarkdownNestedOrderedListFromHeadings,
|
||||||
|
inlineFirstLevel: getMarkdownInlineFirstLevelFromHeadings,
|
||||||
|
}
|
||||||
|
let titleMarkdown = ''
|
||||||
|
if (options.title && options.title.length > 0) {
|
||||||
|
titleMarkdown += options.title + '\n'
|
||||||
|
}
|
||||||
|
const markdownHeadings = markdownHandlersByStyle[options.style](headings, options)
|
||||||
|
if (markdownHeadings === null) {
|
||||||
|
if (options.hideWhenEmpty) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return titleMarkdown + '_Table of contents: no headings found_'
|
||||||
|
}
|
||||||
|
return titleMarkdown + markdownHeadings
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkdownNestedListFromHeadings(headings, options) {
|
||||||
|
return getMarkdownListFromHeadings(headings, false, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkdownNestedOrderedListFromHeadings(headings, options) {
|
||||||
|
return getMarkdownListFromHeadings(headings, true, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkdownListFromHeadings(headings, isOrdered, options) {
|
||||||
|
const prefix = isOrdered ? '1.' : '-'
|
||||||
|
const lines = []
|
||||||
|
const minLevel = options.minLevel > 0
|
||||||
|
? options.minLevel
|
||||||
|
: Math.min(...headings.map((heading) => heading.level))
|
||||||
|
headings.forEach((heading) => {
|
||||||
|
if (heading.level < minLevel) return
|
||||||
|
if (options.maxLevel > 0 && heading.level > options.maxLevel) return
|
||||||
|
lines.push(`${'\t'.repeat(heading.level - minLevel)}${prefix} ${getMarkdownHeading(heading, options)}`)
|
||||||
|
})
|
||||||
|
return lines.length > 0 ? lines.join('\n') : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkdownInlineFirstLevelFromHeadings(headings, options) {
|
||||||
|
const minLevel = options.minLevel > 0
|
||||||
|
? options.minLevel
|
||||||
|
: Math.min(...headings.map((heading) => heading.level))
|
||||||
|
const items = headings
|
||||||
|
.filter((heading) => heading.level === minLevel)
|
||||||
|
.map((heading) => {
|
||||||
|
return getMarkdownHeading(heading, options)
|
||||||
|
})
|
||||||
|
return items.length > 0 ? items.join(' | ') : null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMarkdownHeading(heading, options) {
|
||||||
|
const stripMarkdown = (text) => {
|
||||||
|
text = text.replaceAll('*', '').replaceAll('_', '').replaceAll('`', '')
|
||||||
|
text = text.replaceAll('==', '').replaceAll('~~', '')
|
||||||
|
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Strip markdown links
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
const stripHtml = (text) => stripMarkdown(htmlToMarkdown(text))
|
||||||
|
const stripWikilinks = (text, isForLink) => {
|
||||||
|
// Strip [[link|text]] format
|
||||||
|
// For the text part of the final link we only keep "text"
|
||||||
|
// For the link part we need the text + link
|
||||||
|
// Example: "# Some [[file.md|heading]]" must be translated to "[[#Some file.md heading|Some heading]]"
|
||||||
|
text = text.replace(/\[\[([^\]]+)\|([^\]]+)\]\]/g, isForLink ? '$1 $2' : '$2')
|
||||||
|
text = text.replace(/\[\[([^\]]+)\]\]/g, '$1') // Strip [[link]] format
|
||||||
|
// Replace malformed links & reserved wikilinks chars
|
||||||
|
text = text.replaceAll('[[', '').replaceAll('| ', isForLink ? '' : '- ').replaceAll('|', isForLink ? ' ' : '-')
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
const stripTags = (text) => text.replaceAll('#', '')
|
||||||
|
if (options.includeLinks) {
|
||||||
|
// Remove markdown, HTML & wikilinks from text for readability, as they are not rendered in a wikilink
|
||||||
|
let text = heading.heading
|
||||||
|
text = stripMarkdown(text)
|
||||||
|
text = stripHtml(text)
|
||||||
|
text = stripWikilinks(text, false)
|
||||||
|
// Remove wikilinks & tags from link or it won't be clickable (on the other hand HTML & markdown must stay)
|
||||||
|
let link = heading.heading
|
||||||
|
link = stripWikilinks(link, true)
|
||||||
|
link = stripTags(link)
|
||||||
|
|
||||||
|
// Return wiklink style link
|
||||||
|
return `[[#${link}|${text}]]`
|
||||||
|
// Why not markdown links? Because even if it looks like the text part would have a better compatibility
|
||||||
|
// with complex headings (as it would support HTML, markdown, etc) the link part is messy,
|
||||||
|
// because it requires some encoding that looks buggy and undocumented; official docs state the link must be URL encoded
|
||||||
|
// (https://help.obsidian.md/Linking+notes+and+files/Internal+links#Supported+formats+for+internal+links)
|
||||||
|
// but it doesn't work properly, example: "## Some <em>heading</em> with simple HTML" must be encoded as:
|
||||||
|
// [Some <em>heading</em> with simple HTML](#Some%20<em>heading</em>%20with%20simpler%20HTML)
|
||||||
|
// and not
|
||||||
|
// [Some <em>heading</em> with simple HTML](#Some%20%3Cem%3Eheading%3C%2Fem%3E%20with%20simpler%20HTML)
|
||||||
|
// Also it won't be clickable at all if the heading contains #tags or more complex HTML
|
||||||
|
// (example: ## Some <em style="background: red">heading</em> #with-a-tag)
|
||||||
|
// (unless there is a way to encode these use cases that I didn't find)
|
||||||
|
}
|
||||||
|
return heading.heading
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptionsFromSourceText(sourceText = '') {
|
||||||
|
const options = {}
|
||||||
|
Object.keys(availableOptions).forEach((option) => {
|
||||||
|
options[option] = availableOptions[option].default
|
||||||
|
})
|
||||||
|
sourceText.split('\n').forEach((line) => {
|
||||||
|
const option = parseOptionFromSourceLine(line)
|
||||||
|
if (option !== null) {
|
||||||
|
options[option.name] = option.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptionFromSourceLine(line) {
|
||||||
|
const matches = line.match(/([a-zA-Z0-9._ ]+):(.*)/)
|
||||||
|
if (line.startsWith('#') || !matches) return null
|
||||||
|
const possibleName = matches[1].trim()
|
||||||
|
const optionParams = availableOptions[possibleName]
|
||||||
|
let possibleValue = matches[2].trim()
|
||||||
|
if (!optionParams || optionParams.type !== 'string') {
|
||||||
|
// Strip comments from values except for strings (as a 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)
|
||||||
|
if (value < 0) throw valueError
|
||||||
|
return { name: possibleName, value }
|
||||||
|
}
|
||||||
|
if (optionParams && optionParams.type === 'boolean') {
|
||||||
|
if (!['true', 'false'].includes(possibleValue)) throw valueError
|
||||||
|
return { name: possibleName, value: possibleValue === 'true' }
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
function debug(type, data) {
|
||||||
|
console.log(...[
|
||||||
|
`%cAutomatic Table Of Contents %c${type}:\n`,
|
||||||
|
'color: orange; font-weight: bold',
|
||||||
|
'font-weight: bold',
|
||||||
|
data,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObsidian() {
|
||||||
|
if (typeof process !== 'object') {
|
||||||
|
return true // Obsidian mobile doesn't have a global process object
|
||||||
|
}
|
||||||
|
return !process.env || !process.env.JEST_WORKER_ID // Jest runtime is not Obsidian
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObsidian()) {
|
||||||
|
module.exports = ObsidianAutomaticTableOfContents
|
||||||
|
} else {
|
||||||
|
module.exports = {
|
||||||
|
parseOptionsFromSourceText,
|
||||||
|
getMarkdownFromHeadings,
|
||||||
|
}
|
||||||
|
}
|
10
.obsidian/plugins/obsidian-automatic-table-of-contents-main/manifest.json
vendored
Normal file
10
.obsidian/plugins/obsidian-automatic-table-of-contents-main/manifest.json
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"id": "automatic-table-of-contents",
|
||||||
|
"name": "Automatic Table Of Contents",
|
||||||
|
"version": "1.5.1",
|
||||||
|
"minAppVersion": "1.3.0",
|
||||||
|
"description": "Create a table of contents in a note, that updates itself when the note changes",
|
||||||
|
"author": "Johan Satgé",
|
||||||
|
"authorUrl": "https://github.com/johansatge",
|
||||||
|
"isDesktopOnly": false
|
||||||
|
}
|
3616
.obsidian/plugins/obsidian-automatic-table-of-contents-main/package-lock.json
generated
vendored
Normal file
3616
.obsidian/plugins/obsidian-automatic-table-of-contents-main/package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
.obsidian/plugins/obsidian-automatic-table-of-contents-main/package.json
vendored
Normal file
9
.obsidian/plugins/obsidian-automatic-table-of-contents-main/package.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"test": "jest --config=test/jest.config.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"jest": "^29.7.0"
|
||||||
|
}
|
||||||
|
}
|
218
.obsidian/plugins/obsidian-automatic-table-of-contents-main/test/headings.test.js
vendored
Normal file
218
.obsidian/plugins/obsidian-automatic-table-of-contents-main/test/headings.test.js
vendored
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
const {
|
||||||
|
getMarkdownFromHeadings,
|
||||||
|
parseOptionsFromSourceText,
|
||||||
|
} = require('../main.js')
|
||||||
|
|
||||||
|
const testStandardHeadings = [
|
||||||
|
{ heading: 'Title 1 level 1', level: 1 },
|
||||||
|
{ heading: 'Title 1 level 2', level: 2 },
|
||||||
|
{ heading: 'Title 1 level 3', level: 3 },
|
||||||
|
{ heading: 'Title 2 level 1', level: 1 },
|
||||||
|
{ heading: 'Title 3 level 1', level: 1 },
|
||||||
|
{ heading: 'Title 3 level 2', level: 2 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const testHeadingsWithoutFirstLevel = [
|
||||||
|
{ heading: 'Title 1 level 2', level: 2 },
|
||||||
|
{ heading: 'Title 1 level 3', level: 3 },
|
||||||
|
{ heading: 'Title 1 level 4', level: 4 },
|
||||||
|
{ heading: 'Title 2 level 2', level: 2 },
|
||||||
|
{ heading: 'Title 3 level 2', level: 2 },
|
||||||
|
{ heading: 'Title 3 level 3', level: 3 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const testHeadingsWithSpecialChars = [
|
||||||
|
{ heading: 'Title 1 `level 1` {with special chars}, **bold**, _italic_, #a-tag, ==highlighted== and ~~strikethrough~~ text', level: 1 },
|
||||||
|
{ heading: 'Title 1 level 2 <em style="color: black">with HTML</em>', level: 2 },
|
||||||
|
{ heading: 'Title 1 level 2 [[wikilink1]] [[wikilink2|wikitext2]] [mdlink](https://mdurl)', level: 2 },
|
||||||
|
{ heading: 'Title 1 level 2 [[malformedlink a pi|pe | and [other chars]', level: 2 },
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('Headings', () => {
|
||||||
|
test('Returns default message if no headings', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
const md = getMarkdownFromHeadings([], options)
|
||||||
|
expect(md).toContain('no headings found')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns empty TOC if no headings & option enabled', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.hideWhenEmpty = true
|
||||||
|
const md = getMarkdownFromHeadings([], options)
|
||||||
|
expect(md).toEqual('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented list with links', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
- [[#Title 1 level 1|Title 1 level 1]]
|
||||||
|
- [[#Title 1 level 2|Title 1 level 2]]
|
||||||
|
- [[#Title 1 level 3|Title 1 level 3]]
|
||||||
|
- [[#Title 2 level 1|Title 2 level 1]]
|
||||||
|
- [[#Title 3 level 1|Title 3 level 1]]
|
||||||
|
- [[#Title 3 level 2|Title 3 level 2]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented list with links if no first level', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
const md = getMarkdownFromHeadings(testHeadingsWithoutFirstLevel, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
- [[#Title 1 level 2|Title 1 level 2]]
|
||||||
|
- [[#Title 1 level 3|Title 1 level 3]]
|
||||||
|
- [[#Title 1 level 4|Title 1 level 4]]
|
||||||
|
- [[#Title 2 level 2|Title 2 level 2]]
|
||||||
|
- [[#Title 3 level 2|Title 3 level 2]]
|
||||||
|
- [[#Title 3 level 3|Title 3 level 3]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented ordered list with links', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.style = 'nestedOrderedList'
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
1. [[#Title 1 level 1|Title 1 level 1]]
|
||||||
|
1. [[#Title 1 level 2|Title 1 level 2]]
|
||||||
|
1. [[#Title 1 level 3|Title 1 level 3]]
|
||||||
|
1. [[#Title 2 level 1|Title 2 level 1]]
|
||||||
|
1. [[#Title 3 level 1|Title 3 level 1]]
|
||||||
|
1. [[#Title 3 level 2|Title 3 level 2]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented list with min level', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.minLevel = 2
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
- [[#Title 1 level 2|Title 1 level 2]]
|
||||||
|
- [[#Title 1 level 3|Title 1 level 3]]
|
||||||
|
- [[#Title 3 level 2|Title 3 level 2]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented list with max level', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.maxLevel = 2
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
- [[#Title 1 level 1|Title 1 level 1]]
|
||||||
|
- [[#Title 1 level 2|Title 1 level 2]]
|
||||||
|
- [[#Title 2 level 1|Title 2 level 1]]
|
||||||
|
- [[#Title 3 level 1|Title 3 level 1]]
|
||||||
|
- [[#Title 3 level 2|Title 3 level 2]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented list without links', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.includeLinks = false
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
- 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 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 1]]
|
||||||
|
- [[#Title 1 level 2|Title 1 level 2]]
|
||||||
|
- [[#Title 1 level 3|Title 1 level 3]]
|
||||||
|
- [[#Title 2 level 1|Title 2 level 1]]
|
||||||
|
- [[#Title 3 level 1|Title 3 level 1]]
|
||||||
|
- [[#Title 3 level 2|Title 3 level 2]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented list with sanitized links from special chars', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
const md = getMarkdownFromHeadings(testHeadingsWithSpecialChars, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
- [[#Title 1 \`level 1\` {with special chars}, **bold**, _italic_, a-tag, ==highlighted== and ~~strikethrough~~ text|Title 1 level 1 {with special chars}, bold, italic, #a-tag, highlighted and strikethrough text]]
|
||||||
|
- [[#Title 1 level 2 <em style="color: black">with HTML</em>|Title 1 level 2 <em style="color: black">with HTML</em>]]
|
||||||
|
- [[#Title 1 level 2 wikilink1 wikilink2 wikitext2 [mdlink](https://mdurl)|Title 1 level 2 wikilink1 wikitext2 mdlink]]
|
||||||
|
- [[#Title 1 level 2 malformedlink a pi pe and [other chars]|Title 1 level 2 malformedlink a pi-pe - and [other chars]]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns indented list without links from special chars', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.includeLinks = false
|
||||||
|
const md = getMarkdownFromHeadings(testHeadingsWithSpecialChars, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
- Title 1 \`level 1\` {with special chars}, **bold**, _italic_, #a-tag, ==highlighted== and ~~strikethrough~~ text
|
||||||
|
- Title 1 level 2 <em style="color: black">with HTML</em>
|
||||||
|
- Title 1 level 2 [[wikilink1]] [[wikilink2|wikitext2]] [mdlink](https://mdurl)
|
||||||
|
- Title 1 level 2 [[malformedlink a pi|pe | and [other chars]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test('Returns flat first-level list with links', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.style = 'inlineFirstLevel'
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
[[#Title 1 level 1|Title 1 level 1]] | [[#Title 2 level 1|Title 2 level 1]] | [[#Title 3 level 1|Title 3 level 1]]
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns flat first-level list without links', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.style = 'inlineFirstLevel'
|
||||||
|
options.includeLinks = false
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
Title 1 level 1 | Title 2 level 1 | Title 3 level 1
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns flat list with custom level', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.style = 'inlineFirstLevel'
|
||||||
|
options.includeLinks = false
|
||||||
|
options.minLevel = 3
|
||||||
|
const md = getMarkdownFromHeadings(testStandardHeadings, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
Title 1 level 3
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns flat list with default level', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
options.style = 'inlineFirstLevel'
|
||||||
|
options.includeLinks = false
|
||||||
|
const md = getMarkdownFromHeadings(testHeadingsWithoutFirstLevel, options)
|
||||||
|
const expectedMd = sanitizeMd(`
|
||||||
|
Title 1 level 2 | Title 2 level 2 | Title 3 level 2
|
||||||
|
`)
|
||||||
|
expect(md).toEqual(expectedMd)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function sanitizeMd(md) {
|
||||||
|
return md.replaceAll(' ', '\t').replace(/^\n/, '').replace(/\n$/, '')
|
||||||
|
}
|
8
.obsidian/plugins/obsidian-automatic-table-of-contents-main/test/jest.config.js
vendored
Normal file
8
.obsidian/plugins/obsidian-automatic-table-of-contents-main/test/jest.config.js
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
module.exports = async () => {
|
||||||
|
return {
|
||||||
|
testMatch: [path.join(__dirname, '**/*.test.js')],
|
||||||
|
testTimeout: 1000,
|
||||||
|
}
|
||||||
|
}
|
102
.obsidian/plugins/obsidian-automatic-table-of-contents-main/test/options.test.js
vendored
Normal file
102
.obsidian/plugins/obsidian-automatic-table-of-contents-main/test/options.test.js
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
const { parseOptionsFromSourceText } = require('../main.js')
|
||||||
|
|
||||||
|
describe('Options', () => {
|
||||||
|
test('Returns default options if none are specified', () => {
|
||||||
|
const options = parseOptionsFromSourceText('')
|
||||||
|
expect(options).toEqual({
|
||||||
|
title: '',
|
||||||
|
style: 'nestedList',
|
||||||
|
includeLinks: true,
|
||||||
|
minLevel: 0,
|
||||||
|
maxLevel: 0,
|
||||||
|
hideWhenEmpty: false,
|
||||||
|
debugInConsole: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Returns custom options if specified', () => {
|
||||||
|
const optionsText = `
|
||||||
|
title: # Some title
|
||||||
|
style: inlineFirstLevel # Some comment
|
||||||
|
minLevel: 1
|
||||||
|
maxLevel: 2 # Some other comment
|
||||||
|
includeLinks: false
|
||||||
|
hideWhenEmpty: true
|
||||||
|
debugInConsole: true
|
||||||
|
`
|
||||||
|
const options = parseOptionsFromSourceText(optionsText)
|
||||||
|
expect(options).toEqual({
|
||||||
|
title: '# Some title',
|
||||||
|
style: 'inlineFirstLevel',
|
||||||
|
includeLinks: false,
|
||||||
|
minLevel: 1,
|
||||||
|
maxLevel: 2,
|
||||||
|
hideWhenEmpty: true,
|
||||||
|
debugInConsole: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Accepts comments in options', () => {
|
||||||
|
const options = parseOptionsFromSourceText('maxLevel: 5 # some comment')
|
||||||
|
expect(options.maxLevel).toEqual(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Ignores unknown options', () => {
|
||||||
|
const options = parseOptionsFromSourceText(`
|
||||||
|
maxLevel: 5
|
||||||
|
someOption: someValue
|
||||||
|
`)
|
||||||
|
expect(options.maxLevel).toEqual(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Throw an error if the option value is invalid', () => {
|
||||||
|
test('On style', () => {
|
||||||
|
try {
|
||||||
|
const options = parseOptionsFromSourceText('style: someInvalidStyle')
|
||||||
|
expect(options.style).toEqual('Should have thrown')
|
||||||
|
} catch(error) {
|
||||||
|
expect(error.message).toContain('Invalid value')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test('On minLevel', () => {
|
||||||
|
try {
|
||||||
|
const options = parseOptionsFromSourceText('minLevel: -1')
|
||||||
|
expect(options.minLevel).toEqual('Should have thrown')
|
||||||
|
} catch(error) {
|
||||||
|
expect(error.message).toContain('Invalid value')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test('On maxLevel', () => {
|
||||||
|
try {
|
||||||
|
const options = parseOptionsFromSourceText('maxLevel: -1')
|
||||||
|
expect(options.maxLevel).toEqual('Should have thrown')
|
||||||
|
} catch(error) {
|
||||||
|
expect(error.message).toContain('Invalid value')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test('On includeLinks', () => {
|
||||||
|
try {
|
||||||
|
const options = parseOptionsFromSourceText('includeLinks: no')
|
||||||
|
expect(options.includeLinks).toEqual('Should have thrown')
|
||||||
|
} catch(error) {
|
||||||
|
expect(error.message).toContain('Invalid value')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test('On hideWhenEmpty', () => {
|
||||||
|
try {
|
||||||
|
const options = parseOptionsFromSourceText('hideWhenEmpty: maybe')
|
||||||
|
expect(options.hideWhenEmpty).toEqual('Should have thrown')
|
||||||
|
} catch(error) {
|
||||||
|
expect(error.message).toContain('Invalid value')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test('On debugInConsole', () => {
|
||||||
|
try {
|
||||||
|
const options = parseOptionsFromSourceText('debugInConsole: yes')
|
||||||
|
expect(options.debugInConsole).toEqual('Should have thrown')
|
||||||
|
} catch(error) {
|
||||||
|
expect(error.message).toContain('Invalid value')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Reference in New Issue
Block a user