diff --git a/.gitignore b/.gitignore index e31dfe570c..e517985642 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules *.log .temp +.vscode TODOs.md packages/@vuepress/shared-utils/lib/ types diff --git a/packages/@vuepress/core/lib/prepare/AppContext.js b/packages/@vuepress/core/lib/prepare/AppContext.js index e92f2bc312..f3118add81 100644 --- a/packages/@vuepress/core/lib/prepare/AppContext.js +++ b/packages/@vuepress/core/lib/prepare/AppContext.js @@ -72,6 +72,9 @@ module.exports = class AppContext { this.cwd = process.cwd() this.base = this.siteConfig.base || '/' + if (this.siteConfig.htmlSuffix === undefined) { + this.siteConfig.htmlSuffix = true + } this.themeConfig = this.siteConfig.themeConfig || {} const rawOutDir = this.cliOptions.dest || this.siteConfig.dest @@ -425,7 +428,7 @@ module.exports = class AppContext { */ getSiteData () { - const { locales } = this.siteConfig + const { locales, htmlSuffix } = this.siteConfig if (locales) { Object.keys(locales).forEach(path => { locales[path].path = path @@ -438,7 +441,8 @@ module.exports = class AppContext { base: this.base, pages: this.pages.map(page => page.toJson()), themeConfig: this.siteConfig.themeConfig || {}, - locales + locales, + htmlSuffix } } } diff --git a/packages/@vuepress/core/lib/prepare/Page.js b/packages/@vuepress/core/lib/prepare/Page.js index de83e00523..42413a350c 100644 --- a/packages/@vuepress/core/lib/prepare/Page.js +++ b/packages/@vuepress/core/lib/prepare/Page.js @@ -57,7 +57,7 @@ module.exports = class Page { this._context = context if (relative) { - this.regularPath = encodeURI(fileToPath(relative)) + this.regularPath = encodeURI(fileToPath(relative, context.siteConfig.htmlSuffix)) } else if (path) { this.regularPath = encodeURI(path) } else if (permalink) { diff --git a/packages/@vuepress/shared-utils/__tests__/fileToPath.spec.ts b/packages/@vuepress/shared-utils/__tests__/fileToPath.spec.ts index 114048da55..8c713d0c88 100644 --- a/packages/@vuepress/shared-utils/__tests__/fileToPath.spec.ts +++ b/packages/@vuepress/shared-utils/__tests__/fileToPath.spec.ts @@ -8,7 +8,7 @@ test('fileToPath', () => { 'foo/bar.md': '/foo/bar.html' } Object.keys(asserts).forEach(file => { - expect(fileToPath(file)).toBe(asserts[file]) + expect(fileToPath(file, true)).toBe(asserts[file]) }) }) diff --git a/packages/@vuepress/shared-utils/src/fileToPath.ts b/packages/@vuepress/shared-utils/src/fileToPath.ts index 6dc4827fff..e90e07fe46 100644 --- a/packages/@vuepress/shared-utils/src/fileToPath.ts +++ b/packages/@vuepress/shared-utils/src/fileToPath.ts @@ -2,18 +2,24 @@ import { indexRE, isIndexFile } from './isIndexFile' const extRE = /\.(vue|md)$/ -export = function fileToPath (file: string): string { +export = function fileToPath (file: string, htmlSuffix: boolean) { if (isIndexFile(file)) { // README.md -> / // README.vue -> / // foo/README.md -> /foo/ // foo/README.vue -> /foo/ return file.replace(indexRE, '/$1') - } else { + } else if (htmlSuffix) { // foo.md -> /foo.html // foo.vue -> /foo.html // foo/bar.md -> /foo/bar.html // foo/bar.vue -> /foo/bar.html return `/${file.replace(extRE, '').replace(/\\/g, '/')}.html` + } else { + // foo.md -> /foo/ + // foo.vue -> /foo/ + // foo/bar.md -> /foo/bar/ + // foo/bar.vue -> /foo/bar/ + return `/${file.replace(extRE, '').replace(/\\/g, '/')}/` } } diff --git a/packages/@vuepress/theme-default/components/NavLink.vue b/packages/@vuepress/theme-default/components/NavLink.vue index 452a1c6dc8..62158ba514 100644 --- a/packages/@vuepress/theme-default/components/NavLink.vue +++ b/packages/@vuepress/theme-default/components/NavLink.vue @@ -29,7 +29,7 @@ export default { computed: { link () { - return ensureExt(this.item.link) + return ensureExt(this.item.link, this.$site.htmlSuffix) }, exact () { diff --git a/packages/@vuepress/theme-default/components/SidebarLink.vue b/packages/@vuepress/theme-default/components/SidebarLink.vue index 7d128b4a98..40b249ccc3 100644 --- a/packages/@vuepress/theme-default/components/SidebarLink.vue +++ b/packages/@vuepress/theme-default/components/SidebarLink.vue @@ -22,11 +22,11 @@ export default { }) { // use custom active class matching logic // due to edge case of paths ending with / + hash - const selfActive = isActive($route, item.path) + const selfActive = isActive($route, item.path, $site.htmlSuffix) // for sidebar: auto pages, a hash link should be active if one of its child // matches const active = item.type === 'auto' - ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug)) + ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug, $site.htmlSuffix)) : selfActive const link = renderLink(h, item.path, item.title || item.path, active) @@ -41,10 +41,10 @@ export default { || $themeConfig.displayAllHeaders if (item.type === 'auto') { - return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)] + return [link, renderChildren(h, item.children, item.basePath, $route, $site.htmlSuffix, maxDepth)] } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) { const children = groupHeaders(item.headers) - return [link, renderChildren(h, children, item.path, $route, maxDepth)] + return [link, renderChildren(h, children, item.path, $route, $site.htmlSuffix, maxDepth)] } else { return link } @@ -65,10 +65,10 @@ function renderLink (h, to, text, active) { }, text) } -function renderChildren (h, children, path, route, maxDepth, depth = 1) { +function renderChildren (h, children, path, route, htmlSuffix, maxDepth, depth = 1) { if (!children || depth > maxDepth) return null return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => { - const active = isActive(route, path + '#' + c.slug) + const active = isActive(route, path + '#' + c.slug, htmlSuffix) return h('li', { class: 'sidebar-sub-header' }, [ renderLink(h, path + '#' + c.slug, c.title, active), renderChildren(h, c.children, path, route, maxDepth, depth + 1) diff --git a/packages/@vuepress/theme-default/util/index.js b/packages/@vuepress/theme-default/util/index.js index db331efa3c..c4d641f48c 100644 --- a/packages/@vuepress/theme-default/util/index.js +++ b/packages/@vuepress/theme-default/util/index.js @@ -3,10 +3,12 @@ export const extRE = /\.(md|html)$/ export const endingSlashRE = /\/$/ export const outboundRE = /^(https?:|mailto:|tel:)/ -export function normalize (path) { - return decodeURI(path) +export function normalize (path, htmlSuffix) { + const normalized = decodeURI(path) .replace(hashRE, '') .replace(extRE, '') + + return htmlSuffix ? normalized : normalized.replace(endingSlashRE, '') } export function getHash (path) { @@ -28,41 +30,42 @@ export function isTel (path) { return /^tel:/.test(path) } -export function ensureExt (path) { +export function ensureExt (path, htmlSuffix) { if (isExternal(path)) { return path } const hashMatch = path.match(hashRE) const hash = hashMatch ? hashMatch[0] : '' - const normalized = normalize(path) + const normalized = normalize(path, htmlSuffix) + const ext = htmlSuffix ? '.html' : '/' if (endingSlashRE.test(normalized)) { return path } - return normalized + '.html' + hash + return normalized + ext + hash } -export function isActive (route, path) { +export function isActive (route, path, htmlSuffix) { const routeHash = route.hash const linkHash = getHash(path) if (linkHash && routeHash !== linkHash) { return false } - const routePath = normalize(route.path) - const pagePath = normalize(path) + const routePath = normalize(route.path, htmlSuffix) + const pagePath = normalize(path, htmlSuffix) return routePath === pagePath } -export function resolvePage (pages, rawPath, base) { +export function resolvePage (pages, rawPath, base, htmlSuffix) { if (base) { rawPath = resolvePath(rawPath, base) } - const path = normalize(rawPath) + const path = normalize(rawPath, htmlSuffix) for (let i = 0; i < pages.length; i++) { - if (normalize(pages[i].regularPath) === path) { + if (normalize(pages[i].regularPath, htmlSuffix) === path) { return Object.assign({}, pages[i], { type: 'page', - path: ensureExt(pages[i].path) + path: ensureExt(pages[i].path, htmlSuffix) }) } } @@ -116,7 +119,7 @@ function resolvePath (relative, base, append) { * @returns { SidebarGroup } */ export function resolveSidebarItems (page, regularPath, site, localePath) { - const { pages, themeConfig } = site + const { pages, themeConfig, htmlSuffix } = site const localeConfig = localePath && themeConfig.locales ? themeConfig.locales[localePath] || themeConfig @@ -133,7 +136,7 @@ export function resolveSidebarItems (page, regularPath, site, localePath) { } else { const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig) return config - ? config.map(item => resolveItem(item, pages, base)) + ? config.map(item => resolveItem(item, pages, base, htmlSuffix)) : [] } } @@ -208,11 +211,11 @@ function ensureEndingSlash (path) { : path + '/' } -function resolveItem (item, pages, base, groupDepth = 1) { +function resolveItem (item, pages, base, htmlSuffix, groupDepth = 1) { if (typeof item === 'string') { - return resolvePage(pages, item, base) + return resolvePage(pages, item, base, htmlSuffix) } else if (Array.isArray(item)) { - return Object.assign(resolvePage(pages, item[0], base), { + return Object.assign(resolvePage(pages, item[0], base, htmlSuffix), { title: item[1] }) } else { @@ -232,7 +235,7 @@ function resolveItem (item, pages, base, groupDepth = 1) { path: item.path, title: item.title, sidebarDepth: item.sidebarDepth, - children: children.map(child => resolveItem(child, pages, base, groupDepth + 1)), + children: children.map(child => resolveItem(child, pages, base, htmlSuffix, groupDepth + 1)), collapsable: item.collapsable !== false } }