Skip to content

Commit

Permalink
feat: add plugin code
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Pfaffel <s.pfaffel@gmail.com>
  • Loading branch information
stfsy committed Jan 12, 2025
1 parent 99d94e6 commit 087f850
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 0 deletions.
42 changes: 42 additions & 0 deletions src/components/observer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useRouter } from '@vuepress/client'
import { nextTick, onMounted, onUnmounted, ref } from 'vue'

export default (selector) => {
const router = useRouter()
const visible = ref(false)

const observerOptions = {
root: null,
rootMargin: '0px',
threshold: [0.0, 0.75],
}

function intersectionCallback(entries) {
entries.forEach((entry) => {
visible.value = entry.isIntersecting
})
}

const observer = new IntersectionObserver(intersectionCallback, observerOptions)

function observe() {
const element = window.document.querySelector(selector)
observer.observe(element)
}
function disconnect() {
observer.disconnect()
}

router.afterEach((() => {
disconnect()
nextTick(observe)
}))


onMounted(() => {
nextTick(observe)
})
onUnmounted(disconnect)

return visible
}
156 changes: 156 additions & 0 deletions src/components/outline.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<template>
<Teleport to=".vp-theme-container">
<Transition name="outline">
<div v-if="visible"
class="anchor-right-content">
<ul>
<li class="start">
On this page
</li>
<template v-for="(item, index) in headers"
:key="index">
<template v-if="item.children.length > 0">
<sub-menu :key="item.key"
:menu-info="item" />
</template>
<template v-else>
<li :class="['level', 'level-' + item.level, { active: (!route.hash && index == 0) || route.hash === `#${item.slug}` }]"
@click="headerClick(item)">
{{ item.title }}
</li>
</template>
</template>
</ul>
</div>
</Transition>
</Teleport>
</template>

<script setup>
import { usePageData, useRoute, useRouter } from '@vuepress/client';
import { computed, onMounted, ref } from 'vue';
import observe from 'vuepress-plugin-anchor-right/src/components/observer';
import SubMenu from 'vuepress-plugin-anchor-right/src/components/sub-menu.vue';
const headers = ref([]);
const router = useRouter();
const route = useRoute();
const page = usePageData()
const isFooterVisible = observe('footer')
const isRootPage = computed(() => {
const { path } = page.value
return !path || path === '/' || path === '/index' || path === '/index.html'
})
const visible = computed(() => {
if (isRootPage.value) {
return false
}
return headers.value.length > 0 && isFooterVisible.value === false
})
router.afterEach((_to, _from, failure) => {
if (!failure) {
refresh()
}
})
const refresh = () => {
const page = usePageData();
headers.value = page.value.headers;
}
const headerClick = (item) => {
router.push(`#${item.slug}`);
};
onMounted(refresh);
</script>

<style lang="scss">
@media (max-width: 1000px) {
.anchor-right {
@apply hidden;
}
.sidebar-items {
.sidebar-item-children {
.sidebar-item-children {
@apply block;
}
}
}
.vp-page {
@apply pr-0;
}
}
@media (min-width: 1000px) {
.anchor-right {
@apply block;
}
.sidebar-items {
.sidebar-item-children {
.sidebar-item-children {
@apply hidden;
}
}
}
.vp-page {
@apply pr-64;
}
}
.anchor-right-content {
@apply text-lg right-0 fixed overflow-auto w-56;
top: calc(var(--navbar-height) + 2rem);
max-height: 84vh;
// border-left: #eaecef solid 1px;
ul {
@apply space-y-2;
li {
@apply block;
padding-top: 1px !important;
padding-bottom: 1px !important;
padding-right: 0 !important;
&.start {
@apply mb-8 text-lg font-medium text-gray-400
}
}
}
.level {
@apply block cursor-pointer no-underline;
color: #999;
padding: 4px 12px 4px 0;
margin-left: -1px;
}
.active {
color: var(--c-text-accent);
@apply font-medium;
.menu {
@apply font-normal;
}
}
}
.outline-enter-active,
.outline-leave-active {
transition: opacity 0.25s ease;
}
.outline-enter-from,
.outline-leave-to {
opacity: 0;
}
</style>
49 changes: 49 additions & 0 deletions src/components/sub-menu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script setup>
import { useRoute, useRouter } from '@vuepress/client';
const router = useRouter();
const route = useRoute();
const props = defineProps({
menuInfo: {
type: Object,
},
isFirst: {
type: Boolean,
default: false,
}
});
const headerClick = (item) => {
router.push(`#${item.slug}`);
};
</script>
<template>
<li :class="[
'level',
'level-' + props.menuInfo.level,
{ active: (isFirst && !route.hash) || route.hash === `#${props.menuInfo.slug}` },
]"
@click.prevent="headerClick(props.menuInfo)">
<div>
{{ props.menuInfo.title }}
</div>
<ul class="menu">
<template v-for="(item, index) in props.menuInfo.children"
:key="index">
<sub-menu v-if="item.children.length > 0 && item.level !== 3"
:key="item.key"
:menu-info="item"
:route="route"
:router="router" />

<li v-else
:class="['level', 'level-' + item.level]"
@click.stop="headerClick(item)">
{{ item.title }}
</li>
</template>
</ul>
</li>
</template>
6 changes: 6 additions & 0 deletions src/config/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineClientConfig } from 'vuepress/client'
import Outline from '../components/outline.vue'

export default defineClientConfig({
rootComponents: [Outline]
})
12 changes: 12 additions & 0 deletions src/config/node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getDirname, path } from '@vuepress/utils'

const __dirname = import.meta.dirname || getDirname(import.meta.url)

export default () => {
return () => {
return {
name: '@discue/vuepress-plugin-outline',
clientConfigFile: path.resolve(__dirname, './client.js')
}
}
}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import plugin from './config/node.js'
export default plugin

0 comments on commit 087f850

Please sign in to comment.