diff --git a/main.js b/main.js
index 2957b20..0b57e1d 100644
--- a/main.js
+++ b/main.js
@@ -1,12 +1,14 @@
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'
@@ -159,11 +161,39 @@ function getMarkdownInlineFirstLevelFromHeadings(headings, options) {
}
function getMarkdownHeading(heading, options) {
+ const stripMarkdown = (text) => {
+ text = text.replaceAll('*', '').replaceAll('_', '').replaceAll('`', '')
+ text = text.replaceAll('==', '').replaceAll('~~', '')
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
+ return text
+ }
+ const stripHtml = (text) => stripMarkdown(htmlToMarkdown(text))
+ const stripWikilinks = (text) => text.replaceAll('[', '').replaceAll('|', '').replaceAll(']', '')
+ const stripTags = (text) => text.replaceAll('#', '')
if (options.includeLinks) {
- let cleaned = heading.heading
- // Strip reserved wikilink characters
- cleaned = cleaned.replaceAll('|', '-').replaceAll('[', '{').replaceAll(']', '}')
- return `[[#${cleaned}]]`
+ // 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)
+ // Remove wikilinks & tags from link or it won't be clickable (and HTML & markdown must stay)
+ let link = heading.heading
+ link = stripWikilinks(link)
+ 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 heading with simple HTML" must be encoded as:
+ // [Some heading with simple HTML](#Some%20heading%20with%20simpler%20HTML)
+ // and not
+ // [Some heading 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 heading #with-a-tag)
+ // (unless there is a way to encode these use cases that I didn't find)
}
return heading.heading
}
diff --git a/test/headings.test.js b/test/headings.test.js
index 9080c8b..a20b620 100644
--- a/test/headings.test.js
+++ b/test/headings.test.js
@@ -4,7 +4,7 @@ const {
} = require('../main.js')
const testStandardHeadings = [
- { heading: 'Title [1] | level 1', level: 1 }, // With wiklink characters to be stripped
+ { 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 },
@@ -21,17 +21,22 @@ const testHeadingsWithoutFirstLevel = [
{ 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 with HTML [[wikilink.md]] [md link](https://mdurl)', level: 2 },
+]
+
describe('Headings', () => {
test('Returns indented list with links', () => {
const options = parseOptionsFromSourceText('')
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]]
+- [[#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)
})
@@ -40,12 +45,12 @@ describe('Headings', () => {
const options = parseOptionsFromSourceText('')
const md = getMarkdownFromHeadings(testHeadingsWithoutFirstLevel, options)
const expectedMd = sanitizeMd(`
-- [[#Title 1 level 2]]
- - [[#Title 1 level 3]]
- - [[#Title 1 level 4]]
-- [[#Title 2 level 2]]
-- [[#Title 3 level 2]]
- - [[#Title 3 level 3]]
+- [[#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)
})
@@ -55,9 +60,9 @@ describe('Headings', () => {
options.minLevel = 2
const md = getMarkdownFromHeadings(testStandardHeadings, options)
const expectedMd = sanitizeMd(`
-- [[#Title 1 level 2]]
- - [[#Title 1 level 3]]
-- [[#Title 3 level 2]]
+- [[#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)
})
@@ -67,11 +72,11 @@ describe('Headings', () => {
options.maxLevel = 2
const md = getMarkdownFromHeadings(testStandardHeadings, options)
const expectedMd = sanitizeMd(`
-- [[#Title {1} - level 1]]
- - [[#Title 1 level 2]]
-- [[#Title 2 level 1]]
-- [[#Title 3 level 1]]
- - [[#Title 3 level 2]]
+- [[#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)
})
@@ -81,7 +86,7 @@ describe('Headings', () => {
options.includeLinks = false
const md = getMarkdownFromHeadings(testStandardHeadings, options)
const expectedMd = sanitizeMd(`
-- Title [1] | level 1
+- Title 1 level 1
- Title 1 level 2
- Title 1 level 3
- Title 2 level 1
@@ -97,22 +102,44 @@ describe('Headings', () => {
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]]
+- [[#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 with HTML wikilink.md md link(https://mdurl)|Title 1 level 2 with HTML wikilink.md md link]]
`)
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 with HTML [[wikilink.md]] [md link](https://mdurl)
+`)
+ 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 2 level 1]] | [[#Title 3 level 1]]
+[[#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)
})
@@ -123,7 +150,7 @@ describe('Headings', () => {
options.includeLinks = false
const md = getMarkdownFromHeadings(testStandardHeadings, options)
const expectedMd = sanitizeMd(`
-Title [1] | level 1 | Title 2 level 1 | Title 3 level 1
+Title 1 level 1 | Title 2 level 1 | Title 3 level 1
`)
expect(md).toEqual(expectedMd)
})