@sewadah/selaras-docs 1.2.3 → 1.3.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/CHANGELOG.md +35 -0
- package/README.md +105 -1
- package/app/app.config.ts +2 -1
- package/app/app.vue +3 -1
- package/app/assets/style.css +52 -5
- package/app/components/DocsEditLink.vue +4 -16
- package/app/components/DocsEmptyState.vue +72 -0
- package/app/components/DocsErrorBoundary.vue +27 -0
- package/app/components/DocsErrorIllustration.vue +20 -0
- package/app/components/DocsErrorPage.vue +81 -0
- package/app/components/DocsErrorState.vue +161 -0
- package/app/components/DocsFooter.vue +5 -0
- package/app/components/DocsPageActions.vue +25 -0
- package/app/components/DocsTableOfContents.vue +1 -0
- package/app/components/DocsThemePicker.vue +3 -0
- package/app/components/content/DocsExample.vue +3 -1
- package/app/error.vue +11 -0
- package/app/pages/[...slug].vue +38 -8
- package/app/utils/repository-links.ts +29 -0
- package/content.config.ts +1 -0
- package/package.json +2 -2
- package/types.d.ts +26 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.3.0
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/sewadahapp/selaras/compare/selaras-docs-v1.2.4...selaras-docs-v1.3.0)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- **feat(docs):** add logo to footer attribution ([1af91a6](https://github.com/sewadahapp/selaras/commit/1af91a6b66ac279255a0a6ca421dda89ee6c966e))
|
|
10
|
+
- **feat(docs):** add contextual page contribution actions ([4e21d84](https://github.com/sewadahapp/selaras/commit/4e21d843dc514d6f6f50f0922371fe0cbf94a9c7))
|
|
11
|
+
|
|
12
|
+
### 🩹 Fixes
|
|
13
|
+
|
|
14
|
+
- **fix(docs):** render an empty state for blank Markdown pages ([bf4cb17](https://github.com/sewadahapp/selaras/commit/bf4cb1746311c10ff389e997aaec867b60fd9a83))
|
|
15
|
+
- **fix(docs):** provide an optional theme picker fallback ([3ba8c5d](https://github.com/sewadahapp/selaras/commit/3ba8c5d2d24d45bab98fdae8163de0338ed11d7c))
|
|
16
|
+
- **fix(docs):** align and refine footer layout ([029956a](https://github.com/sewadahapp/selaras/commit/029956a79b11b6d13610348a3f1bec516036dacb))
|
|
17
|
+
|
|
18
|
+
Published against `@sewadah/selaras` ^0.9.0.
|
|
19
|
+
|
|
20
|
+
## v1.2.4
|
|
21
|
+
|
|
22
|
+
[compare changes](https://github.com/sewadahapp/selaras/compare/selaras-docs-v1.2.3...selaras-docs-v1.2.4)
|
|
23
|
+
|
|
24
|
+
### 🚀 Enhancements
|
|
25
|
+
|
|
26
|
+
- **feat(docs):** add interactive application blocks ([102ee68](https://github.com/sewadahapp/selaras/commit/102ee68bef82d12b07dc4788dc8ea2378435e0ea))
|
|
27
|
+
- **feat(docs):** add error pages and local recovery boundaries ([371ed7a](https://github.com/sewadahapp/selaras/commit/371ed7a4fd873fa7e4647ab0b89b85dc0bb7a4b7))
|
|
28
|
+
- **feat(docs):** add SEO metadata and static sharing images ([f6ec141](https://github.com/sewadahapp/selaras/commit/f6ec14152def06f91a62292b53ae2df50813b1d5))
|
|
29
|
+
- **feat(docs):** add Seirama branding and design kit ([9fd9f19](https://github.com/sewadahapp/selaras/commit/9fd9f1948feaa19897ae24e982d33099d50a732c))
|
|
30
|
+
- **feat(forms):** add leading and trailing content slots ([ebd0948](https://github.com/sewadahapp/selaras/commit/ebd094813a274d444319cb0354387c770ab8a6ef))
|
|
31
|
+
|
|
32
|
+
### 🩹 Fixes
|
|
33
|
+
|
|
34
|
+
- **fix(docs):** configure custom domain deployment ([b65908a](https://github.com/sewadahapp/selaras/commit/b65908a7aa4ad0d0b67029b41c338633e9faa50d))
|
|
35
|
+
|
|
36
|
+
Published against `@sewadah/selaras` ^0.9.0.
|
|
37
|
+
|
|
3
38
|
## v1.2.3
|
|
4
39
|
|
|
5
40
|
[compare changes](https://github.com/sewadahapp/selaras/compare/selaras-docs-v1.2.2...selaras-docs-v1.2.3)
|
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ export default defineAppConfig({
|
|
|
57
57
|
repository: {
|
|
58
58
|
url: 'https://github.com/example/project',
|
|
59
59
|
editLinks: true,
|
|
60
|
+
issueLinks: true,
|
|
60
61
|
},
|
|
61
62
|
header: {
|
|
62
63
|
search: true,
|
|
@@ -100,12 +101,35 @@ navBadges:
|
|
|
100
101
|
Use `navigation: false` in a page's frontmatter to omit it from navigation, or
|
|
101
102
|
set `navigation` to an object to override its navigation `title`, `icon`,
|
|
102
103
|
`order`, and `navBadges`. Page frontmatter can also set `toc: false`,
|
|
103
|
-
`aside: false`, `editLink: false`, or `prevNext: false` to hide those parts of
|
|
104
|
+
`aside: false`, `editLink: false`, `issueLink: false`, or `prevNext: false` to hide those parts of
|
|
104
105
|
the page shell. The sidebar also accepts an app-wide `collapsed` default; a
|
|
105
106
|
folder's `collapse` value takes precedence. `main.fluid` expands the
|
|
106
107
|
content width, `main.padded: false` removes its outer inset, and `header.fluid`
|
|
107
108
|
and `footer.fluid` allow those regions to span the viewport.
|
|
108
109
|
|
|
110
|
+
### Page actions
|
|
111
|
+
|
|
112
|
+
With `repository.url` configured, **Edit this page** and **Report an issue**
|
|
113
|
+
appear below the table of contents on desktop. On smaller screens, or when
|
|
114
|
+
the page has no table of contents, they appear after the article and before
|
|
115
|
+
Previous/Next navigation. Pages without an aside use the available content width.
|
|
116
|
+
|
|
117
|
+
Set `repository.branch` and `repository.contentDirectory` to the branch and
|
|
118
|
+
Markdown directory in your repository; their defaults are `main` and `content`.
|
|
119
|
+
Generated issue links include the page title and route in a new issue draft.
|
|
120
|
+
The links open the repository in a new tab; they do not submit changes or issues.
|
|
121
|
+
|
|
122
|
+
Use `repository.editLinks: false` or `repository.issueLinks: false` to hide an
|
|
123
|
+
action site-wide. Set `editLink: false` or `issueLink: false` in frontmatter to
|
|
124
|
+
hide it on one page. GitHub and GitLab URLs are recognized automatically;
|
|
125
|
+
set `repository.issueUrl` to use a different issue tracker or an issue-template
|
|
126
|
+
chooser. Custom issue URLs are used unchanged. With no supported repository or
|
|
127
|
+
custom issue URL, unavailable actions are omitted.
|
|
128
|
+
|
|
129
|
+
Override `components/DocsPageActions.vue` to replace the group, or
|
|
130
|
+
`components/DocsEditLink.vue` to customize the edit link. The
|
|
131
|
+
`DocsTableOfContents` default slot renders content below its heading links.
|
|
132
|
+
|
|
109
133
|
Create an application component with the same name as a layer component, such
|
|
110
134
|
as `components/DocsHeader.vue`, to replace that part of the shell. Smaller
|
|
111
135
|
pieces can be replaced on their own:
|
|
@@ -114,11 +138,91 @@ pieces can be replaced on their own:
|
|
|
114
138
|
| --- | --- | --- |
|
|
115
139
|
| `DocsHeaderBrand` | The header's home link content | `site.logo` and the site name |
|
|
116
140
|
| `DocsFooterBrand` | The start of the footer | `footer.text` |
|
|
141
|
+
| `DocsThemePicker` | Optional header theme control | Empty; provide your own component and set `header.themePicker: true` |
|
|
117
142
|
| `DocsLogo` | The logo image, used by `DocsHeaderBrand` | `site.logo`, switching light/dark; size it with `--selaras-docs-logo-height` |
|
|
118
143
|
|
|
119
144
|
For example, a `components/DocsFooterBrand.vue` containing `<DocsLogo />` adds
|
|
120
145
|
the logo to the footer while leaving the header as configured.
|
|
121
146
|
|
|
147
|
+
The layer does not include the Selaras site's theme presets. To offer your own
|
|
148
|
+
themes, create `components/DocsThemePicker.vue` and enable
|
|
149
|
+
`selarasDocs.header.themePicker` in `app.config.ts`. The layer's empty default
|
|
150
|
+
keeps the header valid when the consuming site does not supply a picker.
|
|
151
|
+
|
|
152
|
+
## Error recovery
|
|
153
|
+
|
|
154
|
+
An existing Markdown file with no body content renders a **No content yet**
|
|
155
|
+
view inside the documentation shell, with a themed vector illustration and a
|
|
156
|
+
link back to the documentation. Zero-byte, whitespace-only and frontmatter-only
|
|
157
|
+
pages remain valid pages rather than 404s. Header, navigation, configured page
|
|
158
|
+
actions and Previous/Next remain available. Adding body content replaces the
|
|
159
|
+
empty view automatically.
|
|
160
|
+
|
|
161
|
+
Set `selarasDocs.empty.title` and `selarasDocs.empty.description` in
|
|
162
|
+
`app.config.ts` to change its copy. Its home action uses `errors.homeTo` and
|
|
163
|
+
`errors.homeLabel`, matching the recovery screens. Override
|
|
164
|
+
`components/DocsEmptyState.vue` for a different empty-page design; its
|
|
165
|
+
`illustration` slot defaults to the existing `DocsErrorIllustration` component.
|
|
166
|
+
|
|
167
|
+
### Errors
|
|
168
|
+
|
|
169
|
+
The layer provides a default `error.vue` for missing pages and fatal errors.
|
|
170
|
+
It preserves the HTTP status code, keeps error pages out of search indexes and
|
|
171
|
+
offers a home link or reload action. The standalone screen avoids loading
|
|
172
|
+
navigation and Content data so it can render when those services fail.
|
|
173
|
+
|
|
174
|
+
Client-side page failures show a local recovery state inside the layout, keeping
|
|
175
|
+
the header and sidebar available. `DocsExample` isolates each live example;
|
|
176
|
+
retrying remounts its content. Navigating to another route clears the page
|
|
177
|
+
boundary. Server rendering errors still use the standalone error screen.
|
|
178
|
+
|
|
179
|
+
Content query failures show a retry state with HTTP 500 during server rendering.
|
|
180
|
+
Only a successful query with no matching document is treated as a 404.
|
|
181
|
+
If a request cannot recover in place, retry reloads the current page to start a
|
|
182
|
+
fresh session. Reloads happen only after the user chooses to retry.
|
|
183
|
+
|
|
184
|
+
Customize the messages and home destination in `app.config.ts`:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
export default defineAppConfig({
|
|
188
|
+
selarasDocs: {
|
|
189
|
+
errors: {
|
|
190
|
+
homeTo: '/guide/introduction',
|
|
191
|
+
homeLabel: 'Back to the guide',
|
|
192
|
+
retryLabel: 'Try again',
|
|
193
|
+
reloadLabel: 'Reload page',
|
|
194
|
+
retryingLabel: 'Trying again…',
|
|
195
|
+
notFound: { title: 'Page not found', description: 'Start again from the guide.' },
|
|
196
|
+
fatal: { title: 'Something went wrong' },
|
|
197
|
+
page: { title: 'This page couldn’t load' },
|
|
198
|
+
example: { title: 'This example couldn’t load' },
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Home destinations are internal routes resolved under the app's `baseURL`.
|
|
205
|
+
The default destination is `/`. Provide an existing page if your site has no
|
|
206
|
+
homepage. Public recovery copy never includes raw exceptions or stack traces.
|
|
207
|
+
|
|
208
|
+
Override `components/DocsErrorIllustration.vue` to replace the default geometric
|
|
209
|
+
document illustration. The optional `missing` boolean distinguishes a 404 from
|
|
210
|
+
other errors. Use inherited theme tokens in inline SVGs for light/dark support.
|
|
211
|
+
`DocsErrorState` also exposes an `illustration` slot. Override `DocsErrorPage`
|
|
212
|
+
for the standalone screen's composition, or supply your own `error.vue` for
|
|
213
|
+
application-specific providers. Applications with their own `app.vue` should
|
|
214
|
+
retain the page boundary inside their layout:
|
|
215
|
+
|
|
216
|
+
```vue
|
|
217
|
+
<template>
|
|
218
|
+
<NuxtLayout>
|
|
219
|
+
<DocsErrorBoundary>
|
|
220
|
+
<NuxtPage />
|
|
221
|
+
</DocsErrorBoundary>
|
|
222
|
+
</NuxtLayout>
|
|
223
|
+
</template>
|
|
224
|
+
```
|
|
225
|
+
|
|
122
226
|
For a host that owns Tailwind compilation or uses a class prefix, set
|
|
123
227
|
`selarasDocs: { css: false }` in `nuxt.config.ts` and import Selaras and the
|
|
124
228
|
docs stylesheet from the host CSS entry.
|
package/app/app.config.ts
CHANGED
|
@@ -7,6 +7,7 @@ export default defineAppConfig({
|
|
|
7
7
|
branch: 'main',
|
|
8
8
|
contentDirectory: 'content',
|
|
9
9
|
editLinks: true,
|
|
10
|
+
issueLinks: true,
|
|
10
11
|
},
|
|
11
12
|
header: {
|
|
12
13
|
showTitle: true,
|
|
@@ -31,7 +32,7 @@ export default defineAppConfig({
|
|
|
31
32
|
links: [
|
|
32
33
|
{
|
|
33
34
|
label: 'Powered by Selaras',
|
|
34
|
-
to: 'https://
|
|
35
|
+
to: 'https://selarasui.my.id',
|
|
35
36
|
target: '_blank',
|
|
36
37
|
rel: 'noreferrer',
|
|
37
38
|
},
|
package/app/app.vue
CHANGED
package/app/assets/style.css
CHANGED
|
@@ -117,6 +117,10 @@
|
|
|
117
117
|
gap: 3rem;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
.selaras-docs-page-grid.selaras-docs-page-grid--wide {
|
|
121
|
+
grid-template-columns: minmax(0, 1fr);
|
|
122
|
+
}
|
|
123
|
+
|
|
120
124
|
.selaras-docs-article {
|
|
121
125
|
min-width: 0;
|
|
122
126
|
}
|
|
@@ -133,21 +137,53 @@
|
|
|
133
137
|
width: 14rem;
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
.selaras-docs-
|
|
140
|
+
.selaras-docs-page-actions {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
gap: 0.25rem;
|
|
144
|
+
margin-top: 1.5rem;
|
|
145
|
+
padding-top: 1rem;
|
|
146
|
+
border-top: 1px solid var(--selaras-resolved-border-default);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.selaras-docs-article > .selaras-docs-page-actions {
|
|
150
|
+
margin-top: 2.5rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.selaras-docs-edit-link,
|
|
154
|
+
.selaras-docs-issue-link {
|
|
137
155
|
display: inline-flex;
|
|
156
|
+
width: fit-content;
|
|
157
|
+
min-height: 2rem;
|
|
138
158
|
align-items: center;
|
|
139
|
-
gap: 0.
|
|
140
|
-
margin-top: 2.5rem;
|
|
159
|
+
gap: 0.5rem;
|
|
141
160
|
color: var(--selaras-resolved-text-muted);
|
|
142
|
-
font-size: 0.
|
|
161
|
+
font-size: 0.8125rem;
|
|
162
|
+
line-height: 1.5;
|
|
143
163
|
text-decoration: none;
|
|
164
|
+
text-underline-offset: 0.25em;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.selaras-docs-edit-link .iconify,
|
|
168
|
+
.selaras-docs-issue-link .iconify {
|
|
169
|
+
width: 1rem;
|
|
170
|
+
height: 1rem;
|
|
171
|
+
flex-shrink: 0;
|
|
144
172
|
}
|
|
145
173
|
|
|
146
|
-
.selaras-docs-edit-link:hover
|
|
174
|
+
.selaras-docs-edit-link:hover,
|
|
175
|
+
.selaras-docs-issue-link:hover {
|
|
147
176
|
color: var(--selaras-resolved-text-default);
|
|
148
177
|
text-decoration: underline;
|
|
149
178
|
}
|
|
150
179
|
|
|
180
|
+
.selaras-docs-edit-link:focus-visible,
|
|
181
|
+
.selaras-docs-issue-link:focus-visible {
|
|
182
|
+
outline: 2px solid var(--selaras-resolved-color-primary-focus);
|
|
183
|
+
outline-offset: 3px;
|
|
184
|
+
border-radius: var(--selaras-resolved-radius-sm);
|
|
185
|
+
}
|
|
186
|
+
|
|
151
187
|
.selaras-docs-surround {
|
|
152
188
|
margin-top: 2rem;
|
|
153
189
|
}
|
|
@@ -219,6 +255,13 @@
|
|
|
219
255
|
background: var(--selaras-resolved-surface-elevated);
|
|
220
256
|
}
|
|
221
257
|
|
|
258
|
+
.selaras-docs-footer-attribution-logo {
|
|
259
|
+
width: 1rem;
|
|
260
|
+
height: 1rem;
|
|
261
|
+
flex-shrink: 0;
|
|
262
|
+
color: var(--selaras-resolved-color-primary-fill);
|
|
263
|
+
}
|
|
264
|
+
|
|
222
265
|
.selaras-docs-footer-links a:focus-visible {
|
|
223
266
|
outline: 2px solid var(--selaras-resolved-color-primary-fill);
|
|
224
267
|
outline-offset: 2px;
|
|
@@ -231,6 +274,10 @@
|
|
|
231
274
|
}
|
|
232
275
|
|
|
233
276
|
@media (min-width: 64rem) {
|
|
277
|
+
.selaras-docs-page-actions--mobile {
|
|
278
|
+
display: none;
|
|
279
|
+
}
|
|
280
|
+
|
|
234
281
|
.selaras-docs-nav-toggle {
|
|
235
282
|
display: none;
|
|
236
283
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { getDocsRepositoryLinks } from '../utils/repository-links'
|
|
3
|
+
|
|
2
4
|
const props = defineProps<{
|
|
3
5
|
path: string
|
|
4
6
|
}>()
|
|
@@ -6,26 +8,12 @@ const props = defineProps<{
|
|
|
6
8
|
const appConfig = useAppConfig()
|
|
7
9
|
const docsConfig = computed(() => appConfig.selarasDocs ?? {})
|
|
8
10
|
|
|
9
|
-
const editUrl = computed(() =>
|
|
10
|
-
const repository = docsConfig.value.repository
|
|
11
|
-
if (!repository?.url || repository.editLinks === false)
|
|
12
|
-
return undefined
|
|
13
|
-
|
|
14
|
-
const base = repository.url.replace(/\/$/, '')
|
|
15
|
-
const relativePath = props.path === '/' ? 'index.md' : `${props.path.replace(/^\//, '')}.md`
|
|
16
|
-
const contentPath = [repository.contentDirectory ?? 'content', relativePath].join('/')
|
|
17
|
-
const branch = repository.branch ?? 'main'
|
|
18
|
-
|
|
19
|
-
if (base.startsWith('https://github.com/'))
|
|
20
|
-
return `${base}/edit/${branch}/${contentPath}`
|
|
21
|
-
if (base.startsWith('https://gitlab.com/'))
|
|
22
|
-
return `${base}/-/edit/${branch}/${contentPath}`
|
|
23
|
-
return undefined
|
|
24
|
-
})
|
|
11
|
+
const editUrl = computed(() => getDocsRepositoryLinks(docsConfig.value.repository, props.path).edit)
|
|
25
12
|
</script>
|
|
26
13
|
|
|
27
14
|
<template>
|
|
28
15
|
<a v-if="editUrl" :href="editUrl" class="selaras-docs-edit-link" target="_blank" rel="noreferrer">
|
|
16
|
+
<SIcon name="hugeicons:pencil-edit-01" aria-hidden="true" />
|
|
29
17
|
Edit this page
|
|
30
18
|
</a>
|
|
31
19
|
</template>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
withDefaults(defineProps<{ heading?: 'h1' | 'h2' }>(), { heading: 'h1' })
|
|
3
|
+
|
|
4
|
+
const appConfig = useAppConfig()
|
|
5
|
+
const config = useRuntimeConfig()
|
|
6
|
+
const headingId = useId()
|
|
7
|
+
const copy = computed(() => appConfig.selarasDocs?.empty ?? {})
|
|
8
|
+
const homeHref = computed(() => {
|
|
9
|
+
const path = appConfig.selarasDocs?.errors?.homeTo ?? '/'
|
|
10
|
+
return `${config.app.baseURL.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
|
|
11
|
+
})
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<section class="selaras-docs-empty-state not-prose" data-docs-empty-state :aria-labelledby="headingId">
|
|
16
|
+
<div class="selaras-docs-empty-artwork">
|
|
17
|
+
<slot name="illustration">
|
|
18
|
+
<DocsErrorIllustration missing />
|
|
19
|
+
</slot>
|
|
20
|
+
</div>
|
|
21
|
+
<component :is="heading" :id="headingId" class="selaras-docs-empty-title">
|
|
22
|
+
{{ copy.title ?? 'No content yet' }}
|
|
23
|
+
</component>
|
|
24
|
+
<p class="selaras-docs-empty-description">
|
|
25
|
+
{{ copy.description ?? 'There’s no content on this page yet. You can explore the rest of the documentation using the navigation.' }}
|
|
26
|
+
</p>
|
|
27
|
+
<SButton as="a" :href="homeHref" variant="outline" color="neutral" class="selaras-docs-empty-action">
|
|
28
|
+
{{ appConfig.selarasDocs?.errors?.homeLabel ?? 'Back to documentation' }}
|
|
29
|
+
</SButton>
|
|
30
|
+
</section>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style scoped>
|
|
34
|
+
.selaras-docs-empty-state {
|
|
35
|
+
max-width: 32rem;
|
|
36
|
+
margin: 3rem auto;
|
|
37
|
+
color: var(--selaras-resolved-text-default);
|
|
38
|
+
text-align: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.selaras-docs-empty-artwork {
|
|
42
|
+
width: min(100%, 18rem);
|
|
43
|
+
margin: 0 auto 1.5rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.selaras-docs-empty-artwork :deep(svg) {
|
|
47
|
+
display: block;
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.selaras-docs-empty-title {
|
|
53
|
+
margin: 0;
|
|
54
|
+
font-size: clamp(1.5rem, 1.25rem + 1vw, 2rem);
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
line-height: 1.25;
|
|
57
|
+
letter-spacing: -0.025em;
|
|
58
|
+
text-wrap: balance;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.selaras-docs-empty-description {
|
|
62
|
+
max-width: 42ch;
|
|
63
|
+
margin: 0.875rem auto 0;
|
|
64
|
+
color: var(--selaras-resolved-text-muted);
|
|
65
|
+
line-height: 1.7;
|
|
66
|
+
text-wrap: pretty;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.selaras-docs-empty-action {
|
|
70
|
+
margin-top: 1.5rem;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SelarasDocsErrorKind } from '../../types'
|
|
3
|
+
import { useTemplateRef, watch } from 'vue'
|
|
4
|
+
import { useRouter } from '#app'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
kind?: Extract<SelarasDocsErrorKind, 'page' | 'example'>
|
|
8
|
+
resetKey?: string
|
|
9
|
+
}>(), { kind: 'page' })
|
|
10
|
+
|
|
11
|
+
const boundary = useTemplateRef('boundary')
|
|
12
|
+
const router = useRouter()
|
|
13
|
+
watch([() => router.currentRoute.value.fullPath, () => props.resetKey], () => boundary.value?.clearError())
|
|
14
|
+
|
|
15
|
+
function isMissing(error: Error) {
|
|
16
|
+
return 'statusCode' in error && error.statusCode === 404
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<NuxtErrorBoundary ref="boundary">
|
|
22
|
+
<slot />
|
|
23
|
+
<template #error="{ error, clearError }">
|
|
24
|
+
<DocsErrorState :kind="kind === 'page' && isMissing(error) ? 'notFound' : kind" @retry="clearError" />
|
|
25
|
+
</template>
|
|
26
|
+
</NuxtErrorBoundary>
|
|
27
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{ missing?: boolean }>()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<svg viewBox="0 0 400 240" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
7
|
+
<path d="M40 187H360" stroke="var(--selaras-resolved-border-default)" stroke-width="2" stroke-linecap="round" />
|
|
8
|
+
<rect x="113" y="41" width="148" height="159" rx="12" fill="var(--selaras-resolved-surface-elevated)" stroke="var(--selaras-resolved-border-default)" stroke-width="2" />
|
|
9
|
+
<path d="M144 24H231L277 70V170C277 177 272 182 265 182H144C137 182 132 177 132 170V36C132 29 137 24 144 24Z" fill="var(--selaras-resolved-surface-default)" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2.5" stroke-linejoin="round" />
|
|
10
|
+
<path d="M231 25V58C231 65 236 70 243 70H276" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2.5" stroke-linejoin="round" />
|
|
11
|
+
<path d="M154 61H193M154 84H213" stroke="var(--selaras-resolved-border-default)" stroke-width="5" stroke-linecap="round" />
|
|
12
|
+
<rect x="154" y="108" width="57" height="48" rx="6" fill="var(--selaras-resolved-color-primary-subtle)" />
|
|
13
|
+
<path v-if="missing" d="M225 113H254V154H225V113Z" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2" stroke-dasharray="4 5" stroke-linejoin="round" />
|
|
14
|
+
<path v-else d="M233 116V135M233 146V147" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="4" stroke-linecap="round" />
|
|
15
|
+
<path d="M56 153C56 122 84 119 105 132M290 109C318 103 342 122 333 146" stroke="var(--selaras-resolved-border-default)" stroke-width="2" stroke-dasharray="4 7" stroke-linecap="round" />
|
|
16
|
+
<circle cx="55" cy="159" r="6" fill="var(--selaras-resolved-color-primary-fill)" />
|
|
17
|
+
<rect x="311" y="144" width="44" height="37" rx="6" fill="var(--selaras-resolved-color-primary-subtle)" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2" />
|
|
18
|
+
<path d="M323 163H343M333 153V173" stroke="var(--selaras-resolved-color-primary-fill)" stroke-width="2" stroke-linecap="round" />
|
|
19
|
+
</svg>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { NuxtError } from '#app'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ error: NuxtError }>()
|
|
5
|
+
const config = useRuntimeConfig()
|
|
6
|
+
const appConfig = useAppConfig()
|
|
7
|
+
const siteName = computed(() => appConfig.selarasDocs?.site?.name ?? 'Documentation')
|
|
8
|
+
const missing = computed(() => props.error.statusCode === 404)
|
|
9
|
+
const pending = ref(false)
|
|
10
|
+
|
|
11
|
+
useSeoMeta({
|
|
12
|
+
title: () => `${missing.value ? 'Page not found' : 'Something went wrong'} — ${siteName.value}`,
|
|
13
|
+
robots: 'noindex',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
function reload() {
|
|
17
|
+
pending.value = true
|
|
18
|
+
window.location.reload()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function goHome(event: MouseEvent) {
|
|
22
|
+
event.preventDefault()
|
|
23
|
+
const path = appConfig.selarasDocs?.errors?.homeTo ?? '/'
|
|
24
|
+
try {
|
|
25
|
+
await clearError({ redirect: path })
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
window.location.assign(`${config.app.baseURL.replace(/\/$/, '')}/${path.replace(/^\//, '')}`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="selaras-docs-root selaras-docs-error-page">
|
|
35
|
+
<header class="selaras-docs-error-header">
|
|
36
|
+
<a :href="config.app.baseURL" class="selaras-docs-error-brand" :aria-label="`${siteName} home`">
|
|
37
|
+
<DocsLogo />
|
|
38
|
+
<span>{{ siteName }}</span>
|
|
39
|
+
</a>
|
|
40
|
+
</header>
|
|
41
|
+
<main class="selaras-docs-error-main">
|
|
42
|
+
<DocsErrorState :kind="missing ? 'notFound' : 'fatal'" :pending="pending" @retry="reload" @home="goHome" />
|
|
43
|
+
</main>
|
|
44
|
+
</div>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<style scoped>
|
|
48
|
+
.selaras-docs-error-page {
|
|
49
|
+
min-height: 100svh;
|
|
50
|
+
color: var(--selaras-resolved-text-default);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.selaras-docs-error-header {
|
|
54
|
+
width: min(100%, 90rem);
|
|
55
|
+
margin: 0 auto;
|
|
56
|
+
padding: 1.5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.selaras-docs-error-brand {
|
|
60
|
+
display: inline-flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 0.75rem;
|
|
63
|
+
color: inherit;
|
|
64
|
+
font-size: 1.125rem;
|
|
65
|
+
font-weight: 650;
|
|
66
|
+
text-decoration: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.selaras-docs-error-brand:focus-visible {
|
|
70
|
+
border-radius: var(--selaras-resolved-radius-sm);
|
|
71
|
+
outline: 2px solid var(--selaras-resolved-color-primary-focus);
|
|
72
|
+
outline-offset: 4px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.selaras-docs-error-main {
|
|
76
|
+
display: grid;
|
|
77
|
+
align-items: center;
|
|
78
|
+
min-height: calc(100svh - 6rem);
|
|
79
|
+
padding: 1.5rem 1.5rem 5rem;
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SelarasDocsErrorKind } from '../../types'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
|
+
kind?: SelarasDocsErrorKind
|
|
6
|
+
pending?: boolean
|
|
7
|
+
showHome?: boolean
|
|
8
|
+
}>(), { kind: 'page', pending: false, showHome: undefined })
|
|
9
|
+
|
|
10
|
+
const emit = defineEmits<{ retry: [], home: [event: MouseEvent] }>()
|
|
11
|
+
const appConfig = useAppConfig()
|
|
12
|
+
const config = useRuntimeConfig()
|
|
13
|
+
const homeHref = computed(() => {
|
|
14
|
+
const path = appConfig.selarasDocs?.errors?.homeTo ?? '/'
|
|
15
|
+
const base = config.app.baseURL.replace(/\/$/, '')
|
|
16
|
+
return `${base}/${path.replace(/^\//, '')}`
|
|
17
|
+
})
|
|
18
|
+
const headingId = useId()
|
|
19
|
+
const heading = useTemplateRef<HTMLElement>('heading')
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
if (props.kind !== 'example')
|
|
22
|
+
heading.value?.focus({ preventScroll: true })
|
|
23
|
+
})
|
|
24
|
+
const copy = computed(() => {
|
|
25
|
+
const defaults = {
|
|
26
|
+
notFound: { title: 'Page not found', description: 'The link may have changed, or this page may no longer exist. You can start again from the homepage.' },
|
|
27
|
+
fatal: { title: 'Something went wrong', description: 'We couldn’t load this page. Reload it to try again, or return to the homepage.' },
|
|
28
|
+
page: { title: 'This page couldn’t load', description: 'Please try again. You can still use the navigation to explore other pages.' },
|
|
29
|
+
example: { title: 'This example couldn’t load', description: 'Try loading the example again. The rest of the documentation is still available.' },
|
|
30
|
+
}
|
|
31
|
+
return { ...defaults[props.kind], ...appConfig.selarasDocs?.errors?.[props.kind] }
|
|
32
|
+
})
|
|
33
|
+
const labels = computed(() => appConfig.selarasDocs?.errors ?? {})
|
|
34
|
+
const showHome = computed(() => props.showHome ?? props.kind !== 'example')
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<section
|
|
39
|
+
class="selaras-docs-error-state not-prose"
|
|
40
|
+
:data-error-kind="kind"
|
|
41
|
+
:aria-labelledby="headingId"
|
|
42
|
+
:role="kind === 'example' ? 'alert' : undefined"
|
|
43
|
+
>
|
|
44
|
+
<div class="selaras-docs-error-artwork">
|
|
45
|
+
<slot name="illustration">
|
|
46
|
+
<DocsErrorIllustration :missing="kind === 'notFound'" />
|
|
47
|
+
</slot>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="selaras-docs-error-copy">
|
|
50
|
+
<component :is="kind === 'example' ? 'h3' : 'h1'" :id="headingId" ref="heading" class="selaras-docs-error-title" :tabindex="kind === 'example' ? undefined : -1">
|
|
51
|
+
{{ copy.title }}
|
|
52
|
+
</component>
|
|
53
|
+
<p class="selaras-docs-error-description">
|
|
54
|
+
{{ copy.description }}
|
|
55
|
+
</p>
|
|
56
|
+
<div class="selaras-docs-error-actions">
|
|
57
|
+
<SButton v-if="kind !== 'notFound'" :loading="pending" :disabled="pending" @click="emit('retry')">
|
|
58
|
+
{{ pending ? labels.retryingLabel ?? 'Trying again…' : kind === 'fatal' ? labels.reloadLabel ?? 'Reload page' : labels.retryLabel ?? 'Try again' }}
|
|
59
|
+
</SButton>
|
|
60
|
+
<SButton v-if="showHome" as="a" :href="homeHref" :variant="kind === 'notFound' ? 'solid' : 'outline'" :color="kind === 'notFound' ? 'primary' : 'neutral'" @click="emit('home', $event)">
|
|
61
|
+
{{ labels.homeLabel ?? 'Go to homepage' }}
|
|
62
|
+
</SButton>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.selaras-docs-error-state {
|
|
70
|
+
width: min(100%, 42rem);
|
|
71
|
+
margin: 3rem auto;
|
|
72
|
+
color: var(--selaras-resolved-text-default);
|
|
73
|
+
text-align: center;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.selaras-docs-error-artwork {
|
|
77
|
+
width: min(100%, 22rem);
|
|
78
|
+
margin: 0 auto 1.5rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.selaras-docs-error-artwork :deep(svg) {
|
|
82
|
+
display: block;
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: auto;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.selaras-docs-error-title {
|
|
88
|
+
margin: 0;
|
|
89
|
+
font-size: clamp(1.75rem, 1.4rem + 1vw, 2.5rem);
|
|
90
|
+
font-weight: 650;
|
|
91
|
+
line-height: 1.15;
|
|
92
|
+
letter-spacing: -0.025em;
|
|
93
|
+
text-wrap: balance;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.selaras-docs-error-title:focus {
|
|
97
|
+
outline: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.selaras-docs-error-description {
|
|
101
|
+
max-width: 44ch;
|
|
102
|
+
margin: 1rem auto 0;
|
|
103
|
+
color: var(--selaras-resolved-text-muted);
|
|
104
|
+
font-size: 1rem;
|
|
105
|
+
line-height: 1.7;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.selaras-docs-error-actions {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-wrap: wrap;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
gap: 0.75rem;
|
|
113
|
+
margin-top: 1.75rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.selaras-docs-error-state[data-error-kind="example"] {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 1.25rem;
|
|
120
|
+
width: 100%;
|
|
121
|
+
margin: 0;
|
|
122
|
+
padding: 1.5rem;
|
|
123
|
+
border: 1px solid var(--selaras-resolved-border-default);
|
|
124
|
+
border-radius: var(--selaras-resolved-radius-lg);
|
|
125
|
+
background: var(--selaras-resolved-surface-elevated);
|
|
126
|
+
text-align: start;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
[data-error-kind="example"] .selaras-docs-error-artwork {
|
|
130
|
+
flex: none;
|
|
131
|
+
width: 6rem;
|
|
132
|
+
margin: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
[data-error-kind="example"] .selaras-docs-error-title {
|
|
136
|
+
font-size: 1rem;
|
|
137
|
+
line-height: 1.4;
|
|
138
|
+
letter-spacing: normal;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
[data-error-kind="example"] .selaras-docs-error-description {
|
|
142
|
+
margin: 0.375rem 0 0;
|
|
143
|
+
font-size: 0.875rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
[data-error-kind="example"] .selaras-docs-error-actions {
|
|
147
|
+
justify-content: flex-start;
|
|
148
|
+
margin-top: 1rem;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (max-width: 30rem) {
|
|
152
|
+
.selaras-docs-error-state[data-error-kind="example"] {
|
|
153
|
+
align-items: flex-start;
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
[data-error-kind="example"] .selaras-docs-error-artwork {
|
|
158
|
+
display: none;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
</style>
|
|
@@ -18,6 +18,11 @@ const footer = computed(() => docsConfig.value.footer)
|
|
|
18
18
|
:aria-label="link.ariaLabel ?? link.label"
|
|
19
19
|
>
|
|
20
20
|
<SIcon v-if="link.icon" :name="link.icon" />
|
|
21
|
+
<svg v-else-if="link.to === 'https://selarasui.my.id'" class="selaras-docs-footer-attribution-logo" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" focusable="false">
|
|
22
|
+
<path d="M28 80 H68 C94 80 118 36 144 36 H228 V62 H144 C118 62 94 106 68 106 H28 Z" />
|
|
23
|
+
<path d="M28 134 H68 C94 134 118 90 144 90 H228 V116 H144 C118 116 94 160 68 160 H28 Z" />
|
|
24
|
+
<path d="M28 188 H68 C94 188 118 144 144 144 H228 V170 H144 C118 170 94 214 68 214 H28 Z" />
|
|
25
|
+
</svg>
|
|
21
26
|
<span v-if="link.label">{{ link.label }}</span>
|
|
22
27
|
</NuxtLink>
|
|
23
28
|
</nav>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { getDocsRepositoryLinks } from '../utils/repository-links'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
|
+
path: string
|
|
6
|
+
title?: string
|
|
7
|
+
editLink?: boolean
|
|
8
|
+
issueLink?: boolean
|
|
9
|
+
}>(), { editLink: true, issueLink: true })
|
|
10
|
+
|
|
11
|
+
const appConfig = useAppConfig()
|
|
12
|
+
const links = computed(() => getDocsRepositoryLinks(appConfig.selarasDocs?.repository, props.path, props.title))
|
|
13
|
+
const hasEditLink = computed(() => props.editLink && !!links.value.edit)
|
|
14
|
+
const hasIssueLink = computed(() => props.issueLink && !!links.value.issue)
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<nav v-if="hasEditLink || hasIssueLink" class="selaras-docs-page-actions" aria-label="Page actions">
|
|
19
|
+
<DocsEditLink v-if="hasEditLink" :path="path" />
|
|
20
|
+
<a v-if="hasIssueLink" :href="links.issue" target="_blank" rel="noreferrer" class="selaras-docs-issue-link">
|
|
21
|
+
<SIcon name="hugeicons:bug-01" aria-hidden="true" />
|
|
22
|
+
Report an issue
|
|
23
|
+
</a>
|
|
24
|
+
</nav>
|
|
25
|
+
</template>
|
|
@@ -18,7 +18,9 @@ const resolvedComponent = computed(() => {
|
|
|
18
18
|
|
|
19
19
|
<template>
|
|
20
20
|
<section v-if="resolvedComponent" :data-example="name">
|
|
21
|
-
<
|
|
21
|
+
<DocsErrorBoundary kind="example" :reset-key="name">
|
|
22
|
+
<component :is="resolvedComponent" />
|
|
23
|
+
</DocsErrorBoundary>
|
|
22
24
|
</section>
|
|
23
25
|
<p v-else role="alert">
|
|
24
26
|
Example "{{ name }}" not found
|
package/app/error.vue
ADDED
package/app/pages/[...slug].vue
CHANGED
|
@@ -10,14 +10,30 @@ import ProsePre from '@sewadah/selaras/components/ProsePre.vue'
|
|
|
10
10
|
|
|
11
11
|
const route = useRoute()
|
|
12
12
|
const appConfig = useAppConfig()
|
|
13
|
-
const { data: page } = await useAsyncData(`selaras-docs:${route.path}`, () => queryCollection('docs').path(route.path).first())
|
|
13
|
+
const { data: page, error, status, refresh } = await useAsyncData(`selaras-docs:${route.path}`, () => queryCollection('docs').path(route.path).first())
|
|
14
14
|
|
|
15
|
-
if (!page.value) {
|
|
15
|
+
if (!page.value && !error.value) {
|
|
16
16
|
throw createError({ statusCode: 404, statusMessage: 'Documentation page not found', fatal: true })
|
|
17
17
|
}
|
|
18
|
+
if (import.meta.server && error.value)
|
|
19
|
+
setResponseStatus(500)
|
|
20
|
+
|
|
21
|
+
async function retryPage() {
|
|
22
|
+
await refresh()
|
|
23
|
+
if (import.meta.client && error.value) {
|
|
24
|
+
window.location.reload()
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
if (!page.value && !error.value)
|
|
28
|
+
showError({ statusCode: 404, statusMessage: 'Documentation page not found' })
|
|
29
|
+
}
|
|
18
30
|
|
|
19
31
|
definePageMeta({ layout: 'selaras-docs' })
|
|
20
|
-
useSeoMeta({
|
|
32
|
+
useSeoMeta({
|
|
33
|
+
title: () => page.value?.title ?? appConfig.selarasDocs?.errors?.page?.title ?? 'Documentation unavailable',
|
|
34
|
+
description: () => page.value?.description ?? appConfig.selarasDocs?.site?.description,
|
|
35
|
+
robots: () => error.value ? 'noindex' : undefined,
|
|
36
|
+
})
|
|
21
37
|
useHead(() => ({
|
|
22
38
|
...(appConfig.selarasDocs?.site?.titleTemplate && { titleTemplate: appConfig.selarasDocs.site.titleTemplate }),
|
|
23
39
|
...(appConfig.selarasDocs?.site?.url && {
|
|
@@ -36,18 +52,30 @@ const proseComponents = {
|
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
const tocLinks = computed(() => (page.value?.body?.toc?.links ?? []) as unknown as TocLink[])
|
|
55
|
+
const hasToc = computed(() => page.value?.toc !== false && page.value?.aside !== false && appConfig.selarasDocs?.toc?.enabled !== false && tocLinks.value.length > 0)
|
|
39
56
|
const surround = useDocsSurround()
|
|
40
57
|
</script>
|
|
41
58
|
|
|
42
59
|
<template>
|
|
43
|
-
<
|
|
44
|
-
|
|
60
|
+
<DocsErrorState v-if="error || !page" kind="page" :pending="status === 'pending'" @retry="retryPage" />
|
|
61
|
+
<SContainer v-else size="full" class="selaras-docs-page" :class="{ 'selaras-docs-page--fluid': appConfig.selarasDocs?.main?.fluid }">
|
|
62
|
+
<div class="selaras-docs-page-grid" :class="{ 'selaras-docs-page-grid--wide': !hasToc }">
|
|
45
63
|
<article class="selaras-docs-article">
|
|
46
64
|
<p v-if="page!.description" class="selaras-docs-description">
|
|
47
65
|
{{ page!.description }}
|
|
48
66
|
</p>
|
|
49
|
-
<ContentRenderer :value="page!" :components="proseComponents" class="selaras-docs-content selaras-prose"
|
|
50
|
-
|
|
67
|
+
<ContentRenderer :value="page!" :components="proseComponents" class="selaras-docs-content selaras-prose">
|
|
68
|
+
<template #empty>
|
|
69
|
+
<DocsEmptyState />
|
|
70
|
+
</template>
|
|
71
|
+
</ContentRenderer>
|
|
72
|
+
<DocsPageActions
|
|
73
|
+
:path="route.path"
|
|
74
|
+
:title="page!.title"
|
|
75
|
+
:edit-link="page!.editLink !== false"
|
|
76
|
+
:issue-link="page!.issueLink !== false"
|
|
77
|
+
:class="{ 'selaras-docs-page-actions--mobile': hasToc }"
|
|
78
|
+
/>
|
|
51
79
|
<SContentSurround
|
|
52
80
|
v-if="page!.prevNext !== false"
|
|
53
81
|
:prev="surround.prev"
|
|
@@ -55,7 +83,9 @@ const surround = useDocsSurround()
|
|
|
55
83
|
class="selaras-docs-surround"
|
|
56
84
|
/>
|
|
57
85
|
</article>
|
|
58
|
-
<DocsTableOfContents v-if="
|
|
86
|
+
<DocsTableOfContents v-if="hasToc" :links="tocLinks">
|
|
87
|
+
<DocsPageActions :path="route.path" :title="page!.title" :edit-link="page!.editLink !== false" :issue-link="page!.issueLink !== false" />
|
|
88
|
+
</DocsTableOfContents>
|
|
59
89
|
</div>
|
|
60
90
|
</SContainer>
|
|
61
91
|
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SelarasDocsAppConfig } from '../../types'
|
|
2
|
+
|
|
3
|
+
export function getDocsRepositoryLinks(repository: SelarasDocsAppConfig['repository'], path: string, title?: string) {
|
|
4
|
+
const links: { edit?: string, issue?: string } = {}
|
|
5
|
+
const base = repository?.url?.replace(/\/$/, '')
|
|
6
|
+
const github = base?.startsWith('https://github.com/')
|
|
7
|
+
const gitlab = base?.startsWith('https://gitlab.com/')
|
|
8
|
+
|
|
9
|
+
if (base && (github || gitlab) && repository?.editLinks !== false) {
|
|
10
|
+
const relativePath = path === '/' ? 'index.md' : `${path.replace(/^\//, '')}.md`
|
|
11
|
+
const contentPath = [repository?.contentDirectory ?? 'content', relativePath].join('/')
|
|
12
|
+
const branch = repository?.branch ?? 'main'
|
|
13
|
+
links.edit = `${base}/${gitlab ? '-/edit' : 'edit'}/${branch}/${contentPath}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (repository?.issueLinks !== false) {
|
|
17
|
+
if (repository?.issueUrl) {
|
|
18
|
+
links.issue = repository.issueUrl
|
|
19
|
+
}
|
|
20
|
+
else if (base && (github || gitlab)) {
|
|
21
|
+
const url = new URL(`${base}/${gitlab ? '-/issues/new' : 'issues/new'}`)
|
|
22
|
+
url.searchParams.set(gitlab ? 'issue[title]' : 'title', `Documentation: ${title ?? path}`)
|
|
23
|
+
url.searchParams.set(gitlab ? 'issue[description]' : 'body', `Documentation page: ${path}\n\n`)
|
|
24
|
+
links.issue = url.href
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return links
|
|
29
|
+
}
|
package/content.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sewadah/selaras-docs",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"description": "Nuxt Content documentation layer for Selaras",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@nuxt/content": "^3.16.0",
|
|
35
35
|
"@nuxt/kit": "^4.5.2",
|
|
36
|
-
"@sewadah/selaras": "^0.
|
|
36
|
+
"@sewadah/selaras": "^0.9.0",
|
|
37
37
|
"tailwindcss": "^4.3.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/types.d.ts
CHANGED
|
@@ -37,7 +37,28 @@ export interface SelarasDocsLogo {
|
|
|
37
37
|
tint?: boolean
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export type SelarasDocsErrorKind = 'notFound' | 'fatal' | 'page' | 'example'
|
|
41
|
+
|
|
42
|
+
export interface SelarasDocsErrorCopy {
|
|
43
|
+
title?: string
|
|
44
|
+
description?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
export interface SelarasDocsAppConfig {
|
|
48
|
+
/** Copy shown for an existing Markdown page with no body content. */
|
|
49
|
+
empty?: {
|
|
50
|
+
title?: string
|
|
51
|
+
description?: string
|
|
52
|
+
}
|
|
53
|
+
/** Recovery messages and the destination of error-screen home links. */
|
|
54
|
+
errors?: Partial<Record<SelarasDocsErrorKind, SelarasDocsErrorCopy>> & {
|
|
55
|
+
/** An internal route, resolved under the application's base URL. @default '/' */
|
|
56
|
+
homeTo?: string
|
|
57
|
+
homeLabel?: string
|
|
58
|
+
retryLabel?: string
|
|
59
|
+
reloadLabel?: string
|
|
60
|
+
retryingLabel?: string
|
|
61
|
+
}
|
|
41
62
|
site?: {
|
|
42
63
|
name?: string
|
|
43
64
|
description?: string
|
|
@@ -54,13 +75,17 @@ export interface SelarasDocsAppConfig {
|
|
|
54
75
|
branch?: string
|
|
55
76
|
contentDirectory?: string
|
|
56
77
|
editLinks?: boolean
|
|
78
|
+
/** Show a page-context issue link. @default true */
|
|
79
|
+
issueLinks?: boolean
|
|
80
|
+
/** Override the new-issue destination, e.g. for a separate issue tracker. */
|
|
81
|
+
issueUrl?: string
|
|
57
82
|
}
|
|
58
83
|
header?: {
|
|
59
84
|
title?: string
|
|
60
85
|
showTitle?: boolean
|
|
61
86
|
search?: boolean
|
|
62
87
|
colorMode?: boolean
|
|
63
|
-
/** Show the consuming site's
|
|
88
|
+
/** Show the consuming site's DocsThemePicker component; the layer default is empty. @default false */
|
|
64
89
|
themePicker?: boolean
|
|
65
90
|
/** Let the header span the viewport instead of the docs shell width. */
|
|
66
91
|
fluid?: boolean
|