UNPKG

hirsun-vuepress-theme-hope-plus

Version:
1,557 lines (1,528 loc) 123 kB
import { watch } from 'chokidar'; import { Logger, ensureEndingSlash, fromEntries, endsWith, getBundlerName, tagHint, addViteConfig, addViteOptimizeDepsInclude, addViteOptimizeDepsExclude, isString, isArray, values, keys, isPlainObject, entries, isFunction, startsWith, getLocales, isLinkHttp, compareDate, getDateInfo, timeTransformer, getAuthor, deepAssign, injectLocalizedDate, getTitleFromFilename, removeLeadingSlash } from 'vuepress-shared/node'; import { getDirname, path, fs, colors, logger as logger$1, sanitizeFileName } from '@vuepress/utils'; import { createRequire } from 'node:module'; import { hashSync } from 'bcrypt-ts/node'; import { useReadingTimePlugin } from 'vuepress-plugin-reading-time2'; import { useSassPalettePlugin } from 'vuepress-plugin-sass-palette'; import { gitPlugin } from '@vuepress/plugin-git'; import { prismjsPlugin } from '@vuepress/plugin-prismjs'; import { externalLinkIconPlugin } from '@vuepress/plugin-external-link-icon'; import { nprogressPlugin } from '@vuepress/plugin-nprogress'; import { themeDataPlugin } from '@vuepress/plugin-theme-data'; import { activeHeaderLinksPlugin } from '@vuepress/plugin-active-header-links'; import { autoCatalogPlugin } from 'vuepress-plugin-auto-catalog'; import { commentPlugin } from 'vuepress-plugin-comment2'; import { componentsPlugin } from 'vuepress-plugin-components'; import { copyCodePlugin } from 'vuepress-plugin-copy-code2'; import { copyrightPlugin } from 'vuepress-plugin-copyright2'; import { feedPlugin } from 'vuepress-plugin-feed2'; import { mdEnhancePlugin } from 'vuepress-plugin-md-enhance'; import { photoSwipePlugin } from 'vuepress-plugin-photo-swipe'; import { pwaPlugin } from 'vuepress-plugin-pwa2'; import { rltPlugin } from 'vuepress-plugin-rtl'; import { seoPlugin } from 'vuepress-plugin-seo2'; import { sitemapPlugin } from 'vuepress-plugin-sitemap2'; import { blogPlugin } from 'vuepress-plugin-blog2'; const __dirname = getDirname(import.meta.url); const logger = new Logger("vuepress-theme-hope"); const BUNDLE_FOLDER = ensureEndingSlash( path.resolve(__dirname, "../bundle") ); const CLIENT_FOLDER = ensureEndingSlash( path.resolve(__dirname, "../client") ); const TEMPLATE_FOLDER = ensureEndingSlash( path.resolve(__dirname, "../../templates") ); const VERSION = createRequire(import.meta.url)("hirsun-vuepress-theme-hope-plus/package.json").version; const getDirAlias = (dir) => { const dirPath = path.resolve(CLIENT_FOLDER, dir); return fs.existsSync(dirPath) ? fs.readdirSync(dirPath).filter( (file) => ( // js files endsWith(file, ".js") || // folder !file.includes(".") ) ).map((file) => [ `@theme-hope/${dir}/${file.replace(/\.js$/, "")}`, path.resolve(CLIENT_FOLDER, dir, file) ]) : []; }; const getEntryAlias = (entry) => { const entryPath = path.resolve(CLIENT_FOLDER, entry, "index.js"); return fs.existsSync(entryPath) ? [`@theme-hope/${entry}/index`, entryPath] : null; }; const getAlias = (isDebug) => { const alias = fromEntries([ // define components ...getDirAlias("components"), // define composables and utils ...["composables", "utils"].map(getEntryAlias).filter( (item) => item !== null ), // define layouts ...getDirAlias("layouts"), // define modules ...fs.readdirSync(path.resolve(CLIENT_FOLDER, "modules")).map((folder) => `modules/${folder}`).map((file) => [ // define module components ...getDirAlias(`${file}/components`), // define module composables and utils ...["composables", "utils"].map((folder) => `${file}/${folder}`).map(getEntryAlias).filter( (item) => item !== null ), // define layouts ...getDirAlias(`${file}/layouts`) ]).flat() ]); if (isDebug) console.log("Theme alias config:", alias); return alias; }; const checkTag = (bundlerOptions, app) => { const bundlerName = getBundlerName(app); if (bundlerName === "vite") { const viteBundlerConfig = bundlerOptions; if (!viteBundlerConfig.vuePluginOptions) viteBundlerConfig.vuePluginOptions = {}; if (!viteBundlerConfig.vuePluginOptions.template) viteBundlerConfig.vuePluginOptions.template = {}; if (!viteBundlerConfig.vuePluginOptions.template.compilerOptions) viteBundlerConfig.vuePluginOptions.template.compilerOptions = {}; const { isCustomElement } = viteBundlerConfig.vuePluginOptions.template.compilerOptions; viteBundlerConfig.vuePluginOptions.template.compilerOptions.isCustomElement = (tag) => { if (isCustomElement) { const result = isCustomElement(tag); if (!result) tagHint(tag, app.env.isDebug); return result; } tagHint(tag, app.env.isDebug); }; } else if (bundlerName === "webpack") { const webpackBundlerConfig = bundlerOptions; if (!webpackBundlerConfig.vue) webpackBundlerConfig.vue = {}; if (!webpackBundlerConfig.vue.compilerOptions) webpackBundlerConfig.vue.compilerOptions = {}; const { isCustomElement } = webpackBundlerConfig.vue.compilerOptions; webpackBundlerConfig.vue.compilerOptions.isCustomElement = (tag) => { if (isCustomElement) { const result = isCustomElement(tag); if (!result) tagHint(tag, app.env.isDebug); return result; } tagHint(tag, app.env.isDebug); }; } }; const extendsBundlerOptions = (bundlerOptions, app) => { addViteConfig(bundlerOptions, app, { build: { chunkSizeWarningLimit: 1024 } }); addViteOptimizeDepsInclude(bundlerOptions, app, "@vueuse/core"); addViteOptimizeDepsExclude(bundlerOptions, app, "@theme-hope"); checkTag(bundlerOptions, app); }; const checkFrontmatter = (page) => { const frontmatter = page.frontmatter; const { filePathRelative } = page; ["category", "tag"].forEach((key) => { if (key in frontmatter && !(isString(frontmatter[key]) || isArray(frontmatter[key]) && frontmatter[key].every(isString))) { logger.warn( `${colors.magenta( key )} property in Page FrontMatter should be ${colors.cyan( "string" )} or ${colors.cyan(" string[]")}${filePathRelative ? `, found in ${filePathRelative}` : ""}.` ); delete frontmatter[key]; } }); if ("date" in frontmatter && !(frontmatter.date instanceof Date)) { logger.warn( `${colors.magenta("date")} in frontMatter should be ${colors.cyan( "a valid Date" )}${filePathRelative ? `, found in ${filePathRelative}` : ""}.` ); delete frontmatter.date; } if ("sidebar" in frontmatter && frontmatter.sidebar !== "heading" && typeof frontmatter.sidebar !== "boolean") { logger.warn( `${colors.magenta("sidebar")} in frontMatter should be ${colors.cyan( "heading" )} or ${colors.cyan("boolean")}${filePathRelative ? `, found in ${filePathRelative}` : ""}.` ); delete frontmatter.sidebar; } ["title", "shortTitle", "containerClass"].forEach((key) => { if (key in frontmatter && !isString(frontmatter[key])) { logger.warn( `${colors.magenta(key)} in frontMatter should be ${colors.cyan( "string" )}${filePathRelative ? `, found in ${filePathRelative}` : ""}.` ); delete frontmatter[key]; } }); [ "home", "navbar", "toc", "index", "lastUpdated", "contributors", "editLink", "breadcrumb", "breadcrumbIcon", "pageview", "article" ].forEach((key) => { if (key in frontmatter && typeof frontmatter[key] !== "boolean") { logger.warn( `${colors.magenta(key)} in frontMatter should be ${colors.cyan( "boolean" )}${filePathRelative ? `, found in ${filePathRelative}` : ""}.` ); delete frontmatter[key]; } }); }; const checkHeader = (markdownOptions, themeData) => { const headerDepth = values(themeData.locales).map(({ headerDepth: headerDepth2 }) => headerDepth2).sort((a = 2, b = 2) => b - a).pop() ?? 2; if (markdownOptions.anchor) { const { level } = markdownOptions.anchor; if (typeof level === "number" || isArray(level) && Array.from({ length: headerDepth + 1 }, (_, index) => index + 1).some( (_, index) => !level.includes(index + 1) )) { logger.warn( `Max ${colors.magenta( "headerDepth" )} is ${headerDepth}, but ${colors.magenta( "markdown.anchor.level" )} is ${JSON.stringify( level )}, which does not extract header level ${headerDepth}.` ); markdownOptions.anchor.level = Array.from( { length: headerDepth + 1 }, (_, index) => index + 1 ); } } else if (markdownOptions.anchor === false && headerDepth !== 0) { logger.error( `MarkdownIt anchor plugin is disabled, which will not extract any header. You should enable it.` ); delete markdownOptions.anchor; } if (markdownOptions.headers === false) { if (headerDepth !== 0) { logger.error( `MarkdownIt header plugin is disabled, which will not extract any header. You should enable it.` ); markdownOptions.headers = { level: Array.from({ length: headerDepth }, (_, index) => index + 2) }; } } else { const { level = [2, 3] } = markdownOptions.headers ??= {}; if (Array.from({ length: headerDepth }, (_, index) => index + 2).some( (item) => !level.includes(item) )) { logger.warn( `Max ${colors.magenta( "headerDepth" )} is ${headerDepth}, but ${colors.magenta( "markdown.headers.level" )} is ${JSON.stringify( level )}, which does not extract header level ${headerDepth}.` ); markdownOptions.headers.level = Array.from( { length: headerDepth }, (_, index) => index + 2 ); } } }; const PLUGIN_CHECKLIST = [ ["@vuepress/plugin-active-header-links", "activeHeaderLinks"], "@vuepress/plugin-theme-data", ["vuepress-plugin-comment2", "comment"], ["vuepress-plugin-components", "components"], ["vuepress-plugin-copy-code2", "copyCode"], ["vuepress-plugin-copyright2", "copyright"], ["vuepress-plugin-feed2", "feed"], ["vuepress-plugin-md-enhance", "mdEnhance"], ["vuepress-plugin-photo-swipe", "photoSwipe"], ["vuepress-plugin-pwa2", "pwa"], ["vuepress-plugin-seo2", "seo"], ["vuepress-plugin-sitemap", "sitemap"] ]; const KNOWN_THEME_PLUGINS = [ "activeHeaderLinks", "autoCatalog", "blog", "components", "comment", "copyCode", "copyright", "externalLinkIcon", "feed", "git", "mdEnhance", "nprogress", "photoSwipe", "prismjs", "pwa", "readingTime", "seo", "sitemap" ]; const checkPluginOptions$1 = (plugins) => { keys(plugins).forEach((key) => { if (!KNOWN_THEME_PLUGINS.includes(key)) logger.warn( `You are setting "${colors.magenta( `plugins.${key}` )}" option in ${colors.cyan( "theme options" )}, but it's not supported by theme. You need to install the plugin yourself and import then call it manually in "${colors.magenta( "plugins" )}" options in ${colors.cyan("vuepress config file")} directly.` ); }); }; const checkUserPlugin = (app) => { PLUGIN_CHECKLIST.forEach(([pluginName, optionName = ""]) => { const themeIndex = app.pluginApi.plugins.findIndex( (item) => item.name === "vuepress-theme-hope" ); const pluginsAfterTheme = app.pluginApi.plugins.slice(themeIndex + 1); if (pluginsAfterTheme.some(({ name }) => name === pluginName)) logger.error( `You are not allowed to use plugin "${colors.magenta( pluginName )}" yourself in ${colors.cyan("vuepress config file")}. ${optionName ? `Set "${colors.magenta(`plugin.${optionName}`)}" in ${colors.cyan( "theme options" )} to customize it.` : ""}` ); }); }; const checkVuePressVersion = (app) => { const sourceFolderPath = app.dir.source(); const mainPackages = []; const subPackages = []; const require = createRequire(`${sourceFolderPath}/`); let dir = sourceFolderPath; do { if (fs.existsSync(path.resolve(dir, "package.json"))) { const content = JSON.parse(fs.readFileSync(path.resolve(dir, "package.json"), "utf-8")); const collectName = (name) => { if (name === "vuepress" || name === "vuepress-vite" || name === "vuepress-webpack") mainPackages.push(name); else if (name.startsWith("@vuepress/")) subPackages.push(name); }; keys(content.dependencies || {}).forEach((name) => collectName(name)); keys(content.devDependencies || {}).forEach((name) => collectName(name)); } if (mainPackages.length || dir === path.dirname(dir)) break; } while (dir = path.dirname(dir)); const mainPackagesVersions = []; mainPackages.forEach((pkg) => { const { version } = require(`${pkg}/package.json`); mainPackagesVersions.push(version); }); const filteredMainPackagesVersions = new Set(mainPackagesVersions); if (filteredMainPackagesVersions.size > 1) { console.error( `Multiple versions of VuePress are detected in the current project: ${[ ...filteredMainPackagesVersions ].map((version) => colors.yellow(version)).join(", ")}` ); return false; } if (filteredMainPackagesVersions.size === 0) { console.error("No VuePress version is detected in the current project"); return false; } const mainVersion = mainPackagesVersions[0]; return subPackages.every((pkg) => { const { version } = require(`${pkg}/package.json`); if (version !== mainVersion) { console.error( `VuePress version mismatch: ${colors.cyan( pkg )} is using ${colors.magenta( version )} while the main VuePress is using ${colors.magenta(mainVersion)}` ); return false; } return true; }); }; const deprecatedLogger = ({ options, deprecatedOption, newOption, msg = "", scope = "" }) => { if (deprecatedOption in options) { logger.warn( `${colors.magenta(deprecatedOption)} is ${colors.yellow("deprecated")}${scope ? ` in ${scope}` : ""}, please use "${colors.magenta(newOption)}" instead.${msg ? ` ${msg}` : ""}` ); if (newOption.includes(".")) { const keys = newOption.split("."); let temp = options; keys.forEach((key, index) => { if (index !== keys.length - 1) { temp[key] = temp[key] || {}; temp = temp[key]; } else { temp[key] = options[deprecatedOption]; } }); } else { options[newOption] = options[deprecatedOption]; } delete options[deprecatedOption]; } }; const droppedLogger = (options, droppedOption, hint = "", newOption = "") => { if (droppedOption in options) { logger.error( `"${colors.magenta(droppedOption)}" is ${colors.red("removed")}${newOption ? `, please use ${colors.magenta(newOption)} instead.` : " and no longer supported"}${hint ? ` ${hint}` : ""}` ); if (!newOption) delete options[droppedOption]; } }; const deprecatedMsg = (deprecatedOptions, hint) => { logger.warn( `"${colors.magenta(deprecatedOptions)}" is ${colors.red( "deprecated" )}, please use "${colors.magenta(hint)}" instead.` ); }; const DEPRECATED_FRONTMATTER_OPTIONS = [ ["authors", "author"], ["categories", "category"], ["tags", "tag"], ["time", "date"], ["visitor", "pageview"], ["sidebarDepth", "headerDepth"], ["copyrightText", "copyright"], ["anchorDisplay", "toc"], ["updateTime", "lastUpdated"], ["contributor", "contributors"], ["editLinks", "editLink"] ]; const DEPRECATED_HOME_FRONTMATTER_OPTIONS = [ ["darkHeroImage", "heroImageDark"], ["action", "actions"] ]; const DROPPED_FRONTMATTER_OPTIONS = [ ["metaTitle", "Please use custom resolver to set metaTitle."], ["mediaLink", "Social media links are no longer displayed in footer."], ["password", "Simple password protection is no longer supported."], ["search", "Search plugin no longer support this option."] ]; const convertFrontmatter = (frontmatter, filePathRelative = null) => { DEPRECATED_FRONTMATTER_OPTIONS.forEach( ([deprecatedOption, newOption]) => deprecatedLogger({ options: frontmatter, deprecatedOption, newOption, scope: `${filePathRelative || ""} frontmatter` }) ); DROPPED_FRONTMATTER_OPTIONS.forEach( (item) => droppedLogger( frontmatter, item[0], `${item[1]}${filePathRelative ? ` (found in ${filePathRelative})` : ""}` ) ); if ("meta" in frontmatter) { logger.warn( `${colors.magenta( "meta" )} in frontmatter is deprecated in V2, please use ${colors.magenta( "head" )} instead.${filePathRelative ? `Found in ${filePathRelative}` : ""}` ); frontmatter["head"] = [ ...frontmatter["head"] || [], frontmatter["meta"].map((item) => ["meta", item]) ]; delete frontmatter["meta"]; } if ("canonicalUrl" in frontmatter) { logger.warn( `${colors.magenta( "canonicalUrl" )} in frontmatter is deprecated, please use ${colors.magenta( "head" )} instead.${filePathRelative ? `Found in ${filePathRelative}` : ""}` ); frontmatter["head"] = [ ...frontmatter["head"] || [], ["link", { rel: "canonical", href: frontmatter["canonicalUrl"] }] ]; delete frontmatter["canonicalUrl"]; } if (frontmatter["home"] === true) { if (frontmatter["layout"] === "Blog") { logger.warn( `${colors.magenta( "layout: Blog" )} in frontmatter is deprecated, please use ${colors.magenta( "layout: BlogHome" )} instead.${filePathRelative ? `Found in ${filePathRelative}` : ""}` ); frontmatter["layout"] = "BlogHome"; } if (!("layout" in frontmatter)) DEPRECATED_HOME_FRONTMATTER_OPTIONS.forEach( ([deprecatedOption, newOption]) => deprecatedLogger({ options: frontmatter, deprecatedOption, newOption, scope: `${filePathRelative || ""} frontmatter` }) ); } return frontmatter; }; const handleNavbarOptions = (config) => config.map((item) => { if (isString(item)) return item; if (isPlainObject(item) && item) { deprecatedLogger({ // @ts-ignore options: item, deprecatedOption: "items", newOption: "children", scope: "navbar" }); if ("children" in item && isArray(item.children)) handleNavbarOptions(item.children); return item; } return null; }).filter((item) => item !== null); const convertNavbarOptions = (config) => { if (config === false) return false; if (isArray(config)) return handleNavbarOptions(config); logger.error(`${colors.magenta("navbar")} config should be an array`); return false; }; const handleArraySidebarOptions = (config) => config.map((item) => { if (isString(item)) return item; if (isPlainObject(item)) { const convertConfig = [ ["title", "text"], ["path", "link"], ["collapsable", "collapsible"] ]; convertConfig.forEach( ([deprecatedOption, newOption]) => deprecatedLogger({ // @ts-ignore options: item, deprecatedOption, newOption, scope: "sidebar" }) ); droppedLogger(item, "sidebarDepth", "Found in sidebar"); if ("children" in item && isArray(item.children)) handleArraySidebarOptions(item.children); return item; } return null; }).filter((item) => item !== null); const convertSidebarOptions = (config) => { if (config === false || config === "structure" || config === "heading") return config; if (isArray(config)) return handleArraySidebarOptions(config); if (isPlainObject(config) && config) return fromEntries( entries(config).map(([key, value]) => { if (isArray(value)) return [key, handleArraySidebarOptions(value)]; if (value === "structure" || value === "heading" || value === false) return [key, value]; logger.error( '"sidebar" value should be an array, "structure", "heading" or false when setting as an object' ); return [key, false]; }) ); logger.error( `${colors.magenta( "sidebar" )} config should be: an array, an object, "structure", "heading" or false` ); return false; }; const DEPRECATED_THEME_OPTIONS = [ // v1 ["darkLogo", "logoDark"], ["navAutoHide", "navbarAutoHide"], ["hideSiteTitleonMobile", "hideSiteNameOnMobile"], ["sidebarDepth ", "headerDepth"], ["prevLinks", "prevLink"], ["nextLinks", "nextLink"], ["editLinks", "editLink"], ["updateTime", "lastUpdated"], ["anchorDisplay", "toc"], ["nav", "navbar"], ["activeHash", "plugins.activeHeaderLinks"], ["comment", "plugins.comment"], ["copyCode", "plugins.copyCode"], ["feed", "plugins.feed"], ["git", "plugins.git"], ["mdEnhance", "plugins.mdEnhance"], ["readingTime", "plugins.readingTime"], ["photoswipe", "plugins.photoswipe"], ["pwa", "plugins.pwa"], ["sitemap", "plugins.sitemap"], ["seo", "plugins.seo"], ["wordPerMinute", "plugins.readingTime.wordPerMinute"], // v2 ["hideSiteNameonMobile", "hideSiteNameOnMobile"], ["fullScreen", "fullscreen"], ["headingDepth", "headerDepth"], ["wideBreakPoint", "pcBreakPoint"] ]; const DROPPED_THEME_OPTIONS = [ // v1 [ "algolia", 'The theme no longer bundles docsearch package, you should install and use "@vuepress/plugin-docsearch".' ], [ "algoliaType", 'The theme no longer bundles docsearch package, you should install and use "@vuepress/plugin-docsearch".' ], [ "custom", "VuePress2 remove markdown slot support, you should extend theme layout to support similar feature." ], [ "displayAllHeaders", "Due to scalability consideration, V2 no longer supports this." ], [ "chunkRename", "Since it's hard to implement such feature on vite, we no longer support this plugin in V2." ], [ "cleanUrl", "Due to better seo consideration, we no longer support this plugin in V2." ], [ "smoothScroll", "We provides smooth scrolling via CSS in V2, so this plugin is no longer needed." ] ]; const handleBlogOptions = (blogOptions) => { if ("links" in blogOptions) { logger.warn( '"blog.links" options is deprecated, please use "blog.medias" instead' ); blogOptions["medias"] = blogOptions["links"]; delete blogOptions["links"]; } if ("perPage" in blogOptions) { logger.warn( '"blog.perPage" options is deprecated, please use "blog.articlePerPage" instead' ); blogOptions["articlePerPage"] = blogOptions["perPage"]; delete blogOptions["perPage"]; } if ("autoExcerpt" in blogOptions) { logger.error( '"blog.autoExcerpt" options is no longer supported, please use "plugins.blog.excerptLength" instead' ); delete blogOptions["autoExcerpt"]; } }; const handleFooterOptions = (options) => { if (isPlainObject(options["footer"]) && options["footer"]) { const footer = options["footer"]; if ("copyright" in footer) { logger.warn( '"footer.copyright" options is deprecated, please use "copyright" instead' ); options["copyright"] = footer["copyright"]; } if ("display" in footer) { logger.warn( '"footer.display" options is deprecated, please use "displayFooter" instead' ); options["displayFooter"] = footer["display"]; } if ("content" in footer) { logger.warn( '"footer.content" options is deprecated, please use "footer" instead' ); options["footer"] = footer["content"]; } else { delete options["footer"]; } } }; const convertThemeOptions = (themeOptions) => { const plugins = themeOptions["plugins"] ??= {}; DEPRECATED_THEME_OPTIONS.forEach( ([deprecatedOption, newOption]) => deprecatedLogger({ options: themeOptions, deprecatedOption, newOption, scope: "themeConfig" }) ); DROPPED_THEME_OPTIONS.forEach((item) => droppedLogger(themeOptions, ...item)); if ("navbar" in themeOptions) themeOptions["navbar"] = convertNavbarOptions(themeOptions["navbar"]); if (isPlainObject(themeOptions["navbarLayout"])) { if ("left" in themeOptions["navbarLayout"]) { logger.warn( `To have better meaning under RTL layout, ${colors.magenta( "navbarLayout.left" )}" option is deprecated, please use ${colors.magenta( "navbarLayout.start" )} instead` ); themeOptions["navbarLayout"]["start"] = themeOptions["navbarLayout"]["left"]; } if ("right" in themeOptions["navbarLayout"]) { logger.warn( `To have better meaning under RTL layout, ${colors.magenta( "navbarLayout.right" )}" option is deprecated, please use ${colors.magenta( "navbarLayout.end" )} instead` ); themeOptions["navbarLayout"]["end"] = themeOptions["navbarLayout"]["right"]; } } if ("sidebar" in themeOptions) themeOptions["sidebar"] = convertSidebarOptions(themeOptions["sidebar"]); if (isPlainObject(themeOptions["blog"]) && themeOptions["blog"]) { handleBlogOptions(themeOptions["blog"]); if (!plugins["blog"]) logger.warn( `Blog feature is tree-shakable in v2, you should set ${colors.magenta( "plugins.blog: true" )} in theme options to enable it.` ); } if (isArray(plugins["components"])) { logger.warn( `${colors.magenta( "plugins.components" )} no longer accepts array, please set it to ${colors.magenta( "plugin.components.components" )} instead.` ); plugins["components"] = { components: plugins["components"] }; } if (isPlainObject(themeOptions["copyright"]) || themeOptions["copyright"] === true) logger.warn( `${colors.magenta( "copyright" )} is deprecated in V2, please use ${colors.magenta( "plugins.copyright" )} instead.` ); if (themeOptions["addThis"]) deprecatedLogger({ options: themeOptions, deprecatedOption: "addThis", newOption: "plugins.components.rootComponents.addThis", scope: "themeConfig" }); if (isPlainObject(themeOptions["encrypt"]) && themeOptions["encrypt"]) { const encrypt = themeOptions["encrypt"]; if ("global" in encrypt && typeof encrypt["global"] !== "boolean") { logger.warn( `${colors.magenta( "encrypt.global" )} is deprecated in V2, please use ${colors.magenta( "encrypt.admin" )} instead.` ); encrypt["admin"] = encrypt["global"]; } if ("status" in encrypt) { logger.warn( `${colors.magenta( "encrypt.status" )} is deprecated, please use ${colors.magenta( "encrypt.global" )} instead.` ); encrypt["global"] = encrypt["status"] === "global"; delete encrypt["status"]; } } handleFooterOptions(themeOptions); if ("locales" in themeOptions && isPlainObject(themeOptions["locales"])) values(themeOptions["locales"]).forEach( (localeConfig) => { DEPRECATED_THEME_OPTIONS.forEach( ([deprecatedOption, newOption]) => deprecatedLogger({ options: localeConfig, deprecatedOption, newOption, scope: "themeConfig.locales" }) ); DROPPED_THEME_OPTIONS.forEach( (item) => droppedLogger(localeConfig, ...item) ); if ("navbar" in localeConfig) localeConfig["navbar"] = convertNavbarOptions(localeConfig["navbar"]); if (isPlainObject(localeConfig["navbarLayout"])) { if ("left" in localeConfig["navbarLayout"]) { logger.warn( `To have better meaning under RTL layout, ${colors.magenta( "navbarLayout.left" )}" option is deprecated, please use ${colors.magenta( "navbarLayout.start" )} instead` ); localeConfig["navbarLayout"]["start"] = localeConfig["navbarLayout"]["left"]; } if ("right" in localeConfig["navbarLayout"]) { logger.warn( `To have better meaning under RTL layout, ${colors.magenta( "navbarLayout.right" )}" option is deprecated, please use ${colors.magenta( "navbarLayout.end" )} instead` ); localeConfig["navbarLayout"]["end"] = localeConfig["navbarLayout"]["right"]; } } if ("sidebar" in localeConfig) localeConfig["sidebar"] = convertSidebarOptions( localeConfig["sidebar"] ); handleFooterOptions(localeConfig); if (isPlainObject(localeConfig["blog"]) && localeConfig["blog"]) { handleBlogOptions(localeConfig["blog"]); if (!plugins["blog"]) logger.warn( 'Blog feature is tree-shakable in v2, you should set "plugins.blog: true" in theme options to enable it.' ); } } ); return themeOptions; }; const defineNavbarConfig = (config) => { deprecatedMsg( "defineNavbarConfig", 'import { navbar } from "vuepress-theme-hope";' ); return config; }; const defineSidebarConfig = (config) => { deprecatedMsg( "defineSidebarConfig", 'import { sidebar } from "vuepress-theme-hope";' ); return config; }; const defineSidebarArrayConfig = (config) => { deprecatedMsg( "defineSidebarArrayConfig", 'import { arraySidebar } from "vuepress-theme-hope";' ); return config; }; const defineSidebarObjectConfig = (config) => { deprecatedMsg( "defineSidebarObjectConfig", 'import { objectSidebar } from "vuepress-theme-hope";' ); return config; }; const defineThemeConfig = (themeConfig) => { deprecatedMsg( "defineThemeConfig", 'import { hopeThemeLegacy } from "vuepress-theme-hope";' ); return convertThemeOptions( themeConfig ); }; const defineHopeConfig = (config) => { logger.warn( `"${colors.magenta("defineHopeConfig")}" is ${colors.red( "deprecated" )}, please use the following code instead: ${colors.magenta(`import { defineUserConfig } from "vuepress"; import { hopeThemeLegacy } from "vuepress-theme-hope"; export default { // site config // ... theme : hopeThemeLegacy({ // theme config // ... }), }; `)} ` ); if ("themeConfig" in config && isPlainObject(config["themeConfig"])) config.theme = hopeTheme(config["themeConfig"]); if (!isFunction(config.theme)) config.theme = hopeTheme({}); return config; }; const navbarConfig = (config2) => { deprecatedMsg( "navbarConfig", 'import { navbar } from "vuepress-theme-hope";' ); return config2; }; const sidebarConfig = (config2) => { deprecatedMsg( "sidebarConfig", 'import { sidebar } from "vuepress-theme-hope";' ); return config2; }; const themeConfig = (themeConfig2) => { deprecatedMsg( "themeConfig", 'import { hopeThemeLegacy } from "vuepress-theme-hope";' ); return convertThemeOptions( themeConfig2 ); }; const checkMarkdownOptions = (options = {}) => { if ("lineNumbers" in options) { logger.warn( `${colors.magenta("markdown.lineNumbers")} is ${colors.yellow( "deprecated" )} in VuePress2, please use ${colors.magenta( "markdown.code.lineNumbers" )} instead.` ); options.code = options.code ?? {}; if (isPlainObject(options.code)) options.code.lineNumbers = options["lineNumbers"]; delete options["lineNumbers"]; } if ("slugify" in options) { logger.error( `${colors.magenta("markdown.slugify")} is ${colors.red( "no longer supported" )} in VuePress2. If you want to change the slugify function anyway, set the following options separately: · ${colors.blue("markdown.anchor.slugify")} · ${colors.blue("markdown.toc.slugify")} · ${colors.blue("markdown.headers.slugify")} ` ); delete options["slugify"]; } if ("pageSuffix" in options) { logger.error( `${colors.magenta("markdown.pageSuffix")} is ${colors.red( "no longer supported" )} in VuePress2.` ); delete options["pageSuffix"]; } if ("externalLinks" in options) { logger.error( `${colors.magenta("markdown.externalLinks")} is ${colors.red( "no longer supported" )} in VuePress2, please use ${colors.magenta( "markdown.links.externalAttrs" )} instead.` ); delete options["externalLinks"]; } if ("plugins" in options) { logger.error( `${colors.magenta("markdown.plugins")} is ${colors.red( "no longer supported" )} in VuePress2, please use ${colors.magenta( "extendsMarkdown" )} hook instead.` ); delete options["plugins"]; } }; const checkPluginOptions = (plugins) => { if (isArray(plugins)) return plugins.flat().filter((item) => { if (isFunction(item)) return true; if (isPlainObject(item)) { const { name } = item; if (!isString(name)) { logger.error( `VuePress2 requires "name" option in plugins and it should strict equal it's package name.` ); return false; } if (!/^(@.*\/)?vuepress-plugin-/.test(name)) { logger.error( "VuePress2 requires plugin name to strict equal a package name, you should fix it" ); return false; } [ // v1 ["ready", "onPrepared"], ["updated", "onWatched"], ["generated", "onGenerated"], ["extendMarkdown", "extendsMarkdown"], ["extendPageData", "extendsPage"], // v2 ["templateSSR", "templateBuild"] ].forEach(([deprecatedOption, newOption]) => { if (deprecatedOption in item) logger.warn( `${colors.magenta( deprecatedOption )} options in plugin options is ${colors.yellow( "deprecated" )} in VuePress2, please use ${colors.magenta(newOption)} instead.` ); item[newOption] = item[deprecatedOption]; delete item[deprecatedOption]; }); [ // v1 "plugins", "chainMarkdown", "extendsCli", "configureWebpack", "chainWebpack", "beforeDevServer", "afterDevServer", "additionalPages", "clientDynamicModules", "enhanceAppFiles", "globalUIComponents", "clientRootMixin", // v2 "clientAppEnhanceFiles", "clientAppRootComponentFiles", "clientAppSetupFiles" ].forEach((removedOption) => { if (removedOption in item) logger.error( `${colors.magenta( removedOption )} option in plugin options is ${colors.red( "no longer supported" )} in VuePress2, make sure you are using a VuePress2 plugin.` ); delete item[removedOption]; }); } return false; }); if (isPlainObject(plugins)) { logger.error( `${colors.magenta('object format "plugins"')} is ${colors.red( "no longer supported" )} in VuePress2, you should import plugins and call them in an array.` ); return []; } return []; }; const checkBundlerOptions = (config2) => { [ "postcss", "stylus", "scss", "sass", "less", "chainWebpack", "configureWebpack", "beforeDevServer", "afterDevServer", "evergreen" ].forEach((removedOption) => { if (removedOption in config2) logger.error( `"${colors.magenta( removedOption )}" option in config file is ${colors.red( "no longer supported" )} in VuePress2, you should set it in bundler options.` ); delete config2[removedOption]; }); }; const config = (userConfig) => { checkMarkdownOptions( userConfig["markdown"] ); checkBundlerOptions(userConfig); userConfig["plugins"] = checkPluginOptions(userConfig["plugins"]); [ ["ready", "onPrepared"], ["updated", "onWatched"], ["generated", "onGenerated"], ["extendMarkdown", "extendsMarkdown"], ["extendPageData", "extendsPage"], ["patterns", "pagePatterns"], ["templateSSR", "templateBuild"] ].forEach(([deprecatedOption, newOption]) => { if (deprecatedOption in userConfig) logger.warn( `"${deprecatedOption}" option in config file is ${colors.yellow( "deprecated" )} in VuePress2, please use "${newOption}" instead.` ); userConfig[newOption] = userConfig[deprecatedOption]; delete userConfig[deprecatedOption]; }); [ ["chainMarkdown", 'please use "extendsMarkdown" instead'], ["extendsCli"], ["configureWebpack", 'please set options in "bundler" instead'], ["chainWebpack", 'please set options in "bundler" instead'], [ "additionalPages", 'please use "app.pages.push(createPage())" in "onInitialized" hook' ], [ "clientDynamicModules", 'please use "app.writeTemp()" in "onPrepared" hook' ], ["clientAppRootComponentFiles", 'please use "clientConfigFile" instead'], ["clientAppSetupFiles", 'please use "clientConfigFile" instead'], ["clientAppEnhanceFiles", 'please use "clientConfigFile" instead'] ].forEach(([removedOption, hint = ""]) => { if (removedOption in userConfig) logger.error( `"${removedOption}" option in config is ${colors.red( "no longer supported" )} in VuePress2${hint ? `, ${hint}.` : "."}` ); delete userConfig[removedOption]; }); if ("extraWatchFiles" in userConfig) { logger.error( `${colors.magenta("extraWatchFiles")} options is ${colors.red( "removed" )} in VuePress2, you should use "onWatched" hook.` ); delete userConfig["extraWatchFiles"]; } return defineHopeConfig(userConfig); }; const checkLegacyStyle = (app) => { if (fs.existsSync(app.dir.source(".vuepress/styles/index.styl")) && !fs.existsSync(app.dir.source(".vuepress/styles/index.scss"))) logger$1.error( "V2 style switched to scss instead of stylus, so you should remove index.styl and create index.scss under .vuepress/styles." ); if (fs.existsSync(app.dir.source(".vuepress/styles/palette.styl")) && !fs.existsSync(app.dir.source(".vuepress/styles/palette.scss")) && !fs.existsSync(app.dir.source(".vuepress/styles/config.scss"))) logger$1.error( "V2 style switched to scss instead of stylus, so you should remove palette.styl and create config.scss and palette.scss under .vuepress/styles." ); }; const getEncryptConfig = (encrypt = {}) => { const result = {}; if (encrypt.admin) { if (encrypt.global) result.global = true; if (isString(encrypt.admin)) result.admin = [hashSync(encrypt.admin)]; else if (isArray(encrypt.admin)) result.admin = encrypt.admin.map((globalToken) => { if (isString(globalToken)) return hashSync(globalToken); logger.error(`You config "themeConfig.encrypt.admin", but your config is invalid. All password MUST be string. But we found one’s type is ${typeof globalToken}. Please fix it!`); return null; }).filter((item) => item !== null); else logger.error( `You are asking for global encryption but you provide invalid "admin" config. Please check "admin" in your "themeConfig.encrypt" config. It can be string or string[], but you are providing ${typeof encrypt.admin}. Please fix it!` ); } if (encrypt.config) result.config = fromEntries( entries(encrypt.config).map(([key, tokens]) => { if (isString(tokens)) return [key, [hashSync(tokens)]]; if (isArray(tokens)) { const encryptedTokens = tokens.map((token) => { if (isString(token)) return hashSync(token); logger.error(`You config "themeConfig.encrypt.config", but your config is invalid. Key ${key}’s value MUST be string or string[]. But it’s type is ${typeof token}. Please fix it!`); return null; }).filter((item) => item !== null); if (encryptedTokens.length) return [key, encryptedTokens]; return null; } logger.error( `You config "themeConfig.encrypt.config", but your config is invalid. The value of key ${key} MUST be string or string[]. But not it’s ${typeof tokens}. Please fix it!` ); return null; }).filter((item) => item !== null) ); return result; }; const checkSocialMediaIcons = (themeData) => { var _a; const icons = {}; const checkIcon = (key, value) => { if (isString(value)) { const templatePath = `${TEMPLATE_FOLDER}socialMediaIcons/${key.toLocaleLowerCase()}.svg`; if (fs.existsSync(templatePath)) { icons[key] = fs.readFileSync(templatePath, { encoding: "utf-8" }); return value; } logger.warn(`${key} icon in blog media config not found!`); return false; } if (isArray(value)) { if (startsWith(value[1], "<svg")) { icons[key] = value[1]; return value[0]; } if (fs.existsSync(value[1])) { icons[key] = fs.readFileSync(value[1], { encoding: "utf-8" }); return value[0]; } logger.warn(`${key}'s icon ${value[1]} in blog media config is invalid!`); return false; } logger.warn(`${key} icon in blog media config has an invalid config!`); return false; }; entries(((_a = themeData.blog) == null ? void 0 : _a.medias) || {}).forEach(([key, value]) => { const result = checkIcon(key, value); if (result) themeData.blog.medias[key] = result; else delete themeData.blog.medias[key]; }); if (themeData.locales) values(themeData.locales).forEach((localeConfig) => { var _a2; entries(((_a2 = localeConfig.blog) == null ? void 0 : _a2.medias) || {}).forEach(([key, value]) => { const result = checkIcon(key, value); if (result) localeConfig.blog.medias[key] = result; else delete localeConfig.blog.medias[key]; }); }); return icons; }; const getStatus = (app, themeOptions) => { var _a, _b; const { locales } = app.options; const { plugins = {} } = themeOptions; return { enableAutoCatalog: plugins.autoCatalog !== false, enableBlog: Boolean(plugins.blog), enableEncrypt: Boolean( themeOptions.encrypt && ("admin" in themeOptions.encrypt || "config" in themeOptions.encrypt) ), enableSlide: Boolean(plugins.mdEnhance && plugins.mdEnhance.presentation), enableReadingTime: plugins.readingTime !== false, blogType: isPlainObject(plugins.blog) ? ((_b = (_a = plugins.blog) == null ? void 0 : _a.type) == null ? void 0 : _b.map(({ key, path }) => ({ key, path: path || `/${key}/` }))) || [] : [], hasMultipleLanguages: keys(locales).length > 1, supportPageview: Boolean( plugins.comment && plugins.comment.provider === "Waline" ) }; }; const brLocale = { lang: "pt-BR", navbarLocales: { langName: "Português", selectLangAriaLabel: "Selecione a língua" }, metaLocales: { author: "Autor", date: "Escrito em", origin: "Original", views: "Visualizações", category: "Categoria", tag: "Tag", readingTime: "Tempo de Leitura", words: "Palavras", toc: "On This Page", prev: "Prev", next: "Next", lastUpdated: "Última atualização", editLink: "Editar esta página", contributors: "Contribuidores", print: "Imprimir" }, blogLocales: { article: "Artigos", articleList: "Lista de Artigos", category: "Categorias", tag: "Tag", timeline: "Linha do Tempo", timelineTitle: "Ontem, de novo!", all: "Todos", intro: "Intro Pessoal", star: "Estrela" }, paginationLocales: { prev: "Anterior", next: "Próximo", navigate: "Pular para", action: "Ir", errorText: "Por favor, digite um número entre 1 e $page !" }, outlookLocales: { themeColor: "Cor do Tema", darkmode: "Modo do Tema", fullscreen: "Full Screen" }, encryptLocales: { iconLabel: "Page Encrypted", placeholder: "Entre a senha", remember: "Remember password", errorHint: "Por favor, entre a senha correta!" }, routeLocales: { notFoundTitle: "Não Encontrado", skipToContent: "Pular para o conteúdo", notFoundMsg: [ "Não há nada aqui.", "Como chegou até aqui?", "Isto é um Quatro-Zero-Quatro.", "Parece que temos alguns links quebrados." ], back: "Voltar", home: "Leve-me para casa", openInNewWindow: "Open in new window" } }; const deLocale = { lang: "de-DE", navbarLocales: { langName: "Deutsch", selectLangAriaLabel: "Sprache wählen" }, metaLocales: { author: "Autor", date: "Datum", origin: "Original", views: "Besucher", category: "Kategorie", tag: "Tag", readingTime: "Lesezeit", words: "Wörter", toc: "Auf dieser Seite", prev: "Vorherige", next: "Nächste", lastUpdated: "Zuletzt geändert", contributors: "Mitwirkende", editLink: "Diese Seite barbeiten", print: "Drucken" }, blogLocales: { article: "Artikel", articleList: "Artikel Liste", category: "Kategorie", tag: "Tag", timeline: "Timeline", timelineTitle: "Yesterday Once More!", all: "Alle", intro: "Persönliche Einleitung", star: "Star" }, paginationLocales: { prev: "Vorherige", next: "Nächste", navigate: "Springe zu", action: "Los", errorText: "Bitte gib eine Nummer zwischen 1 und $page ein!" }, outlookLocales: { themeColor: "Design-Farbe", darkmode: "Design-Modus", fullscreen: "Vollbild" }, encryptLocales: { iconLabel: "Seite verschlüsselt", placeholder: "Passwort eingeben", remember: "Passwort merken", errorHint: "Bitte das korrekte Passwort eingeben!" }, routeLocales: { skipToContent: "Zum Hauptinhalt springen", notFoundTitle: "Seite nicht gefunden", notFoundMsg: [ "Hier gibt es nichts.", "Wie sind wir hier hergekommen?", "Das ist wohl eine Vier-Null-Vier.", "Sieht aus als hättest du einen kaputten Link gefunden." ], back: "Zurück", home: "Zur Startseite", openInNewWindow: "In neuem Fenster öffnen" } }; const deATLocale = { lang: "de-AT", navbarLocales: { langName: "Deutsch (Österreich)", selectLangAriaLabel: "Sprache wählen" }, metaLocales: { author: "Autor", date: "Datum", origin: "Original", views: "Besucher", category: "Kategorie", tag: "Tag", readingTime: "Lesezeit", words: "Wörter", toc: "On This Page", prev: "Prev", next: "Next", lastUpdated: "Zuletzt geändert", contributors: "Mitwirkende", editLink: "Diese Seite barbeiten", print: "Drucken" }, blogLocales: { article: "Artikel", articleList: "Artikel Liste", category: "Kategorie", tag: "Tag", timeline: "Timeline", timelineTitle: "Yesterday Once More!", all: "Alle", intro: "Persönliche Einleitung", star: "Star" }, paginationLocales: { prev: "Vorheriges", next: "Nächstes", navigate: "Springe zu", action: "Los", errorText: "Bitte gib eine Nummer zwischen 1 und $page ein!" }, outlookLocales: { themeColor: "Design-Farbe", darkmode: "Design-Modus", fullscreen: "Full Screen" }, encryptLocales: { iconLabel: "Page Encrypted", placeholder: "Entre a senha", remember: "Remember password", errorHint: "Bitte das korrekte Passwort eingeben!" }, routeLocales: { skipToContent: "Zum Hauptinhalt springen", notFoundTitle: "Seite nicht gefunden", notFoundMsg: [ "Hier gibt es nichts.", "Wie sind wir hier hergekommen?", "Das ist wohl eine Vier-Null-Vier.", "Sieht aus als hättest du einen kaputten Link gefunden." ], back: "Zurück", home: "Zur Startseite", openInNewWindow: "Open in new window" } }; const enLocale = { lang: "en-US", navbarLocales: { langName: "English", selectLangAriaLabel: "Select language" }, metaLocales: { author: "Author", date: "Writing Date", origin: "Original", views: "Page views", category: "Category", tag: "Tag", readingTime: "Reading Time", words: "Words", toc: "On This Page", prev: "Prev", next: "Next", lastUpdated: "Last update", contributors: "Contributors", editLink: "Edit this page", print: "Print" }, blogLocales: { article: "Articles", articleList: "Article List", category: "Category", tag: "Tag", timeline: "Timeline", timelineTitle: "Yesterday Once More!", all: "All", intro: "Personal Intro", star: "Star" }, paginationLocales: { prev: "Prev", next: "Next", navigate: "Jump to", action: "Go", errorText: "Please enter a number between 1 and $page !" }, outlookLocales: { themeColor: "Theme Color", darkmode: "Theme Mode", fullscreen: "Full Screen" }, encryptLocales: { iconLabel: "Page Encrypted", placeholder: "Enter password", remember: "Remember password", errorHint: "Please enter the correct password!" }, routeLocales: { skipToContent: "Skip to main content", notFoundTitle: "Page not found", notFoundMsg: [ "There’s nothing here.", "How