Skip to content

Commit

Permalink
feat: add external link support for nav items (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
kiaking authored Jul 21, 2020
1 parent 583f02e commit 44e91bb
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 45 deletions.
27 changes: 6 additions & 21 deletions src/client/theme-default/components/NavBar.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
// TODO dropdowns
import { computed } from 'vue'
import { useSiteData, useRoute } from 'vitepress'
import { withBase } from '../utils'

const normalizePath = (path: string): string => {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '')
if (path.endsWith('/')) {
path += 'index'
}
return path
}
import { useSiteData } from 'vitepress'
import NavBarLink from './NavBarLink.vue'

export default {
setup() {
const route = useRoute()
const isActiveLink = (link: string): boolean => {
return normalizePath(withBase(link)) === normalizePath(route.path)
}
components: {
NavBarLink
},

setup() {
return {
withBase,
isActiveLink,
navData:
process.env.NODE_ENV === 'production'
? // navbar items do not change in production
Expand Down
29 changes: 5 additions & 24 deletions src/client/theme-default/components/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,11 @@
<span>{{ $site.title }}</span>
</a>
<nav class="nav-links" v-if="navData">
<a
class="nav-link"
v-for="{ text, link, target, rel, ariaLabel } of navData"
:class="{ active: isActiveLink(link) }"
:href="withBase(link)"
:target="target"
:rel="rel"
:aria-label="ariaLabel"
>{{ text }}</a
>
<NavBarLink
v-for="item of navData"
:key="item.link"
:item="item"
/>
</nav>
</template>

Expand All @@ -44,18 +39,4 @@
.nav-links {
list-style-type: none;
}
.nav-link {
color: var(--text-color);
margin-left: 1.5rem;
font-weight: 600;
display: inline-block;
height: 1.75rem;
line-height: 1.75rem;
}
.nav-link:hover,
.nav-link.active {
border-bottom: 2px solid var(--accent-color);
}
</style>
78 changes: 78 additions & 0 deletions src/client/theme-default/components/NavBarLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// TODO dropdowns
import { defineComponent, computed, PropType } from 'vue'
import { useRoute } from 'vitepress'
import { withBase, isExternal } from '../utils'
import { DefaultTheme } from '../config'
import OutboundLink from './icons/OutboundLink.vue'

const normalizePath = (path: string): string => {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '')
if (path.endsWith('/')) {
path += 'index'
}
return path
}

export default defineComponent({
components: {
OutboundLink
},

props: {
item: {
type: Object as PropType<DefaultTheme.NavItemWithLink>,
required: true
}
},

setup(props) {
const item = props.item

const route = useRoute()

const classes = computed(() => ({
active: isActiveLink.value,
external: isExternalLink.value
}))

const isActiveLink = computed(() => {
return normalizePath(withBase(item.link)) === normalizePath(route.path)
})

const isExternalLink = computed(() => {
return isExternal(item.link)
})

const href = computed(() => {
return isExternalLink.value ? item.link : withBase(item.link)
})

const target = computed(() => {
if (item.target) {
return item.target
}

return isExternalLink.value ? '_blank' : ''
})

const rel = computed(() => {
if (item.rel) {
return item.rel
}

return isExternalLink.value ? 'noopener noreferrer' : ''
})

return {
classes,
isActiveLink,
isExternalLink,
href,
target,
rel
}
}
})
35 changes: 35 additions & 0 deletions src/client/theme-default/components/NavBarLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<a
class="nav-link"
:class="classes"
:href="href"
:target="target"
:rel="rel"
:aria-label="item.ariaLabel"
>
{{ item.text }}
<OutboundLink v-if="isExternalLink" />
</a>
</template>

<script src="./NavBarLink"></script>

<style>
.nav-link {
color: var(--text-color);
margin-left: 1.5rem;
font-weight: 600;
display: inline-block;
height: 1.75rem;
line-height: 1.75rem;
}
.nav-link:hover,
.nav-link.active {
border-bottom: 2px solid var(--accent-color);
}
.nav-link.external:hover {
border-bottom: 0;
}
</style>
31 changes: 31 additions & 0 deletions src/client/theme-default/components/icons/OutboundLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template functional>
<svg
class="icon outbound"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
x="0px"
y="0px"
viewBox="0 0 100 100"
width="15"
height="15"
>
<path
fill="currentColor"
d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"
/>
<polygon
fill="currentColor"
points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"
/>
</svg>
</template>

<style>
.icon.outbound {
color: #aaa;
display: inline-block;
vertical-align: middle;
position: relative;
top: -1px;
}
</style>
5 changes: 5 additions & 0 deletions src/client/theme-default/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { useSiteData, Route } from 'vitepress'

export const hashRE = /#.*$/
export const extRE = /\.(md|html)$/
export const outboundRE = /^[a-z]+:/i

export function withBase(path: string) {
return (useSiteData().value.base + path).replace(/\/+/g, '/')
}

export function isExternal(path: string): boolean {
return outboundRE.test(path)
}

export function isActive(route: Route, path?: string): boolean {
if (path === undefined) {
return false
Expand Down

0 comments on commit 44e91bb

Please sign in to comment.