valaxy 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/bin/valaxy.js +1 -1
  2. package/dist/chunk-4LP5LOFC.js +86 -0
  3. package/dist/chunk-BDKTP6RW.js +1 -0
  4. package/dist/chunk-UVMBC7I3.mjs +1 -0
  5. package/dist/chunk-VLZSM7W4.mjs +86 -0
  6. package/dist/config-24b4f209.d.ts +188 -0
  7. package/dist/index.d.ts +316 -151
  8. package/dist/index.js +1 -1
  9. package/dist/index.mjs +1 -1
  10. package/dist/{cli.d.ts → node/cli.d.ts} +0 -0
  11. package/dist/node/cli.js +45 -0
  12. package/dist/node/cli.mjs +45 -0
  13. package/dist/node/index.d.ts +46 -0
  14. package/dist/node/index.js +1 -0
  15. package/dist/node/index.mjs +1 -0
  16. package/package.json +22 -18
  17. package/src/client/components/PostCard.vue +1 -1
  18. package/src/client/components/PostList.vue +5 -5
  19. package/src/client/components/ValaxyCopyright.vue +1 -1
  20. package/src/client/components/ValaxyFooter.vue +1 -1
  21. package/src/client/components/ValaxyOverlay.vue +4 -4
  22. package/src/client/components/ValaxyPagination.vue +16 -14
  23. package/src/client/components/ValaxyRightSidebar.vue +4 -4
  24. package/src/client/components/ValaxySidebar.vue +4 -8
  25. package/src/client/components/ValaxyToc.vue +11 -9
  26. package/src/client/composables/comments/index.ts +1 -0
  27. package/src/client/composables/comments/twikoo.ts +37 -0
  28. package/src/client/composables/comments/waline.ts +8 -5
  29. package/src/client/composables/common.ts +3 -4
  30. package/src/client/composables/search/algolia.ts +2 -1
  31. package/src/client/composables/sidebar.ts +2 -2
  32. package/src/client/composables/tag.ts +9 -2
  33. package/src/client/composables/widgets/backToTop.ts +10 -4
  34. package/src/client/main.ts +1 -8
  35. package/src/client/modules/valaxy.ts +36 -15
  36. package/src/client/shims.d.ts +5 -5
  37. package/src/client/styles/common/button.scss +3 -5
  38. package/src/client/styles/common/code.scss +157 -0
  39. package/src/client/styles/common/hamburger.scss +2 -2
  40. package/src/client/styles/common/markdown.scss +6 -5
  41. package/src/client/styles/common/sidebar.scss +3 -3
  42. package/src/client/styles/common/transition.scss +2 -2
  43. package/src/client/styles/css-vars.scss +39 -0
  44. package/src/client/styles/global/helper.scss +2 -2
  45. package/src/client/styles/global/index.scss +1 -1
  46. package/src/client/styles/global/nprogress.scss +1 -1
  47. package/src/client/styles/global/reset.scss +2 -1
  48. package/src/client/styles/index.scss +12 -1
  49. package/src/client/styles/mixins/config.scss +1 -1
  50. package/src/client/styles/mixins/index.scss +1 -0
  51. package/src/client/styles/palette.scss +6 -8
  52. package/src/client/styles/vars.scss +1 -13
  53. package/src/client/styles/widgets/banner.scss +2 -2
  54. package/src/client/utils/index.ts +0 -2
  55. package/src/node/build.ts +20 -1
  56. package/src/node/cli.ts +32 -19
  57. package/src/node/markdown/highlight.ts +50 -0
  58. package/src/node/markdown/highlightLines.ts +96 -0
  59. package/src/node/markdown/index.ts +22 -11
  60. package/src/node/markdown/markdown-it-katex.ts +20 -10
  61. package/src/node/markdown/parseHeader.ts +4 -2
  62. package/src/node/options.ts +3 -1
  63. package/src/node/plugins/index.ts +53 -24
  64. package/src/node/plugins/markdown.ts +1 -1
  65. package/src/node/plugins/preset.ts +46 -10
  66. package/src/node/plugins/unocss.ts +12 -7
  67. package/src/node/rss.ts +121 -0
  68. package/src/node/server.ts +1 -1
  69. package/src/node/shims.d.ts +15 -0
  70. package/src/node/utils/cli.ts +0 -1
  71. package/src/node/vite.ts +7 -15
  72. package/src/types/config.ts +27 -2
  73. package/tsup.config.ts +1 -0
  74. package/dist/build-CLF7GOPQ.mjs +0 -1
  75. package/dist/build-F47TGGER.js +0 -1
  76. package/dist/chunk-5S6S3FLN.mjs +0 -83
  77. package/dist/chunk-JORQSKHF.mjs +0 -1
  78. package/dist/chunk-TOMJSB6R.js +0 -83
  79. package/dist/chunk-VJNIZVTJ.js +0 -1
  80. package/dist/cli.js +0 -6
  81. package/dist/cli.mjs +0 -6
  82. package/src/client/pages/about/index.md +0 -5
  83. package/src/client/pages/posts/index.md +0 -5
  84. package/src/client/styles/css-vars/dark.scss +0 -17
  85. package/src/client/styles/css-vars/index.scss +0 -18
  86. package/src/client/styles/css-vars/light.scss +0 -9
