@poveste/app 0.19.0 → 0.20.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/dist/bundled/app.css +174 -1
- package/dist/bundled/components/HomeView.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/bundled/components/app/AppLogo.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/bundled/components/app/TopBarChipItem.js +10 -6
- package/dist/bundled/components/layout/LayoutItem.js +1 -0
- package/dist/bundled/components/layout/LayoutItem.vue_vue_type_script_setup_true_lang.js +20 -23
- package/dist/bundled/components/panel/ComponentDocs.js +7 -0
- package/dist/bundled/components/panel/ComponentDocs.vue_vue_type_script_setup_true_lang.js +132 -0
- package/dist/bundled/components/panel/StoryDocs.vue_vue_type_script_setup_true_lang.js +32 -21
- package/dist/bundled/components/panel/StorySourceCode.js +1 -1
- package/dist/bundled/components/panel/StorySourceCode.vue_vue_type_script_setup_true_lang.js +20 -4
- package/dist/bundled/components/story/GenericMountStory.vue_vue_type_script_setup_true_lang.js +3 -7
- package/dist/bundled/components/story/GenericRenderStory.vue_vue_type_script_setup_true_lang.js +3 -10
- package/dist/bundled/components/toolbar/DevOnlyToolbarOpenInEditor.vue_vue_type_script_setup_true_lang.js +0 -1
- package/dist/bundled/router.js +15 -12
- package/dist/bundled/util/client-plugin.js +22 -0
- package/dist/bundled/util/component-docs-data.js +1 -0
- package/dist/bundled/util/component-docs.js +57 -0
- package/dist/bundled/util/docs.js +1 -1
- package/dist/bundled/util/icons.generated.js +2 -2
- package/dist/bundled/util/mapping.js +1 -5
- package/dist/style.css +5 -46
- package/package.json +15 -15
- package/src/app/components/HomeView.vue +2 -2
- package/src/app/components/app/AppLogo.vue +4 -6
- package/src/app/components/app/TopBarChipItem.vue +55 -6
- package/src/app/components/layout/LayoutItem.vue +56 -6
- package/src/app/components/panel/ComponentDocs.vue +257 -0
- package/src/app/components/panel/StoryDocs.vue +11 -2
- package/src/app/components/panel/StorySourceCode.vue +27 -3
- package/src/app/components/story/GenericMountStory.vue +3 -5
- package/src/app/components/story/GenericRenderStory.vue +4 -12
- package/src/app/components/toolbar/DevOnlyToolbarOpenInEditor.vue +0 -1
- package/src/app/router.ts +26 -15
- package/src/app/util/client-plugin.ts +33 -0
- package/src/app/util/component-docs-data.ts +3 -0
- package/src/app/util/component-docs.ts +78 -0
- package/src/app/util/icons.generated.ts +2 -2
- package/src/app/util/mapping.ts +1 -5
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { DocProp } from '@poveste/shared'
|
|
3
|
+
import type { ComponentDocsEntry } from '../../util/component-docs'
|
|
4
|
+
import { tagHref, tagText } from '../../util/component-docs'
|
|
5
|
+
|
|
6
|
+
defineProps<{
|
|
7
|
+
components: ComponentDocsEntry[]
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
function shownDefault(prop: DocProp) {
|
|
11
|
+
return prop.default ?? prop.defaultTag
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div
|
|
17
|
+
class="poveste-component-docs"
|
|
18
|
+
data-testid="component-docs"
|
|
19
|
+
>
|
|
20
|
+
<section
|
|
21
|
+
v-for="component of components"
|
|
22
|
+
:key="component.file"
|
|
23
|
+
data-slot="component"
|
|
24
|
+
:data-component="component.name"
|
|
25
|
+
>
|
|
26
|
+
<h3 data-slot="component-name">
|
|
27
|
+
{{ component.name }}
|
|
28
|
+
</h3>
|
|
29
|
+
|
|
30
|
+
<p
|
|
31
|
+
v-if="component.error"
|
|
32
|
+
data-slot="error"
|
|
33
|
+
>
|
|
34
|
+
Could not read this component's types: {{ component.error }}
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<template v-else-if="component.doc">
|
|
38
|
+
<table
|
|
39
|
+
v-if="component.doc.props.length"
|
|
40
|
+
data-slot="props"
|
|
41
|
+
>
|
|
42
|
+
<thead>
|
|
43
|
+
<tr>
|
|
44
|
+
<th>Prop</th>
|
|
45
|
+
<th>Type</th>
|
|
46
|
+
<th>Default</th>
|
|
47
|
+
<th>Description</th>
|
|
48
|
+
</tr>
|
|
49
|
+
</thead>
|
|
50
|
+
<tbody>
|
|
51
|
+
<tr
|
|
52
|
+
v-for="prop of component.doc.props"
|
|
53
|
+
:key="prop.name"
|
|
54
|
+
:data-prop="prop.name"
|
|
55
|
+
>
|
|
56
|
+
<td>
|
|
57
|
+
<code
|
|
58
|
+
:class="{ deprecated: tagText(prop.tags, 'deprecated') !== undefined }"
|
|
59
|
+
>{{ prop.name }}</code><span
|
|
60
|
+
v-if="prop.required"
|
|
61
|
+
title="Required"
|
|
62
|
+
data-slot="required"
|
|
63
|
+
>*</span>
|
|
64
|
+
</td>
|
|
65
|
+
<td><code>{{ prop.type }}</code></td>
|
|
66
|
+
<td>
|
|
67
|
+
<code v-if="shownDefault(prop) !== undefined">{{ shownDefault(prop) }}</code>
|
|
68
|
+
<span
|
|
69
|
+
v-if="__POVESTE_DEV__ && prop.defaultConflict"
|
|
70
|
+
:title="`The code's default is shown; the @default tag says ${prop.defaultTag}`"
|
|
71
|
+
data-slot="default-conflict"
|
|
72
|
+
>≠</span>
|
|
73
|
+
</td>
|
|
74
|
+
<td>
|
|
75
|
+
<span
|
|
76
|
+
v-if="tagText(prop.tags, 'deprecated') !== undefined"
|
|
77
|
+
data-slot="deprecated"
|
|
78
|
+
>Deprecated<template v-if="tagText(prop.tags, 'deprecated')">: {{ tagText(prop.tags, 'deprecated') }}</template></span>
|
|
79
|
+
{{ prop.description }}
|
|
80
|
+
<template
|
|
81
|
+
v-for="tag of prop.tags.filter(tag => tag.name === 'see' || tag.name === 'link')"
|
|
82
|
+
:key="tag.text"
|
|
83
|
+
>
|
|
84
|
+
<a
|
|
85
|
+
v-if="tagHref(tag.text ?? '')"
|
|
86
|
+
:href="tagHref(tag.text ?? '')"
|
|
87
|
+
target="_blank"
|
|
88
|
+
rel="noopener"
|
|
89
|
+
data-slot="see"
|
|
90
|
+
>{{ tag.text }}</a>
|
|
91
|
+
<span
|
|
92
|
+
v-else
|
|
93
|
+
data-slot="see"
|
|
94
|
+
>{{ tag.text }}</span>
|
|
95
|
+
</template>
|
|
96
|
+
<pre
|
|
97
|
+
v-if="tagText(prop.tags, 'example')"
|
|
98
|
+
data-slot="example"
|
|
99
|
+
><code>{{ tagText(prop.tags, 'example') }}</code></pre>
|
|
100
|
+
</td>
|
|
101
|
+
</tr>
|
|
102
|
+
</tbody>
|
|
103
|
+
</table>
|
|
104
|
+
|
|
105
|
+
<table
|
|
106
|
+
v-if="component.doc.slots.length"
|
|
107
|
+
data-slot="slots"
|
|
108
|
+
>
|
|
109
|
+
<thead>
|
|
110
|
+
<tr>
|
|
111
|
+
<th>Slot</th>
|
|
112
|
+
<th>Type</th>
|
|
113
|
+
<th>Description</th>
|
|
114
|
+
</tr>
|
|
115
|
+
</thead>
|
|
116
|
+
<tbody>
|
|
117
|
+
<tr
|
|
118
|
+
v-for="slot of component.doc.slots"
|
|
119
|
+
:key="slot.name"
|
|
120
|
+
:data-slot-name="slot.name"
|
|
121
|
+
>
|
|
122
|
+
<td><code>{{ slot.name }}</code></td>
|
|
123
|
+
<td><code v-if="slot.type">{{ slot.type }}</code></td>
|
|
124
|
+
<td>{{ slot.description }}</td>
|
|
125
|
+
</tr>
|
|
126
|
+
</tbody>
|
|
127
|
+
</table>
|
|
128
|
+
|
|
129
|
+
<table
|
|
130
|
+
v-if="component.doc.events.length"
|
|
131
|
+
data-slot="events"
|
|
132
|
+
>
|
|
133
|
+
<thead>
|
|
134
|
+
<tr>
|
|
135
|
+
<th>Event</th>
|
|
136
|
+
<th>Payload</th>
|
|
137
|
+
<th>Description</th>
|
|
138
|
+
</tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
<tr
|
|
142
|
+
v-for="event of component.doc.events"
|
|
143
|
+
:key="event.name"
|
|
144
|
+
:data-event="event.name"
|
|
145
|
+
>
|
|
146
|
+
<td><code>{{ event.name }}</code></td>
|
|
147
|
+
<td><code v-if="event.type">{{ event.type }}</code></td>
|
|
148
|
+
<td>{{ event.description }}</td>
|
|
149
|
+
</tr>
|
|
150
|
+
</tbody>
|
|
151
|
+
</table>
|
|
152
|
+
</template>
|
|
153
|
+
</section>
|
|
154
|
+
</div>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
157
|
+
<style scoped>
|
|
158
|
+
.poveste-component-docs {
|
|
159
|
+
padding: 1rem;
|
|
160
|
+
font-size: .875rem;
|
|
161
|
+
color: var(--color-gray-800);
|
|
162
|
+
|
|
163
|
+
/* On the subject, not `.ptw-dark &`: `.ptw-dark` sits above the `@scope` root (#101). */
|
|
164
|
+
&:where(.ptw-dark, .ptw-dark *) {
|
|
165
|
+
color: var(--color-gray-200);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
section + section {
|
|
170
|
+
margin-top: 1.5rem;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
h3 {
|
|
174
|
+
font-weight: 600;
|
|
175
|
+
font-size: 1rem;
|
|
176
|
+
margin-bottom: .5rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
table {
|
|
180
|
+
width: 100%;
|
|
181
|
+
border-collapse: collapse;
|
|
182
|
+
margin-bottom: 1rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
th {
|
|
186
|
+
text-align: start;
|
|
187
|
+
font-weight: 500;
|
|
188
|
+
color: var(--color-gray-500);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
th,
|
|
192
|
+
td {
|
|
193
|
+
padding: .375rem .5rem;
|
|
194
|
+
border-bottom: 1px solid var(--color-gray-200);
|
|
195
|
+
vertical-align: top;
|
|
196
|
+
|
|
197
|
+
&:where(.ptw-dark, .ptw-dark *) {
|
|
198
|
+
border-color: var(--color-gray-750);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
code {
|
|
203
|
+
font-size: .8125rem;
|
|
204
|
+
background-color: var(--color-gray-100);
|
|
205
|
+
border-radius: .25rem;
|
|
206
|
+
padding: 0 .25rem;
|
|
207
|
+
|
|
208
|
+
&:where(.ptw-dark, .ptw-dark *) {
|
|
209
|
+
background-color: var(--color-gray-750);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
pre code {
|
|
214
|
+
display: block;
|
|
215
|
+
padding: .5rem;
|
|
216
|
+
margin-top: .25rem;
|
|
217
|
+
white-space: pre-wrap;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.deprecated {
|
|
221
|
+
text-decoration: line-through;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
[data-slot='required'] {
|
|
225
|
+
color: var(--color-red-500);
|
|
226
|
+
margin-inline-start: .125rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
[data-slot='deprecated'],
|
|
230
|
+
[data-slot='default-conflict'] {
|
|
231
|
+
color: var(--color-amber-600);
|
|
232
|
+
font-weight: 500;
|
|
233
|
+
|
|
234
|
+
&:where(.ptw-dark, .ptw-dark *) {
|
|
235
|
+
color: var(--color-amber-400);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
[data-slot='default-conflict'] {
|
|
240
|
+
margin-inline-start: .25rem;
|
|
241
|
+
cursor: help;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
[data-slot='see'] {
|
|
245
|
+
display: block;
|
|
246
|
+
margin-top: .25rem;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
a[data-slot='see'] {
|
|
250
|
+
color: var(--color-primary-500);
|
|
251
|
+
text-decoration: underline;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
[data-slot='error'] {
|
|
255
|
+
color: var(--color-gray-500);
|
|
256
|
+
}
|
|
257
|
+
</style>
|
|
@@ -5,9 +5,11 @@ import { Icon } from '@iconify/vue'
|
|
|
5
5
|
import { markdownFiles } from 'virtual:$poveste-markdown-files'
|
|
6
6
|
import { computed, nextTick, ref, toRefs, watch, watchEffect } from 'vue'
|
|
7
7
|
import { useRoute, useRouter } from 'vue-router'
|
|
8
|
+
import { useComponentDocs } from '../../util/component-docs.js'
|
|
8
9
|
import { povesteConfig } from '../../util/config.js'
|
|
9
10
|
import BaseEmpty from '../base/BaseEmpty.vue'
|
|
10
11
|
import DevOnlyToolbarOpenInEditor from '../toolbar/DevOnlyToolbarOpenInEditor.vue'
|
|
12
|
+
import ComponentDocs from './ComponentDocs.vue'
|
|
11
13
|
|
|
12
14
|
export function useStoryDoc(story: Ref<Story>) {
|
|
13
15
|
const renderedDoc = ref('')
|
|
@@ -76,6 +78,9 @@ const { story } = toRefs(props)
|
|
|
76
78
|
|
|
77
79
|
const { renderedDoc } = useStoryDoc(story)
|
|
78
80
|
|
|
81
|
+
// Opening the docs is what starts auto-docs extraction (#1159).
|
|
82
|
+
const { components } = useComponentDocs(computed(() => story.value?.id))
|
|
83
|
+
|
|
79
84
|
// Markdown links to other stories
|
|
80
85
|
const router = useRouter()
|
|
81
86
|
|
|
@@ -185,7 +190,7 @@ const filePath = computed(() => story.value.file?.docsFilePath ?? (props.standal
|
|
|
185
190
|
</div>
|
|
186
191
|
|
|
187
192
|
<BaseEmpty
|
|
188
|
-
v-if="!renderedDoc"
|
|
193
|
+
v-if="!renderedDoc && !components?.length"
|
|
189
194
|
>
|
|
190
195
|
<Icon
|
|
191
196
|
icon="carbon:document-unknown"
|
|
@@ -194,11 +199,15 @@ const filePath = computed(() => story.value.file?.docsFilePath ?? (props.standal
|
|
|
194
199
|
No documentation available
|
|
195
200
|
</BaseEmpty>
|
|
196
201
|
<div
|
|
197
|
-
v-
|
|
202
|
+
v-if="renderedDoc"
|
|
198
203
|
ref="renderedEl"
|
|
199
204
|
class="prose dark:prose-invert p-4 max-w-none"
|
|
200
205
|
data-testid="story-docs"
|
|
201
206
|
v-html="renderedDoc"
|
|
202
207
|
/>
|
|
208
|
+
<ComponentDocs
|
|
209
|
+
v-if="components?.length"
|
|
210
|
+
:components="components"
|
|
211
|
+
/>
|
|
203
212
|
</div>
|
|
204
213
|
</template>
|
|
@@ -19,8 +19,21 @@ const props = defineProps<{
|
|
|
19
19
|
// down were both errors about a type nobody meant to declare.
|
|
20
20
|
const generateSourceCodeFn = shallowRef<GenerateSourceCode | null>(null)
|
|
21
21
|
|
|
22
|
+
// Until a source has settled, an empty one means "not yet", not "none": a copy in
|
|
23
|
+
// that window said "Nothing to copy" with the source a moment away (#1314).
|
|
24
|
+
const dynamicSettled = ref(false)
|
|
25
|
+
const staticSettled = ref(false)
|
|
26
|
+
|
|
22
27
|
watchEffect(async () => {
|
|
23
|
-
|
|
28
|
+
let generate: GenerateSourceCode | undefined
|
|
29
|
+
try {
|
|
30
|
+
generate = await loadGenerateSourceCode(props.story)
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
if (!generate) {
|
|
34
|
+
dynamicSettled.value = true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
24
37
|
if (generate) {
|
|
25
38
|
generateSourceCodeFn.value = markRaw(generate)
|
|
26
39
|
}
|
|
@@ -40,6 +53,7 @@ watch(() => [props.variant, generateSourceCodeFn.value], async () => {
|
|
|
40
53
|
if (!generateSourceCodeFn.value) return
|
|
41
54
|
error.value = null
|
|
42
55
|
dynamicSourceCode.value = ''
|
|
56
|
+
dynamicSettled.value = false
|
|
43
57
|
try {
|
|
44
58
|
dynamicSourceCode.value = (await getDynamicSourceCode(props.variant, generateSourceCodeFn.value)) ?? ''
|
|
45
59
|
}
|
|
@@ -47,6 +61,7 @@ watch(() => [props.variant, generateSourceCodeFn.value], async () => {
|
|
|
47
61
|
console.error(e)
|
|
48
62
|
error.value = e instanceof Error ? e.message : String(e)
|
|
49
63
|
}
|
|
64
|
+
dynamicSettled.value = true
|
|
50
65
|
|
|
51
66
|
// Auto-switch
|
|
52
67
|
if (!dynamicSourceCode.value) {
|
|
@@ -62,11 +77,19 @@ watch(() => [props.variant, generateSourceCodeFn.value], async () => {
|
|
|
62
77
|
const staticSourceCode = ref('')
|
|
63
78
|
watch(() => [props.story, props.story?.file?.source], async () => {
|
|
64
79
|
staticSourceCode.value = ''
|
|
65
|
-
|
|
80
|
+
staticSettled.value = false
|
|
81
|
+
try {
|
|
82
|
+
staticSourceCode.value = (await getStaticSourceCode(props.story)) ?? ''
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
staticSettled.value = true
|
|
86
|
+
}
|
|
66
87
|
}, {
|
|
67
88
|
immediate: true,
|
|
68
89
|
})
|
|
69
90
|
|
|
91
|
+
const loading = computed(() => displayedSource.value === 'dynamic' ? !dynamicSettled.value : !staticSettled.value)
|
|
92
|
+
|
|
70
93
|
const displayedSourceCode = computed(() => {
|
|
71
94
|
if (displayedSource.value === 'dynamic') {
|
|
72
95
|
return dynamicSourceCode.value
|
|
@@ -189,6 +212,7 @@ watch(sourceHtml, async () => {
|
|
|
189
212
|
|
|
190
213
|
<HstCopyIcon
|
|
191
214
|
:content="displayedSourceCode"
|
|
215
|
+
:loading="loading"
|
|
192
216
|
class="flex-none"
|
|
193
217
|
/>
|
|
194
218
|
</div>
|
|
@@ -205,7 +229,7 @@ watch(sourceHtml, async () => {
|
|
|
205
229
|
icon="carbon:code-hide"
|
|
206
230
|
class="w-8 h-8 opacity-50 mb-6"
|
|
207
231
|
/>
|
|
208
|
-
<span>Not available</span>
|
|
232
|
+
<span>{{ loading ? 'Loading' : 'Not available' }}</span>
|
|
209
233
|
</BaseEmpty>
|
|
210
234
|
|
|
211
235
|
<textarea
|
|
@@ -7,8 +7,8 @@ export default {
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
8
|
import type { Component } from 'vue'
|
|
9
9
|
import type { Story } from '../../types'
|
|
10
|
-
import { clientSupportPlugins } from 'virtual:$poveste-support-plugins-client'
|
|
11
10
|
import { markRaw, ref, watchEffect } from 'vue'
|
|
11
|
+
import { loadClientPlugin } from '../../util/client-plugin.js'
|
|
12
12
|
|
|
13
13
|
const props = defineProps<{
|
|
14
14
|
story: Story
|
|
@@ -17,10 +17,8 @@ const props = defineProps<{
|
|
|
17
17
|
const mountComponent = ref<Component | null>(null)
|
|
18
18
|
|
|
19
19
|
watchEffect(async () => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
if (clientPlugin) {
|
|
23
|
-
const pluginModule = await clientPlugin()
|
|
20
|
+
const pluginModule = await loadClientPlugin(props.story.file?.supportPluginId)
|
|
21
|
+
if (pluginModule) {
|
|
24
22
|
mountComponent.value = markRaw(pluginModule.MountStory)
|
|
25
23
|
}
|
|
26
24
|
})
|
|
@@ -7,8 +7,8 @@ export default {
|
|
|
7
7
|
<script lang="ts" setup>
|
|
8
8
|
import type { Component } from 'vue'
|
|
9
9
|
import type { Story } from '../../types'
|
|
10
|
-
import { clientSupportPlugins } from 'virtual:$poveste-support-plugins-client'
|
|
11
10
|
import { markRaw, ref, watchEffect } from 'vue'
|
|
11
|
+
import { loadClientPlugin } from '../../util/client-plugin.js'
|
|
12
12
|
|
|
13
13
|
const props = defineProps<{
|
|
14
14
|
story: Story
|
|
@@ -17,17 +17,9 @@ const props = defineProps<{
|
|
|
17
17
|
const mountComponent = ref<Component | null>(null)
|
|
18
18
|
|
|
19
19
|
watchEffect(async () => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const pluginModule = await clientPlugin()
|
|
25
|
-
mountComponent.value = markRaw(pluginModule.RenderStory)
|
|
26
|
-
}
|
|
27
|
-
catch (e) {
|
|
28
|
-
console.error(e)
|
|
29
|
-
throw e
|
|
30
|
-
}
|
|
20
|
+
const pluginModule = await loadClientPlugin(props.story.file?.supportPluginId)
|
|
21
|
+
if (pluginModule) {
|
|
22
|
+
mountComponent.value = markRaw(pluginModule.RenderStory)
|
|
31
23
|
}
|
|
32
24
|
})
|
|
33
25
|
</script>
|
package/src/app/router.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Router } from 'vue-router'
|
|
1
2
|
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
|
2
3
|
import { povesteConfig } from './util/config'
|
|
3
4
|
|
|
@@ -12,18 +13,28 @@ function createRouterHistory() {
|
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
function newRouter(): Router {
|
|
17
|
+
return createRouter({
|
|
18
|
+
history: createRouterHistory(),
|
|
19
|
+
routes: [
|
|
20
|
+
{
|
|
21
|
+
path: '/',
|
|
22
|
+
name: 'home',
|
|
23
|
+
component: () => import('./components/HomeView.vue'),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: '/story/:storyId',
|
|
27
|
+
name: 'story',
|
|
28
|
+
component: () => import('./components/story/StoryView.vue'),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// A page in `poveste dev` can hold two copies of this module: each importer's
|
|
35
|
+
// URL is stamped when the server serves it, and a story edit stamps most of the
|
|
36
|
+
// chrome. Every copy hands out the router the app installed, or the story store
|
|
37
|
+
// read one still at `/` and the page showed no story until reloaded (#1320).
|
|
38
|
+
export const router: Router = import.meta.hot
|
|
39
|
+
? (import.meta.hot.data.router ??= newRouter())
|
|
40
|
+
: newRouter()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ClientSupportPlugin } from '@poveste/shared'
|
|
2
|
+
import type { Component } from 'vue'
|
|
3
|
+
import { reportStoryError, toStoryError } from '@poveste/shared'
|
|
4
|
+
import { clientSupportPlugins } from 'virtual:$poveste-support-plugins-client'
|
|
5
|
+
import { occupant } from './occupant.js'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* A framework's client imports the book's setup file, so a setup file that throws
|
|
9
|
+
* when it loads fails here, for every story, before any story code runs. In a
|
|
10
|
+
* built book nothing else reported it: Vue's production build only logs an async
|
|
11
|
+
* effect's rejection, and every preview stayed blank with the error in the console
|
|
12
|
+
* alone (#1323). Not rethrown, because Vue's development build would rethrow it as
|
|
13
|
+
* an unhandled rejection, whose report would replace this one.
|
|
14
|
+
*/
|
|
15
|
+
export async function loadClientPlugin(supportPluginId: string | undefined): Promise<ClientSupportPlugin<Component> | undefined> {
|
|
16
|
+
const load = supportPluginId ? clientSupportPlugins[supportPluginId] : undefined
|
|
17
|
+
if (!load) {
|
|
18
|
+
return undefined
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return await load()
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const { message, stack } = toStoryError(error)
|
|
25
|
+
const reported = Object.assign(new Error(`The story renderer failed to load, and it imports the setup file: ${message}`), { stack })
|
|
26
|
+
reportStoryError(reported, {
|
|
27
|
+
storyId: occupant.storyId ?? undefined,
|
|
28
|
+
variantId: occupant.variantId ?? undefined,
|
|
29
|
+
})
|
|
30
|
+
console.error(error)
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ComponentDoc, DocTag } from '@poveste/shared'
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
import { onBeforeUnmount, ref, watch } from 'vue'
|
|
4
|
+
|
|
5
|
+
export interface ComponentDocsEntry {
|
|
6
|
+
/** Relative to the book's root. */
|
|
7
|
+
file: string
|
|
8
|
+
/** The file name without its extension. */
|
|
9
|
+
name: string
|
|
10
|
+
doc?: ComponentDoc
|
|
11
|
+
error?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface StoryDocsResult {
|
|
15
|
+
storyId: string
|
|
16
|
+
components: Record<string, { doc?: ComponentDoc, error?: string }>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function toEntries(result: StoryDocsResult): ComponentDocsEntry[] {
|
|
20
|
+
return Object.entries(result.components).map(([file, { doc, error }]) => ({
|
|
21
|
+
file,
|
|
22
|
+
name: file.split('/').pop()!.replace(/\.[^.]+$/, ''),
|
|
23
|
+
...doc ? { doc } : {},
|
|
24
|
+
...error ? { error } : {},
|
|
25
|
+
}))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function tagText(tags: DocTag[], name: string): string | undefined {
|
|
29
|
+
const tag = tags.find(tag => tag.name === name)
|
|
30
|
+
return tag ? (tag.text ?? '') : undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A `@see` or `@link` points somewhere only when it carries a URL. */
|
|
34
|
+
export function tagHref(text: string): string | undefined {
|
|
35
|
+
return /https?:\/\/\S+/.exec(text)?.[0]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The components a story imports, documented. In dev they are asked of the server
|
|
40
|
+
* when this is first used, which is what starts extraction at all (#1159); in a
|
|
41
|
+
* built book they come from what `poveste build` extracted.
|
|
42
|
+
*/
|
|
43
|
+
export function useComponentDocs(storyId: Ref<string | undefined>) {
|
|
44
|
+
const components = ref<ComponentDocsEntry[]>()
|
|
45
|
+
const hot = import.meta.hot
|
|
46
|
+
if (!hot) {
|
|
47
|
+
watch(storyId, async (id) => {
|
|
48
|
+
const { componentDocs } = await import('./component-docs-data')
|
|
49
|
+
const docs = id ? componentDocs[id] : undefined
|
|
50
|
+
components.value = id && docs ? toEntries({ storyId: id, components: docs }) : undefined
|
|
51
|
+
}, { immediate: true })
|
|
52
|
+
return { components }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ask() {
|
|
56
|
+
if (storyId.value) {
|
|
57
|
+
hot!.send('poveste:docgen-request', { storyId: storyId.value })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function onResult(result: StoryDocsResult) {
|
|
61
|
+
if (result.storyId === storyId.value) {
|
|
62
|
+
components.value = toEntries(result)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
hot.on('poveste:docgen-result', onResult)
|
|
67
|
+
hot.on('poveste:docgen-stale', ask)
|
|
68
|
+
onBeforeUnmount(() => {
|
|
69
|
+
hot.off('poveste:docgen-result', onResult)
|
|
70
|
+
hot.off('poveste:docgen-stale', ask)
|
|
71
|
+
})
|
|
72
|
+
watch(storyId, () => {
|
|
73
|
+
components.value = undefined
|
|
74
|
+
ask()
|
|
75
|
+
}, { immediate: true })
|
|
76
|
+
|
|
77
|
+
return { components }
|
|
78
|
+
}
|