@antdv-next/docs-plugins 0.0.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/demo/formatter.d.ts +17 -0
  4. package/dist/demo/formatter.js +32 -0
  5. package/dist/demo/get-demo-id.d.ts +9 -0
  6. package/dist/demo/get-demo-id.js +19 -0
  7. package/dist/demo/index.d.ts +26 -0
  8. package/dist/demo/index.js +359 -0
  9. package/dist/demo/tsToJs.d.ts +9 -0
  10. package/dist/demo/tsToJs.js +60 -0
  11. package/dist/demo/types.d.ts +23 -0
  12. package/dist/index.d.ts +20 -0
  13. package/dist/index.js +19 -0
  14. package/dist/isolate-styles.d.ts +14 -0
  15. package/dist/isolate-styles.js +30 -0
  16. package/dist/markdown.d.ts +39 -0
  17. package/dist/markdown.js +115 -0
  18. package/dist/md-plugin.d.ts +18 -0
  19. package/dist/md-plugin.js +9 -0
  20. package/dist/md2vue.d.ts +16 -0
  21. package/dist/md2vue.js +106 -0
  22. package/dist/plugins/container.d.ts +16 -0
  23. package/dist/plugins/container.js +53 -0
  24. package/dist/plugins/demo.d.ts +29 -0
  25. package/dist/plugins/demo.js +139 -0
  26. package/dist/plugins/github-alerts.d.ts +6 -0
  27. package/dist/plugins/github-alerts.js +49 -0
  28. package/dist/plugins/image.d.ts +12 -0
  29. package/dist/plugins/image.js +17 -0
  30. package/dist/plugins/link.d.ts +14 -0
  31. package/dist/plugins/link.js +25 -0
  32. package/dist/plugins/pre-wrapper.d.ts +10 -0
  33. package/dist/plugins/pre-wrapper.js +26 -0
  34. package/dist/plugins/stackblitz.d.ts +5 -0
  35. package/dist/plugins/stackblitz.js +21 -0
  36. package/dist/plugins/table.d.ts +5 -0
  37. package/dist/plugins/table.js +25 -0
  38. package/dist/shared.d.ts +7 -0
  39. package/dist/shared.js +7 -0
  40. package/dist/utils/short-hash.d.ts +4 -0
  41. package/dist/utils/short-hash.js +21 -0
  42. package/package.json +94 -0
  43. package/src/components/code-demo/code-editor-bridge.vue +36 -0
  44. package/src/components/code-demo/compile-sfc.ts +207 -0
  45. package/src/components/code-demo/context.ts +86 -0
  46. package/src/components/code-demo/expand-icon.vue +12 -0
  47. package/src/components/code-demo/external-link-icon.vue +5 -0
  48. package/src/components/code-demo/index.vue +682 -0
  49. package/src/components/code-demo/virtual.d.ts +38 -0
  50. package/src/demo/formatter.ts +50 -0
  51. package/src/demo/get-demo-id.ts +33 -0
  52. package/src/demo/index.ts +498 -0
  53. package/src/demo/tsToJs.ts +79 -0
  54. package/src/demo/types.ts +23 -0
  55. package/src/index.ts +19 -0
  56. package/src/isolate-styles.ts +47 -0
  57. package/src/markdown.ts +188 -0
  58. package/src/md-plugin.ts +24 -0
  59. package/src/md2vue.ts +163 -0
  60. package/src/plugins/container.ts +135 -0
  61. package/src/plugins/demo.ts +282 -0
  62. package/src/plugins/github-alerts.ts +69 -0
  63. package/src/plugins/image.ts +29 -0
  64. package/src/plugins/link.ts +32 -0
  65. package/src/plugins/pre-wrapper.ts +49 -0
  66. package/src/plugins/stackblitz.ts +32 -0
  67. package/src/plugins/table.ts +39 -0
  68. package/src/shared.ts +4 -0
  69. package/src/utils/short-hash.ts +27 -0
