Skip to content

Commit e365be5

Browse files
authored
Feature/84 add netlify to footer (#89)
* feature (#84): add edit link * feature (#84): add netlify link with some styles
1 parent 6275464 commit e365be5

File tree

3 files changed

+372
-0
lines changed

3 files changed

+372
-0
lines changed

src/.vuepress/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ module.exports = {
148148
]
149149
}
150150
],
151+
repo: 'vuejs/docs-next',
152+
editLinks: true,
153+
editLinkText: 'Edit this on GitHub!',
151154
sidebarDepth: 2,
152155
sidebar: {
153156
collapsable: false,
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<template>
2+
<footer class="page-edit">
3+
<div v-if="editLink" class="edit-link">
4+
Caught a mistake or want to contribute to the documentation?
5+
<a :href="editLink" target="_blank" rel="noopener noreferrer">{{
6+
editLinkText
7+
}}</a>
8+
<OutboundLink />
9+
</div>
10+
11+
<div>
12+
Deployed on <a href="https://url.netlify.com/HJ8X2mxP8">Netlify</a>
13+
</div>
14+
15+
<div v-if="lastUpdated" class="last-updated">
16+
<span class="prefix">{{ lastUpdatedText }}:</span>
17+
<span class="time">{{ lastUpdated }}</span>
18+
</div>
19+
</footer>
20+
</template>
21+
22+
<script>
23+
import isNil from 'lodash/isNil'
24+
import { endingSlashRE, outboundRE } from '../util'
25+
26+
export default {
27+
name: 'PageEdit',
28+
29+
computed: {
30+
lastUpdated() {
31+
return this.$page.lastUpdated
32+
},
33+
34+
lastUpdatedText() {
35+
if (typeof this.$themeLocaleConfig.lastUpdated === 'string') {
36+
return this.$themeLocaleConfig.lastUpdated
37+
}
38+
if (typeof this.$site.themeConfig.lastUpdated === 'string') {
39+
return this.$site.themeConfig.lastUpdated
40+
}
41+
return 'Last Updated'
42+
},
43+
44+
editLink() {
45+
const showEditLink = isNil(this.$page.frontmatter.editLink)
46+
? this.$site.themeConfig.editLinks
47+
: this.$page.frontmatter.editLink
48+
49+
const {
50+
repo,
51+
docsDir = '',
52+
docsBranch = 'master',
53+
docsRepo = repo
54+
} = this.$site.themeConfig
55+
56+
if (showEditLink && docsRepo && this.$page.relativePath) {
57+
return this.createEditLink(
58+
repo,
59+
docsRepo,
60+
docsDir,
61+
docsBranch,
62+
this.$page.relativePath
63+
)
64+
}
65+
return null
66+
},
67+
68+
editLinkText() {
69+
return (
70+
this.$themeLocaleConfig.editLinkText ||
71+
this.$site.themeConfig.editLinkText ||
72+
`Edit this page`
73+
)
74+
}
75+
},
76+
77+
methods: {
78+
createEditLink(repo, docsRepo, docsDir, docsBranch, path) {
79+
const bitbucket = /bitbucket.org/
80+
if (bitbucket.test(repo)) {
81+
const base = outboundRE.test(docsRepo) ? docsRepo : repo
82+
return (
83+
base.replace(endingSlashRE, '') +
84+
`/src` +
85+
`/${docsBranch}/` +
86+
(docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') +
87+
path +
88+
`?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
89+
)
90+
}
91+
92+
const base = outboundRE.test(docsRepo)
93+
? docsRepo
94+
: `https://github.com/${docsRepo}`
95+
return (
96+
base.replace(endingSlashRE, '') +
97+
`/edit` +
98+
`/${docsBranch}/` +
99+
(docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') +
100+
path
101+
)
102+
}
103+
}
104+
}
105+
</script>
106+
107+
<style>
108+
/*
109+
This entire style block is MVP style wise and will likely
110+
be changed with the new atomic theme. Changes are welcome!
111+
*/
112+
.edit-link {
113+
margin-bottom: 0.5rem;
114+
}
115+
116+
.page-edit {
117+
padding: 0 1.5rem;
118+
display: flex;
119+
align-items: center;
120+
flex-direction: column;
121+
max-width: 740px;
122+
margin: 0 auto;
123+
}
124+
</style>

src/.vuepress/theme/util/index.js

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
export const hashRE = /#.*$/
2+
export const extRE = /\.(md|html)$/
3+
export const endingSlashRE = /\/$/
4+
export const outboundRE = /^[a-z]+:/i
5+
6+
export function normalize (path) {
7+
return decodeURI(path)
8+
.replace(hashRE, '')
9+
.replace(extRE, '')
10+
}
11+
12+
export function getHash (path) {
13+
const match = path.match(hashRE)
14+
if (match) {
15+
return match[0]
16+
}
17+
}
18+
19+
export function isExternal (path) {
20+
return outboundRE.test(path)
21+
}
22+
23+
export function isMailto (path) {
24+
return /^mailto:/.test(path)
25+
}
26+
27+
export function isTel (path) {
28+
return /^tel:/.test(path)
29+
}
30+
31+
export function ensureExt (path) {
32+
if (isExternal(path)) {
33+
return path
34+
}
35+
const hashMatch = path.match(hashRE)
36+
const hash = hashMatch ? hashMatch[0] : ''
37+
const normalized = normalize(path)
38+
39+
if (endingSlashRE.test(normalized)) {
40+
return path
41+
}
42+
return normalized + '.html' + hash
43+
}
44+
45+
export function isActive (route, path) {
46+
const routeHash = decodeURIComponent(route.hash)
47+
const linkHash = getHash(path)
48+
if (linkHash && routeHash !== linkHash) {
49+
return false
50+
}
51+
const routePath = normalize(route.path)
52+
const pagePath = normalize(path)
53+
return routePath === pagePath
54+
}
55+
56+
export function resolvePage (pages, rawPath, base) {
57+
if (isExternal(rawPath)) {
58+
return {
59+
type: 'external',
60+
path: rawPath
61+
}
62+
}
63+
if (base) {
64+
rawPath = resolvePath(rawPath, base)
65+
}
66+
const path = normalize(rawPath)
67+
for (let i = 0; i < pages.length; i++) {
68+
if (normalize(pages[i].regularPath) === path) {
69+
return Object.assign({}, pages[i], {
70+
type: 'page',
71+
path: ensureExt(pages[i].path)
72+
})
73+
}
74+
}
75+
console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`)
76+
return {}
77+
}
78+
79+
function resolvePath (relative, base, append) {
80+
const firstChar = relative.charAt(0)
81+
if (firstChar === '/') {
82+
return relative
83+
}
84+
85+
if (firstChar === '?' || firstChar === '#') {
86+
return base + relative
87+
}
88+
89+
const stack = base.split('/')
90+
91+
// remove trailing segment if:
92+
// - not appending
93+
// - appending to trailing slash (last segment is empty)
94+
if (!append || !stack[stack.length - 1]) {
95+
stack.pop()
96+
}
97+
98+
// resolve relative path
99+
const segments = relative.replace(/^\//, '').split('/')
100+
for (let i = 0; i < segments.length; i++) {
101+
const segment = segments[i]
102+
if (segment === '..') {
103+
stack.pop()
104+
} else if (segment !== '.') {
105+
stack.push(segment)
106+
}
107+
}
108+
109+
// ensure leading slash
110+
if (stack[0] !== '') {
111+
stack.unshift('')
112+
}
113+
114+
return stack.join('/')
115+
}
116+
117+
/**
118+
* @param { Page } page
119+
* @param { string } regularPath
120+
* @param { SiteData } site
121+
* @param { string } localePath
122+
* @returns { SidebarGroup }
123+
*/
124+
export function resolveSidebarItems (page, regularPath, site, localePath) {
125+
const { pages, themeConfig } = site
126+
127+
const localeConfig = localePath && themeConfig.locales
128+
? themeConfig.locales[localePath] || themeConfig
129+
: themeConfig
130+
131+
const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar
132+
if (pageSidebarConfig === 'auto') {
133+
return resolveHeaders(page)
134+
}
135+
136+
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar
137+
if (!sidebarConfig) {
138+
return []
139+
} else {
140+
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)
141+
return config
142+
? config.map(item => resolveItem(item, pages, base))
143+
: []
144+
}
145+
}
146+
147+
/**
148+
* @param { Page } page
149+
* @returns { SidebarGroup }
150+
*/
151+
function resolveHeaders (page) {
152+
const headers = groupHeaders(page.headers || [])
153+
return [{
154+
type: 'group',
155+
collapsable: false,
156+
title: page.title,
157+
path: null,
158+
children: headers.map(h => ({
159+
type: 'auto',
160+
title: h.title,
161+
basePath: page.path,
162+
path: page.path + '#' + h.slug,
163+
children: h.children || []
164+
}))
165+
}]
166+
}
167+
168+
export function groupHeaders (headers) {
169+
// group h3s under h2
170+
headers = headers.map(h => Object.assign({}, h))
171+
let lastH2
172+
headers.forEach(h => {
173+
if (h.level === 2) {
174+
lastH2 = h
175+
} else if (lastH2) {
176+
(lastH2.children || (lastH2.children = [])).push(h)
177+
}
178+
})
179+
return headers.filter(h => h.level === 2)
180+
}
181+
182+
export function resolveNavLinkItem (linkItem) {
183+
return Object.assign(linkItem, {
184+
type: linkItem.items && linkItem.items.length ? 'links' : 'link'
185+
})
186+
}
187+
188+
/**
189+
* @param { Route } route
190+
* @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
191+
* @returns { base: string, config: SidebarConfig }
192+
*/
193+
export function resolveMatchingConfig (regularPath, config) {
194+
if (Array.isArray(config)) {
195+
return {
196+
base: '/',
197+
config: config
198+
}
199+
}
200+
for (const base in config) {
201+
if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
202+
return {
203+
base,
204+
config: config[base]
205+
}
206+
}
207+
}
208+
return {}
209+
}
210+
211+
function ensureEndingSlash (path) {
212+
return /(\.html|\/)$/.test(path)
213+
? path
214+
: path + '/'
215+
}
216+
217+
function resolveItem (item, pages, base, groupDepth = 1) {
218+
if (typeof item === 'string') {
219+
return resolvePage(pages, item, base)
220+
} else if (Array.isArray(item)) {
221+
return Object.assign(resolvePage(pages, item[0], base), {
222+
title: item[1]
223+
})
224+
} else {
225+
if (groupDepth > 3) {
226+
console.error(
227+
'[vuepress] detected a too deep nested sidebar group.'
228+
)
229+
}
230+
const children = item.children || []
231+
if (children.length === 0 && item.path) {
232+
return Object.assign(resolvePage(pages, item.path, base), {
233+
title: item.title
234+
})
235+
}
236+
return {
237+
type: 'group',
238+
path: item.path,
239+
title: item.title,
240+
sidebarDepth: item.sidebarDepth,
241+
children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)),
242+
collapsable: item.collapsable !== false
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)