Skip to content

Commit

Permalink
adding collapsible table of content
Browse files Browse the repository at this point in the history
  • Loading branch information
nirajacharya2 committed Feb 29, 2024
1 parent c6ae9b5 commit 7a729db
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 30 deletions.
29 changes: 2 additions & 27 deletions src/components/detail/ContentSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@
<div class="blog-content-sidebar">
<div class="toc">
<div class="title sharp-border">Table of content</div>
<div v-for="(item, index) in toc"
:key="index"
class="toc--item"
@click="scrollToHeading(item.id)"
>
<mdi-menu-right class="mdi-circle" />
{{ item.text }}
</div>
<TOC :toc='toc' :hidden="false"/>
</div>
<br>
<div class="title sharp-border">Tags</div>
Expand All @@ -28,6 +21,7 @@
</template>
<script setup>
import { defineProps } from "vue"
import TOC from "./TOC.vue"
defineProps({
toc: {
Expand All @@ -42,25 +36,6 @@ defineProps({
}
})
const scrollToHeading = (headingId) => {
const xpath = `//*[@id="${headingId}"]`
const heading = document.evaluate(
xpath,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue
if (heading) {
const position = heading.getBoundingClientRect()
// scrolls to 100px above element
window.scrollTo({
left: position.left,
top: position.top + window.scrollY - 100,
behavior: "smooth"
})
}
}
</script>
<style lang="scss">
.blog-content-sidebar {
Expand Down
81 changes: 81 additions & 0 deletions src/components/detail/TOC.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<template>
<div>
<div
v-for="item in toc"
:key="item.id"
:style="{
'padding-left': item.val * 0.2 + 'em',
display: hidden ? 'none' : 'block',
}"
@click="scrollToHeading(item.id, $event)"
>
<mdi-menu-right class="mdi-circle" @click="hideNested($event)" />
{{ item.title }}
<TOC v-if="isArray(item.node)" :toc="item.node" :hidden="hidden" />
</div>
</div>
</template>

<script setup>
import { defineProps } from "vue"
// TODO: hide with prop insted of -> hideNested
defineProps({
toc: {
type: Array
},
hidden: {
type: Boolean
}
})
const isArray = (c) => {
return Array.isArray(c)
}
const scrollToHeading = (headingId, e) => {
e.stopPropagation()
const xpath = `//*[@id="${headingId}"]`
const heading = document.evaluate(
xpath,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue
if (heading) {
const position = heading.getBoundingClientRect()
window.scrollTo({
left: position.left,
top: position.top + window.scrollY - 100,
behavior: "smooth"
})
}
}
const hideNested = (e) => {
e.preventDefault()
e.stopPropagation()
e = e.target
while (e.tagName !== "DIV") {
e = e.parentElement
}
e.childNodes.forEach((element) => {
if (element.tagName === "DIV") {
if (element.classList.length === 0) {
element.style.display = "none"
element.classList.add("hide")
} else {
element.style.display = "block"
element.classList.remove("hide")
}
}
})
}
</script>
<style scoped>
.hide {
display: none;
}
</style>
44 changes: 41 additions & 3 deletions src/helpers/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,50 @@ export const getTableOfContent = (source) => {
const tokens = marked.lexer(source)
// remove the first 2 tokens to avoid meta information
// and then return every tokens that is of header type
const headers = tokens.slice(2).filter(token => token.type === "heading" && token.depth <= 4)
headers.forEach(header => {
const hg = []
const headers = tokens
.slice(2)
.filter((token) => token.type === "heading")// && token.depth <= 4)
headers.forEach((header) => {
const html = marked.parser([header])
const doc = new DOMParser().parseFromString(html, "text/xml")
header.id = doc.firstChild.id
header.text = doc.firstChild.textContent
hg.push(new Graph(header.depth, header.text, header.id, null))
})
return headers
const stack = [hg[0]]
const ans = []
hg.forEach((h) => {
if (h.val === stack[0].val) {
ans.push(h)
stack.push(h)
} else if (h.val > stack[stack.length - 1].val) {
stack[stack.length - 1].addNode(h)
stack.push(h)
} else if (h.val < stack[stack.length - 1].val) {
while (!(h.val > stack[stack.length - 1].val)) {
stack.splice(stack.length - 1, 1)
}
stack[stack.length - 1].addNode(h)
stack.push(h)
} else {
stack.splice(stack.length - 1, 1)
stack[stack.length - 1].addNode(h)
stack.push(h)
}
})
return ans
}

class Graph {
constructor (val, title, id) {
this.val = val
this.title = title
this.id = id
this.node = []
}

addNode (node) {
this.node.push(node)
}
}

0 comments on commit 7a729db

Please sign in to comment.