@@ -0,0 +1,282 @@
1
+ import type { MarkdownItEnv, MarkdownItHeader } from '@mdit-vue/types'
2
+ import type MarkdownIt from 'markdown-it'
3
+ import pathe from 'pathe'
4
+ import { getDemoId } from '../demo/get-demo-id'
5
+
6
+ const HEADING_LEVEL_RE = /^h([2-6])$/
7
+ const SRC_ATTR_RE = /(\s|^)src=(['"])(.*?)\2/gi
8
+
9
+ export interface DemoMarkdownPluginOptions {
10
+ /** 包裹 demo 的标签名,默认 `demo` */
11
+ wrapper?: string
12
+ /**
13
+ * demo 锚点归属模式:
14
+ * - `examples`:demo 统一挂到 slug 为 `examples` 的标题下(antdv-next 约定,默认)
15
+ * - `section`:demo 挂到当前所在章节标题下(跟随标题层级)
16
+ */
17
+ headerMode?: 'examples' | 'section'
18
+ /**
19
+ * 生产构建时跳过 `debug` demo 的目录收集
20
+ * @default true
21
+ */
22
+ debugDemo?: boolean
23
+ }
24
+
25
+ declare module '@mdit-vue/types' {
26
+ interface MarkdownItEnv {
27
+ id?: string
28
+ }
29
+ }
30
+
31
+ function checkWrapper(content: string, wrapper = 'demo') {
32
+ return new RegExp(`<${wrapper}(\\s|>|/)`, 'i').test(content)
33
+ }
34
+
35
+ function isProdDebugDemo(tag: string) {
36
+ if (process.env.NODE_ENV !== 'production') {
37
+ return false
38
+ }
39
+
40
+ const debugAttr = tag.match(/(?:^|\s)debug(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s/>]+)))?/i)
41
+ if (!debugAttr) {
42
+ return false
43
+ }
44
+
45
+ const value = debugAttr[1] ?? debugAttr[2] ?? debugAttr[3]
46
+ return value === undefined || value === '' || value === 'true'
47
+ }
48
+
49
+ function flattenHeaders(headers: MarkdownItHeader[] = []) {
50
+ const headerMap = new Map<string, MarkdownItHeader>()
51
+
52
+ const visit = (items: MarkdownItHeader[]) => {
53
+ for (const item of items) {
54
+ headerMap.set(item.slug, item)
55
+ if (item.children?.length)
56
+ visit(item.children)
57
+ }
58
+ }
59
+
60
+ visit(headers)
61
+ return headerMap
62
+ }
63
+
64
+ function flattenHeadersInOrder(headers: MarkdownItHeader[] = []) {
65
+ const flat: MarkdownItHeader[] = []
66
+
67
+ const visit = (items: MarkdownItHeader[]) => {
68
+ for (const item of items) {
69
+ flat.push(item)
70
+ if (item.children?.length)
71
+ visit(item.children)
72
+ }
73
+ }
74
+
75
+ visit(headers)
76
+ return flat
77
+ }
78
+
79
+ function getHeadingSlug(token: { attrs?: [string, string][] | null }) {
80
+ return token.attrs?.find(attr => attr[0] === 'id')?.[1]
81
+ }
82
+
83
+ function getHeadingTitle(token?: { type?: string, content?: string }) {
84
+ if (token?.type !== 'inline')
85
+ return ''
86
+ return token.content?.trim() ?? ''
87
+ }
88
+
89
+ function getHeadingLevel(token: { tag?: string }) {
90
+ const match = token.tag?.match(HEADING_LEVEL_RE)
91
+ if (!match)
92
+ return null
93
+ return Number(match[1])
94
+ }
95
+
96
+ export function replaceSrcPath(
97
+ content: string,
98
+ id: string,
99
+ root: string,
100
+ wrapper = 'demo',
101
+ parentHeader?: MarkdownItHeader,
102
+ skipDebugDemo = false,
103
+ ) {
104
+ function replaceSrcInTag(tagMatch: string, titleContent?: string) {
105
+ return tagMatch.replace(SRC_ATTR_RE, (srcMatch, prefix, quote, srcValue) => {
106
+ if (!srcValue || srcValue.startsWith('/'))
107
+ return srcMatch
108
+
109
+ const dir = pathe.dirname(id)
110
+ const filePath = pathe.resolve(dir, srcValue)
111
+ const relative = pathe.relative(root, filePath)
112
+
113
+ if (parentHeader && titleContent && !(skipDebugDemo && isProdDebugDemo(tagMatch))) {
114
+ const slug = getDemoId(filePath)
115
+ const item = {
116
+ level: parentHeader.level + 1,
117
+ title: titleContent,
118
+ slug,
119
+ link: `#${slug}`,
120
+ children: [],
121
+ }
122
+ if (parentHeader.children)
123
+ parentHeader.children.push(item)
124
+ else
125
+ parentHeader.children = [item]
126
+ }
127
+
128
+ return `${prefix}src=${quote}${relative.startsWith('/') ? relative : `/${relative}`}${quote}`
129
+ })
130
+ }
131
+
132
+ // 1. First, match closed tags <demo>Title</demo> to extract title content
133
+ const closedTag = new RegExp(`(<${wrapper}(?!-)\\b[^>]*>)([\\s\\S]*?)<\\/${wrapper}>`, 'gi')
134
+ let result = content.replace(closedTag, (tagMatch, openTag, titleContent) => {
135
+ return tagMatch.replace(
136
+ openTag,
137
+ replaceSrcInTag(openTag, titleContent?.trim()),
138
+ )
139
+ })
140
+
141
+ // 2. Then, match self-closing tags <demo ... />
142
+ const selfClosing = new RegExp(`<${wrapper}(?!-)\\b[^>]*/\\s*>`, 'gi')
143
+ result = result.replace(selfClosing, tagMatch => replaceSrcInTag(tagMatch))
144
+
145
+ // 3. Finally, match standalone open tags <demo ...> (when parsed separately from closing tag)
146
+ const openTag = new RegExp(`<${wrapper}(?!-)\\b[^>]*>`, 'gi')
147
+ return result.replace(openTag, tagMatch => replaceSrcInTag(tagMatch))
148
+ }
149
+
150
+ export function demoMarkdownPlugin(
151
+ md: MarkdownIt,
152
+ config: { root?: string } & DemoMarkdownPluginOptions = {},
153
+ ) {
154
+ const wrapper = config.wrapper ?? 'demo'
155
+ const headerMode = config.headerMode ?? 'examples'
156
+ const skipDebugDemo = config.debugDemo ?? true
157
+ // 保存原始 render 函数
158
+ const originalRender = md.renderer.render.bind(md.renderer)
159
+
160
+ md.renderer.render = function render(tokens, options, env: MarkdownItEnv) {
161
+ const root = config.root ?? process.cwd()
162
+ const currentId = env.id || ''
163
+ const headers = env.headers
164
+
165
+ // 'examples' 模式:所有 demo 统一挂到 slug 为 examples 的标题下
166
+ const fixedHeader = headerMode === 'examples'
167
+ ? headers?.find(item => item.slug === 'examples')
168
+ : undefined
169
+
170
+ const headerMap = flattenHeaders(headers)
171
+ const orderedHeaders = flattenHeadersInOrder(headers)
172
+ const headerIndexBySlug = new Map(
173
+ orderedHeaders.map((item, index) => [item.slug, index]),
174
+ )
175
+ const activeHeaders = new Map<number, MarkdownItHeader>()
176
+ let headerCursor = 0
177
+
178
+ function getCurrentHeader() {
179
+ const levels = Array.from(activeHeaders.keys()).sort(
180
+ (left, right) => right - left,
181
+ )
182
+ const nearestLevel = levels[0]
183
+ return nearestLevel ? activeHeaders.get(nearestLevel) : undefined
184
+ }
185
+
186
+ function resolveHeading(
187
+ headingLevel: number,
188
+ headingSlug?: string,
189
+ headingTitle?: string,
190
+ ) {
191
+ if (headingSlug) {
192
+ const bySlug = headerMap.get(headingSlug)
193
+ if (bySlug) {
194
+ const index = headerIndexBySlug.get(headingSlug)
195
+ if (typeof index === 'number')
196
+ headerCursor = Math.max(headerCursor, index + 1)
197
+ return bySlug
198
+ }
199
+ }
200
+
201
+ for (let index = headerCursor; index < orderedHeaders.length; index += 1) {
202
+ const header = orderedHeaders[index]!
203
+ if (header.level !== headingLevel)
204
+ continue
205
+ if (headingTitle && header.title !== headingTitle)
206
+ continue
207
+
208
+ headerCursor = index + 1
209
+ return header
210
+ }
211
+
212
+ return undefined
213
+ }
214
+
215
+ function processToken(token: {
216
+ type?: string
217
+ tag?: string
218
+ attrs?: [string, string][] | null
219
+ content?: string
220
+ children?: unknown[] | null
221
+ }) {
222
+ const tokenType = token.type ?? ''
223
+ const tokenContent = token.content ?? ''
224
+
225
+ if (
226
+ (tokenType === 'html_block'
227
+ || tokenType === 'html_inline'
228
+ || tokenType === 'inline')
229
+ && checkWrapper(tokenContent, wrapper)
230
+ ) {
231
+ token.content = replaceSrcPath(
232
+ tokenContent,
233
+ currentId,
234
+ root,
235
+ wrapper,
236
+ headerMode === 'examples' ? fixedHeader : getCurrentHeader(),
237
+ skipDebugDemo,
238
+ )
239
+ }
240
+
241
+ if (token.children) {
242
+ for (const child of token.children as (typeof token)[]) {
243
+ processToken(child)
244
+ }
245
+ }
246
+ }
247
+
248
+ const typedTokens = tokens as typeof tokens & Array<{
249
+ type: string
250
+ tag?: string
251
+ attrs?: [string, string][] | null
252
+ content?: string
253
+ children?: unknown[] | null
254
+ }>
255
+
256
+ typedTokens.forEach((token, index) => {
257
+ if (token.type === 'heading_open') {
258
+ const headingLevel = getHeadingLevel(token)
259
+ if (headingLevel) {
260
+ for (const level of activeHeaders.keys()) {
261
+ if (level >= headingLevel)
262
+ activeHeaders.delete(level)
263
+ }
264
+
265
+ const headingSlug = getHeadingSlug(token)
266
+ const headingTitle = getHeadingTitle(typedTokens[index + 1])
267
+ const header = resolveHeading(
268
+ headingLevel,
269
+ headingSlug,
270
+ headingTitle,
271
+ )
272
+ if (header)
273
+ activeHeaders.set(headingLevel, header)
274
+ }
275
+ }
276
+
277
+ processToken(token)
278
+ })
279
+
280
+ return originalRender(tokens, options, env)
281
+ }
282
+ }
@@ -0,0 +1,69 @@
1
+ import type MarkdownIt from 'markdown-it'
2
+ import type { ContainerOptions } from './container'
3
+
4
+ const markerRE
5
+ = /^\[!(TIP|NOTE|INFO|IMPORTANT|WARNING|CAUTION|DANGER)\]([^\n\r]*)/i
6
+
7
+ export function gitHubAlertsPlugin(md: MarkdownIt, options?: ContainerOptions) {
8
+ const titleMark = {
9
+ tip: options?.tipLabel || 'TIP',
10
+ note: options?.noteLabel || 'NOTE',
11
+ info: options?.infoLabel || 'INFO',
12
+ important: options?.importantLabel || 'IMPORTANT',
13
+ warning: options?.warningLabel || 'WARNING',
14
+ caution: options?.cautionLabel || 'CAUTION',
15
+ danger: options?.dangerLabel || 'DANGER',
16
+ } as Record<string, string>
17
+
18
+ md.core.ruler.after('block', 'github-alerts', (state) => {
19
+ const tokens = state.tokens
20
+ for (let i = 0; i < tokens.length; i++) {
21
+ if (tokens[i]!.type !== 'blockquote_open')
22
+ continue
23
+
24
+ const startIndex = i
25
+ const open = tokens[startIndex]!
26
+ let endIndex = i + 1
27
+ while (
28
+ endIndex < tokens.length
29
+ && (tokens[endIndex]!.type !== 'blockquote_close'
30
+ || tokens[endIndex]!.level !== open.level)
31
+ ) {
32
+ endIndex++
33
+ }
34
+ if (endIndex === tokens.length)
35
+ continue
36
+ const close = tokens[endIndex]!
37
+ const firstContent = tokens
38
+ .slice(startIndex, endIndex + 1)
39
+ .find(token => token.type === 'inline')
40
+ if (!firstContent)
41
+ continue
42
+ const match = firstContent.content.match(markerRE)
43
+ if (!match)
44
+ continue
45
+ const type = match[1]!.toLowerCase()
46
+ const title = match[2]?.trim() || titleMark[type] || capitalize(type)
47
+ firstContent.content = firstContent.content
48
+ .slice(match[0].length)
49
+ .trimStart()
50
+ open.type = 'github_alert_open'
51
+ open.tag = 'div'
52
+ open.meta = {
53
+ title,
54
+ type,
55
+ }
56
+ close.type = 'github_alert_close'
57
+ close.tag = 'div'
58
+ }
59
+ })
60
+ md.renderer.rules.github_alert_open = function (tokens, idx) {
61
+ const { title, type } = tokens[idx]!.meta
62
+ const attrs = ''
63
+ return `<div class="${type} custom-block github-alert"${attrs}><p class="custom-block-title">${title}</p>\n`
64
+ }
65
+ }
66
+
67
+ function capitalize(str: string) {
68
+ return str.charAt(0).toUpperCase() + str.slice(1)
69
+ }
@@ -0,0 +1,29 @@
1
+ // markdown-it plugin for normalizing image source
2
+
3
+ import type MarkdownIt from 'markdown-it'
4
+ import { EXTERNAL_URL_RE } from '../shared'
5
+
6
+ export interface ImagePluginOptions {
7
+ /**
8
+ * Support native lazy loading for the `<img>` tag.
9
+ * @default false
10
+ */
11
+ lazyLoading?: boolean
12
+ }
13
+
14
+ export function imagePlugin(md: MarkdownIt, { lazyLoading }: ImagePluginOptions = {}) {
15
+ const imageRule = md.renderer.rules.image!
16
+ md.renderer.rules.image = (tokens, idx, options, env, self) => {
17
+ const token = tokens[idx]!
18
+ let url = token.attrGet('src')
19
+ if (url && !EXTERNAL_URL_RE.test(url)) {
20
+ if (!/^\.?\//.test(url))
21
+ url = `./${url}`
22
+ token.attrSet('src', decodeURIComponent(url))
23
+ }
24
+ if (lazyLoading)
25
+ token.attrSet('loading', 'lazy')
26
+
27
+ return imageRule(tokens, idx, options, env, self)
28
+ }
29
+ }
@@ -0,0 +1,32 @@
1
+ import type MarkdownIt from 'markdown-it'
2
+ import { EXTERNAL_URL_RE } from '../shared'
3
+
4
+ /**
5
+ * 让 markdown 中的外部链接在新标签页打开。
6
+ *
7
+ * 仅对带协议(http(s)://、mailto: 等)的外部链接生效,
8
+ * 站内相对链接保持默认行为,避免影响路由内跳转与锚点导航。
9
+ *
10
+ * 已通过 `markdown-it-attrs` 等方式显式声明的 `target` / `rel`
11
+ * 不会被覆盖,保留作者对单个链接的控制权。
12
+ */
13
+ export function linkPlugin(md: MarkdownIt) {
14
+ const defaultLinkRender
15
+ = md.renderer.rules.link_open
16
+ || ((tokens, idx, options, _env, self) =>
17
+ self.renderToken(tokens, idx, options))
18
+
19
+ md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
20
+ const token = tokens[idx]!
21
+ const href = token.attrGet('href')
22
+
23
+ if (href && EXTERNAL_URL_RE.test(href)) {
24
+ if (!token.attrGet('target'))
25
+ token.attrSet('target', '_blank')
26
+ if (!token.attrGet('rel'))
27
+ token.attrSet('rel', 'noopener noreferrer')
28
+ }
29
+
30
+ return defaultLinkRender(tokens, idx, options, env, self)
31
+ }
32
+ }
@@ -0,0 +1,49 @@
1
+ import type MarkdownIt from 'markdown-it'
2
+
3
+ export interface Options {
4
+ hasSingleTheme: boolean
5
+ }
6
+
7
+ export function preWrapperPlugin(md: MarkdownIt, options: Options) {
8
+ const fence = md.renderer.rules.fence!
9
+ md.renderer.rules.fence = (...args) => {
10
+ const [tokens, idx] = args
11
+ const token = tokens[idx]!
12
+
13
+ // remove title from info
14
+ token.info = token.info.replace(/\[.*\]/, '')
15
+
16
+ const active = / active(?: |$)/.test(token.info) ? ' active' : ''
17
+ token.info = token.info.replace(/ active$/, '').replace(/ active /, ' ')
18
+
19
+ const lang = extractLang(token.info)
20
+ const rawCode = fence(...args)
21
+ return `<div class="language-${lang}${getAdaptiveThemeMarker(
22
+ options,
23
+ )}${active}"><button title="Copy Code" class="copy"></button><span class="lang">${lang}</span>${rawCode}</div>`
24
+ }
25
+ }
26
+
27
+ export function getAdaptiveThemeMarker(options: Options) {
28
+ return options.hasSingleTheme ? '' : ' ant-code-theme'
29
+ }
30
+
31
+ export function extractTitle(info: string, html = false) {
32
+ if (html) {
33
+ return (
34
+ info.replace(/<!--[\s\S]*?-->/g, '').match(/data-title="(.*?)"/)?.[1] || ''
35
+ )
36
+ }
37
+ return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || 'txt'
38
+ }
39
+
40
+ function extractLang(info: string) {
41
+ return info
42
+ .trim()
43
+ .replace(/=(\d*)/, '')
44
+ // eslint-disable-next-line regexp/optimal-quantifier-concatenation
45
+ .replace(/:(no-)?line-numbers(\{| |$|=\d*).*/, '')
46
+ .replace(/(-vue|\{| ).*$/, '')
47
+ .replace(/^vue-html$/, 'template')
48
+ .replace(/^ansi$/, '')
49
+ }
@@ -0,0 +1,32 @@
1
+ import type MarkdownIt from 'markdown-it'
2
+
3
+ export function stackblitzPlugin(md: MarkdownIt) {
4
+ const fence = md.renderer.rules.fence!
5
+ md.renderer.rules.fence = (...args) => {
6
+ const [tokens, idx] = args
7
+ const token = tokens[idx]!
8
+ const info = token.info.trim()
9
+
10
+ if (info.startsWith('stackblitz')) {
11
+ const code = token.content
12
+ // Extract title from {title="..."}
13
+ const titleMatch = info.match(/\{[^}]*title\s*=\s*"([^"]*)"[^}]*\}/)
14
+ const title = titleMatch ? titleMatch[1]! : ''
15
+
16
+ // Encode the code for safe HTML attribute usage
17
+ const encodedCode = encodeURIComponent(code)
18
+
19
+ return `<stackblitz code="${encodedCode}" title="${escapeHtml(title)}"></stackblitz>`
20
+ }
21
+
22
+ return fence(...args)
23
+ }
24
+ }
25
+
26
+ function escapeHtml(str: string) {
27
+ return str
28
+ .replace(/&/g, '&amp;')
29
+ .replace(/"/g, '&quot;')
30
+ .replace(/</g, '&lt;')
31
+ .replace(/>/g, '&gt;')
32
+ }
@@ -0,0 +1,39 @@
1
+ import type MarkdownIt from 'markdown-it'
2
+ import type StateCore from 'markdown-it/lib/rules_core/state_core.mjs'
3
+
4
+ export function tablePlugin(md: MarkdownIt) {
5
+ md.core.ruler.push('table_api_attribute', (state: StateCore) => {
6
+ const tokens = state.tokens
7
+ let inApiSection = false
8
+
9
+ for (let i = 0; i < tokens.length; i++) {
10
+ const token = tokens[i]!
11
+
12
+ // 检测 ## 标题
13
+ if (token.type === 'heading_open' && token.tag === 'h2') {
14
+ // 获取标题内容(下一个 token 是 inline)
15
+ const inlineToken = tokens[i + 1]
16
+ if (inlineToken && inlineToken.type === 'inline') {
17
+ const headingText = inlineToken.content.trim().toLowerCase()
18
+ // 检查是否是 Api 标题(支持 Api、API、api 等)
19
+ if (headingText === 'api') {
20
+ inApiSection = true
21
+ }
22
+ else {
23
+ // 遇到其他 ## 标题,结束 Api 区域
24
+ inApiSection = false
25
+ }
26
+ }
27
+ }
28
+
29
+ // 如果在 Api 区域内,给 table 添加类名
30
+ if (inApiSection && token.type === 'table_open') {
31
+ const existingClass = token.attrGet('class') || ''
32
+ const newClass = existingClass ? `${existingClass} component-table-api` : 'component-table-api'
33
+ token.attrSet('class', newClass)
34
+ }
35
+ }
36
+
37
+ return true
38
+ })
39
+ }
package/src/shared.ts ADDED
@@ -0,0 +1,4 @@
1
+ export const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i
2
+ export const SCRIPT_REGEX = /<script\b[^>]*>[\s\S]*?<\/script>/gi
3
+ export const STYLE_REGEX = /<style\b[^>]*>[\s\S]*?<\/style>/gi
4
+ export const DOCS_REGEX = /<docs\b[^>]*>[\s\S]*?<\/docs>/gi
@@ -0,0 +1,27 @@
1
+ const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
2
+
3
+ function toBase62(num: number): string {
4
+ let str = ''
5
+ do {
6
+ str = BASE62[num % 62] + str
7
+ num = Math.floor(num / 62)
8
+ } while (num > 0)
9
+ return str
10
+ }
11
+
12
+ export function shortHash(str: string): string {
13
+ const encoder = new TextEncoder()
14
+ const bytes = encoder.encode(str)
15
+
16
+ let hash = 2166136261
17
+ for (let i = 0; i < bytes.length; i++) {
18
+ hash ^= bytes[i]!
19
+ hash += (hash << 1)
20
+ + (hash << 4)
21
+ + (hash << 7)
22
+ + (hash << 8)
23
+ + (hash << 24)
24
+ }
25
+
26
+ return toBase62(hash >>> 0) // 无符号整数 → Base62
27
+ }