Skip to content

Commit 7b7068e

Browse files
authored
feat(plugin-notice): add markdown format support for notice content, close #376 (#382)
1 parent 4270cd9 commit 7b7068e

File tree

20 files changed

+310
-59
lines changed

20 files changed

+310
-59
lines changed

docs/plugins/features/notice.md

+36-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ Each notice options needs to contain a `path` or `match` option, which is used t
3333
A notice configuration item includes:
3434

3535
- `title`: Notice title, support both text and HTMLString
36-
- `content`: Notice content, support both text and HTMLString
36+
- `content`: Notice content, support both text, HTMLString and Markdown
37+
38+
- When using `Markdown` as content, the `contentType` should be set to `markdown`.
39+
40+
- You can also use `contentFile` to specify the absolute path of a file, with the file format being `.md` or `.html`, to read the notice content from the file.
41+
3742
- `actions`: Notice actions
3843

3944
Should be an array of objects containing:
@@ -51,6 +56,7 @@ Here is an example:
5156

5257
```ts
5358
import { noticePlugin } from '@vuepress/plugin-notice'
59+
import { path } from 'vuepress/utils'
5460

5561
export default {
5662
plugins: [
@@ -72,7 +78,21 @@ export default {
7278
{
7379
path: '/zh/',
7480
title: 'Notice Title',
75-
content: 'Notice Content',
81+
contentType: 'markdown',
82+
content: '**Notice Content** [link](https://example.com)',
83+
actions: [
84+
{
85+
text: 'Primary Action',
86+
link: 'https://theme-hope.vuejs.press/',
87+
type: 'primary',
88+
},
89+
{ text: 'Default Action' },
90+
],
91+
},
92+
{
93+
path: '/example/',
94+
title: 'Notice Title',
95+
contentFile: path.resolve(__dirname, 'notice.md'),
7696
actions: [
7797
{
7898
text: 'Primary Action',
@@ -132,7 +152,20 @@ However, if you want users to confirm the notice, you can set `confirm: true`, s
132152
/**
133153
* Notice content
134154
*/
135-
content: string
155+
content?: string
156+
157+
/**
158+
* Notice content type
159+
* @default 'html'
160+
*/
161+
contentType?: 'html' | 'markdown'
162+
163+
/**
164+
* Notice content file absolute path, file format should be `.md` or `.html`.
165+
* Prioritize using the file content as `content`.
166+
* @example '/path/to/notice.md'
167+
*/
168+
contentFile?: string
136169

137170
/**
138171
* Notice key

docs/zh/plugins/features/notice.md

+34-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ export default {
3333
一个公告配置包括:
3434

3535
- `title`: 通知标题,支持文本和 HTMLString
36-
- `content`: 通知内容,支持文本和 HTMLString
36+
- `content`: 通知内容,支持文本、HTMLString 和 Markdown
37+
38+
- 使用 `Markdown` 作为内容时,应设置 `contentType``markdown`
39+
- 还可以使用 `contentFile` 指定文件绝对路径,文件格式为 `.md``.html`,从文件中读取通知内容。
40+
3741
- `actions`: 通知操作
3842

3943
应该是包含以下内容的对象数组:
@@ -51,6 +55,7 @@ export default {
5155

5256
```ts
5357
import { noticePlugin } from '@vuepress/plugin-notice'
58+
import { path } from 'vuepress/utils'
5459

