config.mts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import "dotenv/config";
  2. import { fileURLToPath, URL } from "node:url";
  3. import { defineConfig } from "vitepress";
  4. import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
  5. import Icons from "unplugin-icons/vite";
  6. import { FileSystemIconLoader } from "unplugin-icons/loaders";
  7. import { SVG, cleanupSVG, parseColors, runSVGO } from "@iconify/tools";
  8. import ImageFiguresPlugin from "markdown-it-image-figures";
  9. import {
  10. S3Client,
  11. ListObjectsCommand,
  12. GetObjectCommand,
  13. } from "@aws-sdk/client-s3";
  14. import fs from "node:fs";
  15. import path from "node:path";
  16. import { exec } from "child_process";
  17. import { buildSideBar } from "../utils/sideBar";
  18. const DOC_BASE_PATH = "pages";
  19. if (process.env.NODE_ENV === "production") {
  20. // 执行清理pages文件夹命令
  21. await new Promise((resolve, reject) =>
  22. exec(
  23. `git checkout -- ${DOC_BASE_PATH} & git clean -df ${DOC_BASE_PATH}`,
  24. (error, stdout, stderr) => {
  25. if (error) {
  26. console.error(`清理pages时出错: ${error.message}`);
  27. reject(stderr);
  28. }
  29. console.log(`清理pages结果: ${stdout}`);
  30. resolve(stdout);
  31. }
  32. )
  33. );
  34. }
  35. // 从S3构建pages里面的markdown文件
  36. const s3 = new S3Client({
  37. credentials: {
  38. accessKeyId: process.env.VITE_AWS_S3_ACCESS_KEY_ID!,
  39. secretAccessKey: process.env.VITE_AWS_S3_SECRET_ACCESS_KEY!,
  40. },
  41. region: process.env.VITE_AWS_S3_REGION!,
  42. });
  43. const command = new ListObjectsCommand({
  44. Bucket: process.env.VITE_DOCS_LIST_BUCKET,
  45. });
  46. const { Contents: contents } = await s3.send(command);
  47. await Promise.all(
  48. contents!.map((content) => {
  49. return new Promise(async (resolve, reject) => {
  50. try {
  51. const command = new GetObjectCommand({
  52. Bucket: process.env.VITE_DOCS_LIST_BUCKET,
  53. Key: content.Key,
  54. ResponseCacheControl: "no-cache",
  55. });
  56. const file = await s3.send(command);
  57. const writePath = path.join(DOC_BASE_PATH, content.Key!);
  58. const directory = path.dirname(writePath);
  59. fs.mkdirSync(directory, { recursive: true });
  60. fs.writeFileSync(writePath, await file.Body!.transformToString());
  61. resolve(content.Key);
  62. } catch (e) {
  63. reject(e);
  64. }
  65. fs.writeFile;
  66. });
  67. })
  68. );
  69. // 构建sideBar数据
  70. function readJsonFile(filePath) {
  71. try {
  72. // 同步读取文件内容,如果文件不存在会抛出异常
  73. const data = fs.readFileSync(filePath, "utf8");
  74. return JSON.parse(data); // 解析 JSON 数据
  75. } catch (err) {
  76. if (err.code === "ENOENT") {
  77. console.error("File not found:", filePath);
  78. // 可以返回默认值或者进行其他处理
  79. return {}; // 返回 null 或者其他默认值
  80. } else {
  81. throw err; // 抛出其他异常
  82. }
  83. }
  84. }
  85. const sideBarSortMap = {
  86. "zh-CN": readJsonFile("pages/zh-CN::SIDEBAR_SORTED_MAP.json"),
  87. "zh-HK": readJsonFile("pages/zh-HK::SIDEBAR_SORTED_MAP.json"),
  88. "en-US": readJsonFile("pages/en-US::SIDEBAR_SORTED_MAP.json"),
  89. };
  90. let { rootSideBar, zhHKSideBar, enUSSideBar } = buildSideBar(
  91. contents,
  92. sideBarSortMap
  93. );
  94. // import util from "util";
  95. // console.log(
  96. // util.inspect(sideBarSortMap, {
  97. // showHidden: false,
  98. // depth: null,
  99. // colors: true,
  100. // }),
  101. // util.inspect(rootSideBar, { showHidden: false, depth: null, colors: true })
  102. // );
  103. // https://vitepress.dev/reference/site-config
  104. export default defineConfig({
  105. title: "可可智慧教育平台",
  106. description: "可可智慧教育平台",
  107. base: "/help/",
  108. srcDir: DOC_BASE_PATH,
  109. ignoreDeadLinks: true,
  110. lastUpdated: true,
  111. themeConfig: {
  112. search: {
  113. provider: "local",
  114. options: {
  115. locales: {
  116. root: {
  117. translations: {
  118. button: {
  119. buttonText: "搜索文档",
  120. buttonAriaLabel: "搜索文档",
  121. },
  122. modal: {
  123. noResultsText: "无法找到相关结果",
  124. resetButtonTitle: "清除查询条件",
  125. footer: {
  126. selectText: "选择",
  127. navigateText: "切换",
  128. closeText: "关闭",
  129. },
  130. },
  131. },
  132. },
  133. "zh-HK": {
  134. translations: {
  135. button: {
  136. buttonText: "搜索文档hk",
  137. buttonAriaLabel: "搜索文档hk",
  138. },
  139. modal: {
  140. noResultsText: "无法找到相关结果",
  141. resetButtonTitle: "清除查询条件",
  142. footer: {
  143. selectText: "选择",
  144. navigateText: "切换",
  145. closeText: "关闭",
  146. },
  147. },
  148. },
  149. },
  150. },
  151. },
  152. },
  153. },
  154. appearance: false,
  155. markdown: {
  156. breaks: true,
  157. config: (md) => {
  158. md.use(ImageFiguresPlugin, { figcaption: true });
  159. },
  160. },
  161. vite: {
  162. publicDir: "../public",
  163. envDir: "../",
  164. ssr: {
  165. // SSG Vue-i18n workaround
  166. noExternal: ["vue-i18n", "mark.js"],
  167. },
  168. plugins: [
  169. VueI18nPlugin({}),
  170. Icons({
  171. compiler: "vue3",
  172. customCollections: {
  173. "ccrbi-plain": FileSystemIconLoader(
  174. "assets/icons/plain",
  175. async (svgStr) => {
  176. const svg = new SVG(svgStr);
  177. cleanupSVG(svg);
  178. parseColors(svg, {
  179. defaultColor: "currentColor",
  180. callback: (attr, colorStr, color) => {
  181. // console.log('Color:', colorStr, color);
  182. // 普通图标
  183. return "currentColor";
  184. // Change black to 'currentColor'
  185. // const blackColor = stringToColor("black");
  186. // if (color && compareColors(color, blackColor!)) {
  187. // return "currentColor";
  188. // }
  189. // switch (color?.type) {
  190. // case "none":
  191. // case "current":
  192. // return color;
  193. // }
  194. // throw new Error(
  195. // `Unexpected color "${colorStr}" in attribute ${attr}`
  196. // );
  197. },
  198. });
  199. // Optimise, but do not change shapes because they are animated
  200. runSVGO(svg, {
  201. keepShapes: true,
  202. });
  203. return svg.toMinifiedString({});
  204. }
  205. ),
  206. "ccrbi-colored": FileSystemIconLoader(
  207. "assets/icons/colored",
  208. async (svgStr) => {
  209. const svg = new SVG(svgStr);
  210. cleanupSVG(svg);
  211. // Optimise, but do not change shapes because they are animated
  212. runSVGO(svg, {
  213. keepShapes: true,
  214. });
  215. return svg.toMinifiedString({});
  216. }
  217. ),
  218. },
  219. }),
  220. ],
  221. resolve: {
  222. alias: [
  223. {
  224. find: /^.*\/VPNavBar\.vue$/,
  225. replacement: fileURLToPath(
  226. new URL("../components/CustomNavBar.vue", import.meta.url)
  227. ),
  228. },
  229. {
  230. find: "@/",
  231. replacement: fileURLToPath(new URL("../", import.meta.url)),
  232. },
  233. ],
  234. },
  235. },
  236. locales: {
  237. root: {
  238. label: "简体中文",
  239. lang: "zh-CN",
  240. themeConfig: {
  241. outline: { label: "本页目录", level: "deep" },
  242. logo: "/logo.png",
  243. siteTitle: false,
  244. // https://vitepress.dev/reference/default-theme-config
  245. // nav: [
  246. // { text: 'Home', link: '/' },
  247. // { text: 'Examples', link: '/markdown-examples' }
  248. // ],
  249. // sidebar: [
  250. // { text: "关于CocoBlockly X", link: "/docs" },
  251. // { text: "常见问题解答", link: "/docs/常见问题解答" },
  252. // {
  253. // text: "开始使用CocoBlockly X",
  254. // link: "/docs/start-using-cocoblockly-x",
  255. // collapsed: true,
  256. // items: [],
  257. // },
  258. // {
  259. // text: "电子模块基本教学",
  260. // collapsed: true,
  261. // items: [{ text: "todo", link: "/docs" }],
  262. // },
  263. // ],
  264. sidebar: rootSideBar,
  265. // socialLinks: [
  266. // { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
  267. // ]
  268. },
  269. },
  270. "zh-HK": {
  271. label: "繁体中文",
  272. lang: "zh-HK",
  273. themeConfig: {
  274. outline: { label: "本頁目錄", level: "deep" },
  275. logo: "/logo.png",
  276. siteTitle: false,
  277. // https://vitepress.dev/reference/default-theme-config
  278. // nav: [
  279. // { text: 'Home', link: '/' },
  280. // { text: 'Examples', link: '/markdown-examples' }
  281. // ],
  282. // sidebar: [
  283. // { text: "什麽是CocoBlockly X", link: "/zh-HK/docs" },
  284. // { text: "常見問題解答", link: "/zh-HK/docs/faq" },
  285. // {
  286. // text: "開始使用CocoBlockly X",
  287. // link: "/zh-HK/docs/start-using-cocoblockly-x",
  288. // },
  289. // {
  290. // text: "電子模組基本教學",
  291. // collapsed: true,
  292. // items: [{ text: "todo", link: "/zh-HK/docs" }],
  293. // },
  294. // ],
  295. sidebar: zhHKSideBar,
  296. // socialLinks: [
  297. // { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
  298. // ]
  299. },
  300. },
  301. "en-US": {
  302. label: "English",
  303. lang: "en-US",
  304. themeConfig: {
  305. outline: { label: "On this page", level: "deep" },
  306. logo: "/logo.png",
  307. siteTitle: false,
  308. // https://vitepress.dev/reference/default-theme-config
  309. // nav: [
  310. // { text: 'Home', link: '/' },
  311. // { text: 'Examples', link: '/markdown-examples' }
  312. // ],
  313. // sidebar: [
  314. // { text: "什麽是CocoBlockly X", link: "/zh-HK/docs" },
  315. // { text: "常見問題解答", link: "/zh-HK/docs/faq" },
  316. // {
  317. // text: "開始使用CocoBlockly X",
  318. // link: "/zh-HK/docs/start-using-cocoblockly-x",
  319. // },
  320. // {
  321. // text: "電子模組基本教學",
  322. // collapsed: true,
  323. // items: [{ text: "todo", link: "/zh-HK/docs" }],
  324. // },
  325. // ],
  326. sidebar: enUSSideBar,
  327. // socialLinks: [
  328. // { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
  329. // ]
  330. },
  331. },
  332. },
  333. });