vitepress-plugin-abbr 0.5.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.
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,155 @@
1
+ # vitepress-plugin-abbr
2
+
3
+ Add abbreviation support to your VitePress site, rendering abbreviations with an interactive tooltip
4
+ that displays the full description on hover or focus.
5
+
6
+ 为 VitePress 添加缩写词支持,将缩写词渲染为可交互的提示框,在悬停或聚焦时显示其完整描述。
7
+
8
+ Forked and modified from [`markdown-it-abbr`](https://github.com/markdown-it/markdown-it-abbr).
9
+
10
+ ## Usage
11
+
12
+ ### With Vitepress-tuck
13
+
14
+ **Installation:**
15
+
16
+ ```bash
17
+ # npm
18
+ npm install -D vitepress-tuck vitepress-plugin-abbr
19
+ # pnpm
20
+ pnpm add -D vitepress-tuck vitepress-plugin-abbr
21
+ # yarn
22
+ yarn add -D vitepress-tuck vitepress-plugin-abbr
23
+ ```
24
+
25
+ **Configuration:**
26
+
27
+ ```ts
28
+ // .vitepress/config.ts
29
+ import abbr from 'vitepress-plugin-abbr'
30
+ import { defineConfig } from 'vitepress-tuck'
31
+
32
+ export default defineConfig({
33
+ plugins: [abbr()],
34
+ })
35
+ ```
36
+
37
+ ```ts
38
+ // .vitepress/theme/index.ts
39
+ import type { Theme } from 'vitepress'
40
+ import enhanceApp from 'virtual:enhance-app'
41
+ import DefaultTheme from 'vitepress/theme'
42
+ import 'vitepress-plugin-abbr/style.css'
43
+
44
+ export default {
45
+ extends: DefaultTheme,
46
+ enhanceApp(ctx) {
47
+ enhanceApp(ctx)
48
+ },
49
+ } satisfies Theme
50
+ ```
51
+
52
+ ### With Vitepress
53
+
54
+ **Installation:**
55
+
56
+ ```bash
57
+ # npm
58
+ npm install -D vitepress-plugin-abbr
59
+ # pnpm
60
+ pnpm add -D vitepress-plugin-abbr
61
+ # yarn
62
+ yarn add -D vitepress-plugin-abbr
63
+ ```
64
+
65
+ **Configuration:**
66
+
67
+ ```ts
68
+ // .vitepress/config.ts
69
+ import { defineConfig } from 'vitepress'
70
+ import { abbrMarkdownPlugin } from 'vitepress-plugin-abbr'
71
+
72
+ export default defineConfig({
73
+ markdown: {
74
+ config: (md) => {
75
+ md.use(abbrMarkdownPlugin)
76
+ },
77
+ },
78
+ vite: {
79
+ ssr: {
80
+ noExternal: ['vitepress-plugin-abbr'],
81
+ },
82
+ },
83
+ })
84
+ ```
85
+
86
+ ```ts
87
+ // .vitepress/theme/index.ts
88
+ import type { Theme } from 'vitepress'
89
+ import { VPAbbreviation } from 'vitepress-plugin-abbr/client'
90
+ import DefaultTheme from 'vitepress/theme'
91
+ import 'vitepress-plugin-abbr/style.css'
92
+
93
+ export default {
94
+ extends: DefaultTheme,
95
+ enhanceApp(ctx) {
96
+ ctx.app.component('VPAbbreviation', VPAbbreviation)
97
+ },
98
+ } satisfies Theme
99
+ ```
100
+
101
+ ## Syntax
102
+
103
+ Define an abbreviation with the `*[ABBR]: Full description` syntax, then use the abbreviation anywhere
104
+ in your markdown content. The abbreviation text will be rendered with a dotted underline and a help cursor,
105
+ and hovering or focusing it shows a tooltip with the full description.
106
+
107
+ 使用 `*[缩写]: 完整描述` 语法定义缩写词,然后在 markdown 内容的任意位置使用该缩写词。
108
+ 缩写词文本将渲染为带点状下划线和 help 鼠标样式的文本,悬停或聚焦时会显示包含完整描述的提示框。
109
+
110
+ ```md
111
+ The HTML specification is maintained by the W3C.
112
+
113
+ *[HTML]: HyperText Markup Language
114
+ *[W3C]: World Wide Web Consortium
115
+ ```
116
+
117
+ The description supports inline markdown, such as `**bold**`, `*italic*`, `[links](https://example.com)`, and `code`.
118
+
119
+ 描述支持内联 markdown 语法,例如 `**加粗**`、`*斜体*`、`[链接](https://example.com)` 和 `代码`。
120
+
121
+ ```md
122
+ The **HTML** spec is maintained by the W3C.
123
+
124
+ *[HTML]: HyperText Markup Language
125
+ *[W3C]: World [Wide Web](https://www.w3.org/) Consortium
126
+ ```
127
+
128
+ ## Global Abbreviations
129
+
130
+ Instead of (or in addition to) defining abbreviations inline in each markdown file,
131
+ you can provide a global preset of abbreviations through the plugin options. Inline definitions take
132
+ precedence over global ones when both define the same abbreviation.
133
+
134
+ 可以在每个 markdown 文件中内联定义缩写词,也可以通过插件选项提供全局缩写词预设
135
+ 当两者定义了相同的缩写词时,内联定义优先于全局定义。
136
+
137
+ ```ts
138
+ // .vitepress/config.ts
139
+ import abbr from 'vitepress-plugin-abbr'
140
+ import { defineConfig } from 'vitepress-tuck'
141
+
142
+ export default defineConfig({
143
+ plugins: [
144
+ abbr({
145
+ HTML: 'HyperText Markup Language',
146
+ W3C: 'World Wide Web Consortium',
147
+ }),
148
+ ],
149
+ })
150
+ ```
151
+
152
+ With the configuration above, every occurrence of `HTML` and `W3C` across all pages will be
153
+ rendered as abbreviations automatically, without needing inline `*[...]:` definitions.
154
+
155
+ 配置上述内容后,所有页面中出现的 `HTML` 和 `W3C` 都会自动渲染为缩写词,无需逐个文件添加 `*[...]:` 内联定义。
@@ -0,0 +1,47 @@
1
+ import { EnhanceAppContext } from "vitepress/client";
2
+
3
+ //#region src/client/VPAbbreviation.vue.d.ts
4
+ declare var __VLS_1: {}, __VLS_21: {};
5
+ type __VLS_Slots = {} & {
6
+ default?: (props: typeof __VLS_1) => any;
7
+ } & {
8
+ tooltip?: (props: typeof __VLS_21) => any;
9
+ };
10
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
11
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
+ declare const _default: typeof __VLS_export;
13
+ type __VLS_WithSlots<T, S> = T & {
14
+ new (): {
15
+ $slots: S;
16
+ };
17
+ };
18
+ //#endregion
19
+ //#region src/client/index.d.ts
20
+ /**
21
+ * Enhances the VitePress application by registering the `VPAbbreviation`
22
+ * component globally under the name `Abbreviation`, so it can be used
23
+ * directly in Vue templates or rendered markdown content.
24
+ *
25
+ * 通过全局注册 `VPAbbreviation` 组件来增强 VitePress 应用,
26
+ * 使其可在 Vue 模板或渲染的 markdown 内容中直接使用。
27
+ *
28
+ * @example
29
+ * `.vitepress/theme/index.ts`
30
+ * ```ts
31
+ * import type { Theme } from 'vitepress'
32
+ * import { enhanceAppWithAbbr } from 'vitepress-plugin-abbr/client'
33
+ * import DefaultTheme from 'vitepress/theme'
34
+ *
35
+ * export default {
36
+ * extends: DefaultTheme,
37
+ * enhanceApp(ctx) {
38
+ * enhanceAppWithAbbr(ctx)
39
+ * },
40
+ * } satisfies Theme
41
+ * ```
42
+ */
43
+ declare function enhanceAppWithAbbr({
44
+ app
45
+ }: EnhanceAppContext): void;
46
+ //#endregion
47
+ export { _default as VPAbbreviation, enhanceAppWithAbbr };
@@ -0,0 +1,110 @@
1
+ import "../style.css";
2
+ import { Fragment, Teleport, Transition, createBlock, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, normalizeStyle, openBlock, ref, renderSlot, resolveComponent, unref, useTemplateRef, vShow, withCtx, withDirectives } from "vue";
3
+ import { arrow, autoUpdate, offset, shift, useFloating } from "@floating-ui/vue";
4
+ //#region src/client/VPAbbreviation.vue
5
+ const _sfc_main = /*@__PURE__*/ defineComponent({
6
+ inheritAttrs: false,
7
+ __name: "VPAbbreviation",
8
+ setup(__props) {
9
+ /**
10
+ * VPAbbreviation component for rendering abbreviations with an interactive
11
+ * tooltip in VitePress.
12
+ *
13
+ * VPAbbreviation 组件,用于在 VitePress 中渲染带有可交互提示框的缩写词。
14
+ *
15
+ * The abbreviation text is rendered as an underlined, help-cursor span. When
16
+ * the user hovers over or focuses the span, a floating tooltip anchored below
17
+ * the text is shown, displaying the full description provided via the
18
+ * `#tooltip` slot. The tooltip is teleported to `body` and positioned with
19
+ * `@floating-ui/vue`, applying offset, flip, shift, and arrow middleware for
20
+ * robust placement near viewport edges.
21
+ *
22
+ * 缩写词文本渲染为带下划线且鼠标为 help 样式的 span。当用户悬停或聚焦该
23
+ * span 时,会在文本下方显示一个浮动提示框,展示通过 `#tooltip` 插槽提供
24
+ * 的完整描述。提示框被传送到 `body`,并使用 `@floating-ui/vue` 定位,
25
+ * 应用 offset、flip、shift 和 arrow 中间件以保证在视口边缘附近的稳定定位。
26
+ */
27
+ const abbr = useTemplateRef("abbr");
28
+ const tooltip = useTemplateRef("tooltip");
29
+ const tooltipArrow = useTemplateRef("tooltipArrow");
30
+ const show = ref(false);
31
+ const { floatingStyles, middlewareData } = useFloating(abbr, tooltip, {
32
+ whileElementsMounted: autoUpdate,
33
+ placement: "bottom",
34
+ middleware: [
35
+ offset(10),
36
+ shift({ padding: 20 }),
37
+ arrow({
38
+ element: tooltipArrow,
39
+ padding: 4
40
+ })
41
+ ]
42
+ });
43
+ return (_ctx, _cache) => {
44
+ const _component_ClientOnly = resolveComponent("ClientOnly");
45
+ return openBlock(), createElementBlock(Fragment, null, [createElementVNode("span", mergeProps({
46
+ ref_key: "abbr",
47
+ ref: abbr,
48
+ class: "vp-abbr"
49
+ }, _ctx.$attrs, {
50
+ onMouseenter: _cache[0] || (_cache[0] = ($event) => show.value = true),
51
+ onMouseleave: _cache[1] || (_cache[1] = ($event) => show.value = false)
52
+ }), [renderSlot(_ctx.$slots, "default")], 16), createVNode(_component_ClientOnly, null, {
53
+ default: withCtx(() => [(openBlock(), createBlock(Teleport, { to: "body" }, [createVNode(Transition, {
54
+ name: "fade-in",
55
+ persisted: ""
56
+ }, {
57
+ default: withCtx(() => [withDirectives(createElementVNode("div", {
58
+ ref_key: "tooltip",
59
+ ref: tooltip,
60
+ role: "tooltip",
61
+ class: "vp-abbr-popover",
62
+ style: normalizeStyle(unref(floatingStyles)),
63
+ onMouseenter: _cache[2] || (_cache[2] = ($event) => show.value = true),
64
+ onMouseleave: _cache[3] || (_cache[3] = ($event) => show.value = false)
65
+ }, [createElementVNode("span", {
66
+ ref_key: "tooltipArrow",
67
+ ref: tooltipArrow,
68
+ class: "tooltip-arrow",
69
+ style: normalizeStyle({
70
+ left: unref(middlewareData).arrow?.x != null ? `${unref(middlewareData).arrow.x}px` : "",
71
+ top: unref(middlewareData).arrow?.y != null ? `${unref(middlewareData).arrow.y}px` : ""
72
+ })
73
+ }, null, 4), renderSlot(_ctx.$slots, "tooltip")], 36), [[vShow, show.value]])]),
74
+ _: 3
75
+ })]))]),
76
+ _: 3
77
+ })], 64);
78
+ };
79
+ }
80
+ });
81
+ //#endregion
82
+ //#region src/client/index.ts
83
+ /**
84
+ * Enhances the VitePress application by registering the `VPAbbreviation`
85
+ * component globally under the name `Abbreviation`, so it can be used
86
+ * directly in Vue templates or rendered markdown content.
87
+ *
88
+ * 通过全局注册 `VPAbbreviation` 组件来增强 VitePress 应用,
89
+ * 使其可在 Vue 模板或渲染的 markdown 内容中直接使用。
90
+ *
91
+ * @example
92
+ * `.vitepress/theme/index.ts`
93
+ * ```ts
94
+ * import type { Theme } from 'vitepress'
95
+ * import { enhanceAppWithAbbr } from 'vitepress-plugin-abbr/client'
96
+ * import DefaultTheme from 'vitepress/theme'
97
+ *
98
+ * export default {
99
+ * extends: DefaultTheme,
100
+ * enhanceApp(ctx) {
101
+ * enhanceAppWithAbbr(ctx)
102
+ * },
103
+ * } satisfies Theme
104
+ * ```
105
+ */
106
+ function enhanceAppWithAbbr({ app }) {
107
+ app.component("VPAbbreviation", _sfc_main);
108
+ }
109
+ //#endregion
110
+ export { _sfc_main as VPAbbreviation, enhanceAppWithAbbr };
@@ -0,0 +1,47 @@
1
+ import { EnhanceAppContext } from "vitepress/client";
2
+
3
+ //#region src/client/VPAbbreviation.vue.d.ts
4
+ declare var __VLS_1: {}, __VLS_21: {};
5
+ type __VLS_Slots = {} & {
6
+ default?: (props: typeof __VLS_1) => any;
7
+ } & {
8
+ tooltip?: (props: typeof __VLS_21) => any;
9
+ };
10
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
11
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
+ declare const _default: typeof __VLS_export;
13
+ type __VLS_WithSlots<T, S> = T & {
14
+ new (): {
15
+ $slots: S;
16
+ };
17
+ };
18
+ //#endregion
19
+ //#region src/client/index.d.ts
20
+ /**
21
+ * Enhances the VitePress application by registering the `VPAbbreviation`
22
+ * component globally under the name `Abbreviation`, so it can be used
23
+ * directly in Vue templates or rendered markdown content.
24
+ *
25
+ * 通过全局注册 `VPAbbreviation` 组件来增强 VitePress 应用,
26
+ * 使其可在 Vue 模板或渲染的 markdown 内容中直接使用。
27
+ *
28
+ * @example
29
+ * `.vitepress/theme/index.ts`
30
+ * ```ts
31
+ * import type { Theme } from 'vitepress'
32
+ * import { enhanceAppWithAbbr } from 'vitepress-plugin-abbr/client'
33
+ * import DefaultTheme from 'vitepress/theme'
34
+ *
35
+ * export default {
36
+ * extends: DefaultTheme,
37
+ * enhanceApp(ctx) {
38
+ * enhanceAppWithAbbr(ctx)
39
+ * },
40
+ * } satisfies Theme
41
+ * ```
42
+ */
43
+ declare function enhanceAppWithAbbr({
44
+ app
45
+ }: EnhanceAppContext): void;
46
+ //#endregion
47
+ export { _default as VPAbbreviation, enhanceAppWithAbbr };
@@ -0,0 +1,128 @@
1
+ import { Teleport, Transition, createBlock, createVNode, defineComponent, mergeProps, openBlock, ref, renderSlot, resolveComponent, unref, useSSRContext, useTemplateRef, vShow, withCtx, withDirectives } from "vue";
2
+ import { ssrRenderAttrs, ssrRenderComponent, ssrRenderSlot, ssrRenderStyle, ssrRenderTeleport } from "vue/server-renderer";
3
+ import { arrow, autoUpdate, offset, shift, useFloating } from "@floating-ui/vue";
4
+ //#region src/client/VPAbbreviation.vue
5
+ const _sfc_main = /*@__PURE__*/ defineComponent({
6
+ inheritAttrs: false,
7
+ __name: "VPAbbreviation",
8
+ __ssrInlineRender: true,
9
+ setup(__props) {
10
+ /**
11
+ * VPAbbreviation component for rendering abbreviations with an interactive
12
+ * tooltip in VitePress.
13
+ *
14
+ * VPAbbreviation 组件,用于在 VitePress 中渲染带有可交互提示框的缩写词。
15
+ *
16
+ * The abbreviation text is rendered as an underlined, help-cursor span. When
17
+ * the user hovers over or focuses the span, a floating tooltip anchored below
18
+ * the text is shown, displaying the full description provided via the
19
+ * `#tooltip` slot. The tooltip is teleported to `body` and positioned with
20
+ * `@floating-ui/vue`, applying offset, flip, shift, and arrow middleware for
21
+ * robust placement near viewport edges.
22
+ *
23
+ * 缩写词文本渲染为带下划线且鼠标为 help 样式的 span。当用户悬停或聚焦该
24
+ * span 时,会在文本下方显示一个浮动提示框,展示通过 `#tooltip` 插槽提供
25
+ * 的完整描述。提示框被传送到 `body`,并使用 `@floating-ui/vue` 定位,
26
+ * 应用 offset、flip、shift 和 arrow 中间件以保证在视口边缘附近的稳定定位。
27
+ */
28
+ const abbr = useTemplateRef("abbr");
29
+ const tooltip = useTemplateRef("tooltip");
30
+ const tooltipArrow = useTemplateRef("tooltipArrow");
31
+ const show = ref(false);
32
+ const { floatingStyles, middlewareData } = useFloating(abbr, tooltip, {
33
+ whileElementsMounted: autoUpdate,
34
+ placement: "bottom",
35
+ middleware: [
36
+ offset(10),
37
+ shift({ padding: 20 }),
38
+ arrow({
39
+ element: tooltipArrow,
40
+ padding: 4
41
+ })
42
+ ]
43
+ });
44
+ return (_ctx, _push, _parent, _attrs) => {
45
+ const _component_ClientOnly = resolveComponent("ClientOnly");
46
+ _push(`<!--[--><span${ssrRenderAttrs(mergeProps({
47
+ ref_key: "abbr",
48
+ ref: abbr,
49
+ class: "vp-abbr"
50
+ }, _ctx.$attrs))}>`);
51
+ ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent);
52
+ _push(`</span>`);
53
+ _push(ssrRenderComponent(_component_ClientOnly, null, {
54
+ default: withCtx((_, _push, _parent, _scopeId) => {
55
+ if (_push) ssrRenderTeleport(_push, (_push) => {
56
+ _push(`<div role="tooltip" class="vp-abbr-popover" style="${ssrRenderStyle([unref(floatingStyles), show.value ? null : { display: "none" }])}"${_scopeId}><span class="tooltip-arrow" style="${ssrRenderStyle({
57
+ left: unref(middlewareData).arrow?.x != null ? `${unref(middlewareData).arrow.x}px` : "",
58
+ top: unref(middlewareData).arrow?.y != null ? `${unref(middlewareData).arrow.y}px` : ""
59
+ })}"${_scopeId}></span>`);
60
+ ssrRenderSlot(_ctx.$slots, "tooltip", {}, null, _push, _parent, _scopeId);
61
+ _push(`</div>`);
62
+ }, "body", false, _parent);
63
+ else return [(openBlock(), createBlock(Teleport, { to: "body" }, [createVNode(Transition, {
64
+ name: "fade-in",
65
+ persisted: ""
66
+ }, {
67
+ default: withCtx(() => [withDirectives(createVNode("div", {
68
+ ref_key: "tooltip",
69
+ ref: tooltip,
70
+ role: "tooltip",
71
+ class: "vp-abbr-popover",
72
+ style: unref(floatingStyles),
73
+ onMouseenter: ($event) => show.value = true,
74
+ onMouseleave: ($event) => show.value = false
75
+ }, [createVNode("span", {
76
+ ref_key: "tooltipArrow",
77
+ ref: tooltipArrow,
78
+ class: "tooltip-arrow",
79
+ style: {
80
+ left: unref(middlewareData).arrow?.x != null ? `${unref(middlewareData).arrow.x}px` : "",
81
+ top: unref(middlewareData).arrow?.y != null ? `${unref(middlewareData).arrow.y}px` : ""
82
+ }
83
+ }, null, 4), renderSlot(_ctx.$slots, "tooltip")], 44, ["onMouseenter", "onMouseleave"]), [[vShow, show.value]])]),
84
+ _: 3
85
+ })]))];
86
+ }),
87
+ _: 3
88
+ }, _parent));
89
+ _push(`<!--]-->`);
90
+ };
91
+ }
92
+ });
93
+ const _sfc_setup = _sfc_main.setup;
94
+ _sfc_main.setup = (props, ctx) => {
95
+ const ssrContext = useSSRContext();
96
+ (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/client/VPAbbreviation.vue");
97
+ return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
98
+ };
99
+ //#endregion
100
+ //#region src/client/index.ts
101
+ /**
102
+ * Enhances the VitePress application by registering the `VPAbbreviation`
103
+ * component globally under the name `Abbreviation`, so it can be used
104
+ * directly in Vue templates or rendered markdown content.
105
+ *
106
+ * 通过全局注册 `VPAbbreviation` 组件来增强 VitePress 应用,
107
+ * 使其可在 Vue 模板或渲染的 markdown 内容中直接使用。
108
+ *
109
+ * @example
110
+ * `.vitepress/theme/index.ts`
111
+ * ```ts
112
+ * import type { Theme } from 'vitepress'
113
+ * import { enhanceAppWithAbbr } from 'vitepress-plugin-abbr/client'
114
+ * import DefaultTheme from 'vitepress/theme'
115
+ *
116
+ * export default {
117
+ * extends: DefaultTheme,
118
+ * enhanceApp(ctx) {
119
+ * enhanceAppWithAbbr(ctx)
120
+ * },
121
+ * } satisfies Theme
122
+ * ```
123
+ */
124
+ function enhanceAppWithAbbr({ app }) {
125
+ app.component("VPAbbreviation", _sfc_main);
126
+ }
127
+ //#endregion
128
+ export { _sfc_main as VPAbbreviation, enhanceAppWithAbbr };
@@ -0,0 +1,44 @@
1
+ @import url("vitepress-plugin-toolkit/styles/transition/fade-in.css");
2
+
3
+ .vp-abbr {
4
+ position: relative;
5
+ text-decoration: underline dotted currentcolor;
6
+ text-underline-offset: 4px;
7
+ cursor: help;
8
+ }
9
+
10
+ .vp-abbr-popover {
11
+ position: absolute;
12
+ width: max-content;
13
+ padding: 4px 8px;
14
+ font-size: 14px;
15
+ font-weight: bold;
16
+ color: var(--vp-c-text-1);
17
+ background-color: var(--vp-c-bg);
18
+ border: 1px solid var(--vp-c-divider);
19
+ border-radius: 4px;
20
+ box-shadow: var(--vp-shadow-2);
21
+ transition: 0.25s ease-in-out;
22
+ transition-property: color, border-color, box-shadow;
23
+ }
24
+
25
+ .vp-abbr-popover .tooltip-arrow,
26
+ .vp-abbr-popover .tooltip-arrow::after {
27
+ position: absolute;
28
+ display: block;
29
+ width: 8px;
30
+ height: 8px;
31
+ border: 8px solid transparent;
32
+ }
33
+
34
+ .vp-abbr-popover .tooltip-arrow {
35
+ top: -16px;
36
+ left: 4px;
37
+ border-bottom-color: var(--vp-c-divider);
38
+ }
39
+
40
+ .vp-abbr-popover .tooltip-arrow::after {
41
+ content: "";
42
+ border-bottom-color: var(--vp-c-bg);
43
+ transform: translate(-8px, -7px);
44
+ }
@@ -0,0 +1,82 @@
1
+ import { PluginWithOptions } from "markdown-it";
2
+
3
+ //#region src/node/plugin.d.ts
4
+ /**
5
+ * VitePress plugin for abbreviation support in markdown content.
6
+ *
7
+ * VitePress 插件,用于在 markdown 内容中支持缩写词。
8
+ *
9
+ * Forked and modified from `markdown-it-abbr`. The plugin registers the
10
+ * abbreviation definition and replace rules in markdown-it, and wires up the
11
+ * client-side `VPAbbreviation` component via `componentResolver` so that
12
+ * abbreviations render with an interactive tooltip showing their full
13
+ * description. It also pre-bundles the `@floating-ui/vue` dependency used by
14
+ * the tooltip.
15
+ *
16
+ * 从 `markdown-it-abbr` 分叉并修改。该插件在 markdown-it 中注册缩写词
17
+ * 定义与替换规则,并通过 `componentResolver` 接入客户端 `VPAbbreviation`
18
+ * 组件,使缩写词渲染时带有可交互的提示框,展示其完整描述。同时预构建
19
+ * 提示框所依赖的 `@floating-ui/vue`。
20
+ *
21
+ * Abbreviations can be defined inline in markdown using the syntax
22
+ * `*[ABBR]: Full description`, or provided globally through the
23
+ * `abbreviations` option. Inline definitions take precedence over global ones
24
+ * when both define the same abbreviation.
25
+ *
26
+ * 缩写词可以在 markdown 中通过 `*[缩写]: 完整描述` 语法内联定义,也可以
27
+ * 通过 `abbreviations` 选项全局提供。当两者定义了相同的缩写词时,内联
28
+ * 定义优先于全局定义。
29
+ *
30
+ * @param abbreviations - Global abbreviations preset, a map of abbreviation
31
+ * to its full description / 全局缩写词预设,缩写词到其完整描述的映射
32
+ * @example
33
+ * `.vitepress/config.ts` without global abbreviations
34
+ * ```ts
35
+ * import { defineConfig } from 'vitepress-tuck'
36
+ * import abbr from 'vitepress-plugin-abbr'
37
+ *
38
+ * export default defineConfig({
39
+ * plugins: [abbr()],
40
+ * })
41
+ * ```
42
+ * @example
43
+ * `.vitepress/config.ts` with global abbreviations
44
+ * ```ts
45
+ * import { defineConfig } from 'vitepress-tuck'
46
+ * import abbr from 'vitepress-plugin-abbr'
47
+ *
48
+ * export default defineConfig({
49
+ * plugins: [
50
+ * abbr({
51
+ * HTML: 'HyperText Markup Language',
52
+ * W3C: 'World Wide Web Consortium',
53
+ * }),
54
+ * ],
55
+ * })
56
+ * ```
57
+ * @example
58
+ * // Markdown usage
59
+ * ```md
60
+ * The HTML specification is maintained by the W3C.
61
+ *
62
+ * *[HTML]: HyperText Markup Language
63
+ * *[W3C]: World Wide Web Consortium
64
+ * ```
65
+ */
66
+ declare const abbr: (options?: Record<string, string> | undefined) => import("vitepress-tuck").VitepressPlugin;
67
+ //#endregion
68
+ //#region src/node/markdown.d.ts
69
+ /**
70
+ * Abbreviation plugin - Enable abbreviation syntax
71
+ *
72
+ * 缩写词插件 - 启用缩写词语法
73
+ *
74
+ * Definition syntax: *[ABBREV]: Full description
75
+ * 定义语法:*[缩写]: 完整描述
76
+ *
77
+ * @param md - Markdown-it instance / Markdown-it 实例
78
+ * @param globalAbbreviations - Global abbreviations preset / 全局缩写词预设
79
+ */
80
+ declare const abbrMarkdownPlugin: PluginWithOptions<Record<string, string>>;
81
+ //#endregion
82
+ export { abbr, abbr as default, abbrMarkdownPlugin };
@@ -0,0 +1,282 @@
1
+ import { definePlugin } from "vitepress-tuck";
2
+ import { cleanMarkdownEnv } from "vitepress-plugin-toolkit";
3
+ //#region ../../node_modules/.pnpm/@pengzhanbo+utils@3.7.3/node_modules/@pengzhanbo/utils/dist/index.js
4
+ const T_NULL = "null";
5
+ const DANGEROUS_KEYS = /* @__PURE__ */ new Set([
6
+ "__proto__",
7
+ "constructor",
8
+ "prototype"
9
+ ]);
10
+ function getTypeName(s) {
11
+ return Object.prototype.toString.call(s);
12
+ }
13
+ function typeOf(s) {
14
+ const type = typeof s;
15
+ return s === null ? T_NULL : type === "object" || type === "function" ? getTypeName(s).slice(8, -1).toLowerCase() : type;
16
+ }
17
+ function isTypeof(s, type) {
18
+ return typeOf(s) === type;
19
+ }
20
+ function isNull(v) {
21
+ return v === null;
22
+ }
23
+ function isPlainObject(v) {
24
+ if (!isTypeof(v, "object")) return false;
25
+ const proto = Object.getPrototypeOf(v);
26
+ return isNull(proto) || proto === Object.prototype;
27
+ }
28
+ function isEmptyObject(v) {
29
+ if (!isPlainObject(v)) return false;
30
+ for (const _ in v) return false;
31
+ return true;
32
+ }
33
+ function objectKeys(obj) {
34
+ return Object.keys(obj);
35
+ }
36
+ function objectMap(obj, mapper) {
37
+ return Object.fromEntries(Object.entries(obj).map(([k, v]) => mapper(k, v)).filter((entry) => !isUndefined(entry)).filter(([k]) => !DANGEROUS_KEYS.has(k)));
38
+ }
39
+ function isUndefined(v) {
40
+ return v === void 0;
41
+ }
42
+ //#endregion
43
+ //#region src/node/markdown.ts
44
+ /**
45
+ * Abbreviation plugin - Enable abbreviation syntax
46
+ *
47
+ * 缩写词插件 - 启用缩写词语法
48
+ *
49
+ * Definition syntax: *[ABBREV]: Full description
50
+ * 定义语法:*[缩写]: 完整描述
51
+ *
52
+ * @param md - Markdown-it instance / Markdown-it 实例
53
+ * @param globalAbbreviations - Global abbreviations preset / 全局缩写词预设
54
+ */
55
+ const abbrMarkdownPlugin = (md, globalAbbreviations = {}) => {
56
+ const { arrayReplaceAt, escapeRE, lib } = md.utils;
57
+ globalAbbreviations = objectMap(globalAbbreviations, (key, value) => [key.startsWith(":") ? key : `:${key}`, value]);
58
+ const WORDING_REGEXP_TEXT = `${lib.ucmicro.P.source}|${lib.ucmicro.Z.source}|[${" \r\n$+<=>^`|~".split("").map(escapeRE).join("")}]`;
59
+ /**
60
+ * Abbreviation definition rule
61
+ *
62
+ * 缩写词定义规则
63
+ *
64
+ * @param state - State block / 状态块
65
+ * @param startLine - Start line number / 开始行号
66
+ * @param _endLine - End line number / 结束行号
67
+ * @param silent - Silent mode / 静默模式
68
+ * @returns Whether matched / 是否匹配
69
+ */
70
+ const abbrDefinition = (state, startLine, _endLine, silent) => {
71
+ let labelEnd = -1;
72
+ let pos = state.bMarks[startLine] + state.tShift[startLine];
73
+ const max = state.eMarks[startLine];
74
+ if (pos + 2 >= max || state.src.charAt(pos++) !== "*" || state.src.charAt(pos++) !== "[") return false;
75
+ const labelStart = pos;
76
+ while (pos < max) {
77
+ const ch = state.src.charAt(pos);
78
+ if (ch === "[") return false;
79
+ if (ch === "]") {
80
+ labelEnd = pos;
81
+ break;
82
+ }
83
+ if (ch === "\\") pos++;
84
+ pos++;
85
+ }
86
+ if (labelEnd < 0 || state.src.charAt(labelEnd + 1) !== ":") return false;
87
+ /* istanbul ignore if -- @preserve */
88
+ if (silent) return true;
89
+ const label = state.src.slice(labelStart, labelEnd).replace(/\\(.)/g, "$1");
90
+ const title = state.src.slice(labelEnd + 2, max).trim();
91
+ if (!label.length || !title.length) return false;
92
+ (state.env.abbreviations ??= {})[`:${label}`] ??= title;
93
+ state.line = startLine + 1;
94
+ return true;
95
+ };
96
+ /**
97
+ * Abbreviation replace rule
98
+ *
99
+ * 缩写词替换规则
100
+ *
101
+ * @param state - State core / 核心状态
102
+ */
103
+ const abbrReplace = (state) => {
104
+ if (state.env.disableAbbrReplace) return;
105
+ const tokens = state.tokens;
106
+ const { abbreviations: localAbbreviations } = state.env;
107
+ if (!localAbbreviations && isEmptyObject(globalAbbreviations)) return;
108
+ const abbreviations = {
109
+ ...globalAbbreviations,
110
+ ...localAbbreviations
111
+ };
112
+ const abbreviationsRegExpText = objectKeys(abbreviations).map((x) => x.substring(1)).sort((a, b) => b.length - a.length).map(escapeRE).join("|");
113
+ const regexpSimple = new RegExp(`(?:${abbreviationsRegExpText})`);
114
+ const regExp = new RegExp(`(^|${WORDING_REGEXP_TEXT})(${abbreviationsRegExpText})($|${WORDING_REGEXP_TEXT})`, "g");
115
+ for (const token of tokens) {
116
+ if (token.type !== "inline") continue;
117
+ let children = token.children;
118
+ for (let index = children.length - 1; index >= 0; index--) {
119
+ const currentToken = children[index];
120
+ if (currentToken.type !== "text") continue;
121
+ const text = currentToken.content;
122
+ regExp.lastIndex = 0;
123
+ if (!regexpSimple.test(text)) continue;
124
+ const nodes = [];
125
+ let match;
126
+ let pos = 0;
127
+ while (match = regExp.exec(text)) {
128
+ const [, before, word, after] = match;
129
+ if (match.index > 0 || before.length > 0) {
130
+ const token = new state.Token("text", "", 0);
131
+ token.content = text.slice(pos, match.index + before.length);
132
+ nodes.push(token);
133
+ }
134
+ const abbrToken = new state.Token("abbreviation", "Abbreviation", 0);
135
+ abbrToken.content = word;
136
+ abbrToken.info = abbreviations[`:${word}`];
137
+ nodes.push(abbrToken);
138
+ regExp.lastIndex -= after.length;
139
+ pos = regExp.lastIndex;
140
+ }
141
+ /* istanbul ignore if -- @preserve */
142
+ if (!nodes.length) continue;
143
+ if (pos < text.length) {
144
+ const token = new state.Token("text", "", 0);
145
+ token.content = text.slice(pos);
146
+ nodes.push(token);
147
+ }
148
+ token.children = children = arrayReplaceAt(children, index, nodes);
149
+ }
150
+ }
151
+ };
152
+ md.block.ruler.before("reference", "abbr_definition", abbrDefinition, { alt: ["paragraph", "reference"] });
153
+ md.core.ruler.after("linkify", "abbr_replace", abbrReplace);
154
+ /**
155
+ * Custom renderer for the `abbreviation` token.
156
+ *
157
+ * `abbreviation` token 的自定义渲染器。
158
+ *
159
+ * Renders the abbreviation as a `VPAbbreviation` component tag. The
160
+ * abbreviation text is placed as the default slot content, and when a
161
+ * description (`info`) exists, it is rendered as inline markdown and passed
162
+ * to the `#tooltip` slot. The rendered description is also used to produce
163
+ * an `aria-label` (HTML tags stripped) for accessibility.
164
+ *
165
+ * 将缩写词渲染为 `VPAbbreviation` 组件标签。缩写词文本作为默认插槽内容,
166
+ * 当存在描述(`info`)时,将其作为内联 markdown 渲染后传入 `#tooltip`
167
+ * 插槽。渲染后的描述还会去除 HTML 标签生成 `aria-label`,以支持无障碍访问。
168
+ *
169
+ * @param tokens - Token array / token 数组
170
+ * @param idx - Current token index / 当前 token 索引
171
+ * @param _ - Render options (unused) / 渲染选项(未使用)
172
+ * @param env - Render environment containing abbreviations / 包含缩写词的渲染环境
173
+ * @returns Rendered HTML string / 渲染后的 HTML 字符串
174
+ */
175
+ md.renderer.rules.abbreviation = (tokens, idx, _, env) => {
176
+ const { content, info } = tokens[idx];
177
+ const rendered = md.renderInline(info, {
178
+ ...cleanMarkdownEnv(env, ["abbreviations"]),
179
+ disableAbbrReplace: true
180
+ });
181
+ return `<VPAbbreviation aria-label="${rendered.replace(/<[^>]*>/g, "")}">${content}${info ? `<template #tooltip>${rendered}</template>` : ""}</VPAbbreviation>`;
182
+ };
183
+ };
184
+ //#endregion
185
+ //#region src/node/plugin.ts
186
+ /**
187
+ * VitePress plugin for abbreviation support in markdown content.
188
+ *
189
+ * VitePress 插件,用于在 markdown 内容中支持缩写词。
190
+ *
191
+ * Forked and modified from `markdown-it-abbr`. The plugin registers the
192
+ * abbreviation definition and replace rules in markdown-it, and wires up the
193
+ * client-side `VPAbbreviation` component via `componentResolver` so that
194
+ * abbreviations render with an interactive tooltip showing their full
195
+ * description. It also pre-bundles the `@floating-ui/vue` dependency used by
196
+ * the tooltip.
197
+ *
198
+ * 从 `markdown-it-abbr` 分叉并修改。该插件在 markdown-it 中注册缩写词
199
+ * 定义与替换规则,并通过 `componentResolver` 接入客户端 `VPAbbreviation`
200
+ * 组件,使缩写词渲染时带有可交互的提示框,展示其完整描述。同时预构建
201
+ * 提示框所依赖的 `@floating-ui/vue`。
202
+ *
203
+ * Abbreviations can be defined inline in markdown using the syntax
204
+ * `*[ABBR]: Full description`, or provided globally through the
205
+ * `abbreviations` option. Inline definitions take precedence over global ones
206
+ * when both define the same abbreviation.
207
+ *
208
+ * 缩写词可以在 markdown 中通过 `*[缩写]: 完整描述` 语法内联定义,也可以
209
+ * 通过 `abbreviations` 选项全局提供。当两者定义了相同的缩写词时,内联
210
+ * 定义优先于全局定义。
211
+ *
212
+ * @param abbreviations - Global abbreviations preset, a map of abbreviation
213
+ * to its full description / 全局缩写词预设,缩写词到其完整描述的映射
214
+ * @example
215
+ * `.vitepress/config.ts` without global abbreviations
216
+ * ```ts
217
+ * import { defineConfig } from 'vitepress-tuck'
218
+ * import abbr from 'vitepress-plugin-abbr'
219
+ *
220
+ * export default defineConfig({
221
+ * plugins: [abbr()],
222
+ * })
223
+ * ```
224
+ * @example
225
+ * `.vitepress/config.ts` with global abbreviations
226
+ * ```ts
227
+ * import { defineConfig } from 'vitepress-tuck'
228
+ * import abbr from 'vitepress-plugin-abbr'
229
+ *
230
+ * export default defineConfig({
231
+ * plugins: [
232
+ * abbr({
233
+ * HTML: 'HyperText Markup Language',
234
+ * W3C: 'World Wide Web Consortium',
235
+ * }),
236
+ * ],
237
+ * })
238
+ * ```
239
+ * @example
240
+ * // Markdown usage
241
+ * ```md
242
+ * The HTML specification is maintained by the W3C.
243
+ *
244
+ * *[HTML]: HyperText Markup Language
245
+ * *[W3C]: World Wide Web Consortium
246
+ * ```
247
+ */
248
+ const abbr = definePlugin((abbreviations) => ({
249
+ name: "vitepress-plugin-abbr",
250
+ componentResolver: ["VPAbbreviation"],
251
+ markdown: { config(md) {
252
+ md.use(abbrMarkdownPlugin, abbreviations);
253
+ } },
254
+ vite: { optimizeDeps: { include: ["@floating-ui/vue"] } }
255
+ }));
256
+ //#endregion
257
+ //#region src/node/index.ts
258
+ /**
259
+ * Node-side entry point for the VitePress abbreviation plugin.
260
+ *
261
+ * VitePress 缩写词插件的 Node 端入口。
262
+ *
263
+ * Re-exports the markdown-it plugin and the main plugin factory, and exposes
264
+ * the plugin factory as the default export for `vitepress-tuck` integration.
265
+ *
266
+ * 重新导出 markdown-it 插件和主插件工厂,并将插件工厂作为默认导出,
267
+ * 以便与 `vitepress-tuck` 集成。
268
+ *
269
+ * @example
270
+ * `.vitepress/config.ts`
271
+ * ```ts
272
+ * import { defineConfig } from 'vitepress-tuck'
273
+ * import abbr from 'vitepress-plugin-abbr'
274
+ *
275
+ * export default defineConfig({
276
+ * plugins: [abbr()],
277
+ * })
278
+ * ```
279
+ */
280
+ var node_default = abbr;
281
+ //#endregion
282
+ export { abbr, abbrMarkdownPlugin, node_default as default };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "vitepress-plugin-abbr",
3
+ "type": "module",
4
+ "version": "0.5.0",
5
+ "description": "Add abbreviation support to VitePress",
6
+ "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
7
+ "license": "MIT",
8
+ "homepage": "https://tuck.pengzhanbo.cn/plugins/abbr",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/pengzhanbo/vitepress-tuck.git",
12
+ "directory": "packages/plugin-abbr"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/pengzhanbo/vitepress-tuck/issues"
16
+ },
17
+ "keywords": [
18
+ "vitepress",
19
+ "vitepress-plugin",
20
+ "abbr",
21
+ "abbreviation"
22
+ ],
23
+ "exports": {
24
+ ".": "./dist/node/index.js",
25
+ "./client": {
26
+ "browser": "./dist/client/browser/index.js",
27
+ "default": "./dist/client/ssr/index.js"
28
+ },
29
+ "./style.css": "./dist/client/style.css"
30
+ },
31
+ "module": "./dist/node/index.js",
32
+ "types": "./dist/node/index.d.ts",
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "peerDependencies": {
37
+ "vitepress": "^1.6.4 || ^2.0.0-alpha.17",
38
+ "vue": "^3.5.0"
39
+ },
40
+ "dependencies": {
41
+ "@floating-ui/vue": "^2.0.0",
42
+ "@vueuse/core": "^14.3.0",
43
+ "vitepress-plugin-toolkit": "0.5.0",
44
+ "vitepress-tuck": "0.5.0"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "provenance": true
49
+ },
50
+ "scripts": {
51
+ "clean": "rimraf --glob ./dist",
52
+ "dev": "pnpm '/(tsdown|copy):watch/'",
53
+ "build": "pnpm tsdown && pnpm copy",
54
+ "copy": "cpx \"src/**/*.css\" dist",
55
+ "copy:watch": "pnpm copy -w",
56
+ "tsdown": "tsdown --config-loader unrun",
57
+ "tsdown:watch": "pnpm tsdown -w"
58
+ }
59
+ }