5560
export default {
5661
plugins: [
@@ -72,7 +77,21 @@ export default {
7277
{
7378
path: '/zh/',
7479
title: 'Notice Title',
75-
content: 'Notice Content',
80+
contentType: 'markdown',
81+
content: '**Notice Content** [link](https://example.com)',
82+
actions: [
83+
{
84+
text: 'Primary Action',
85+
link: 'https://theme-hope.vuejs.press/',
86+
type: 'primary',
87+
},
88+
{ text: 'Default Action' },
89+
],
90+
},
91+
{
92+
path: '/example/',
93+
title: 'Notice Title',
94+
contentFile: path.resolve(__dirname, 'notice.md'),
7695
actions: [
7796
{
7897
text: 'Primary Action',
@@ -134,6 +153,19 @@ export default {
134153
*/
135154
content: string
136155

156+
/**
157+
* 通知内容类型
158+
* @default 'html'
159+
*/
160+
contentType?: 'html' | 'markdown'
161+
162+
/**
163+
* 通知内容文件绝对路径, 文件格式支持 `.md` 或 `.html`
164+
* 优先使用文件内容作为 `content`
165+
* @example '/path/to/notice.md'
166+
*/
167+
contentFile?: string
168+
137169
/**
138170
* Notice 的 key
139171
*

e2e/docs/.vuepress/config.ts

+13
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,19 @@ export default defineUserConfig({
256256
],
257257
fullscreen: true,
258258
},
259+
{
260+
match: /^\/notice\/file\.html$/,
261+
title: 'Notice Title',
262+
contentFile: path.resolve(__dirname, './notice.md'),
263+
actions: [
264+
{
265+
text: 'Primary Action',
266+
link: 'https://example.com/',
267+
type: 'primary',
268+
},
269+
{ text: 'Default Action' },
270+
],
271+
},
259272
],
260273
}),
261274
photoSwipePlugin(),

e2e/docs/.vuepress/notice.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**Notice Content** [link](https://example.com)

e2e/docs/notice/file.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Load Notice Content From File

e2e/tests/plugin-notice/notice.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,19 @@ test.describe('notice', () => {
3434

3535
await expect(page.locator('.vp-notice-wrapper')).toHaveCount(0)
3636
})
37+
38+
test('load notice content from markdown file', async ({ page }) => {
39+
await page.goto('notice/file.html')
40+
41+
await expect(page.locator('.vp-notice-wrapper')).toHaveCount(1)
42+
43+
await expect(page.locator('.vp-notice-content strong')).toHaveText(
44+
'Notice Content',
45+
)
46+
47+
await expect(page.locator('.vp-notice-content a')).toHaveAttribute(
48+
'href',
49+
'https://example.com',
50+
)
51+
})
3752
})

plugins/features/plugin-notice/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"dependencies": {
4141
"@vuepress/helper": "workspace:*",
4242
"@vueuse/core": "^12.7.0",
43+
"chokidar": "^3.6.0",
4344
"vue": "^3.5.13"
4445
},
4546
"peerDependencies": {

plugins/features/plugin-notice/src/client/components/Notice.ts

+4-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { isLinkAbsolute, isLinkHttp, startsWith } from '@vuepress/helper/client'
22
import { watchImmediate } from '@vueuse/core'
3-
import type { PropType } from 'vue'
43
import {
54
TransitionGroup,
65
computed,
@@ -10,38 +9,23 @@ import {
109
ref,
1110
} from 'vue'
1211
import { useRoutePath, useRouter } from 'vuepress/client'
13-
import type { NoticeItemOptions } from '../../shared/index.js'
12+
import { useNoticeOptions } from '../composables/index.js'
1413
import { CloseIcon } from './CloseIcon.js'
1514

1615
import '../styles/notice.css'
1716

18-
type NoticeClientOption = Omit<NoticeItemOptions, 'key'> & {
19-
noticeKey?: string
20-
} & ({ match: string } | { path: string })
21-
2217
export const Notice = defineComponent({
2318
name: 'Notice',
2419

25-
props: {
26-
/**
27-
* Notice locales settings
28-
*
29-
* 通知的多语言设置
30-
*/
31-
config: {
32-
type: Array as PropType<NoticeClientOption[]>,
33-
required: true,
34-
},
35-
},
36-
37-
setup(props) {
20+
setup() {
3821
const router = useRouter()
3922
const routePath = useRoutePath()
23+
const noticeOptions = useNoticeOptions()
4024

4125
const isVisible = ref(false)
4226

4327
const matchedConfig = computed(() => {
44-
const option = props.config.find((item) =>
28+
const option = noticeOptions.value.find((item) =>
4529
'match' in item
4630
? new RegExp(item.match).test(routePath.value)
4731
: startsWith(routePath.value, item.path),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useNoticeOptions.js'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NOTICE_OPTIONS } from '@internal/noticeOptions'
2+
import type { Ref } from 'vue'
3+
import { ref } from 'vue'
4+
import type { NoticeAttrOptions } from '../../shared/index.js'
5+
6+
declare const __VUE_HMR_RUNTIME__: Record<string, unknown>
7+
8+
export type NoticeOptionsRef = Ref<NoticeAttrOptions[]>
9+
10+
export const noticeOptions: NoticeOptionsRef = ref(
11+
NOTICE_OPTIONS,
12+
) as NoticeOptionsRef
13+
14+
export const useNoticeOptions = (): NoticeOptionsRef => noticeOptions
15+
16+
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
17+
__VUE_HMR_RUNTIME__.updateNoticeOptions = (data: NoticeAttrOptions[]) => {
18+
noticeOptions.value = data
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { h } from 'vue'
22
import { defineClientConfig } from 'vuepress/client'
3-
import type { NoticeAttrOptions } from '../shared/index.js'
43
import { Notice } from './components/index.js'
54

65
import './styles/vars.css'
76

8-
declare const __NOTICE_OPTIONS__: NoticeAttrOptions[]
9-
107
export default defineClientConfig({
11-
rootComponents: [() => h(Notice, { config: __NOTICE_OPTIONS__ })],
8+
rootComponents: [() => h(Notice)],
129
})
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './components/index.js'
2+
export * from './composables/index.js'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module '@internal/noticeOptions' {
2+
import type { NoticeAttrOptions } from '../shared/index.js'
3+
4+
export const NOTICE_OPTIONS: NoticeAttrOptions[]
5+
}

plugins/features/plugin-notice/src/node/getNoticeOptions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export const getNoticeOptions = (
44
options: NoticeOptions[] = [],
55
): NoticeAttrOptions[] =>
66
options
7-
.map(({ key, ...item }) =>
7+
.map(({ key, contentType, contentFile, ...item }) =>
88
'match' in item
99
? {
1010
...item,

plugins/features/plugin-notice/src/node/noticePlugin.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import type { Plugin } from 'vuepress/core'
2-
import { getDirname, logger, path } from 'vuepress/utils'
3-
import { getNoticeOptions } from './getNoticeOptions.js'
4-
import { PLUGIN_NAME } from './logger.js'
2+
import { getDirname, path } from 'vuepress/utils'
3+
import { PLUGIN_NAME, logger } from './logger.js'
54
import type { NoticePluginOptions } from './options.js'
5+
import {
6+
prepareNoticeOptions,
7+
watchNoticeOptions,
8+
} from './prepareNoticeOptions.js'
69

710
const __dirname = import.meta.dirname || getDirname(import.meta.url)
811

@@ -14,8 +17,12 @@ export const noticePlugin =
1417
return {
1518
name: PLUGIN_NAME,
1619

17-
define: {
18-
__NOTICE_OPTIONS__: getNoticeOptions(options.config),
20+
onPrepared: async () => {
21+
await prepareNoticeOptions(app, options.config)
22+
},
23+
24+
onWatched: (_, watchers) => {
25+
watchers.push(watchNoticeOptions(app, options.config))
1926
},
2027

2128
clientConfigFile: path.resolve(__dirname, '../client/config.js'),

0 commit comments

Comments
 (0)