vitepress-plugin-field 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/dist/client/browser/index.d.ts +30 -0
- package/dist/client/browser/index.js +64 -0
- package/dist/client/ssr/index.d.ts +30 -0
- package/dist/client/ssr/index.js +56 -0
- package/dist/client/style.css +77 -0
- package/dist/node/index.d.ts +91 -0
- package/dist/node/index.js +124 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pengzhanbo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# vitepress-plugin-field
|
|
2
|
+
|
|
3
|
+
Render structured API fields and properties documentation in your VitePress site.
|
|
4
|
+
|
|
5
|
+
在 VitePress 中渲染结构化的 API 字段/属性文档。
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### With Vitepress-tuck
|
|
10
|
+
|
|
11
|
+
**Installation:**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# npm
|
|
15
|
+
npm install -D vitepress-tuck vitepress-plugin-field
|
|
16
|
+
# pnpm
|
|
17
|
+
pnpm add -D vitepress-tuck vitepress-plugin-field
|
|
18
|
+
# yarn
|
|
19
|
+
yarn add -D vitepress-tuck vitepress-plugin-field
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Configuration:**
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// .vitepress/config.ts
|
|
26
|
+
import field from 'vitepress-plugin-field'
|
|
27
|
+
import { defineConfig } from 'vitepress-tuck'
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
plugins: [field()],
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### With Vitepress
|
|
35
|
+
|
|
36
|
+
**Installation:**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# npm
|
|
40
|
+
npm install -D vitepress-plugin-field
|
|
41
|
+
# pnpm
|
|
42
|
+
pnpm add -D vitepress-plugin-field
|
|
43
|
+
# yarn
|
|
44
|
+
yarn add -D vitepress-plugin-field
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Configuration:**
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// .vitepress/config.ts
|
|
51
|
+
import { defineConfig } from 'vitepress'
|
|
52
|
+
import { fieldMarkdownPlugin } from 'vitepress-plugin-field'
|
|
53
|
+
|
|
54
|
+
export default defineConfig({
|
|
55
|
+
markdown: {
|
|
56
|
+
config: (md) => {
|
|
57
|
+
md.use(fieldMarkdownPlugin)
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// .vitepress/theme/index.ts
|
|
65
|
+
import { enhanceAppWithField } from 'vitepress-plugin-field/client'
|
|
66
|
+
import type { Theme } from 'vitepress'
|
|
67
|
+
import DefaultTheme from 'vitepress/theme'
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
extends: DefaultTheme,
|
|
71
|
+
enhanceApp(ctx) {
|
|
72
|
+
enhanceAppWithField(ctx)
|
|
73
|
+
},
|
|
74
|
+
} satisfies Theme
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Syntax
|
|
78
|
+
|
|
79
|
+
Use `::: field` containers to document API fields and properties, with JSDoc-style tags for metadata.
|
|
80
|
+
|
|
81
|
+
### Basic Field
|
|
82
|
+
|
|
83
|
+
```md
|
|
84
|
+
::: field count
|
|
85
|
+
@type Number
|
|
86
|
+
@default 0
|
|
87
|
+
|
|
88
|
+
Count value.
|
|
89
|
+
:::
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Field with Tags
|
|
93
|
+
|
|
94
|
+
Supported tags: `@name`, `@type`, `@default`, `@required`, `@deprecated`, `@optional`, `@description`.
|
|
95
|
+
|
|
96
|
+
```md
|
|
97
|
+
::: field userName
|
|
98
|
+
@type String
|
|
99
|
+
@required
|
|
100
|
+
User's unique identifier name.
|
|
101
|
+
:::
|
|
102
|
+
|
|
103
|
+
::: field email
|
|
104
|
+
@type String
|
|
105
|
+
@optional
|
|
106
|
+
@default example@mail.com
|
|
107
|
+
Contact email.
|
|
108
|
+
:::
|
|
109
|
+
|
|
110
|
+
::: field oldField
|
|
111
|
+
@type String
|
|
112
|
+
@deprecated
|
|
113
|
+
This field is deprecated. Please use a new field instead.
|
|
114
|
+
:::
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Field Group
|
|
118
|
+
|
|
119
|
+
Use `::: field-group` to group related fields together.
|
|
120
|
+
|
|
121
|
+
```md
|
|
122
|
+
::: field-group
|
|
123
|
+
|
|
124
|
+
::: field id
|
|
125
|
+
@type Number
|
|
126
|
+
@required
|
|
127
|
+
Unique identifier.
|
|
128
|
+
:::
|
|
129
|
+
|
|
130
|
+
::: field name
|
|
131
|
+
@type String
|
|
132
|
+
@optional
|
|
133
|
+
Display name.
|
|
134
|
+
:::
|
|
135
|
+
|
|
136
|
+
::: field createdAt
|
|
137
|
+
@type Date
|
|
138
|
+
@default Date.now()
|
|
139
|
+
Created at.
|
|
140
|
+
:::
|
|
141
|
+
|
|
142
|
+
:::
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Explicit Description
|
|
146
|
+
|
|
147
|
+
Use `@description` tag for explicit description text. Any non-tag line also feeds into description.
|
|
148
|
+
|
|
149
|
+
```md
|
|
150
|
+
::: field count
|
|
151
|
+
@description This field represents the total number of active users in the system.
|
|
152
|
+
@type Number
|
|
153
|
+
@default 0
|
|
154
|
+
:::
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Custom Field Name
|
|
158
|
+
|
|
159
|
+
Override the field name via `@name` tag.
|
|
160
|
+
|
|
161
|
+
```md
|
|
162
|
+
::: field count
|
|
163
|
+
@name count
|
|
164
|
+
@type Number
|
|
165
|
+
@default 0
|
|
166
|
+
:::
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Component
|
|
170
|
+
|
|
171
|
+
The `VPField` component is also available for direct use:
|
|
172
|
+
|
|
173
|
+
```vue
|
|
174
|
+
<VPField name="count" type="Number" required>
|
|
175
|
+
<p>User count.</p>
|
|
176
|
+
</VPField>
|
|
177
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EnhanceAppContext } from "vitepress";
|
|
2
|
+
|
|
3
|
+
//#region src/client/VPField.vue.d.ts
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
name: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
optional?: boolean;
|
|
9
|
+
deprecated?: boolean;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
};
|
|
12
|
+
declare var __VLS_1: {};
|
|
13
|
+
type __VLS_Slots = {} & {
|
|
14
|
+
default?: (props: typeof __VLS_1) => any;
|
|
15
|
+
};
|
|
16
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
20
|
+
new (): {
|
|
21
|
+
$slots: S;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/client/index.d.ts
|
|
26
|
+
declare function enhanceAppWithField({
|
|
27
|
+
app
|
|
28
|
+
}: EnhanceAppContext): void;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { _default as VPField, enhanceAppWithField };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import "../style.css";
|
|
2
|
+
import { computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, renderSlot, toDisplayString } from "vue";
|
|
3
|
+
//#region src/client/VPField.vue
|
|
4
|
+
const _hoisted_1 = { class: "field-meta" };
|
|
5
|
+
const _hoisted_2 = { class: "name" };
|
|
6
|
+
const _hoisted_3 = {
|
|
7
|
+
key: 1,
|
|
8
|
+
class: "deprecated"
|
|
9
|
+
};
|
|
10
|
+
const _hoisted_4 = {
|
|
11
|
+
key: 2,
|
|
12
|
+
class: "type"
|
|
13
|
+
};
|
|
14
|
+
const _hoisted_5 = {
|
|
15
|
+
key: 0,
|
|
16
|
+
class: "default-value"
|
|
17
|
+
};
|
|
18
|
+
const _hoisted_6 = {
|
|
19
|
+
key: 1,
|
|
20
|
+
class: "description"
|
|
21
|
+
};
|
|
22
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
23
|
+
__name: "VPField",
|
|
24
|
+
props: {
|
|
25
|
+
name: {},
|
|
26
|
+
type: {},
|
|
27
|
+
required: { type: Boolean },
|
|
28
|
+
optional: { type: Boolean },
|
|
29
|
+
deprecated: { type: Boolean },
|
|
30
|
+
defaultValue: {}
|
|
31
|
+
},
|
|
32
|
+
setup(__props) {
|
|
33
|
+
const badge = computed(() => __props.required ? "Required" : __props.optional ? "Optional" : "");
|
|
34
|
+
return (_ctx, _cache) => {
|
|
35
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(["vp-field", {
|
|
36
|
+
required: __props.required,
|
|
37
|
+
optional: __props.optional,
|
|
38
|
+
deprecated: __props.deprecated
|
|
39
|
+
}]) }, [
|
|
40
|
+
createElementVNode("p", _hoisted_1, [
|
|
41
|
+
createElementVNode("span", _hoisted_2, toDisplayString(__props.name), 1),
|
|
42
|
+
badge.value ? (openBlock(), createElementBlock("span", {
|
|
43
|
+
key: 0,
|
|
44
|
+
class: normalizeClass({
|
|
45
|
+
required: __props.required,
|
|
46
|
+
optional: __props.optional
|
|
47
|
+
})
|
|
48
|
+
}, toDisplayString(badge.value), 3)) : createCommentVNode("v-if", true),
|
|
49
|
+
__props.deprecated ? (openBlock(), createElementBlock("span", _hoisted_3, "Deprecated")) : createCommentVNode("v-if", true),
|
|
50
|
+
__props.type ? (openBlock(), createElementBlock("span", _hoisted_4, [createElementVNode("code", null, toDisplayString(__props.type), 1)])) : createCommentVNode("v-if", true)
|
|
51
|
+
]),
|
|
52
|
+
__props.defaultValue ? (openBlock(), createElementBlock("p", _hoisted_5, [createElementVNode("code", null, toDisplayString(__props.defaultValue), 1)])) : createCommentVNode("v-if", true),
|
|
53
|
+
_ctx.$slots.default ? (openBlock(), createElementBlock("div", _hoisted_6, [renderSlot(_ctx.$slots, "default")])) : createCommentVNode("v-if", true)
|
|
54
|
+
], 2);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/client/index.ts
|
|
60
|
+
function enhanceAppWithField({ app }) {
|
|
61
|
+
app.component("VPField", _sfc_main);
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { _sfc_main as VPField, enhanceAppWithField };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { EnhanceAppContext } from "vitepress";
|
|
2
|
+
|
|
3
|
+
//#region src/client/VPField.vue.d.ts
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
name: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
optional?: boolean;
|
|
9
|
+
deprecated?: boolean;
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
};
|
|
12
|
+
declare var __VLS_1: {};
|
|
13
|
+
type __VLS_Slots = {} & {
|
|
14
|
+
default?: (props: typeof __VLS_1) => any;
|
|
15
|
+
};
|
|
16
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
18
|
+
declare const _default: typeof __VLS_export;
|
|
19
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
20
|
+
new (): {
|
|
21
|
+
$slots: S;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/client/index.d.ts
|
|
26
|
+
declare function enhanceAppWithField({
|
|
27
|
+
app
|
|
28
|
+
}: EnhanceAppContext): void;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { _default as VPField, enhanceAppWithField };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { computed, defineComponent, mergeProps, useSSRContext } from "vue";
|
|
2
|
+
import { ssrInterpolate, ssrRenderAttrs, ssrRenderClass, ssrRenderSlot } from "vue/server-renderer";
|
|
3
|
+
//#region src/client/VPField.vue
|
|
4
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
5
|
+
__name: "VPField",
|
|
6
|
+
__ssrInlineRender: true,
|
|
7
|
+
props: {
|
|
8
|
+
name: {},
|
|
9
|
+
type: {},
|
|
10
|
+
required: { type: Boolean },
|
|
11
|
+
optional: { type: Boolean },
|
|
12
|
+
deprecated: { type: Boolean },
|
|
13
|
+
defaultValue: {}
|
|
14
|
+
},
|
|
15
|
+
setup(__props) {
|
|
16
|
+
const badge = computed(() => __props.required ? "Required" : __props.optional ? "Optional" : "");
|
|
17
|
+
return (_ctx, _push, _parent, _attrs) => {
|
|
18
|
+
_push(`<div${ssrRenderAttrs(mergeProps({ class: ["vp-field", {
|
|
19
|
+
required: __props.required,
|
|
20
|
+
optional: __props.optional,
|
|
21
|
+
deprecated: __props.deprecated
|
|
22
|
+
}] }, _attrs))}><p class="field-meta"><span class="name">${ssrInterpolate(__props.name)}</span>`);
|
|
23
|
+
if (badge.value) _push(`<span class="${ssrRenderClass({
|
|
24
|
+
required: __props.required,
|
|
25
|
+
optional: __props.optional
|
|
26
|
+
})}">${ssrInterpolate(badge.value)}</span>`);
|
|
27
|
+
else _push(`<!---->`);
|
|
28
|
+
if (__props.deprecated) _push(`<span class="deprecated">Deprecated</span>`);
|
|
29
|
+
else _push(`<!---->`);
|
|
30
|
+
if (__props.type) _push(`<span class="type"><code>${ssrInterpolate(__props.type)}</code></span>`);
|
|
31
|
+
else _push(`<!---->`);
|
|
32
|
+
_push(`</p>`);
|
|
33
|
+
if (__props.defaultValue) _push(`<p class="default-value"><code>${ssrInterpolate(__props.defaultValue)}</code></p>`);
|
|
34
|
+
else _push(`<!---->`);
|
|
35
|
+
if (_ctx.$slots.default) {
|
|
36
|
+
_push(`<div class="description">`);
|
|
37
|
+
ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent);
|
|
38
|
+
_push(`</div>`);
|
|
39
|
+
} else _push(`<!---->`);
|
|
40
|
+
_push(`</div>`);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const _sfc_setup = _sfc_main.setup;
|
|
45
|
+
_sfc_main.setup = (props, ctx) => {
|
|
46
|
+
const ssrContext = useSSRContext();
|
|
47
|
+
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/client/VPField.vue");
|
|
48
|
+
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/client/index.ts
|
|
52
|
+
function enhanceAppWithField({ app }) {
|
|
53
|
+
app.component("VPField", _sfc_main);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { _sfc_main as VPField, enhanceAppWithField };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
.vp-field {
|
|
2
|
+
width: 100%;
|
|
3
|
+
margin: 16px 0;
|
|
4
|
+
transition: border-color 0.25s ease;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.vp-field + .vp-field {
|
|
8
|
+
padding-top: 8px;
|
|
9
|
+
border-top: solid 1px var(--vp-c-divider);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.vp-field .field-meta {
|
|
13
|
+
display: flex;
|
|
14
|
+
gap: 8px;
|
|
15
|
+
align-items: flex-start;
|
|
16
|
+
margin: 8px 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.vp-field .field-meta .name {
|
|
20
|
+
font-size: 18px;
|
|
21
|
+
font-weight: 500;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.vp-field.deprecated .field-meta .name {
|
|
25
|
+
text-decoration: line-through;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.vp-field .field-meta .required,
|
|
29
|
+
.vp-field .field-meta .optional,
|
|
30
|
+
.vp-field .field-meta .deprecated {
|
|
31
|
+
display: inline-block;
|
|
32
|
+
padding: 2px 8px;
|
|
33
|
+
font-size: 12px;
|
|
34
|
+
font-style: italic;
|
|
35
|
+
line-height: 1;
|
|
36
|
+
border-radius: 8px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.vp-field .field-meta .required {
|
|
40
|
+
color: var(--vp-c-success-2);
|
|
41
|
+
border: solid 1px var(--vp-c-success-2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.vp-field .field-meta .optional {
|
|
45
|
+
color: var(--vp-c-text-3);
|
|
46
|
+
border: solid 1px var(--vp-c-divider);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.vp-field .field-meta .deprecated {
|
|
50
|
+
color: var(--vp-c-danger-2);
|
|
51
|
+
border: solid 1px var(--vp-c-danger-2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.vp-field .field-meta .type {
|
|
55
|
+
flex: 1 2;
|
|
56
|
+
text-align: right;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.vp-field .default-value {
|
|
60
|
+
margin: 0;
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
line-height: 1.7;
|
|
63
|
+
transform: translateY(-4px);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.vp-field .description :where(p, ul, ol) {
|
|
67
|
+
margin: 8px 0;
|
|
68
|
+
line-height: 24px;
|
|
69
|
+
color: var(--vp-c-text-2);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.vp-field-group {
|
|
73
|
+
padding: 0 20px;
|
|
74
|
+
margin: 16px 0;
|
|
75
|
+
border: solid 1px var(--vp-c-divider);
|
|
76
|
+
border-radius: 6px;
|
|
77
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { PluginSimple } from "markdown-it";
|
|
2
|
+
|
|
3
|
+
//#region src/node/fieldPlugin.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parsed result from a `::: field` container block.
|
|
6
|
+
*
|
|
7
|
+
* 字段对象 — 解析自 `::: field` 容器块的结构化结果
|
|
8
|
+
*/
|
|
9
|
+
interface FieldObject {
|
|
10
|
+
/**
|
|
11
|
+
* Field name — defaults to `info`, overridable via `@name`
|
|
12
|
+
*
|
|
13
|
+
* 字段名称 — 默认从 `info` 中获取,可通过 `@name` 覆盖
|
|
14
|
+
*/
|
|
15
|
+
name: string;
|
|
16
|
+
/**
|
|
17
|
+
* `@type` value, undefined if not set
|
|
18
|
+
*
|
|
19
|
+
* 类型注解 — 如果未设置则为 `undefined`
|
|
20
|
+
*/
|
|
21
|
+
type?: string;
|
|
22
|
+
/**
|
|
23
|
+
* `@default` value, preserved as-is
|
|
24
|
+
*
|
|
25
|
+
* 默认值 — 保持原样
|
|
26
|
+
*/
|
|
27
|
+
default?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether `@required` is present
|
|
30
|
+
*
|
|
31
|
+
* 是否为必填项 — 是否在 `@required` 标签中
|
|
32
|
+
*/
|
|
33
|
+
required?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether `@deprecated` is present
|
|
36
|
+
*
|
|
37
|
+
* 是否为已弃用项 — 是否在 `@deprecated` 标签中
|
|
38
|
+
*/
|
|
39
|
+
deprecated?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether `@optional` is present
|
|
42
|
+
*
|
|
43
|
+
* 是否为可选项 — 是否在 `@optional` 标签中
|
|
44
|
+
*/
|
|
45
|
+
optional?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Description text, may span multiple lines joined by `\n`
|
|
48
|
+
*
|
|
49
|
+
* 描述文本 — 可跨多行,以 `\n` 连接
|
|
50
|
+
*/
|
|
51
|
+
description?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse the body of a `::: field` container into a structured `FieldObject`.
|
|
55
|
+
*
|
|
56
|
+
* Supports a JSDoc-style tag syntax:
|
|
57
|
+
* - `@name` — override the field name (derived from `info` by default)
|
|
58
|
+
* - `@type` — type annotation
|
|
59
|
+
* - `@default` — default value
|
|
60
|
+
* - `@required` — mark as required (boolean flag)
|
|
61
|
+
* - `@deprecated` — mark as deprecated (boolean flag)
|
|
62
|
+
* - `@optional` — mark as optional (boolean flag)
|
|
63
|
+
* - `@description` — explicit description; any non-tag line also feeds into description
|
|
64
|
+
*
|
|
65
|
+
* Unknown `@`-prefixed tags are treated as description text.
|
|
66
|
+
* Empty lines are ignored and never interrupt a description paragraph.
|
|
67
|
+
*
|
|
68
|
+
* 将 `::: field` 容器的正文解析为结构化的 `FieldObject`。
|
|
69
|
+
*
|
|
70
|
+
* 支持类 JSDoc 的标签语法:
|
|
71
|
+
* - `@name` — 覆盖字段名称(默认从 `info` 派生)
|
|
72
|
+
* - `@type` — 类型注解
|
|
73
|
+
* - `@default` — 默认值
|
|
74
|
+
* - `@required` — 标记为必需(布尔标志)
|
|
75
|
+
* - `@deprecated` — 标记为已弃用(布尔标志)
|
|
76
|
+
* - `@optional` — 标记为可选(布尔标志)
|
|
77
|
+
* - `@description` — 显式描述;任何非标签行也会被纳入描述
|
|
78
|
+
*
|
|
79
|
+
* 未知的以 `@` 开头的标签将被视为描述文本。
|
|
80
|
+
* 空行会被忽略,且不会中断描述段落。
|
|
81
|
+
*
|
|
82
|
+
* @param content Raw text inside the `:::` container
|
|
83
|
+
* @param info Text after `::: field` on the opening line (the field name)
|
|
84
|
+
*/
|
|
85
|
+
declare function parseFieldContent(content: string, info: string): FieldObject;
|
|
86
|
+
declare const fieldMarkdownPlugin: PluginSimple;
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/node/index.d.ts
|
|
89
|
+
declare const _default: (option?: unknown) => import("vitepress-tuck").VitepressPlugin;
|
|
90
|
+
//#endregion
|
|
91
|
+
export { type FieldObject, _default as default, fieldMarkdownPlugin, parseFieldContent };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { definePlugin } from "vitepress-tuck";
|
|
2
|
+
import { isUndefined } from "@pengzhanbo/utils";
|
|
3
|
+
import { createContainerPlugin, createContainerSyntaxPlugin, stringifyAttrs } from "vitepress-plugin-toolkit";
|
|
4
|
+
//#region src/node/fieldPlugin.ts
|
|
5
|
+
/** Tags that carry structured meaning; everything else is description text. */
|
|
6
|
+
const KNOWN_TAGS = new Set([
|
|
7
|
+
"name",
|
|
8
|
+
"type",
|
|
9
|
+
"default",
|
|
10
|
+
"required",
|
|
11
|
+
"deprecated",
|
|
12
|
+
"optional",
|
|
13
|
+
"description"
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* Parse the body of a `::: field` container into a structured `FieldObject`.
|
|
17
|
+
*
|
|
18
|
+
* Supports a JSDoc-style tag syntax:
|
|
19
|
+
* - `@name` — override the field name (derived from `info` by default)
|
|
20
|
+
* - `@type` — type annotation
|
|
21
|
+
* - `@default` — default value
|
|
22
|
+
* - `@required` — mark as required (boolean flag)
|
|
23
|
+
* - `@deprecated` — mark as deprecated (boolean flag)
|
|
24
|
+
* - `@optional` — mark as optional (boolean flag)
|
|
25
|
+
* - `@description` — explicit description; any non-tag line also feeds into description
|
|
26
|
+
*
|
|
27
|
+
* Unknown `@`-prefixed tags are treated as description text.
|
|
28
|
+
* Empty lines are ignored and never interrupt a description paragraph.
|
|
29
|
+
*
|
|
30
|
+
* 将 `::: field` 容器的正文解析为结构化的 `FieldObject`。
|
|
31
|
+
*
|
|
32
|
+
* 支持类 JSDoc 的标签语法:
|
|
33
|
+
* - `@name` — 覆盖字段名称(默认从 `info` 派生)
|
|
34
|
+
* - `@type` — 类型注解
|
|
35
|
+
* - `@default` — 默认值
|
|
36
|
+
* - `@required` — 标记为必需(布尔标志)
|
|
37
|
+
* - `@deprecated` — 标记为已弃用(布尔标志)
|
|
38
|
+
* - `@optional` — 标记为可选(布尔标志)
|
|
39
|
+
* - `@description` — 显式描述;任何非标签行也会被纳入描述
|
|
40
|
+
*
|
|
41
|
+
* 未知的以 `@` 开头的标签将被视为描述文本。
|
|
42
|
+
* 空行会被忽略,且不会中断描述段落。
|
|
43
|
+
*
|
|
44
|
+
* @param content Raw text inside the `:::` container
|
|
45
|
+
* @param info Text after `::: field` on the opening line (the field name)
|
|
46
|
+
*/
|
|
47
|
+
function parseFieldContent(content, info) {
|
|
48
|
+
const lines = content.split("\n");
|
|
49
|
+
const result = {
|
|
50
|
+
name: info.trim(),
|
|
51
|
+
description: ""
|
|
52
|
+
};
|
|
53
|
+
/** Accumulates the current description paragraph. */
|
|
54
|
+
let currentDesc = "";
|
|
55
|
+
/** Completed description segments, joined with `\n` at the end. */
|
|
56
|
+
const descriptions = [];
|
|
57
|
+
function flushDesc() {
|
|
58
|
+
if (currentDesc) {
|
|
59
|
+
descriptions.push(currentDesc);
|
|
60
|
+
currentDesc = "";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const rawLine of lines) {
|
|
64
|
+
const line = rawLine.trim();
|
|
65
|
+
if (line.startsWith("@")) {
|
|
66
|
+
const spaceIdx = line.indexOf(" ");
|
|
67
|
+
let tag;
|
|
68
|
+
let rest;
|
|
69
|
+
if (spaceIdx === -1) {
|
|
70
|
+
tag = line.slice(1);
|
|
71
|
+
rest = "";
|
|
72
|
+
} else {
|
|
73
|
+
tag = line.slice(1, spaceIdx).toLowerCase();
|
|
74
|
+
rest = line.slice(spaceIdx + 1).trim();
|
|
75
|
+
}
|
|
76
|
+
if (KNOWN_TAGS.has(tag)) {
|
|
77
|
+
flushDesc();
|
|
78
|
+
switch (tag) {
|
|
79
|
+
case "name":
|
|
80
|
+
case "type":
|
|
81
|
+
case "default":
|
|
82
|
+
rest && (result[tag] = rest);
|
|
83
|
+
break;
|
|
84
|
+
case "required":
|
|
85
|
+
case "deprecated":
|
|
86
|
+
case "optional":
|
|
87
|
+
result[tag] = true;
|
|
88
|
+
break;
|
|
89
|
+
case "description":
|
|
90
|
+
currentDesc = rest;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
if (currentDesc) currentDesc += "\n";
|
|
95
|
+
currentDesc += line;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
if (currentDesc) currentDesc += "\n";
|
|
99
|
+
currentDesc += line;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
flushDesc();
|
|
103
|
+
result.description = descriptions.join("\n");
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
const fieldMarkdownPlugin = (md) => {
|
|
107
|
+
createContainerPlugin(md, "field-group", { before: () => "<div class=\"vp-field-group\">" });
|
|
108
|
+
createContainerSyntaxPlugin(md, "field", (tokens, idx, _, env) => {
|
|
109
|
+
const { info, content } = tokens[idx];
|
|
110
|
+
const { description, type, default: defaultValue, ...props } = parseFieldContent(content, info);
|
|
111
|
+
return `<VPField${stringifyAttrs(props)}${isUndefined(type) ? "" : ` type="${type}"`}${isUndefined(defaultValue) ? "" : ` default-value="${defaultValue}"`}>${description ? md.render(description, env) : ""}</VPField>`;
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/node/index.ts
|
|
116
|
+
var node_default = definePlugin(() => ({
|
|
117
|
+
name: "vitepress-plugin-field",
|
|
118
|
+
client: { enhance: "enhanceAppWithField" },
|
|
119
|
+
markdown: { config(md) {
|
|
120
|
+
md.use(fieldMarkdownPlugin);
|
|
121
|
+
} }
|
|
122
|
+
}));
|
|
123
|
+
//#endregion
|
|
124
|
+
export { node_default as default, fieldMarkdownPlugin, parseFieldContent };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vitepress-plugin-field",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"description": "Render file tree structure in your VitePress site.",
|
|
6
|
+
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pengzhanbo/vitepress-tuck.git",
|
|
11
|
+
"directory": "packages/plugin-field"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"vitepress",
|
|
15
|
+
"vitepress-plugin",
|
|
16
|
+
"field"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./dist/node/index.js",
|
|
20
|
+
"./client": {
|
|
21
|
+
"browser": "./dist/client/browser/index.js",
|
|
22
|
+
"default": "./dist/client/ssr/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./style.css": "./dist/client/style.css"
|
|
25
|
+
},
|
|
26
|
+
"module": "./dist/node/index.js",
|
|
27
|
+
"types": "./dist/node/index.d.ts",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vitepress": "^1.6.4 || ^2.0.0-alpha.17",
|
|
33
|
+
"vue": "^3.5.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@pengzhanbo/utils": "^3.7.3",
|
|
37
|
+
"vitepress-plugin-toolkit": "0.1.1",
|
|
38
|
+
"vitepress-tuck": "0.1.1"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"clean": "rimraf --glob ./dist",
|
|
45
|
+
"dev": "pnpm '/(tsdown|copy):watch/'",
|
|
46
|
+
"build": "pnpm tsdown && pnpm copy",
|
|
47
|
+
"copy": "cpx \"src/**/*.css\" dist",
|
|
48
|
+
"copy:watch": "pnpm copy -w",
|
|
49
|
+
"tsdown": "tsdown --config-loader unrun",
|
|
50
|
+
"tsdown:watch": "pnpm tsdown -w"
|
|
51
|
+
}
|
|
52
|
+
}
|