text: export const hashRE = /#.*$/;
  export const extRE = /\.(md|html)$/;
  export const endingSlashRE = /\/$/;
  export const outboundRE = /^[a-z]+:/i;
  
  export function normalize(path) {
    return decodeURI(path)
      .replace(hashRE, "")
      .replace(extRE, "");
  }
  
  export function getHash(path) {
    const match = path.match(hashRE);
    if (match) {
      return match[0];
    }
  }
  
  export function isExternal(path) {
    return outboundRE.test(path);
  }
  
  export function isMailto(path) {
    return /^mailto:/.test(path);
  }
  
  export function isTel(path) {
    return /^tel:/.test(path);
  }
  
  export function ensureExt(path) {
    if (isExternal(path)) {
      return path;
    }
    const hashMatch = path.match(hashRE);
    const hash = hashMatch ? hashMatch[0] : "";
    const normalized = normalize(path);
  
    if (endingSlashRE.test(normalized)) {
      return path;
    }
    return normalized + ".html" + hash;
  }
  
  export function isActive(route, path) {
    const routeHash = decodeURIComponent(route.hash);
    const linkHash = getHash(path);
    if (linkHash && routeHash !== linkHash) {
      return false;
    }
    const routePath = normalize(route.path);
    const pagePath = normalize(path);
    return routePath === pagePath;
  }
  
  export function resolvePage(pages, rawPath, base) {
    if (isExternal(rawPath)) {
      return {
        type: "external",
        path: rawPath
      };
    }
    if (base) {
      rawPath = resolvePath(rawPath, base);
    }
    const path = normalize(rawPath);
    for (let i = 0; i < pages.length; i++) {
      if (normalize(pages[i].regularPath) === path) {
        return Object.assign({}, pages[i], {
          type: "page",
          path: ensureExt(pages[i].path)
        });
      }
    }
    console.error(
      `[vuepress] No matching page found for sidebar item "${rawPath}"`
    );
    return {};
  }
  
  function resolvePath(relative, base, append) {
    const firstChar = relative.charAt(0);
    if (firstChar === "/") {
      return relative;
    }
  
    if (firstChar === "?" || firstChar === "#") {
      return base + relative;
    }
  
    const stack = base.split("/");
  
    // remove trailing segment if:
    // - not appending
    // - appending to trailing slash (last segment is empty)
    if (!append || !stack[stack.length - 1]) {
      stack.pop();
    }
  
    // resolve relative path
    const segments = relative.replace(/^\//, "").split("/");
    for (let i = 0; i < segments.length; i++) {
      const segment = segments[i];
      if (segment === "..") {
        stack.pop();
      } else if (segment !== ".") {
        stack.push(segment);
      }
    }
  
    // ensure leading slash
    if (stack[0] !== "") {
      stack.unshift("");
    }
  
    return stack.join("/");
  }
  
  /**
   * @param { Page } page
   * @param { string } regularPath
   * @param { SiteData } site
   * @param { string } localePath
   * @returns { SidebarGroup }
   */
  export function resolveSidebarItems(page, regularPath, site, localePath) {
    const { pages, themeConfig } = site;
  
    const localeConfig =
      localePath && themeConfig.locales
        ? themeConfig.locales[localePath] || themeConfig
        : themeConfig;
  
    const pageSidebarConfig =
      page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar;
    if (pageSidebarConfig === "auto") {
      return resolveHeaders(page);
    }
  
    const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar;
    if (!sidebarConfig) {
      return [];
    } else {
      const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig);
      if (config === "auto") {
        return resolveHeaders(page);
      }
      return config ? config.map(item => resolveItem(item, pages, base)) : [];
    }
  }
  
  /**
   * @param { Page } page
   * @returns { SidebarGroup }
   */
  function resolveHeaders(page) {
    const headers = groupHeaders(page.headers || []);
    return [
      {
        type: "group",
        collapsable: false,
        title: page.title,
        path: null,
        children: headers.map(h => ({
          type: "auto",
          title: h.title,
          basePath: page.path,
          path: page.path + "#" + h.slug,
          children: h.children || []
        }))
      }
    ];
  }
  
  export function groupHeaders(headers) {
    // group h3s under h2
    headers = headers.map(h => Object.assign({}, h));
    let lastH2;
    headers.forEach(h => {
      if (h.level === 2) {
        lastH2 = h;
      } else if (lastH2) {
        (lastH2.children || (lastH2.children = [])).push(h);
      }
    });
    return headers.filter(h => h.level === 2);
  }
  
  export function resolveNavLinkItem(linkItem) {
    return Object.assign(linkItem, {
      type: linkItem.items && linkItem.items.length ? "links" : "link"
    });
  }
  
  /**
   * @param { Route } route
   * @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
   * @returns { base: string, config: SidebarConfig }
   */
  export function resolveMatchingConfig(regularPath, config) {
    if (Array.isArray(config)) {
      return {
        base: "/",
        config: config
      };
    }
    for (const base in config) {
      if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
        return {
          base,
          config: config[base]
        };
      }
    }
    return {};
  }
  
  function ensureEndingSlash(path) {
    return /(\.html|\/)$/.test(path) ? path : path + "/";
  }
  
  function resolveItem(item, pages, base, groupDepth = 1) {
    if (typeof item === "string") {
      return resolvePage(pages, item, base);
    } else if (Array.isArray(item)) {
      return Object.assign(resolvePage(pages, item[0], base), {
        title: item[1]
      });
    } else {
      const children = item.children || [];
      if (children.length === 0 && item.path) {
        return Object.assign(resolvePage(pages, item.path, base), {
          title: item.title
        });
      }
      return {
        type: "group",
        path: item.path,
        title: item.title,
        sidebarDepth: item.sidebarDepth,
        children: children.map(child =>
          resolveItem(child, pages, base, groupDepth + 1)
        ),
        collapsable: item.collapsable !== false
      };
    }
  }
  