index.js 5.7 KB

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