Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add external link support for nav items #46

Merged
merged 1 commit into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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