Skip to content

Commit

Permalink
Merge pull request #34 from johansatge/strip-headings
Browse files Browse the repository at this point in the history
Strip headings for readability / wikilinks compatibility
  • Loading branch information
johansatge committed Feb 18, 2024
2 parents 0ceeefa + 7de7ed9 commit 3b3f169
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 34 deletions.
46 changes: 42 additions & 4 deletions main.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -159,11 +161,47 @@ 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') // 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
return text
}
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, 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
}
Expand Down
90 changes: 60 additions & 30 deletions 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 }, // 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 },
Expand All @@ -21,17 +21,23 @@ 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 <em style="color: black">with HTML</em>', level: 2 },
{ heading: 'Title 1 level 2 [[wikilink1]] [[wikilink2|wikitext2]] [mdlink](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)
})
Expand All @@ -40,12 +46,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)
})
Expand All @@ -55,9 +61,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)
})
Expand All @@ -67,11 +73,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)
})
Expand All @@ -81,7 +87,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
Expand All @@ -97,22 +103,46 @@ 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 <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]]
`)
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)
`)
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)
})
Expand All @@ -123,7 +153,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)
})
Expand Down

0 comments on commit 3b3f169

Please sign in to comment.