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 }