@antdv-next/docs-plugins 0.0.1 → 0.0.3
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.
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { CheckOutlined, CodeOutlined, CopyOutlined, EditOutlined, ThunderboltOutlined } from '@antdv-next/icons'
|
|
5
|
-
import { aquaBlue, atomDark } from '@codesandbox/sandpack-themes'
|
|
6
|
-
import { useClipboard, useDebounceFn } from '@vueuse/core'
|
|
7
|
-
import { Alert, Flex, Skeleton, Spin, Tabs, Tooltip } from 'antdv-next'
|
|
8
|
-
import { createStyles } from 'antdv-style'
|
|
9
|
-
import { loadDemo } from 'virtual:demos'
|
|
10
|
-
import { computed, defineAsyncComponent, markRaw, nextTick, onBeforeUnmount, shallowRef, watch } from 'vue'
|
|
11
|
-
import { useRoute, useRouter } from 'vue-router'
|
|
12
|
-
import { getDemoId } from '../../demo/get-demo-id'
|
|
13
|
-
import CodeEditorBridge from './code-editor-bridge.vue'
|
|
14
|
-
import { compileSfcSource } from './compile-sfc'
|
|
15
|
-
import { resolveLabel, useDemoContext } from './context'
|
|
16
|
-
import ExpandIcon from './expand-icon.vue'
|
|
17
|
-
import ExternalLinkIcon from './external-link-icon.vue'
|
|
2
|
+
import { ThemeProvider, useTheme } from 'antdv-style'
|
|
3
|
+
import DemoPanel from './demo-panel.vue'
|
|
18
4
|
|
|
19
5
|
defineOptions({
|
|
20
6
|
name: 'Demo',
|
|
@@ -39,644 +25,28 @@ const props = withDefaults(defineProps<{
|
|
|
39
25
|
debug: false,
|
|
40
26
|
})
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const useStyles = createStyles(({ token }) => ({
|
|
47
|
-
root: {
|
|
48
|
-
'breakInside': 'avoid',
|
|
49
|
-
'display': 'flow-root',
|
|
50
|
-
'overflow': 'hidden',
|
|
51
|
-
'position': 'relative',
|
|
52
|
-
'boxSizing': 'border-box',
|
|
53
|
-
'border': `1px solid ${token.colorSplit}`,
|
|
54
|
-
'borderRadius': token.borderRadiusLG,
|
|
55
|
-
'background': token.colorBgContainer,
|
|
56
|
-
'transition': 'border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease',
|
|
57
|
-
'margin': '16px 0',
|
|
58
|
-
'&.border-primary': {
|
|
59
|
-
borderColor: token.colorPrimary,
|
|
60
|
-
boxShadow: `0 0 0 3px color-mix(in srgb, ${token.colorPrimary} 12%, transparent)`,
|
|
61
|
-
},
|
|
62
|
-
'&.ant-doc-demo-box-debug': {
|
|
63
|
-
borderColor: '#d3adf7',
|
|
64
|
-
},
|
|
65
|
-
'& .ant-doc-demo-box-demo': {
|
|
66
|
-
padding: '24px',
|
|
67
|
-
borderBottom: `1px solid ${token.colorSplit}`,
|
|
68
|
-
borderRadius: '8px 8px 0 0',
|
|
69
|
-
background: token.colorBgContainer,
|
|
70
|
-
},
|
|
71
|
-
'& .ant-doc-demo-box-skeleton': {
|
|
72
|
-
minHeight: 160,
|
|
73
|
-
},
|
|
74
|
-
'&.ant-doc-demo-box-simplify': {
|
|
75
|
-
borderRadius: 0,
|
|
76
|
-
background: 'transparent',
|
|
77
|
-
},
|
|
78
|
-
'&.ant-doc-demo-box-simplify .ant-doc-demo-box-demo': {
|
|
79
|
-
padding: 0,
|
|
80
|
-
borderBottom: 0,
|
|
81
|
-
background: 'transparent',
|
|
82
|
-
},
|
|
83
|
-
'& .ant-doc-demo-box-meta.markdown': {
|
|
84
|
-
position: 'relative',
|
|
85
|
-
width: '100%',
|
|
86
|
-
fontSize: 14,
|
|
87
|
-
borderRadius: '0 0 6px 6px',
|
|
88
|
-
transition: 'background-color 0.4s',
|
|
89
|
-
},
|
|
90
|
-
'& .ant-doc-demo-box-meta-description': {
|
|
91
|
-
padding: '18px 12px 24px',
|
|
92
|
-
},
|
|
93
|
-
'& .ant-doc-demo-box-meta-description p': {
|
|
94
|
-
margin: 0,
|
|
95
|
-
},
|
|
96
|
-
'& .ant-doc-demo-box-title': {
|
|
97
|
-
position: 'absolute',
|
|
98
|
-
top: -16,
|
|
99
|
-
marginLeft: 16,
|
|
100
|
-
padding: '1px 8px',
|
|
101
|
-
borderRadius: '6px 6px 0 0',
|
|
102
|
-
backgroundColor: token.colorBgContainer,
|
|
103
|
-
transition: 'background-color 0.4s',
|
|
104
|
-
},
|
|
105
|
-
'& .ant-doc-demo-box-title a': {
|
|
106
|
-
color: token.colorText,
|
|
107
|
-
textDecoration: 'none',
|
|
108
|
-
fontSize: 16,
|
|
109
|
-
fontWeight: 500,
|
|
110
|
-
},
|
|
111
|
-
'& .ant-doc-demo-box-actions': {
|
|
112
|
-
display: 'flex',
|
|
113
|
-
justifyContent: 'center',
|
|
114
|
-
padding: '12px 0',
|
|
115
|
-
borderTop: `1px dashed ${token.colorSplit}`,
|
|
116
|
-
opacity: 0.7,
|
|
117
|
-
transition: 'opacity 0.3s',
|
|
118
|
-
},
|
|
119
|
-
'&:hover .ant-doc-demo-box-actions': {
|
|
120
|
-
opacity: 1,
|
|
121
|
-
},
|
|
122
|
-
'& .ant-doc-demo-box-code-action': {
|
|
123
|
-
display: 'inline-flex',
|
|
124
|
-
alignItems: 'center',
|
|
125
|
-
justifyContent: 'center',
|
|
126
|
-
width: 16,
|
|
127
|
-
height: 16,
|
|
128
|
-
border: 0,
|
|
129
|
-
padding: 0,
|
|
130
|
-
background: 'transparent',
|
|
131
|
-
color: token.colorTextSecondary,
|
|
132
|
-
cursor: 'pointer',
|
|
133
|
-
transition: 'color 0.24s ease',
|
|
134
|
-
},
|
|
135
|
-
'& .ant-doc-demo-box-code-action:hover': {
|
|
136
|
-
color: token.colorPrimary,
|
|
137
|
-
},
|
|
138
|
-
'& .ant-doc-demo-box-edit-icon': {
|
|
139
|
-
marginInlineStart: 4,
|
|
140
|
-
color: token.colorTextTertiary,
|
|
141
|
-
},
|
|
142
|
-
'& .ant-doc-demo-box-code': {
|
|
143
|
-
position: 'relative',
|
|
144
|
-
lineHeight: 2,
|
|
145
|
-
padding: `${token.paddingSM}px ${token.padding}px`,
|
|
146
|
-
},
|
|
147
|
-
'& .ant-doc-demo-box-code-loading': {
|
|
148
|
-
display: 'flex',
|
|
149
|
-
justifyContent: 'center',
|
|
150
|
-
paddingBlock: token.paddingLG,
|
|
151
|
-
},
|
|
152
|
-
'& .ant-doc-demo-box-compile-error': {
|
|
153
|
-
margin: 0,
|
|
154
|
-
padding: '8px 16px',
|
|
155
|
-
background: token.colorErrorBg,
|
|
156
|
-
color: token.colorError,
|
|
157
|
-
fontSize: 12,
|
|
158
|
-
lineHeight: 1.6,
|
|
159
|
-
whiteSpace: 'pre-wrap',
|
|
160
|
-
},
|
|
161
|
-
'& .ant-doc-demo-box-code-tabs': {
|
|
162
|
-
borderTop: `1px dashed ${token.colorSplit}`,
|
|
163
|
-
},
|
|
164
|
-
'& .ant-doc-demo-box-code-tabs .ant-tabs-nav': {
|
|
165
|
-
marginBottom: 0,
|
|
166
|
-
},
|
|
167
|
-
'& .ant-doc-demo-box-code-tabs .ant-tabs-tab': {
|
|
168
|
-
fontSize: 12,
|
|
169
|
-
},
|
|
170
|
-
'& .ant-doc-demo-box-code-copy': {
|
|
171
|
-
position: 'absolute',
|
|
172
|
-
top: 10,
|
|
173
|
-
right: 10,
|
|
174
|
-
zIndex: 1,
|
|
175
|
-
display: 'inline-flex',
|
|
176
|
-
alignItems: 'center',
|
|
177
|
-
justifyContent: 'center',
|
|
178
|
-
width: 24,
|
|
179
|
-
height: 24,
|
|
180
|
-
border: 0,
|
|
181
|
-
padding: 0,
|
|
182
|
-
background: 'transparent',
|
|
183
|
-
color: token.colorIcon,
|
|
184
|
-
cursor: 'pointer',
|
|
185
|
-
},
|
|
186
|
-
'& .ant-doc-demo-box-code-copied': {
|
|
187
|
-
color: token.colorSuccess,
|
|
188
|
-
},
|
|
189
|
-
'& .ant-doc-demo-box-collapse-btn': {
|
|
190
|
-
display: 'flex',
|
|
191
|
-
alignItems: 'center',
|
|
192
|
-
justifyContent: 'center',
|
|
193
|
-
gap: 8,
|
|
194
|
-
padding: '8px 0',
|
|
195
|
-
borderTop: `1px dashed ${token.colorSplit}`,
|
|
196
|
-
color: token.colorTextSecondary,
|
|
197
|
-
cursor: 'pointer',
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
}))
|
|
201
|
-
|
|
202
|
-
function t(key: Parameters<typeof resolveLabel>[1]) {
|
|
203
|
-
return resolveLabel(context, key)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const { styles } = useStyles()
|
|
207
|
-
|
|
208
|
-
// Debug demos are visible while developing but stripped from the production docs.
|
|
209
|
-
const hidden = computed(() => Boolean(props.debug) && import.meta.env.PROD)
|
|
210
|
-
|
|
211
|
-
// demo 模块经 virtual:demos 注册表懒加载(代码分割,按需拉取)
|
|
212
|
-
const demo = shallowRef<DemoModule | undefined>(undefined)
|
|
213
|
-
let demoLoadToken = 0
|
|
214
|
-
|
|
215
|
-
watch(() => props.src, (newSrc) => {
|
|
216
|
-
const token = ++demoLoadToken
|
|
217
|
-
demo.value = undefined
|
|
218
|
-
if (!newSrc)
|
|
219
|
-
return
|
|
220
|
-
loadDemo(newSrc).then((mod) => {
|
|
221
|
-
if (token === demoLoadToken && mod)
|
|
222
|
-
demo.value = mod
|
|
223
|
-
}).catch(() => {})
|
|
224
|
-
}, { immediate: true })
|
|
225
|
-
|
|
226
|
-
// 按需加载的源码数据
|
|
227
|
-
const sourceData = shallowRef<DemoSourceData | null>(null)
|
|
228
|
-
const sourceLoading = shallowRef(false)
|
|
229
|
-
const sourceLoadError = shallowRef<Error | null>(null)
|
|
230
|
-
let sourceLoadPromise: Promise<void> | null = null
|
|
231
|
-
let sourceAbortController: AbortController | null = null
|
|
232
|
-
// HMR 发生在面板收起期间时标记过期,下次展开时重新加载
|
|
233
|
-
let sourceStale = false
|
|
234
|
-
|
|
235
|
-
function releaseSource() {
|
|
236
|
-
sourceAbortController?.abort()
|
|
237
|
-
sourceAbortController = null
|
|
238
|
-
sourceData.value = null
|
|
239
|
-
sourceLoadError.value = null
|
|
240
|
-
sourceLoadPromise = null
|
|
241
|
-
sourceLoading.value = false
|
|
242
|
-
sourceStale = false
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async function ensureSourceLoaded() {
|
|
246
|
-
// 如果源码已过期(HMR 发生在收起期间),先释放旧数据
|
|
247
|
-
if (sourceStale)
|
|
248
|
-
releaseSource()
|
|
249
|
-
if (sourceData.value || !demo.value)
|
|
250
|
-
return
|
|
251
|
-
if (sourceLoadPromise)
|
|
252
|
-
return sourceLoadPromise
|
|
253
|
-
|
|
254
|
-
const currentDemo = demo.value
|
|
255
|
-
const abortController = new AbortController()
|
|
256
|
-
sourceAbortController = abortController
|
|
257
|
-
sourceLoading.value = true
|
|
258
|
-
sourceLoadError.value = null
|
|
259
|
-
|
|
260
|
-
const request = currentDemo
|
|
261
|
-
.loadSource(abortController.signal)
|
|
262
|
-
.then((data) => {
|
|
263
|
-
if (demo.value === currentDemo && !abortController.signal.aborted)
|
|
264
|
-
sourceData.value = data
|
|
265
|
-
})
|
|
266
|
-
.catch((error) => {
|
|
267
|
-
if (abortController.signal.aborted)
|
|
268
|
-
return
|
|
269
|
-
const loadError
|
|
270
|
-
= error instanceof Error ? error : new Error(String(error))
|
|
271
|
-
if (demo.value === currentDemo)
|
|
272
|
-
sourceLoadError.value = loadError
|
|
273
|
-
throw loadError
|
|
274
|
-
})
|
|
275
|
-
.finally(() => {
|
|
276
|
-
if (sourceLoadPromise === request) {
|
|
277
|
-
sourceAbortController = null
|
|
278
|
-
sourceLoadPromise = null
|
|
279
|
-
sourceLoading.value = false
|
|
280
|
-
}
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
sourceLoadPromise = request
|
|
284
|
-
return request
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const route = useRoute()
|
|
288
|
-
const router = useRouter()
|
|
289
|
-
|
|
290
|
-
const hasJsSource = computed(() => {
|
|
291
|
-
const jsSource = sourceData.value?.jsSource?.trim()
|
|
292
|
-
return Boolean(jsSource)
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
const extraFiles = computed<DemoExtraFile[]>(() => sourceData.value?.extraFiles ?? [])
|
|
296
|
-
|
|
297
|
-
const codeTabKeys = computed(() => {
|
|
298
|
-
const keys: string[] = ['ts']
|
|
299
|
-
if (hasJsSource.value)
|
|
300
|
-
keys.push('js')
|
|
301
|
-
for (const file of extraFiles.value)
|
|
302
|
-
keys.push(file.name)
|
|
303
|
-
return keys
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
// ts/js 切换:每个 Demo 实例维护独立的偏好,初始值取自站点提供的持久化
|
|
307
|
-
// (不通过 computed 依赖全局状态,避免一个 demo 切换导致所有 demo 同步切换)
|
|
308
|
-
const localTsJs = shallowRef<'ts' | 'js'>(context.preferredCodeType?.get() ?? 'ts')
|
|
309
|
-
const localCodeKey = shallowRef<string | null>(null)
|
|
310
|
-
|
|
311
|
-
const activeCodeType = computed<string>({
|
|
312
|
-
get() {
|
|
313
|
-
if (localCodeKey.value && codeTabKeys.value.includes(localCodeKey.value))
|
|
314
|
-
return localCodeKey.value
|
|
315
|
-
const preferred = localTsJs.value
|
|
316
|
-
if (codeTabKeys.value.includes(preferred))
|
|
317
|
-
return preferred
|
|
318
|
-
return 'ts'
|
|
319
|
-
},
|
|
320
|
-
set(value) {
|
|
321
|
-
if (value === 'ts' || value === 'js') {
|
|
322
|
-
localTsJs.value = value
|
|
323
|
-
context.preferredCodeType?.set(value)
|
|
324
|
-
localCodeKey.value = null
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
localCodeKey.value = value
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
const activeExtraFile = computed(() =>
|
|
333
|
-
extraFiles.value.find(file => file.name === activeCodeType.value),
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
/** 将 demo 相对导入路径映射为 sandpack 虚拟文件路径 */
|
|
337
|
-
function extraFileToSandpackPath(name: string) {
|
|
338
|
-
return `/src/${name.replace(/^(\.\.?\/)+/, '')}`
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/** 代码 tab 显示名(去掉 ./ 前缀) */
|
|
342
|
-
function displayFileName(name: string) {
|
|
343
|
-
return name.replace(/^(\.\.?\/)+/, '')
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const mainSourceCode = computed(() => {
|
|
347
|
-
if (activeCodeType.value === 'js')
|
|
348
|
-
return sourceData.value?.jsSource ?? sourceData.value?.source ?? ''
|
|
349
|
-
return sourceData.value?.source ?? ''
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
// 编辑器当前展示的源码(主 demo 或伴生文件)
|
|
353
|
-
const activeSourceCode = computed(() => {
|
|
354
|
-
if (activeExtraFile.value)
|
|
355
|
-
return activeExtraFile.value.code
|
|
356
|
-
return mainSourceCode.value
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
const description = computed(() => {
|
|
360
|
-
const locales = demo.value?.locales ?? {}
|
|
361
|
-
const locale = context.locale?.() ?? 'zh-CN'
|
|
362
|
-
const localeData = locales[locale] || locales['zh-CN'] || locales['en-US'] || Object.values(locales)[0] || {}
|
|
363
|
-
return localeData?.html ?? ''
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
const component = computed(() => typeof demo.value?.component === 'function' ? defineAsyncComponent(demo.value.component) : demo.value?.component)
|
|
367
|
-
const id = computed(() => {
|
|
368
|
-
if (!props.src)
|
|
369
|
-
return ''
|
|
370
|
-
return getDemoId(props.src)
|
|
371
|
-
})
|
|
372
|
-
const showCode = shallowRef(false)
|
|
373
|
-
const liveComponent = shallowRef<any>(null)
|
|
374
|
-
const compileError = shallowRef<string | null>(null)
|
|
375
|
-
const currentCode = shallowRef<string | null>(null)
|
|
376
|
-
const editorBridgeRef = shallowRef<{ resetCode: (code: string) => void }>()
|
|
377
|
-
|
|
378
|
-
function handleShowCode() {
|
|
379
|
-
showCode.value = !showCode.value
|
|
380
|
-
if (!showCode.value) {
|
|
381
|
-
// 收起时只重置实时编辑状态,保留源码以便快速重新展开
|
|
382
|
-
liveComponent.value = null
|
|
383
|
-
compileError.value = null
|
|
384
|
-
currentCode.value = null
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
// 展开时按需加载源码(ensureSourceLoaded 内部会处理过期重载)
|
|
388
|
-
void ensureSourceLoaded().catch(() => {})
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// 切换 demo 时释放旧源码并重置多文件 tab
|
|
393
|
-
watch(demo, () => {
|
|
394
|
-
localCodeKey.value = null
|
|
395
|
-
releaseSource()
|
|
396
|
-
}, { flush: 'sync' })
|
|
397
|
-
|
|
398
|
-
// 展开代码面板时触发加载(收起时不释放,保留源码)
|
|
399
|
-
watch([showCode, demo], ([visible, currentDemo]) => {
|
|
400
|
-
if (!visible)
|
|
401
|
-
return
|
|
402
|
-
if (currentDemo)
|
|
403
|
-
void ensureSourceLoaded().catch(() => {})
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
// HMR 触发的 sourceVersion 变化:展开时立即重载,收起时标记过期
|
|
407
|
-
watch(
|
|
408
|
-
() => demo.value?.sourceVersion,
|
|
409
|
-
(version, previousVersion) => {
|
|
410
|
-
if (version === previousVersion)
|
|
411
|
-
return
|
|
412
|
-
// 如果用户正在编辑,暂缓重载,避免覆盖编辑内容
|
|
413
|
-
if (currentCode.value !== null || liveComponent.value !== null) {
|
|
414
|
-
sourceStale = true
|
|
415
|
-
return
|
|
416
|
-
}
|
|
417
|
-
if (showCode.value) {
|
|
418
|
-
releaseSource()
|
|
419
|
-
void ensureSourceLoaded().catch(() => {})
|
|
420
|
-
}
|
|
421
|
-
else if (sourceData.value) {
|
|
422
|
-
sourceStale = true
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
onBeforeUnmount(releaseSource)
|
|
428
|
-
|
|
429
|
-
// Reset live component and editor code when tab changes
|
|
430
|
-
watch(activeCodeType, () => {
|
|
431
|
-
liveComponent.value = null
|
|
432
|
-
compileError.value = null
|
|
433
|
-
currentCode.value = null
|
|
434
|
-
// 等 sandpack 完成 activeFile 切换后再重置内容,避免写入错误的文件
|
|
435
|
-
nextTick(() => {
|
|
436
|
-
editorBridgeRef.value?.resetCode(activeSourceCode.value)
|
|
437
|
-
})
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
const debouncedCompile = useDebounceFn(async (newCode: string) => {
|
|
441
|
-
// 伴生文件 tab 不参与主 demo 的实时编译
|
|
442
|
-
if (activeExtraFile.value)
|
|
443
|
-
return
|
|
444
|
-
// Code matches original source (e.g. after tab switch reset), skip compilation
|
|
445
|
-
if (newCode === mainSourceCode.value) {
|
|
446
|
-
liveComponent.value = null
|
|
447
|
-
compileError.value = null
|
|
448
|
-
return
|
|
449
|
-
}
|
|
450
|
-
const { component: comp, error } = await compileSfcSource(newCode, context.modules)
|
|
451
|
-
if (comp) {
|
|
452
|
-
liveComponent.value = markRaw(comp)
|
|
453
|
-
compileError.value = null
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
compileError.value = error
|
|
457
|
-
}
|
|
458
|
-
}, 300)
|
|
459
|
-
|
|
460
|
-
function handleCodeChange(newCode: string) {
|
|
461
|
-
currentCode.value = newCode
|
|
462
|
-
debouncedCompile(newCode)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const sandpackTheme = computed(() => context.isDark?.() ? atomDark : aquaBlue)
|
|
466
|
-
|
|
467
|
-
const sandpackFiles = computed(() => {
|
|
468
|
-
const files: Record<string, string> = {
|
|
469
|
-
'/src/App.vue': mainSourceCode.value,
|
|
470
|
-
}
|
|
471
|
-
for (const file of extraFiles.value) {
|
|
472
|
-
files[extraFileToSandpackPath(file.name)] = file.code
|
|
473
|
-
}
|
|
474
|
-
return files
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
// 当前激活的 sandpack 文件(多文件 tab 时切换)
|
|
478
|
-
const sandpackActiveFile = computed(() => {
|
|
479
|
-
if (activeExtraFile.value)
|
|
480
|
-
return extraFileToSandpackPath(activeExtraFile.value.name)
|
|
481
|
-
return '/src/App.vue'
|
|
482
|
-
})
|
|
483
|
-
|
|
484
|
-
const sandpackOptions = computed(() => ({
|
|
485
|
-
autorun: false,
|
|
486
|
-
activeFile: sandpackActiveFile.value,
|
|
487
|
-
}))
|
|
488
|
-
|
|
489
|
-
const active = computed(() => route.hash === `#${id.value}`)
|
|
490
|
-
function handleScroll(e: Event) {
|
|
491
|
-
e.preventDefault()
|
|
492
|
-
e.stopPropagation()
|
|
493
|
-
router.push({
|
|
494
|
-
path: route.path,
|
|
495
|
-
hash: `#${id.value}`,
|
|
496
|
-
})
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const titleRef = shallowRef<HTMLElement>()
|
|
500
|
-
|
|
501
|
-
async function handleStackBlitz() {
|
|
502
|
-
if (!context.openStackBlitz)
|
|
503
|
-
return
|
|
28
|
+
// antdv-style 的 useStyles 需要 <ThemeProvider> 祖先;站点未提供时由组件自给,
|
|
29
|
+
// 提供时则直接复用(避免嵌套 provider 造成 token 漂移)
|
|
30
|
+
function hasThemeProvider() {
|
|
504
31
|
try {
|
|
505
|
-
|
|
32
|
+
useTheme()
|
|
33
|
+
return true
|
|
506
34
|
}
|
|
507
35
|
catch {
|
|
508
|
-
|
|
509
|
-
return
|
|
510
|
-
}
|
|
511
|
-
if (mainSourceCode.value) {
|
|
512
|
-
context.openStackBlitz({
|
|
513
|
-
title: titleRef.value?.textContent || 'Demo',
|
|
514
|
-
code: mainSourceCode.value,
|
|
515
|
-
})
|
|
36
|
+
return false
|
|
516
37
|
}
|
|
517
38
|
}
|
|
518
39
|
|
|
519
|
-
|
|
520
|
-
if (!context.openPlayground)
|
|
521
|
-
return
|
|
522
|
-
context.openPlayground(mainSourceCode.value)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const demoStyle = computed(() => {
|
|
526
|
-
const style: CSSProperties = {}
|
|
527
|
-
if (props.compact) {
|
|
528
|
-
style.padding = '0px'
|
|
529
|
-
style.overflow = 'hidden'
|
|
530
|
-
}
|
|
531
|
-
if (props.background === 'grey') {
|
|
532
|
-
style.backgroundColor = 'var(--ant-color-bg-layout)'
|
|
533
|
-
}
|
|
534
|
-
return style
|
|
535
|
-
})
|
|
536
|
-
|
|
537
|
-
const copySource = computed(() => currentCode.value ?? activeSourceCode.value)
|
|
538
|
-
|
|
539
|
-
const { copied, copy } = useClipboard({
|
|
540
|
-
source: copySource,
|
|
541
|
-
legacy: true,
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
const cls = computed(() => {
|
|
545
|
-
const cls: string[] = []
|
|
546
|
-
if (active.value) {
|
|
547
|
-
cls.push('border-primary')
|
|
548
|
-
}
|
|
549
|
-
if (props.simplify) {
|
|
550
|
-
cls.push('ant-doc-demo-box-simplify')
|
|
551
|
-
}
|
|
552
|
-
if (props.debug) {
|
|
553
|
-
cls.push('ant-doc-demo-box-debug')
|
|
554
|
-
}
|
|
555
|
-
return cls
|
|
556
|
-
})
|
|
40
|
+
const wrap = hasThemeProvider()
|
|
557
41
|
</script>
|
|
558
42
|
|
|
559
43
|
<template>
|
|
560
|
-
<
|
|
561
|
-
<
|
|
562
|
-
<
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
<!-- Preview area: always visible, shows live-compiled or original component -->
|
|
569
|
-
<section v-if="!iframe" class="ant-doc-demo-box-demo vp-raw" :style="demoStyle">
|
|
570
|
-
<Skeleton v-if="!demo" active :paragraph="{ rows: 5 }" />
|
|
571
|
-
<Suspense v-else>
|
|
572
|
-
<component :is="liveComponent || component" v-if="liveComponent || demo?.component" />
|
|
573
|
-
<template #fallback>
|
|
574
|
-
<Skeleton active :paragraph="{ rows: 5 }" />
|
|
575
|
-
</template>
|
|
576
|
-
</Suspense>
|
|
577
|
-
</section>
|
|
578
|
-
<template v-else-if="context.iframeRenderer">
|
|
579
|
-
<component :is="context.iframeRenderer(id, props.iframe)" />
|
|
580
|
-
</template>
|
|
581
|
-
<!-- Compile error hint -->
|
|
582
|
-
<div v-if="compileError && showCode" class="ant-doc-demo-box-compile-error">
|
|
583
|
-
<pre>{{ compileError }}</pre>
|
|
584
|
-
</div>
|
|
585
|
-
<!-- Meta: title, description, actions -->
|
|
586
|
-
<section class="ant-doc-demo-box-meta markdown">
|
|
587
|
-
<div class="ant-doc-demo-box-title">
|
|
588
|
-
<a ref="titleRef" :href="`#${id}`" @click="handleScroll">
|
|
589
|
-
<slot />
|
|
590
|
-
</a>
|
|
591
|
-
<a target="_blank" rel="noopener" class="ant-doc-demo-box-edit-icon">
|
|
592
|
-
<EditOutlined />
|
|
593
|
-
</a>
|
|
594
|
-
</div>
|
|
595
|
-
<div v-if="description" class="ant-doc-demo-box-meta-description">
|
|
596
|
-
<div v-html="description" />
|
|
597
|
-
</div>
|
|
598
|
-
<Flex class="ant-doc-demo-box-actions" wrap gap="middle">
|
|
599
|
-
<a v-if="context.openStackBlitz" class="ant-doc-demo-box-code-action" @click="handleStackBlitz">
|
|
600
|
-
<Tooltip :title="t('action.stackblitz')">
|
|
601
|
-
<ThunderboltOutlined />
|
|
602
|
-
</Tooltip>
|
|
603
|
-
</a>
|
|
604
|
-
<a
|
|
605
|
-
v-if="context.demoPageUrl"
|
|
606
|
-
class="ant-doc-demo-box-code-action"
|
|
607
|
-
:href="context.demoPageUrl(id)"
|
|
608
|
-
target="_blank"
|
|
609
|
-
rel="noopener"
|
|
610
|
-
>
|
|
611
|
-
<Tooltip :title="t('action.externalLink')">
|
|
612
|
-
<ExternalLinkIcon />
|
|
613
|
-
</Tooltip>
|
|
614
|
-
</a>
|
|
615
|
-
<div v-if="context.openPlayground" class="ant-doc-demo-box-code-action" @click="handleOpenPlayground">
|
|
616
|
-
<Tooltip :title="t('action.openPlayground')">
|
|
617
|
-
<CodeOutlined />
|
|
618
|
-
</Tooltip>
|
|
619
|
-
</div>
|
|
620
|
-
<div class="ant-doc-demo-box-expand-icon ant-doc-demo-box-code-action" @click="handleShowCode">
|
|
621
|
-
<Tooltip :title="t(`action.${showCode ? 'expandedCode' : 'expandCode'}`)">
|
|
622
|
-
<ExpandIcon :expanded="showCode" />
|
|
623
|
-
</Tooltip>
|
|
624
|
-
</div>
|
|
625
|
-
</Flex>
|
|
626
|
-
</section>
|
|
627
|
-
<!-- Code editor (only when expanded) -->
|
|
628
|
-
<template v-if="showCode">
|
|
629
|
-
<!-- 加载中 -->
|
|
630
|
-
<div v-if="sourceLoading" class="ant-doc-demo-box-code-loading">
|
|
631
|
-
<Spin />
|
|
632
|
-
</div>
|
|
633
|
-
<!-- 加载失败 -->
|
|
634
|
-
<div v-else-if="sourceLoadError" class="ant-doc-demo-box-code">
|
|
635
|
-
<Alert type="error" :message="t('action.loadError')" />
|
|
636
|
-
</div>
|
|
637
|
-
<!-- 正常展示 -->
|
|
638
|
-
<template v-else>
|
|
639
|
-
<div class="ant-doc-demo-box-code-tabs">
|
|
640
|
-
<Tabs
|
|
641
|
-
v-model:active-key="activeCodeType"
|
|
642
|
-
centered
|
|
643
|
-
size="small"
|
|
644
|
-
>
|
|
645
|
-
<TabPane key="ts" :tab="t('type.typescript')" />
|
|
646
|
-
<TabPane v-if="hasJsSource" key="js" :tab="t('type.javascript')" />
|
|
647
|
-
<TabPane
|
|
648
|
-
v-for="file in extraFiles"
|
|
649
|
-
:key="file.name"
|
|
650
|
-
:tab="displayFileName(file.name)"
|
|
651
|
-
/>
|
|
652
|
-
</Tabs>
|
|
653
|
-
</div>
|
|
654
|
-
<div class="ant-doc-demo-box-code">
|
|
655
|
-
<Tooltip :title="t(`action.${copied ? 'copied' : 'copy'}`)">
|
|
656
|
-
<div class="ant-doc-demo-box-code-copy" :class="copied ? 'ant-doc-demo-box-code-copied' : ''" @click="copy()">
|
|
657
|
-
<CopyOutlined v-if="!copied" />
|
|
658
|
-
<CheckOutlined v-else />
|
|
659
|
-
</div>
|
|
660
|
-
</Tooltip>
|
|
661
|
-
<SandpackProvider
|
|
662
|
-
template="vite-vue-ts"
|
|
663
|
-
:files="sandpackFiles"
|
|
664
|
-
:theme="sandpackTheme"
|
|
665
|
-
:options="sandpackOptions"
|
|
666
|
-
>
|
|
667
|
-
<CodeEditorBridge
|
|
668
|
-
ref="editorBridgeRef"
|
|
669
|
-
@update:code="handleCodeChange"
|
|
670
|
-
/>
|
|
671
|
-
</SandpackProvider>
|
|
672
|
-
</div>
|
|
673
|
-
<!-- Collapse button at bottom -->
|
|
674
|
-
<div class="ant-doc-demo-box-collapse-btn" @click="handleShowCode">
|
|
675
|
-
<ExpandIcon :expanded="showCode" />
|
|
676
|
-
<span>{{ t('action.expandedCode') }}</span>
|
|
677
|
-
</div>
|
|
678
|
-
</template>
|
|
679
|
-
</template>
|
|
680
|
-
</template>
|
|
681
|
-
</section>
|
|
44
|
+
<ThemeProvider v-if="!wrap">
|
|
45
|
+
<DemoPanel v-bind="props">
|
|
46
|
+
<slot />
|
|
47
|
+
</DemoPanel>
|
|
48
|
+
</ThemeProvider>
|
|
49
|
+
<DemoPanel v-else v-bind="props">
|
|
50
|
+
<slot />
|
|
51
|
+
</DemoPanel>
|
|
682
52
|
</template>
|