@purview-dev/astroc4 0.0.0-stage → 1.0.0-prerelease.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +179 -2
- package/package.json +96 -4
- package/src/components/DiagramFrame.astro +72 -0
- package/src/components/DiagramShell.astro +121 -0
- package/src/components/ErrorBoundary.tsx +52 -0
- package/src/components/LikeC4Fence.astro +62 -0
- package/src/components/LikeC4InlineView.tsx +67 -0
- package/src/components/LikeC4ProjectView.tsx +153 -0
- package/src/components/LikeC4View.astro +80 -0
- package/src/components/LoadingPlaceholder.astro +67 -0
- package/src/components/icon-source.ts +68 -0
- package/src/components/labels.ts +62 -0
- package/src/components/render-icon.tsx +41 -0
- package/src/components/utils.ts +69 -0
- package/src/expressive-code.d.mts +28 -0
- package/src/expressive-code.mjs +46 -0
- package/src/fences.mjs +288 -0
- package/src/grammar/langium.mjs +559 -0
- package/src/grammar/like-c4.langium +1357 -0
- package/src/grammar/likec4.tmLanguage.json +327 -0
- package/src/grammar/source.mjs +410 -0
- package/src/grammar/textmate.mjs +439 -0
- package/src/index.d.mts +64 -0
- package/src/index.mjs +228 -0
- package/src/translations.mjs +33 -0
- package/src/types.d.ts +12 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Kieron Lanning
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,180 @@
|
|
|
1
|
-
#
|
|
1
|
+
# astroc4
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Starlight plugin for interactive [LikeC4](https://likec4.dev) architecture
|
|
4
|
+
diagrams.
|
|
5
|
+
|
|
6
|
+
- `<LikeC4View viewId="…" />` — render a view from the site's `.c4` model.
|
|
7
|
+
- ` ```likec4 ` fences in `.mdx` — either inline DSL, compiled and laid out at
|
|
8
|
+
build time, or an empty fence with `view="…"` pointing at the model.
|
|
9
|
+
- LikeC4 syntax highlighting for Expressive Code (`.c4` files, ` ```c4 `),
|
|
10
|
+
derived at build time from the Langium grammar in
|
|
11
|
+
[likec4/likec4](https://github.com/likec4/likec4) — no `ec.config.mjs`.
|
|
12
|
+
- Every UI string translatable through Starlight's i18n.
|
|
13
|
+
|
|
14
|
+
Full documentation lives in [`docs/wiki`](docs/wiki/index.md) (published with
|
|
15
|
+
`mkdocs.yml`).
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bun add @purview-dev/astroc4@prerelease # 1.0.0-prerelease.N
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Published to npm under the `prerelease` dist-tag, so `bun add @purview-dev/astroc4` keeps
|
|
24
|
+
resolving the last stable release. Astroc4 declares its peer dependencies, so
|
|
25
|
+
Bun and npm install them with it; under pnpm add them explicitly (`astro`,
|
|
26
|
+
`@astrojs/starlight`, `@astrojs/react`, `@astrojs/markdown-satteri`, `likec4`,
|
|
27
|
+
`@likec4/core`, `react`, `react-dom`, `shiki`).
|
|
28
|
+
|
|
29
|
+
Working on astroc4 itself? `bun add @purview-dev/astroc4@file:../path/to/astroc4` copies the
|
|
30
|
+
package into `node_modules`, so run `bun install` in the site again after
|
|
31
|
+
changing the plugin.
|
|
32
|
+
|
|
33
|
+
## Use
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
// astro.config.mjs
|
|
37
|
+
import starlight from '@astrojs/starlight';
|
|
38
|
+
import react from '@astrojs/react';
|
|
39
|
+
import astroc4 from '@purview-dev/astroc4';
|
|
40
|
+
|
|
41
|
+
export default defineConfig({
|
|
42
|
+
integrations: [starlight({ plugins: [astroc4()] }), react()],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```mdx
|
|
47
|
+
import LikeC4View from '@purview-dev/astroc4/components/LikeC4View.astro';
|
|
48
|
+
|
|
49
|
+
<LikeC4View viewId="index" height={560} />
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The plugin registers `LikeC4VitePlugin`, which compiles every
|
|
53
|
+
`likec4.config.json` project under the site root. Keep exactly the projects you
|
|
54
|
+
mean to render there: the first one found is the default for `<LikeC4View>`
|
|
55
|
+
without a `projectId`, so a stray `.c4` project (a vendored example, say) can
|
|
56
|
+
silently replace your own.
|
|
57
|
+
|
|
58
|
+
### Options
|
|
59
|
+
|
|
60
|
+
| Option | Default | |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `vitePlugin` | `{}` | Passed to `LikeC4VitePlugin`, e.g. `{ workspace: 'diagrams' }`. |
|
|
63
|
+
| `fences` | `true` | Turn ` ```likec4 ` fences into diagrams. |
|
|
64
|
+
| `highlighting` | `true` | Highlight LikeC4 source. See below. |
|
|
65
|
+
|
|
66
|
+
### Syntax highlighting
|
|
67
|
+
|
|
68
|
+
The plugin reads `packages/language-server/src/like-c4.langium` from
|
|
69
|
+
`likec4/likec4`, derives a TextMate grammar from it, and registers that with
|
|
70
|
+
the site's Expressive Code — so ` ```c4 ` fences, ` ```likec4 ` fences and
|
|
71
|
+
`<Code lang="likec4">` all highlight with no configuration. Highlighting
|
|
72
|
+
follows the language: when upstream adds a shape or a keyword, the next build
|
|
73
|
+
picks it up.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
starlight({
|
|
77
|
+
plugins: [
|
|
78
|
+
astroc4({
|
|
79
|
+
highlighting: {
|
|
80
|
+
ref: 'v1.59.4', // branch, tag or commit SHA; default 'main'
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Option | Default | |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `ref` | `'main'` | Branch, tag or commit SHA. Pin a tag or SHA for reproducible CI. |
|
|
90
|
+
| `repo` | `'likec4/likec4'` | Read the grammar from a fork or mirror. |
|
|
91
|
+
| `offline` | `false` | Use the bundled snapshot, never the network. |
|
|
92
|
+
| `onError` | `'fail'` | `fail`, `warn` or `silent` when the grammar cannot be fetched. |
|
|
93
|
+
| `cacheTtl` | `3600` | Seconds a cached fetch stays fresh. |
|
|
94
|
+
| `refresh` | `false` | Ignore the cache and refetch. |
|
|
95
|
+
| `cacheDir` | `node_modules/.cache/astroc4` | Where to cache. |
|
|
96
|
+
| `baseUrl` | `raw.githubusercontent.com` | Raw host to read from. |
|
|
97
|
+
| `timeout` | `15000` | Request timeout in milliseconds. |
|
|
98
|
+
|
|
99
|
+
`ASTROC4_REF` and `ASTROC4_OFFLINE=1` override `ref` and `offline`, which keeps
|
|
100
|
+
CI configuration out of the config file.
|
|
101
|
+
|
|
102
|
+
The grammar is resolved while Astro loads its config, so `astro dev`,
|
|
103
|
+
`astro build`, `astro check` and `astro sync` all fail on a `ref` that does not
|
|
104
|
+
exist, on a repository that cannot be reached, or on a file that is no longer a
|
|
105
|
+
LikeC4 grammar — the same way any other build error does. Air-gapped builds use
|
|
106
|
+
`offline: true`; builds that must not hard-fail use `onError: 'warn'`, which
|
|
107
|
+
falls back to the last fetched grammar and then to the bundled snapshot.
|
|
108
|
+
|
|
109
|
+
Fetched grammars are cached per `repo@ref`, so switching `ref` never thrashes
|
|
110
|
+
and repeated builds do not touch the network.
|
|
111
|
+
|
|
112
|
+
A site that already has an `ec.config.mjs` keeps working, and can pin the
|
|
113
|
+
bundled grammar there instead:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// ec.config.mjs
|
|
117
|
+
import { defineEcConfig } from '@astrojs/starlight/expressive-code';
|
|
118
|
+
import { likec4Language } from '@purview-dev/astroc4/expressive-code';
|
|
119
|
+
|
|
120
|
+
export default defineEcConfig({ shiki: { langs: [likec4Language] } });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Expressive Code concatenates `shiki.langs` when it merges an `ec.config.mjs`
|
|
124
|
+
with the integration options, so its own languages and the plugin's coexist.
|
|
125
|
+
|
|
126
|
+
### `<LikeC4View>` props
|
|
127
|
+
|
|
128
|
+
| Prop | Type | Default |
|
|
129
|
+
| --- | --- | --- |
|
|
130
|
+
| `viewId` | `string` | required |
|
|
131
|
+
| `projectId` | `string` | first project |
|
|
132
|
+
| `title` / `description` | `string` | translated defaults |
|
|
133
|
+
| `height` | `number` | `480` |
|
|
134
|
+
| `collapsible` / `defaultOpen` | `boolean` | `true` |
|
|
135
|
+
|
|
136
|
+
### Fence options
|
|
137
|
+
|
|
138
|
+
` ```likec4 view="…" title="…" description="…" height=420 collapsible=false defaultOpen=false `
|
|
139
|
+
|
|
140
|
+
### Translating
|
|
141
|
+
|
|
142
|
+
Add any key from `src/translations.mjs` to the site's
|
|
143
|
+
`src/content/i18n/<lang>.json`. Placeholders are `{name}` — single braces,
|
|
144
|
+
because some are filled in by the browser, not by i18next.
|
|
145
|
+
|
|
146
|
+
## Icons
|
|
147
|
+
|
|
148
|
+
Use a bundled pack (`azure:`, `aws:`, `gcp:`, `tech:`, `bootstrap:`), an
|
|
149
|
+
absolute URL, or a file under the site's `public/` referenced by a path
|
|
150
|
+
relative to the `.c4` file. Don't use LikeC4's `imageAliases`: the compiler
|
|
151
|
+
bakes a build-machine `file://` path into the client bundle.
|
|
152
|
+
|
|
153
|
+
## Develop
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
bun install
|
|
157
|
+
bun run dev # playground at http://localhost:4321 (English + French)
|
|
158
|
+
bun run test # hermetic unit suite: Langium parsing, grammar, fences, plugin
|
|
159
|
+
bun run test:network # the same, plus a check against the live likec4/likec4
|
|
160
|
+
bun run test:site # pack the tarball and build a real Starlight site from it
|
|
161
|
+
bun run check # astro check over playground/ and src/
|
|
162
|
+
bun run grammar:check # fail if the committed grammar snapshot is stale
|
|
163
|
+
bun run build && bun run preview
|
|
164
|
+
bun scripts/smoke.mjs http://localhost:4321 # headless: hydration, icons, highlighting, i18n
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`Justfile` wraps each of those (`just verify` runs what the PR workflow runs).
|
|
168
|
+
`src/grammar/likec4.tmLanguage.json` and `src/grammar/like-c4.langium` are
|
|
169
|
+
generated — `bun run grammar:sync [--ref <tag>]` refreshes them from upstream,
|
|
170
|
+
and never by hand.
|
|
171
|
+
|
|
172
|
+
CI lives in `.github/workflows`: `pr.yml` runs the organisation gate plus the
|
|
173
|
+
unit suite, the browser smoke test and the packed-consumer build; `release.yml`
|
|
174
|
+
publishes to npm with **trusted publishing** — an OIDC token, no stored npm
|
|
175
|
+
secret — under the `prerelease` dist-tag for prerelease versions, then tags the
|
|
176
|
+
release. See [`docs/wiki/Repository-CI-CD.md`](docs/wiki/Repository-CI-CD.md).
|
|
177
|
+
|
|
178
|
+
Open the folder in VS Code and install the recommended **LikeC4** extension
|
|
179
|
+
for the language server: diagnostics, go-to-definition, hover and a live
|
|
180
|
+
preview of `.c4` files.
|
package/package.json
CHANGED
|
@@ -1,6 +1,98 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@purview-dev/astroc4",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0-prerelease.1",
|
|
4
|
+
"description": "Interactive LikeC4 architecture diagrams for Astro and Starlight: <LikeC4View>, ```likec4 fences, and LikeC4 syntax highlighting derived from the live Langium grammar.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Kieron Lanning",
|
|
8
|
+
"url": "https://kieronlanning.dev/"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://purview.dev/projects/astroc4/",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/purview-dev/astroc4/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/purview-dev/astroc4.git"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"likec4",
|
|
20
|
+
"c4-model",
|
|
21
|
+
"architecture-as-code",
|
|
22
|
+
"architecture-diagrams",
|
|
23
|
+
"diagrams",
|
|
24
|
+
"astro",
|
|
25
|
+
"astro-integration",
|
|
26
|
+
"starlight",
|
|
27
|
+
"starlight-plugin",
|
|
28
|
+
"expressive-code",
|
|
29
|
+
"shiki",
|
|
30
|
+
"syntax-highlighting",
|
|
31
|
+
"documentation"
|
|
32
|
+
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./src/index.mjs",
|
|
36
|
+
"./expressive-code": "./src/expressive-code.mjs",
|
|
37
|
+
"./grammar/likec4.tmLanguage.json": "./src/grammar/likec4.tmLanguage.json",
|
|
38
|
+
"./components/LikeC4View.astro": "./src/components/LikeC4View.astro",
|
|
39
|
+
"./components/LikeC4Fence.astro": "./src/components/LikeC4Fence.astro"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"src",
|
|
43
|
+
"!src/**/__tests__",
|
|
44
|
+
"!src/**/__fixtures__"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"dev": "cd playground && astro dev",
|
|
48
|
+
"build": "cd playground && astro build",
|
|
49
|
+
"preview": "cd playground && astro preview",
|
|
50
|
+
"check": "cd playground && astro check",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"test:network": "ASTROC4_NETWORK=1 vitest run src/grammar/__tests__/network.test.ts",
|
|
54
|
+
"test:site": "bun scripts/test-site.mjs",
|
|
55
|
+
"test:smoke": "node scripts/smoke.mjs",
|
|
56
|
+
"grammar:sync": "bun scripts/sync-grammar.mjs",
|
|
57
|
+
"grammar:check": "bun scripts/sync-grammar.mjs --check",
|
|
58
|
+
"likec4:validate": "likec4 validate playground/src/likec4"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@astrojs/markdown-satteri": ">=0.4.6",
|
|
62
|
+
"@astrojs/react": ">=7.1.2",
|
|
63
|
+
"@astrojs/starlight": ">=0.42.8",
|
|
64
|
+
"@likec4/core": ">=1.59.4",
|
|
65
|
+
"astro": ">=7.4.4",
|
|
66
|
+
"likec4": ">=1.59.4",
|
|
67
|
+
"react": ">=19.0.0",
|
|
68
|
+
"react-dom": ">=19.0.0",
|
|
69
|
+
"shiki": ">=1.0.0"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@astrojs/check": "^0.9.12",
|
|
73
|
+
"@astrojs/markdown-satteri": "^0.4.6",
|
|
74
|
+
"@astrojs/react": "^7.1.2",
|
|
75
|
+
"@astrojs/starlight": "^0.42.8",
|
|
76
|
+
"@likec4/core": "^1.59.4",
|
|
77
|
+
"@types/react": "^19.3.0",
|
|
78
|
+
"@types/react-dom": "^19.3.0",
|
|
79
|
+
"astro": "^7.4.4",
|
|
80
|
+
"likec4": "^1.59.4",
|
|
81
|
+
"playwright": "^1.63.0",
|
|
82
|
+
"react": "^19.3.0",
|
|
83
|
+
"react-dom": "^19.3.0",
|
|
84
|
+
"sharp": "^0.35.3",
|
|
85
|
+
"shiki": "^4.5.0",
|
|
86
|
+
"typescript": "^6.0.3",
|
|
87
|
+
"vitest": "^5.0.5"
|
|
88
|
+
},
|
|
89
|
+
"engines": {
|
|
90
|
+
"bun": ">=1.4.4",
|
|
91
|
+
"node": ">=22.19.0"
|
|
92
|
+
},
|
|
93
|
+
"packageManager": "bun@1.4.4",
|
|
94
|
+
"publishConfig": {
|
|
95
|
+
"access": "public"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
// DiagramFrame.astro
|
|
3
|
+
// path: src/components/DiagramFrame.astro
|
|
4
|
+
// The sized container a LikeC4 island renders into. Shared by <LikeC4View>
|
|
5
|
+
// (project model) and <LikeC4Fence> (inline DSL) so both size identically.
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Rendered height in pixels. */
|
|
9
|
+
height?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { height = 480 } = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<div class="likec4-frame" style={`--likec4-height: ${height}px;`}>
|
|
16
|
+
<slot />
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
/* LikeC4's renderer measures its container, so it needs a concrete height
|
|
21
|
+
rather than collapsing to the intrinsic size of an absolutely-positioned
|
|
22
|
+
canvas. The per-instance value arrives as an inline custom property. */
|
|
23
|
+
.likec4-frame {
|
|
24
|
+
min-height: var(--likec4-height);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* :global() because the markup below this point is created at runtime by
|
|
28
|
+
LikeC4 (or by the React error boundary) and so carries no scope hash. */
|
|
29
|
+
.likec4-frame :global(.likec4-view),
|
|
30
|
+
.likec4-frame :global([data-likec4-view]) {
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: var(--likec4-height);
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
border-radius: 0.5rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.likec4-frame :global(.likec4-error) {
|
|
38
|
+
border: 1px solid var(--sl-color-red);
|
|
39
|
+
border-radius: 0.5rem;
|
|
40
|
+
background: var(--sl-color-red-low);
|
|
41
|
+
padding: 1rem;
|
|
42
|
+
font-size: 0.875rem;
|
|
43
|
+
line-height: 1.25rem;
|
|
44
|
+
color: var(--sl-color-red-high);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.likec4-frame :global(.likec4-error p) {
|
|
48
|
+
margin: 0.25rem 0 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.likec4-frame :global(.likec4-error .likec4-error__title) {
|
|
52
|
+
margin: 0;
|
|
53
|
+
font-weight: 600;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.likec4-frame :global(.likec4-error__retry) {
|
|
57
|
+
margin-top: 0.75rem;
|
|
58
|
+
border: 1px solid var(--sl-color-red);
|
|
59
|
+
border-radius: 0.375rem;
|
|
60
|
+
background: transparent;
|
|
61
|
+
padding: 0.375rem 0.75rem;
|
|
62
|
+
font: inherit;
|
|
63
|
+
font-size: 0.75rem;
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
color: inherit;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.likec4-frame :global(.likec4-error__retry:hover) {
|
|
70
|
+
background: var(--sl-color-bg);
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
// DiagramShell.astro
|
|
3
|
+
// path: src/components/DiagramShell.astro
|
|
4
|
+
// Framed, optionally collapsible container around a diagram so it reads as a
|
|
5
|
+
// deliberate block inside the prose rather than a loose widget.
|
|
6
|
+
//
|
|
7
|
+
// Plain CSS over Starlight's custom properties rather than utility classes: a
|
|
8
|
+
// host site's Tailwind does not scan this package, so any class it did not
|
|
9
|
+
// already generate would silently be unstyled.
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
hint?: string;
|
|
15
|
+
/** Whether the shell can be collapsed/expanded. */
|
|
16
|
+
collapsible?: boolean;
|
|
17
|
+
/** Initial open state when collapsible is enabled. */
|
|
18
|
+
defaultOpen?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { title, description, hint, collapsible = true, defaultOpen = true } = Astro.props;
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<section class="not-content likec4-shell">
|
|
25
|
+
{
|
|
26
|
+
collapsible ? (
|
|
27
|
+
<details open={defaultOpen}>
|
|
28
|
+
<summary class="likec4-shell__header">
|
|
29
|
+
<div class="likec4-shell__heading">
|
|
30
|
+
<h3 class="likec4-shell__title">{title}</h3>
|
|
31
|
+
{description && <p class="likec4-shell__description">{description}</p>}
|
|
32
|
+
{hint && <p class="likec4-shell__hint">{hint}</p>}
|
|
33
|
+
</div>
|
|
34
|
+
</summary>
|
|
35
|
+
|
|
36
|
+
<div class="likec4-shell__body">
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
</details>
|
|
40
|
+
) : (
|
|
41
|
+
<>
|
|
42
|
+
<header class="likec4-shell__header">
|
|
43
|
+
<h3 class="likec4-shell__title likec4-shell__title--block">{title}</h3>
|
|
44
|
+
{description && <p class="likec4-shell__description">{description}</p>}
|
|
45
|
+
{hint && <p class="likec4-shell__hint">{hint}</p>}
|
|
46
|
+
</header>
|
|
47
|
+
|
|
48
|
+
<div class="likec4-shell__body">
|
|
49
|
+
<slot />
|
|
50
|
+
</div>
|
|
51
|
+
</>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.likec4-shell {
|
|
58
|
+
margin-block: 1em;
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
61
|
+
border-radius: 0.75rem;
|
|
62
|
+
background: var(--sl-color-bg-nav);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.likec4-shell__header {
|
|
66
|
+
border-bottom: 1px solid var(--sl-color-gray-5);
|
|
67
|
+
background: var(--sl-color-bg);
|
|
68
|
+
padding: 1em 1.25rem;
|
|
69
|
+
line-height: 1.4;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Use the native details/summary marker so this matches Starlight's own
|
|
73
|
+
collapsible blocks instead of inventing a second disclosure style. */
|
|
74
|
+
summary.likec4-shell__header {
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.likec4-shell__heading {
|
|
79
|
+
display: inline-block;
|
|
80
|
+
vertical-align: top;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.likec4-shell__title {
|
|
84
|
+
display: inline;
|
|
85
|
+
margin: 0;
|
|
86
|
+
font-size: 1rem;
|
|
87
|
+
line-height: 1.5rem;
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
color: var(--sl-color-white);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.likec4-shell__title--block {
|
|
93
|
+
display: block;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.likec4-shell__description {
|
|
97
|
+
margin: 0.25rem 0 0;
|
|
98
|
+
font-size: 0.875rem;
|
|
99
|
+
line-height: 1.25rem;
|
|
100
|
+
color: var(--sl-color-gray-2);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.likec4-shell__hint {
|
|
104
|
+
margin: 0.5rem 0 0;
|
|
105
|
+
font-size: 0.75rem;
|
|
106
|
+
line-height: 1rem;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
color: var(--sl-color-text-accent);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.likec4-shell__body {
|
|
112
|
+
padding: 1em 1.25rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@media (min-width: 48rem) {
|
|
116
|
+
.likec4-shell__header,
|
|
117
|
+
.likec4-shell__body {
|
|
118
|
+
padding-inline: 1.5rem;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// ErrorBoundary.tsx
|
|
2
|
+
// path: src/components/ErrorBoundary.tsx
|
|
3
|
+
// Keeps a crashing LikeC4 island (the renderer depends on browser-only
|
|
4
|
+
// measurement APIs) from blanking the surrounding docs page. Styled by the
|
|
5
|
+
// `.likec4-error` rules in DiagramFrame.astro, which is always its ancestor.
|
|
6
|
+
|
|
7
|
+
import { Component, type ReactNode } from 'react';
|
|
8
|
+
|
|
9
|
+
import type { IslandLabels } from './labels';
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
labels: Pick<IslandLabels, 'errorTitle' | 'errorBody' | 'errorRetry'>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type State = {
|
|
17
|
+
hasError: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default class ErrorBoundary extends Component<Props, State> {
|
|
21
|
+
state: State = { hasError: false };
|
|
22
|
+
|
|
23
|
+
static getDerivedStateFromError(): State {
|
|
24
|
+
return { hasError: true };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
componentDidCatch(error: unknown) {
|
|
28
|
+
console.error('[likec4] Diagram render error:', error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
handleRetry = () => {
|
|
32
|
+
this.setState({ hasError: false });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
render() {
|
|
36
|
+
if (this.state.hasError) {
|
|
37
|
+
const { labels } = this.props;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div role="alert" className="likec4-error">
|
|
41
|
+
<p className="likec4-error__title">{labels.errorTitle}</p>
|
|
42
|
+
<p>{labels.errorBody}</p>
|
|
43
|
+
<button type="button" onClick={this.handleRetry} className="likec4-error__retry">
|
|
44
|
+
{labels.errorRetry}
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.props.children;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
// LikeC4Fence.astro
|
|
3
|
+
// path: src/components/LikeC4Fence.astro
|
|
4
|
+
// Target of the ```likec4 fence transform. Authors never write this directly —
|
|
5
|
+
// src/fences.mjs rewrites the fence into it, passing the model it compiled at
|
|
6
|
+
// build time as a JSON string.
|
|
7
|
+
|
|
8
|
+
import DiagramFrame from './DiagramFrame.astro';
|
|
9
|
+
import DiagramShell from './DiagramShell.astro';
|
|
10
|
+
import LikeC4InlineView from './LikeC4InlineView';
|
|
11
|
+
import LoadingPlaceholder from './LoadingPlaceholder.astro';
|
|
12
|
+
import { getIslandLabels, getTranslator } from './labels';
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Layouted model, JSON-encoded by the fence plugin. */
|
|
16
|
+
model: string;
|
|
17
|
+
viewId: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Arrives as a string via the MDX attribute. */
|
|
21
|
+
height?: string;
|
|
22
|
+
collapsible?: string;
|
|
23
|
+
defaultOpen?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const t = getTranslator(Astro.locals.t);
|
|
27
|
+
const labels = getIslandLabels(t, 'likec4.fence.loading');
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
model,
|
|
31
|
+
viewId,
|
|
32
|
+
title = t('likec4.fence.title'),
|
|
33
|
+
description,
|
|
34
|
+
height = '420',
|
|
35
|
+
collapsible = 'true',
|
|
36
|
+
defaultOpen = 'true',
|
|
37
|
+
} = Astro.props;
|
|
38
|
+
|
|
39
|
+
let parsedModel: unknown;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
parsedModel = JSON.parse(model);
|
|
43
|
+
} catch (cause) {
|
|
44
|
+
throw new Error('<LikeC4Fence> received a model that is not valid JSON.', { cause });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parsedHeight = Number.parseInt(height, 10);
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
<DiagramShell
|
|
51
|
+
title={title}
|
|
52
|
+
description={description}
|
|
53
|
+
hint={t('likec4.fence.hint')}
|
|
54
|
+
collapsible={collapsible !== 'false'}
|
|
55
|
+
defaultOpen={defaultOpen !== 'false'}
|
|
56
|
+
>
|
|
57
|
+
<DiagramFrame height={Number.isFinite(parsedHeight) ? parsedHeight : 420}>
|
|
58
|
+
<LikeC4InlineView model={parsedModel} viewId={viewId} labels={labels} client:only="react">
|
|
59
|
+
<LoadingPlaceholder slot="fallback" label={labels.loading} />
|
|
60
|
+
</LikeC4InlineView>
|
|
61
|
+
</DiagramFrame>
|
|
62
|
+
</DiagramShell>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// LikeC4InlineView.tsx
|
|
2
|
+
// path: src/components/LikeC4InlineView.tsx
|
|
3
|
+
// Renders a LikeC4 model that was compiled at build time from a ```likec4
|
|
4
|
+
// fence, rather than loaded from the workspace project.
|
|
5
|
+
//
|
|
6
|
+
// The DSL is parsed and laid out by the fence plugin during the build (layout
|
|
7
|
+
// needs a WASM graph engine, so it cannot happen in the browser); this island
|
|
8
|
+
// only rehydrates the resulting plain-JSON model.
|
|
9
|
+
|
|
10
|
+
import { getProjectIcons } from 'likec4:icons';
|
|
11
|
+
import { projects } from 'likec4:projects';
|
|
12
|
+
// `@likec4/core/model`, not `likec4/model`: the latter is the Node entry point
|
|
13
|
+
// and pulls `node:module`'s createRequire into the browser bundle, which fails
|
|
14
|
+
// at hydration with "createRequire is not a function". This is the same module
|
|
15
|
+
// `likec4/react` itself imports.
|
|
16
|
+
import { LikeC4Model } from '@likec4/core/model';
|
|
17
|
+
import type { aux, UnknownLayouted } from 'likec4/model';
|
|
18
|
+
import { LikeC4View as GenericLikeC4View, LikeC4ModelProvider } from 'likec4/react';
|
|
19
|
+
import { useMemo } from 'react';
|
|
20
|
+
|
|
21
|
+
import ErrorBoundary from './ErrorBoundary';
|
|
22
|
+
import { DEFAULT_LABELS, type IslandLabels } from './labels';
|
|
23
|
+
import { withImageIcons } from './render-icon';
|
|
24
|
+
|
|
25
|
+
type Props = {
|
|
26
|
+
/** Layouted model dump produced at build time. */
|
|
27
|
+
model: unknown;
|
|
28
|
+
viewId: string;
|
|
29
|
+
/** Translated on the server; defaults to English. */
|
|
30
|
+
labels?: IslandLabels;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function LikeC4InlineViewContent({ model, viewId }: Props) {
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dump is plain JSON
|
|
35
|
+
const likec4model = useMemo(() => LikeC4Model.create(model as any), [model]);
|
|
36
|
+
|
|
37
|
+
// Inline models are not workspace projects, so they have no icon bundle of
|
|
38
|
+
// their own. The icon ids (`tech:…`, `aws:…`) are global, so borrow the
|
|
39
|
+
// renderer from the first project rather than shipping a second copy.
|
|
40
|
+
const iconRenderer = useMemo(() => {
|
|
41
|
+
const first = projects[0];
|
|
42
|
+
|
|
43
|
+
return first ? withImageIcons(getProjectIcons(String(first.id))) : undefined;
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<LikeC4ModelProvider likec4model={likec4model}>
|
|
48
|
+
<GenericLikeC4View
|
|
49
|
+
viewId={viewId as aux.ViewId<UnknownLayouted>}
|
|
50
|
+
controls={false}
|
|
51
|
+
renderIcon={iconRenderer}
|
|
52
|
+
browser={{
|
|
53
|
+
enableFocusMode: false,
|
|
54
|
+
enableSearch: false,
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
</LikeC4ModelProvider>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default function LikeC4InlineView({ labels = DEFAULT_LABELS, ...props }: Props) {
|
|
62
|
+
return (
|
|
63
|
+
<ErrorBoundary labels={labels}>
|
|
64
|
+
<LikeC4InlineViewContent {...props} />
|
|
65
|
+
</ErrorBoundary>
|
|
66
|
+
);
|
|
67
|
+
}
|