@@ -0,0 +1,96 @@
1
+ // Modified from https://github.com/egoist/markdown-it-highlight-lines
2
+ import type MarkdownIt from 'markdown-it'
3
+
4
+ const wrapperRE = /^<pre .*?><code>/
5
+
6
+ export const highlightLinePlugin = (md: MarkdownIt) => {
7
+ const fence = md.renderer.rules.fence!
8
+ md.renderer.rules.fence = (...args) => {
9
+ const [tokens, idx, options] = args
10
+ const token = tokens[idx]
11
+
12
+ // due to use of markdown-it-attrs, the {0} syntax would have been converted
13
+ // to attrs on the token
14
+ const attr = token.attrs && token.attrs[0]
15
+ if (!attr)
16
+ return fence(...args)
17
+
18
+ const lines = attr[0]
19
+ if (!lines || !/[\d,-]+/.test(lines))
20
+ return fence(...args)
21
+
22
+ const lineNumbers = lines
23
+ .split(',')
24
+ .map(v => v.split('-').map(v => parseInt(v, 10)))
25
+
26
+ const code = options.highlight
27
+ ? options.highlight(token.content, token.info, '')
28
+ : token.content
29
+
30
+ const rawCode = code.replace(wrapperRE, '')
31
+ const highlightLinesCode = rawCode
32
+ .split('\n')
33
+ .map((split, index) => {
34
+ const lineNumber = index + 1
35
+ const inRange = lineNumbers.some(([start, end]) => {
36
+ if (start && end)
37
+ return lineNumber >= start && lineNumber <= end
38
+
39
+ return lineNumber === start
40
+ })
41
+ if (inRange)
42
+ return '<div class="highlighted">&nbsp;</div>'
43
+
44
+ return '<br>'
45
+ })
46
+ .join('')
47
+
48
+ const highlightLinesWrapperCode = `<div class="highlight-lines">${highlightLinesCode}</div>`
49
+
50
+ return highlightLinesWrapperCode + code
51
+ }
52
+ }
53
+
54
+ // markdown-it plugin for wrapping <pre> ... </pre>.
55
+ //
56
+ // If your plugin was chained before preWrapper, you can add additional element directly.
57
+ // If your plugin was chained after preWrapper, you can use these slots:
58
+ // 1. <!--beforebegin-->
59
+ // 2. <!--afterbegin-->
60
+ // 3. <!--beforeend-->
61
+ // 4. <!--afterend-->
62
+ export const preWrapperPlugin = (md: MarkdownIt) => {
63
+ const fence = md.renderer.rules.fence!
64
+ md.renderer.rules.fence = (...args) => {
65
+ const [tokens, idx] = args
66
+ const token = tokens[idx]
67
+ const rawCode = fence(...args)
68
+ return `<div class="language-${token.info.trim()}">${rawCode}</div>`
69
+ }
70
+ }
71
+
72
+ // markdown-it plugin for generating line numbers.
73
+ // It depends on preWrapper plugin.
74
+ export const lineNumberPlugin = (md: MarkdownIt) => {
75
+ const fence = md.renderer.rules.fence!
76
+ md.renderer.rules.fence = (...args) => {
77
+ const rawCode = fence(...args)
78
+ const code = rawCode.slice(
79
+ rawCode.indexOf('<code>'),
80
+ rawCode.indexOf('</code>'),
81
+ )
82
+
83
+ const lines = code.split('\n')
84
+ const lineNumbersCode = [...Array(lines.length - 1)]
85
+ .map((line, index) => `<span class="line-number">${index + 1}</span><br>`)
86
+ .join('')
87
+
88
+ const lineNumbersWrapperCode = `<div class="line-numbers-wrapper">${lineNumbersCode}</div>`
89
+
90
+ const finalCode = rawCode
91
+ .replace(/<\/div>$/, `${lineNumbersWrapperCode}</div>`)
92
+ .replace(/"(language-\w+)"/, '"$1 line-numbers-mode"')
93
+
94
+ return finalCode
95
+ }
96
+ }
@@ -2,9 +2,10 @@ import type MarkdownIt from 'markdown-it'
2
2
 
