hirsun-vuepress-theme-hope-plus
Version:
A light vuepress theme with tons of features and typewriter effect
1,557 lines (1,528 loc) • 123 kB
JavaScript
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