diff --git a/src/client/theme-default/components/NavBar.vue b/src/client/theme-default/components/NavBar.vue index b99bfafafbff..564b07ecbca7 100644 --- a/src/client/theme-default/components/NavBar.vue +++ b/src/client/theme-default/components/NavBar.vue @@ -12,7 +12,7 @@ /> {{ $site.title }} - + diff --git a/src/client/theme-default/components/NavBarLinks.ts b/src/client/theme-default/components/NavBarLinks.ts index e41aa966e6a2..f1133d63cf43 100644 --- a/src/client/theme-default/components/NavBarLinks.ts +++ b/src/client/theme-default/components/NavBarLinks.ts @@ -1,7 +1,12 @@ import { computed } from 'vue' -import { useSiteDataByRoute } from 'vitepress' +import { useSiteData, useSiteDataByRoute } from 'vitepress' import NavBarLink from './NavBarLink.vue' import NavDropdownLink from './NavDropdownLink.vue' +import { DefaultTheme } from '../config' + +const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map( + (platform) => [platform, new RegExp(platform, 'i')] as const +) export default { components: { @@ -10,13 +15,39 @@ export default { }, setup() { + const siteDataByRoute = useSiteDataByRoute() + const siteData = useSiteData() + const repoInfo = computed(() => { + const theme = siteData.value.themeConfig as DefaultTheme.Config + const repo = theme.docsRepo || theme.repo + let text: string | undefined = theme.repoLabel + + if (repo) { + const link = /^https?:/.test(repo) ? repo : `https://github.com/${repo}` + if (!text) { + // if no label is provided, deduce it from the repo url + const repoHosts = link.match(/^https?:\/\/[^/]+/) + if (repoHosts) { + const repoHost = repoHosts[0] + const foundPlatform = platforms.find(([_platform, re]) => + re.test(repoHost) + ) + text = foundPlatform && foundPlatform[0] + } + } + + return { link, text: text || 'Source' } + } + return null + }) return { navData: process.env.NODE_ENV === 'production' ? // navbar items do not change in production - useSiteDataByRoute().value.themeConfig.nav + siteDataByRoute.value.themeConfig.nav : // use computed in dev for hot reload - computed(() => useSiteDataByRoute().value.themeConfig.nav) + computed(() => siteDataByRoute.value.themeConfig.nav), + repoInfo } } } diff --git a/src/client/theme-default/components/NavBarLinks.vue b/src/client/theme-default/components/NavBarLinks.vue index df0907aea1ef..ab02e2b4928c 100644 --- a/src/client/theme-default/components/NavBarLinks.vue +++ b/src/client/theme-default/components/NavBarLinks.vue @@ -1,9 +1,12 @@ diff --git a/src/client/theme-default/components/PageEdit.ts b/src/client/theme-default/components/PageEdit.ts new file mode 100644 index 000000000000..761d9bae5635 --- /dev/null +++ b/src/client/theme-default/components/PageEdit.ts @@ -0,0 +1,81 @@ +import { computed } from 'vue' +import OutboundLink from './icons/OutboundLink.vue' +import { endingSlashRE, isExternal } from '/@theme/utils' +import { usePageData, useSiteData } from 'vitepress' +import { DefaultTheme } from '../config' + +function createEditLink( + repo: string, + docsRepo: string, + docsDir: string, + docsBranch: string, + path: string +) { + const bitbucket = /bitbucket.org/ + if (bitbucket.test(repo)) { + const base = isExternal(docsRepo) ? docsRepo : repo + return ( + base.replace(endingSlashRE, '') + + `/src` + + `/${docsBranch}/` + + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') + + path + + `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default` + ) + } + + const base = isExternal(docsRepo) + ? docsRepo + : `https://github.com/${docsRepo}` + return ( + base.replace(endingSlashRE, '') + + `/edit` + + `/${docsBranch}/` + + (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') + + path + ) +} + +export default { + components: { + OutboundLink + }, + + setup() { + const pageData = usePageData() + const siteData = useSiteData() + + const editLink = computed(() => { + const showEditLink: boolean | undefined = + pageData.value.frontmatter.editLink == null + ? siteData.value.themeConfig.editLinks + : pageData.value.frontmatter.editLink + const { + repo, + docsDir = '', + docsBranch = 'master', + docsRepo = repo + } = siteData.value.themeConfig + + const { relativePath } = pageData.value + if (showEditLink && relativePath && repo) { + return createEditLink( + repo, + docsRepo || repo, + docsDir, + docsBranch, + relativePath + ) + } + return null + }) + const editLinkText = computed( + () => siteData.value.themeConfig.editLinkText || 'Edit this page' + ) + + return { + editLink, + editLinkText + } + } +} diff --git a/src/client/theme-default/components/PageEdit.vue b/src/client/theme-default/components/PageEdit.vue new file mode 100644 index 000000000000..8070dedf4003 --- /dev/null +++ b/src/client/theme-default/components/PageEdit.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/src/client/theme-default/config.ts b/src/client/theme-default/config.ts index 7fe5f028867a..31b2af74befa 100644 --- a/src/client/theme-default/config.ts +++ b/src/client/theme-default/config.ts @@ -4,7 +4,48 @@ export namespace DefaultTheme { nav?: NavItem[] | false sidebar?: SideBarConfig | MultiSideBarConfig search?: SearchConfig | false - editLink?: EditLinkConfig | false + + /** + * GitHub repository following the format /. + * + * @example vuejs/vue-next + */ + repo?: string + /** + * Customize the header label. Defaults to GitHub/Gitlab/Bitbucket depending + * on the provided repo + * + * @exampe `"Contribute!"` + */ + repoLabel?: string + + /** + * If your docs are in a different repository from your main project + * + * @example `"vuejs/docs-next"` + */ + docsRepo?: string + /** + * If your docs are not at the root of the repo. + * + * @example `"docs"` + */ + docsDir?: string + /** + * If your docs are in a different branch. Defaults to `master` + * @example `"next"` + */ + docsBranch?: string + + /** + * Enable links to edit pages at the bottom of the page + */ + editLinks?: boolean + /** + * Custom text for edit link. Defaults to "Edit this page" + */ + editLinkText?: string + lastUpdated?: string | boolean prevLink?: boolean nextLink?: boolean @@ -70,13 +111,4 @@ export namespace DefaultTheme { indexName: string } } - - // edit link ----------------------------------------------------------------- - - export interface EditLinkConfig { - repo: string - dir?: string - branch?: string - text?: string - } } diff --git a/src/client/theme-default/utils.ts b/src/client/theme-default/utils.ts index b2ff533ed543..d0b97b95523b 100644 --- a/src/client/theme-default/utils.ts +++ b/src/client/theme-default/utils.ts @@ -2,6 +2,7 @@ import { useSiteData, Route } from 'vitepress' export const hashRE = /#.*$/ export const extRE = /\.(md|html)$/ +export const endingSlashRE = /\/$/ export const outboundRE = /^[a-z]+:/i export function withBase(path: string) { diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index cb6c917ca029..f6715b1e349a 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -43,6 +43,7 @@ export function createMarkdownToVueRenderFn( title: inferTitle(frontmatter, content), frontmatter, headers: data.headers, + relativePath: file.replace(/\\/g, '/'), lastUpdated } diff --git a/types/shared.d.ts b/types/shared.d.ts index 3cf71d5dfa2e..840e6aebbea5 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -25,6 +25,7 @@ export interface PageData { title: string frontmatter: Record headers: Header[] + relativePath: string lastUpdated: number next?: { text: string; link: string } prev?: { text: string; link: string }