3
3
  import Anchor from 'markdown-it-anchor'
4
4
  import Emoji from 'markdown-it-emoji'
5
- import Prism from 'markdown-it-prism'
6
5
  import LinkAttributes from 'markdown-it-link-attributes'
7
6
  import TOC from 'markdown-it-table-of-contents'
7
+ import TaskLists from 'markdown-it-task-lists'
8
+ import attrs from 'markdown-it-attrs'
8
9
 
9
10
  import type { KatexOptions } from 'katex'
10
11
  import Katex from '../markdown/markdown-it-katex'
@@ -12,6 +13,8 @@ import { containerPlugin } from '../markdown/markdown-it-container'
12
13
  import { headingPlugin } from '../markdown/headings'
13
14
  import { slugify } from './slugify'
14
15
  import { parseHeader } from './parseHeader'
16
+ import { highlight } from './highlight'
17
+ import { highlightLinePlugin, preWrapperPlugin } from './highlightLines'
15
18
 
16
19
  export interface Header {
17
20
  level: number
@@ -39,18 +42,25 @@ export interface MarkdownOptions extends MarkdownIt.Options {
39
42
  }
40
43
 
41
44
  export function setupMarkdownPlugins(md: MarkdownIt, mdOptions: MarkdownOptions = {}) {
45
+ md.set({
46
+ highlight,
47
+ })
42
48
  md
49
+ .use(highlightLinePlugin)
50
+ .use(preWrapperPlugin)
43
51
  .use(containerPlugin)
44
52
  .use(headingPlugin)
45
- // https://prismjs.com/
46
- md.use(Prism)
47
- md.use(LinkAttributes, {
48
- matcher: (link: string) => /^https?:\/\//.test(link),
49
- attrs: {
50
- target: '_blank',
51
- rel: 'noopener',
52
- },
53
- })
53
+ // .use(lineNumberPlugin)
54
+ // https://github.com/arve0/markdown-it-attrs
55
+ // add classes
56
+ md.use(attrs)
57
+ .use(LinkAttributes, {
58
+ matcher: (link: string) => /^https?:\/\//.test(link),
59
+ attrs: {
60
+ target: '_blank',
61
+ rel: 'noopener',
62
+ },
63
+ })
54
64
  md.use(Katex, mdOptions.katex)
55
65
  .use(Anchor, {
56
66
  slugify,
@@ -58,11 +68,12 @@ export function setupMarkdownPlugins(md: MarkdownIt, mdOptions: MarkdownOptions
58
68
  })
59
69
  .use(TOC, {
60
70
  slugify,
61
- includeLevel: [2, 3],
71
+ includeLevel: [2, 3, 4],
62
72
  format: parseHeader,
63
73
  ...mdOptions.toc,
64
74
  })
65
75
  .use(Emoji)
76
+ .use(TaskLists)
66
77
 
67
78
  const originalRender = md.render
68
79
  md.render = (...args) => {
@@ -41,11 +41,13 @@ function isValidDelim(state: any, pos: number) {
41
41
  function math_inline(state: any, silent: boolean) {
42
42
  let match, token, res, pos
43
43
 
44
- if (state.src[state.pos] !== '$') return false
44
+ if (state.src[state.pos] !== '$')
45
+ return false
45
46
 
46
47
  res = isValidDelim(state, state.pos)
47
48
  if (!res.can_open) {
48
- if (!silent) state.pending += '$'
49
+ if (!silent)
50
+ state.pending += '$'
49
51
  state.pos += 1
50
52
  return true
51
53
  }
@@ -64,20 +66,23 @@ function math_inline(state: any, silent: boolean) {
64
66
  while (state.src[pos] === '\\') pos -= 1
65
67
 
66
68
  // Even number of escapes, potential closing delimiter found
67
- if (((match - pos) % 2) === 1) break
69
+ if (((match - pos) % 2) === 1)
70
+ break
68
71
  match += 1
69
72
  }
70
73
 
71
74
  // No closing delimter found. Consume $ and continue.
72
75
  if (match === -1) {
73
- if (!silent) state.pending += '$'
76
+ if (!silent)
77
+ state.pending += '$'
74
78
  state.pos = start
75
79
  return true
76
80
  }
77
81
 
78
82
  // Check if we have empty content, ie: $$. Do not parse.
79
83
  if (match - start === 0) {
80
- if (!silent) state.pending += '$$'
84
+ if (!silent)
85
+ state.pending += '$$'
81
86
  state.pos = start + 1
82
87
  return true
83
88
  }
@@ -85,7 +90,8 @@ function math_inline(state: any, silent: boolean) {
85
90
  // Check for valid closing delimiter
86
91
  res = isValidDelim(state, match)
87
92
  if (!res.can_close) {
88
- if (!silent) state.pending += '$'
93
+ if (!silent)
94
+ state.pending += '$'
89
95
  state.pos = start
90
96
  return true
91
97
  }
@@ -106,13 +112,16 @@ function math_block(state: any, start: number, end: number, silent: boolean) {
106
112
  let pos = state.bMarks[start] + state.tShift[start]
107
113
  let max = state.eMarks[start]
108
114
 
109
- if (pos + 2 > max) return false
110
- if (state.src.slice(pos, pos + 2) !== '$$') return false
115
+ if (pos + 2 > max)
116
+ return false
117
+ if (state.src.slice(pos, pos + 2) !== '$$')
118
+ return false
111
119
 
112
120
  pos += 2
113
121
  firstLine = state.src.slice(pos, max)
114
122
 
115
- if (silent) return true
123
+ if (silent)
124
+ return true
116
125
  if (firstLine.trim().slice(-2) === '$$') {
117
126
  // Single line expression
118
127
  firstLine = firstLine.trim().slice(0, -2)
@@ -122,7 +131,8 @@ function math_block(state: any, start: number, end: number, silent: boolean) {
122
131
  for (next = start; !found;) {
123
132
  next++
124
133
 
125
- if (next >= end) break
134
+ if (next >= end)
135
+ break
126
136
 
127
137
  pos = state.bMarks[next] + state.tShift[next]
128
138
  max = state.eMarks[next]
@@ -48,8 +48,10 @@ export const removeNonCodeWrappedHTML = (str: string) => {
48
48
  }
49
49
 
50
50
  const compose = (...processors: ((str: string) => string)[]) => {
51
- if (processors.length === 0) return (input: string) => input
52
- if (processors.length === 1) return processors[0]
51
+ if (processors.length === 0)
52
+ return (input: string) => input
53
+ if (processors.length === 1)
54
+ return processors[0]
53
55
  return processors.reduce((prev, next) => {
54
56
  return str => next(prev(str))
55
57
  })
@@ -16,6 +16,7 @@ export interface ValaxyEntryOptions {
16
16
  }
17
17
 
18
18
  export interface ResolvedValaxyOptions {
19
+ mode: 'dev' | 'build'
19
20
  /**
20
21
  * Client root path
21
22
  * @default 'valaxy/src/client'
@@ -69,7 +70,7 @@ export function getThemeRoot(name: string, entry: string) {
69
70
  }
70
71
 
71
72
  // for cli options
72
- export async function resolveOptions(options: ValaxyEntryOptions) {
73
+ export async function resolveOptions(options: ValaxyEntryOptions, mode: ResolvedValaxyOptions['mode'] = 'dev') {
73
74
  const clientRoot = resolve(resolveImportPath('valaxy/package.json'), 'src/client')
74
75
  const userRoot = resolve(options.userRoot || process.cwd())
75
76
 
@@ -77,6 +78,7 @@ export async function resolveOptions(options: ValaxyEntryOptions) {
77
78
  const themeRoot = getThemeRoot(theme, userRoot)
78
79
 
79
80
  const valaxyOptions: ResolvedValaxyOptions = {
81
+ mode,
80
82
  clientRoot,
81
83
  userRoot,
82
84
  themeRoot,
@@ -8,33 +8,58 @@ import type { ResolvedValaxyOptions, ValaxyServerOptions } from '../options'
8
8
  import { toAtFS } from '../utils'
9
9
  import { VALAXY_CONFIG_ID } from './valaxy'
10
10
 
11
- export function createValaxyPlugin(options: ResolvedValaxyOptions, serverOptions: ValaxyServerOptions = {}): Plugin {
12
- const valaxyPrefix = '/@valaxy'
13
-
14
- let valaxyConfig = options.config
11
+ /**
12
+ * for /@valaxyjs/styles
13
+ * @param roots
14
+ * @returns
15
+ */
16
+ function generateStyles(roots: string[]) {
17
+ const imports: string[] = []
18
+
19
+ for (const root of roots) {
20
+ const styles: string[] = []
21
+
22
+ const autoloadNames = ['index', 'css-vars']
23
+ autoloadNames.forEach((name) => {
24
+ styles.push(join(root, 'styles', `${name}.css`))
25
+ styles.push(join(root, 'styles', `${name}.scss`))
26
+ })
27
+
28
+ for (const style of styles) {
29
+ if (fs.existsSync(style))
30
+ imports.push(`import "${toAtFS(style)}"`)
31
+ }
32
+ }
33
+ return imports.join('\n')
34
+ }
15
35
 
16
- const roots = [options.userRoot, options.themeRoot]
36
+ function generateLocales(roots: string[]) {
37
+ const imports: string[] = [
38
+ 'const messages = { "zh-CN": {}, en: {} }',
39
+ ]
40
+ const languages = ['zh-CN', 'en']
41
+
42
+ roots.forEach((root, i) => {
43
+ languages.forEach((lang) => {
44
+ const langYml = `${root}/locales/${lang}.yml`
45
+ if (fs.existsSync(langYml)) {
46
+ const varName = lang.replace('-', '') + i
47
+ imports.push(`import ${varName} from "${langYml}"`)
48
+ imports.push(`Object.assign(messages['${lang}'], ${varName})`)
49
+ }
50
+ })
51
+ })
17
52
 
18
- function generateUserStyles() {
19
- const imports: string[] = []
53
+ imports.push('export default messages')
54
+ return imports.join('\n')
55
+ }
20
56
 
21
- for (const root of roots) {
22
- const styles = [
23
- join(root, 'styles', 'vars.scss'),
24
- join(root, 'styles', 'index.css'),
25
- join(root, 'styles', 'index.scss'),
26
- ]
57
+ export function createValaxyPlugin(options: ResolvedValaxyOptions, serverOptions: ValaxyServerOptions = {}): Plugin {
58
+ const valaxyPrefix = '/@valaxy'
27
59
 
28
- for (const style of styles) {
29
- if (fs.existsSync(style)) {
30
- imports.push(`import "${toAtFS(style)}"`)
31
- continue
32
- }
33
- }
34
- }
60
+ let valaxyConfig = options.config
35
61
 
36
- return imports.join('\n')
37
- }
62
+ const roots = [options.clientRoot, options.themeRoot, options.userRoot]
38
63
 
39
64
  return {
40
65
  name: 'Valaxy',
@@ -60,7 +85,10 @@ export function createValaxyPlugin(options: ResolvedValaxyOptions, serverOptions
60
85
 
61
86
  // generate styles
62
87
  if (id === '/@valaxyjs/styles')
63
- return generateUserStyles()
88
+ return generateStyles(roots)
89
+
90
+ if (id === '/@valaxyjs/locales')
91
+ return generateLocales(roots)
64
92
 
65
93
  if (id.startsWith(valaxyPrefix))
66
94
  return ''
@@ -69,7 +97,8 @@ export function createValaxyPlugin(options: ResolvedValaxyOptions, serverOptions
69
97
  async handleHotUpdate(ctx) {
70
98
  // handle valaxy.config.ts hmr
71
99
  const { file, server } = ctx
72
- if (file !== options.configFile) return
100
+ if (file !== options.configFile)
101
+ return
73
102
 
74
103
  const { config } = await resolveConfig()
75
104
 
@@ -47,7 +47,7 @@ export function createMarkdownPlugin(options: ResolvedValaxyOptions): Plugin[] {
47
47
  handleHotUpdate(ctx) {
48
48
  const { file, server } = ctx
49
49
  // send headers
50
- if (file.endsWith('.md')) {
50
+ if (file.endsWith('.md') && _md && _md.__data) {
51
51
  server.ws.send({
52
52
  type: 'custom',
53
53
  event: 'valaxy:pageHeaders',
@@ -1,4 +1,3 @@
1
-
2
1
  import fs from 'fs'
3
2
  import type { PluginOption } from 'vite'
4
3
 
@@ -17,7 +16,6 @@ import Inspect from 'vite-plugin-inspect'
17
16
 
18
17
  import chalk from 'chalk'
19
18
  import type { ResolvedValaxyOptions, ValaxyServerOptions } from '../options'
20
- import type { Mode } from '../vite'
21
19
  import { setupMarkdownPlugins } from '../markdown'
22
20
  import { createMarkdownPlugin, excerpt_separator } from './markdown'
23
21
  import { createUnocssPlugin } from './unocss'
@@ -28,12 +26,11 @@ export interface ValaxyPluginOptions {
28
26
  components?: Parameters<typeof Components>[0]
29
27
  }
30
28
 
31
- export function ViteValaxyPlugins(
29
+ export async function ViteValaxyPlugins(
32
30
  options: ResolvedValaxyOptions,
33
31
  serverOptions: ValaxyServerOptions = {},
34
32
  pluginOptions: ValaxyPluginOptions = {},
35
- mode: Mode = 'dev',
36
- ): (PluginOption | PluginOption[])[] | undefined {
33
+ ): Promise<(PluginOption | PluginOption[])[] | undefined> {
37
34
  const { clientRoot, themeRoot, userRoot } = options
38
35
 
39
36
  const MarkdownPlugin = createMarkdownPlugin(options)
@@ -46,13 +43,49 @@ export function ViteValaxyPlugins(
46
43
 
47
44
  const roots = [clientRoot, themeRoot, userRoot]
48
45
 
46
+ const { default: ThemePlugin } = await import(`valaxy-theme-${options.theme}`)
47
+
48
+ const customElements = new Set([
49
+ // katex
50
+ 'annotation',
51
+ 'math',
52
+ 'menclose',
53
+ 'mfrac',
54
+ 'mglyph',
55
+ 'mi',
56
+ 'mlabeledtr',
57
+ 'mn',
58
+ 'mo',
59
+ 'mover',
60
+ 'mpadded',
61
+ 'mphantom',
62
+ 'mroot',
63
+ 'mrow',
64
+ 'mspace',
65
+ 'msqrt',
66
+ 'mstyle',
67
+ 'msub',
68
+ 'msubsup',
69
+ 'msup',
70
+ 'mtable',
71
+ 'mtd',
72
+ 'mtext',
73
+ 'mtr',
74
+ 'munder',
75
+ 'munderover',
76
+ 'semantics',
77
+
78
+ // meting
79
+ 'meting-js',
80
+ ])
81
+
49
82
  return [
50
83
  Vue({
51
84
  include: [/\.vue$/, /\.md$/],
52
85
  template: {
53
86
  compilerOptions: {
54
87
  isCustomElement: (tag) => {
55
- return ['meting-js'].includes(tag)
88
+ return customElements.has(tag)
56
89
  },
57
90
  },
58
91
  },
@@ -62,6 +95,8 @@ export function ViteValaxyPlugins(
62
95
  MarkdownPlugin,
63
96
  createConfigPlugin(options),
64
97
 
98
+ ThemePlugin(options.config.themeConfig),
99
+
65
100
  // https://github.com/hannoeru/vite-plugin-pages
66
101
  Pages({
67
102
  extensions: ['vue', 'md'],
@@ -71,7 +106,8 @@ export function ViteValaxyPlugins(
71
106
  */
72
107
  extendRoute(route) {
73
108
  let path = route.component
74
- if (!route.meta) route.meta = {}
109
+ if (!route.meta)
110
+ route.meta = {}
75
111
 
76
112
  if (route.path === '/')
77
113
  route.meta.layout = 'home'
@@ -82,7 +118,7 @@ export function ViteValaxyPlugins(
82
118
  path = pagePath
83
119
  })
84
120
  const md = fs.readFileSync(path, 'utf-8')
85
- const { data, excerpt } = matter(md, { excerpt_separator })
121
+ const { data, excerpt, content } = matter(md, { excerpt_separator })
86
122
 
87
123
  // warn for post frontmatter
88
124
  if (route.path.startsWith('/posts/')) {
@@ -98,7 +134,7 @@ export function ViteValaxyPlugins(
98
134
 
99
135
  // to refactor
100
136
  // get active header by runtime query head, not render
101
- mdIt.render(md)
137
+ mdIt.render(content)
102
138
  route.meta.headers = _md.__data?.headers
103
139
 
104
140
  // set default updated
@@ -178,6 +214,6 @@ export function ViteValaxyPlugins(
178
214
 
179
215
  // https://github.com/antfu/vite-plugin-inspect
180
216
  // Visit http://localhost:3333/__inspect/ to see the inspector
181
- mode === 'dev' && Inspect(),
217
+ options.mode === 'dev' && Inspect(),
182
218
  ]
183
219
  }
@@ -54,7 +54,7 @@ export const createUnocssConfig = (options: ResolvedValaxyOptions) => {
54
54
  ['yun-card', 'transition yun-transition shadow hover:shadow-lg'],
55
55
  ['btn', 'px-4 py-1 rounded inline-block bg-sky-600 text-white cursor-pointer hover:bg-sky-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],
56
56
  ['icon-btn', 'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-sky-600'],
57
- ['val-card', 'transition shadow hover:shadow-lg'],
57
+ ['va-card', 'transition shadow hover:shadow-lg'],
58
58
  ],
59
59
  presets: [
60
60
  presetUno(),
@@ -66,7 +66,12 @@ export const createUnocssConfig = (options: ResolvedValaxyOptions) => {
66
66
  presetTypography(),
67
67
  presetWebFonts({
68
68
  fonts: {
69
- serif: 'Noto Serif SC',
69
+ serif: [
70
+ {
71
+ name: 'Noto Serif SC',
72
+ weights: [900],
73
+ },
74
+ ],
70
75
  // sans: 'DM Sans',
71
76
  // mono: 'DM Mono',
72
77
  },
@@ -75,19 +80,19 @@ export const createUnocssConfig = (options: ResolvedValaxyOptions) => {
75
80
  rules: [
76
81
  // more see '~/styles/global/helper.scss'
77
82
  ['yun-transition', {
78
- 'transition-duration': 'var(--yun-transition-duration)',
83
+ 'transition-duration': 'var(--va-transition-duration)',
79
84
  }],
80
85
  ['yun-text-light', {
81
- color: 'var(--yun-c-text-light)',
86
+ color: 'var(--va-c-text-light)',
82
87
  }],
83
88
  ['font-serif', {
84
- 'font-family': 'var(--yun-font-serif)',
89
+ 'font-family': 'var(--va-font-serif)',
85
90
  }],
86
91
  ['font-sans', {
87
- 'font-family': 'var(--yun-font-sans)',
92
+ 'font-family': 'var(--va-font-sans)',
88
93
  }],
89
94
  ['font-mono', {
90
- 'font-family': 'var(--yun-font-mono)',
95
+ 'font-family': 'var(--va-font-mono)',
91
96
  }],
92
97
  ],
93
98
  transformers: [
@@ -0,0 +1,121 @@
1
+ import { dirname } from 'path'
2
+ import chalk from 'chalk'
3
+
4
+ import fg from 'fast-glob'
5
+ import fs from 'fs-extra'
6
+ import matter from 'gray-matter'
7
+ import MarkdownIt from 'markdown-it'
8
+ import type { Author, FeedOptions, Item } from 'feed'
9
+ import { Feed } from 'feed'
10
+ import consola from 'consola'
11
+ import type { ResolvedValaxyOptions } from './options'
12
+
13
+ const markdown = MarkdownIt({
14
+ html: true,
15
+ breaks: true,
16
+ linkify: true,
17
+ })
18
+
19
+ /**
20
+ * generate rss
21
+ * @param options
22
+ * @returns
23
+ */
24
+ export async function build(options: ResolvedValaxyOptions) {
25
+ const { config } = options
26
+
27
+ if (!config.url) {
28
+ consola.error('You must set "config.url" to generate rss.')
29
+ return
30
+ }
31
+
32
+ const siteUrl = config.url.endsWith('/') ? config.url : `${config.url}/`
33
+ const DOMAIN = config.url.endsWith('/') ? config.url.slice(0, -1) : config.url
34
+ const author: Author = {
35
+ name: options.config.author.name,
36
+ email: options.config.author.email,
37
+ link: options.config.author.link,
38
+ }
39
+
40
+ consola.info(`RSS Site Url: ${chalk.cyan(siteUrl)}`)
41
+
42
+ const ccVersion = (config.license.type === 'zero') ? '1.0' : '4.0'
43
+ const feedOptions: FeedOptions = {
44
+ title: config.title,
45
+ description: config.description,
46
+ id: siteUrl,
47
+ link: siteUrl,
48
+ copyright: `CC ${config.license.type.toUpperCase()} ${ccVersion} ${new Date().getFullYear()} © ${config.author.name}`,
49
+ feedLinks: {
50
+ json: `${siteUrl}feed.json`,
51
+ atom: `${siteUrl}feed.atom`,
52
+ rss: `${siteUrl}feed.xml`,
53
+ },
54
+ }
55
+
56
+ // generate
57
+ const files = await fg(`${options.userRoot}/pages/posts/*.md`)
58
+
59
+ const posts: Item[] = []
60
+ files
61
+ .forEach((i) => {
62
+ const raw = fs.readFileSync(i, 'utf-8')
63
+ const { data, content, excerpt } = matter(raw)
64
+
65
+ // not add to posts
66
+ if (!data.date) {
67
+ consola.warn(`Do you forget to write date for ${chalk.dim(i)}`)
68
+ return false
69
+ }
70
+
71
+ // todo i18n
72
+
73
+ // render excerpt
74
+ const html = markdown.render(excerpt || content)
75
+ .replace('src="/', `src="${DOMAIN}/`)
76
+
77
+ if (data.image?.startsWith('/'))
78
+ data.image = DOMAIN + data.image
79
+
80
+ posts.push({
81
+ title: '',
82
+ ...data,
83
+ date: new Date(data.date),
84
+ published: new Date(data.updated || data.date),
85
+ content: html,
86
+ author: [author],
87
+ link: DOMAIN + i.replace(/^pages(.+)\.md$/, '$1'),
88
+ })
89
+ })
90
+
91
+ // sort by updated
92
+ posts.sort((a, b) => +new Date(b.published || b.date) - +new Date(a.published || a.date))
93
+ // await writeFeed('feed', feedOptions, posts)
94
+
95
+ // write
96
+ feedOptions.author = author
97
+ feedOptions.image = config.author.avatar.startsWith('http') ? config.author.avatar : `${DOMAIN}${config.author.avatar}`
98
+ feedOptions.favicon = `${DOMAIN}/${config.feed.favicon}`
99
+
100
+ const feed = new Feed(feedOptions)
101
+ posts.forEach(item => feed.addItem(item))
102
+ // items.forEach(i=> console.log(i.title, i.date))
103
+
104
+ await fs.ensureDir(dirname(`./dist/${config.feed.name}`))
105
+ const path = './dist'
106
+
107
+ const types = ['xml', 'atom', 'json']
108
+ types.forEach((type) => {
109
+ let data = ''
110
+ let name = `${path}/${config.feed.name || 'feed'}.${type}`
111
+ if (type === 'xml') { data = feed.rss2() }
112
+ else if (type === 'atom') {
113
+ if (!config.feed.name)
114
+ name = `${path}/atom.xml`
115
+ data = feed.atom1()
116
+ }
117
+ else if (type === 'json') { data = feed.json1() }
118
+ fs.writeFileSync(name, data, 'utf-8')
119
+ consola.info(`${type}: ${name}`)
120
+ })
121
+ }