@purview-dev/astroc4 1.0.0-prerelease.5 → 1.0.0-prerelease.6
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/README.md +155 -95
- package/dist/expressive-code.d.ts +0 -4
- package/dist/expressive-code.js.map +5 -5
- package/dist/fences.d.ts +37 -22
- package/dist/file-layout.d.ts +4 -0
- package/dist/grammar/langium.d.ts +0 -4
- package/dist/grammar/source.d.ts +0 -12
- package/dist/grammar/textmate.d.ts +0 -2
- package/dist/icon-assets.d.ts +28 -0
- package/dist/icon-plugin.d.ts +16 -0
- package/dist/index.d.ts +12 -49
- package/dist/index.js +626 -78
- package/dist/index.js.map +15 -10
- package/dist/integration.d.ts +14 -24
- package/dist/integration.js +609 -43
- package/dist/integration.js.map +11 -5
- package/dist/projects.d.ts +24 -0
- package/dist/translations.d.ts +1 -0
- package/dist/workspace.d.ts +5 -0
- package/package.json +8 -3
- package/src/components/DiagramFrame.astro +13 -23
- package/src/components/DiagramShell.astro +18 -30
- package/src/components/ErrorBoundary.tsx +1 -5
- package/src/components/LikeC4Fence.astro +1 -5
- package/src/components/LikeC4InlineView.tsx +6 -16
- package/src/components/LikeC4ProjectView.tsx +0 -5
- package/src/components/LikeC4View.astro +73 -28
- package/src/components/LoadingPlaceholder.astro +9 -14
- package/src/components/icon-source.ts +5 -22
- package/src/components/labels.ts +10 -16
- package/src/components/md-fences.tsx +37 -0
- package/src/components/render-icon.tsx +0 -9
- package/src/components/utils.ts +40 -7
- package/src/translations.ts +3 -10
|
@@ -1,47 +1,87 @@
|
|
|
1
1
|
---
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// <LikeC4View viewId="index" />
|
|
7
|
-
// <LikeC4View viewId="edge" projectId="my-project" height={560} />
|
|
2
|
+
// The project is resolved on the server, so a bad `project` fails the build
|
|
3
|
+
// rather than rendering an error box.
|
|
4
|
+
|
|
5
|
+
import { likec4IdFor, projectNames } from 'virtual:astroc4/projects';
|
|
8
6
|
|
|
9
7
|
import DiagramFrame from './DiagramFrame.astro';
|
|
10
8
|
import DiagramShell from './DiagramShell.astro';
|
|
9
|
+
import LikeC4InlineView from './LikeC4InlineView';
|
|
11
10
|
import LikeC4ProjectView from './LikeC4ProjectView';
|
|
12
11
|
import LoadingPlaceholder from './LoadingPlaceholder.astro';
|
|
13
12
|
import { getIslandLabels, getTranslator } from './labels';
|
|
13
|
+
import { fileKey, resolveProjectId } from './utils';
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
|
-
/** View id inside the selected LikeC4 project. */
|
|
17
16
|
viewId: string;
|
|
18
|
-
/**
|
|
17
|
+
/** A project named in the plugin's `projects` option, or discovered under the site root. */
|
|
18
|
+
project?: string;
|
|
19
|
+
/** @deprecated Use `project`. */
|
|
19
20
|
projectId?: string;
|
|
20
|
-
/**
|
|
21
|
+
/** A single `.c4` file, relative to the site's `src/` folder. */
|
|
22
|
+
file?: string;
|
|
21
23
|
title?: string;
|
|
22
24
|
description?: string;
|
|
23
|
-
/**
|
|
25
|
+
/** Height in pixels. */
|
|
24
26
|
height?: number;
|
|
25
27
|
collapsible?: boolean;
|
|
26
28
|
defaultOpen?: boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
// Starlight
|
|
30
|
-
// property (and an empty `Locals` interface, so naming it would not type-check),
|
|
31
|
-
// which is why this is read as `unknown` and `getTranslator` falls back to
|
|
32
|
-
// English when it is absent.
|
|
31
|
+
// `t` is Starlight's; outside Starlight `App.Locals` is empty, hence `unknown`.
|
|
33
32
|
const t = getTranslator((Astro.locals as { t?: unknown }).t);
|
|
34
33
|
const labels = getIslandLabels(t, 'likec4.view.loading');
|
|
35
34
|
|
|
36
35
|
const {
|
|
37
36
|
viewId,
|
|
37
|
+
project,
|
|
38
38
|
projectId,
|
|
39
|
+
file,
|
|
39
40
|
title = t('likec4.view.title'),
|
|
40
41
|
description = t('likec4.view.description'),
|
|
41
42
|
height = 480,
|
|
42
43
|
collapsible = true,
|
|
43
44
|
defaultOpen = true,
|
|
44
45
|
} = Astro.props;
|
|
46
|
+
|
|
47
|
+
let inlineModel: unknown;
|
|
48
|
+
let likec4Id: string | undefined;
|
|
49
|
+
|
|
50
|
+
if (file !== undefined) {
|
|
51
|
+
// Compiled on import by the file-layout plugin.
|
|
52
|
+
const layouts = import.meta.glob<{ dump: unknown; viewIds: string[] }>('/src/**/*.c4', {
|
|
53
|
+
query: '?astroc4-layout',
|
|
54
|
+
import: 'default',
|
|
55
|
+
});
|
|
56
|
+
const key = fileKey(file);
|
|
57
|
+
const load = key ? layouts[key] : undefined;
|
|
58
|
+
|
|
59
|
+
if (!load) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`[astroc4] <LikeC4View file="${file}"> must name a .c4 file under src/` +
|
|
62
|
+
(key ? `; ${key} does not exist.` : '.'),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const layout = await load();
|
|
67
|
+
|
|
68
|
+
if (!layout.viewIds.includes(viewId)) {
|
|
69
|
+
throw new Error(`[astroc4] ${key} has no view "${viewId}". Declared: ${layout.viewIds.join(', ')}.`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
inlineModel = layout.dump;
|
|
73
|
+
} else if (projectNames.length > 0) {
|
|
74
|
+
const resolution = resolveProjectId(project ?? projectId, projectNames, labels);
|
|
75
|
+
|
|
76
|
+
if (resolution.error) {
|
|
77
|
+
throw new Error(`[astroc4] <LikeC4View viewId="${viewId}">: ${resolution.error}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
likec4Id = likec4IdFor[resolution.resolvedProjectId!];
|
|
81
|
+
} else {
|
|
82
|
+
// The site took discovery over, so only the island knows the projects.
|
|
83
|
+
likec4Id = project ?? projectId;
|
|
84
|
+
}
|
|
45
85
|
---
|
|
46
86
|
|
|
47
87
|
<DiagramShell
|
|
@@ -57,33 +97,38 @@ const {
|
|
|
57
97
|
</div>
|
|
58
98
|
|
|
59
99
|
<DiagramFrame height={height}>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
100
|
+
{
|
|
101
|
+
inlineModel !== undefined ? (
|
|
102
|
+
<LikeC4InlineView model={inlineModel} viewId={viewId} labels={labels} client:only="react">
|
|
103
|
+
<LoadingPlaceholder slot="fallback" label={labels.loading} />
|
|
104
|
+
</LikeC4InlineView>
|
|
105
|
+
) : (
|
|
106
|
+
<LikeC4ProjectView viewId={viewId} projectId={likec4Id} labels={labels} client:only="react">
|
|
107
|
+
{/* Without `slot="fallback"`, Astro parks it in an inert <template>. */}
|
|
108
|
+
<LoadingPlaceholder slot="fallback" label={labels.loading} />
|
|
109
|
+
</LikeC4ProjectView>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
67
112
|
</DiagramFrame>
|
|
68
113
|
</DiagramShell>
|
|
69
114
|
|
|
70
115
|
<style>
|
|
71
|
-
/*
|
|
116
|
+
/* See DiagramShell for the token contract. */
|
|
72
117
|
.likec4-badge {
|
|
73
|
-
--
|
|
74
|
-
--
|
|
75
|
-
--
|
|
118
|
+
--_likec4-border: var(--likec4-border, var(--sl-color-gray-5, rgba(128, 128, 128, 0.35)));
|
|
119
|
+
--_likec4-surface: var(--likec4-surface, var(--sl-color-bg, transparent));
|
|
120
|
+
--_likec4-text-muted: var(--likec4-text-muted, var(--sl-color-gray-2, rgba(128, 128, 128, 0.95)));
|
|
76
121
|
|
|
77
122
|
display: inline-flex;
|
|
78
123
|
align-items: center;
|
|
79
124
|
gap: 0.5rem;
|
|
80
125
|
margin-bottom: 0.75rem;
|
|
81
|
-
border: 1px solid var(--
|
|
126
|
+
border: 1px solid var(--_likec4-border);
|
|
82
127
|
border-radius: 9999px;
|
|
83
|
-
background: var(--
|
|
128
|
+
background: var(--_likec4-surface);
|
|
84
129
|
padding: 0.25rem 0.75rem;
|
|
85
130
|
font-size: 0.75rem;
|
|
86
131
|
line-height: 1rem;
|
|
87
|
-
color: var(--
|
|
132
|
+
color: var(--_likec4-text-muted);
|
|
88
133
|
}
|
|
89
134
|
</style>
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
---
|
|
2
|
-
// LoadingPlaceholder.astro
|
|
3
|
-
// path: src/components/LoadingPlaceholder.astro
|
|
4
|
-
// Slot fallback rendered server-side while a `client:only` island hydrates.
|
|
5
|
-
// Reserves roughly the right amount of space so the page doesn't jump.
|
|
6
|
-
|
|
7
2
|
interface Props {
|
|
8
3
|
label: string;
|
|
9
4
|
}
|
|
@@ -22,16 +17,16 @@ const { label } = Astro.props;
|
|
|
22
17
|
</div>
|
|
23
18
|
|
|
24
19
|
<style>
|
|
25
|
-
/*
|
|
20
|
+
/* See DiagramShell for the token contract. */
|
|
26
21
|
.likec4-loading {
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
--
|
|
30
|
-
--
|
|
22
|
+
--_likec4-border: var(--likec4-border, var(--sl-color-gray-5, rgba(128, 128, 128, 0.35)));
|
|
23
|
+
--_likec4-surface: var(--likec4-surface, var(--sl-color-bg, transparent));
|
|
24
|
+
--_likec4-text-muted: var(--likec4-text-muted, var(--sl-color-gray-2, rgba(128, 128, 128, 0.95)));
|
|
25
|
+
--_likec4-skeleton: var(--likec4-skeleton, var(--sl-color-gray-6, rgba(128, 128, 128, 0.18)));
|
|
31
26
|
|
|
32
|
-
border: 1px dashed var(--
|
|
27
|
+
border: 1px dashed var(--_likec4-border);
|
|
33
28
|
border-radius: 0.5rem;
|
|
34
|
-
background: var(--
|
|
29
|
+
background: var(--_likec4-surface);
|
|
35
30
|
padding: 1.25rem;
|
|
36
31
|
}
|
|
37
32
|
|
|
@@ -40,7 +35,7 @@ const { label } = Astro.props;
|
|
|
40
35
|
font-size: 0.875rem;
|
|
41
36
|
line-height: 1.25rem;
|
|
42
37
|
font-weight: 500;
|
|
43
|
-
color: var(--
|
|
38
|
+
color: var(--_likec4-text-muted);
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
.likec4-loading__lines {
|
|
@@ -51,7 +46,7 @@ const { label } = Astro.props;
|
|
|
51
46
|
.likec4-loading__line {
|
|
52
47
|
height: 1rem;
|
|
53
48
|
border-radius: 0.25rem;
|
|
54
|
-
background: var(--
|
|
49
|
+
background: var(--_likec4-skeleton);
|
|
55
50
|
animation: likec4-pulse 1.6s ease-in-out infinite;
|
|
56
51
|
}
|
|
57
52
|
|
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// LikeC4 resolves a *relative* icon path against the .c4 file and stores an
|
|
6
|
-
// absolute `file://` URL in the compiled model. That path is baked into the
|
|
7
|
-
// client bundle, so the icon 404s for every visitor and the author's local
|
|
8
|
-
// directory structure ships with the site.
|
|
9
|
-
//
|
|
10
|
-
// Files under `public/` are the exception worth supporting: Astro serves them
|
|
11
|
-
// verbatim at the site root, so the absolute path can be rewritten back to the
|
|
12
|
-
// URL it will actually be served from.
|
|
1
|
+
// Local icons are normally rewritten to `/_astroc4/…` URLs at build time by
|
|
2
|
+
// src/icon-plugin.ts; a `file://` value reaching here is one the build could
|
|
3
|
+
// not attribute to a project.
|
|
13
4
|
|
|
14
5
|
/** Everything after a `/public/` segment, which Astro serves at the site root. */
|
|
15
6
|
const PUBLIC_SEGMENT = /\/public\/(.+)$/;
|
|
16
7
|
|
|
17
|
-
/** A path the compiler resolved on the build machine, rather than a pack id. */
|
|
18
8
|
const LOCAL_PATH = /^(file:\/\/|virtual:\/)/;
|
|
19
9
|
|
|
20
10
|
export type IconSource =
|
|
@@ -23,14 +13,8 @@ export type IconSource =
|
|
|
23
13
|
| { kind: 'unresolvable'; path: string };
|
|
24
14
|
|
|
25
15
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* - `pack` — a bundled icon id (`tech:nginx`, `aws:…`, …)
|
|
29
|
-
* or anything else the LikeC4 renderer handles itself.
|
|
30
|
-
* - `url` — render directly as an `<img src>`.
|
|
31
|
-
* - `unresolvable` — a build-machine path with no web equivalent. Rendering
|
|
32
|
-
* nothing is better than a broken image, and the caller
|
|
33
|
-
* warns so it is not silently wrong.
|
|
16
|
+
* `pack` is anything the LikeC4 renderer handles itself; `unresolvable` is a
|
|
17
|
+
* build-machine path with no web equivalent, which renders nothing (and warns).
|
|
34
18
|
*/
|
|
35
19
|
export function classifyIcon(icon: string | null | undefined): IconSource {
|
|
36
20
|
const value = icon?.trim();
|
|
@@ -39,7 +23,6 @@ export function classifyIcon(icon: string | null | undefined): IconSource {
|
|
|
39
23
|
return { kind: 'pack' };
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
// Absolute URLs (and protocol-relative ones) are already web-addressable.
|
|
43
26
|
if (/^(https?:)?\/\//i.test(value)) {
|
|
44
27
|
return { kind: 'url', src: value };
|
|
45
28
|
}
|
package/src/components/labels.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// Resolves the plugin's UI strings for a page. The Astro wrappers translate on
|
|
4
|
-
// the server and pass the islands plain strings, because `Astro.locals.t` does
|
|
5
|
-
// not exist in the browser.
|
|
1
|
+
// The Astro wrappers translate on the server and pass islands plain strings,
|
|
2
|
+
// because `Astro.locals.t` does not exist in the browser.
|
|
6
3
|
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// declarations carry and what the site's bundler maps back to this `.ts` file.
|
|
11
|
-
import { translations } from '../translations.js';
|
|
4
|
+
// `files` publishes `src/translations.ts` next to `src/components`, so this
|
|
5
|
+
// resolves in an installed package.
|
|
6
|
+
import { translations } from '../translations.ts';
|
|
12
7
|
|
|
13
8
|
export type TranslationKey = keyof (typeof translations)['en'];
|
|
14
9
|
export type Translate = (key: TranslationKey) => string;
|
|
15
10
|
|
|
16
|
-
/** Every string an island can show after hydration. */
|
|
17
11
|
export type IslandLabels = {
|
|
18
12
|
loading: string;
|
|
19
13
|
errorTitle: string;
|
|
@@ -23,6 +17,8 @@ export type IslandLabels = {
|
|
|
23
17
|
noProjects: string;
|
|
24
18
|
/** Template: `{projectId}`, `{available}`. */
|
|
25
19
|
unknownProject: string;
|
|
20
|
+
/** Template: `{available}`. */
|
|
21
|
+
projectRequired: string;
|
|
26
22
|
unresolvedProject: string;
|
|
27
23
|
/** Template: `{projectId}`. */
|
|
28
24
|
loadFailed: string;
|
|
@@ -30,10 +26,7 @@ export type IslandLabels = {
|
|
|
30
26
|
|
|
31
27
|
const english: Translate = (key) => translations.en[key];
|
|
32
28
|
|
|
33
|
-
/**
|
|
34
|
-
* Wrap `Astro.locals.t`, falling back to English when it is absent — which it
|
|
35
|
-
* is on any page Starlight does not render, such as a plain `src/pages/` route.
|
|
36
|
-
*/
|
|
29
|
+
/** `t` is absent on any page Starlight does not render, so English is the fallback. */
|
|
37
30
|
export function getTranslator(t: unknown): Translate {
|
|
38
31
|
if (typeof t !== 'function') return english;
|
|
39
32
|
|
|
@@ -53,6 +46,7 @@ export function getIslandLabels(t: Translate, loadingKey: TranslationKey): Islan
|
|
|
53
46
|
viewIdRequired: t('likec4.error.viewIdRequired'),
|
|
54
47
|
noProjects: t('likec4.error.noProjects'),
|
|
55
48
|
unknownProject: t('likec4.error.unknownProject'),
|
|
49
|
+
projectRequired: t('likec4.error.projectRequired'),
|
|
56
50
|
unresolvedProject: t('likec4.error.unresolvedProject'),
|
|
57
51
|
loadFailed: t('likec4.error.loadFailed'),
|
|
58
52
|
};
|
|
@@ -60,7 +54,7 @@ export function getIslandLabels(t: Translate, loadingKey: TranslationKey): Islan
|
|
|
60
54
|
|
|
61
55
|
export const DEFAULT_LABELS: IslandLabels = getIslandLabels(english, 'likec4.view.loading');
|
|
62
56
|
|
|
63
|
-
/**
|
|
57
|
+
/** Unknown placeholders are left in place so they show up. */
|
|
64
58
|
export function format(template: string, values: Record<string, string>): string {
|
|
65
59
|
return template.replace(/\{(\w+)\}/g, (match, name: string) => values[name] ?? match);
|
|
66
60
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Mounts the islands into the placeholders src/fences.ts writes for .md pages.
|
|
2
|
+
|
|
3
|
+
import { likec4IdFor, projectNames } from 'virtual:astroc4/projects';
|
|
4
|
+
import { createRoot } from 'react-dom/client';
|
|
5
|
+
|
|
6
|
+
import { DEFAULT_LABELS } from './labels';
|
|
7
|
+
import LikeC4InlineView from './LikeC4InlineView';
|
|
8
|
+
import LikeC4ProjectView from './LikeC4ProjectView';
|
|
9
|
+
import { resolveProjectId } from './utils';
|
|
10
|
+
|
|
11
|
+
const SELECTOR = '[data-astroc4-fence]:not([data-astroc4-mounted])';
|
|
12
|
+
|
|
13
|
+
export function mountFences(root: ParentNode = document): void {
|
|
14
|
+
for (const placeholder of root.querySelectorAll<HTMLElement>(SELECTOR)) {
|
|
15
|
+
placeholder.dataset.astroc4Mounted = '';
|
|
16
|
+
|
|
17
|
+
const frame = placeholder.querySelector<HTMLElement>('.likec4-frame') ?? placeholder;
|
|
18
|
+
const { viewId = '', model, project } = placeholder.dataset;
|
|
19
|
+
|
|
20
|
+
createRoot(frame).render(
|
|
21
|
+
model !== undefined ? (
|
|
22
|
+
<LikeC4InlineView model={JSON.parse(model)} viewId={viewId} labels={DEFAULT_LABELS} />
|
|
23
|
+
) : (
|
|
24
|
+
<LikeC4ProjectView viewId={viewId} projectId={likec4Id(project)} labels={DEFAULT_LABELS} />
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A page's project name as LikeC4's id; anything unknown is left for the island to report. */
|
|
31
|
+
function likec4Id(project: string | undefined): string | undefined {
|
|
32
|
+
if (projectNames.length === 0) return project;
|
|
33
|
+
|
|
34
|
+
const { resolvedProjectId } = resolveProjectId(project, projectNames, DEFAULT_LABELS);
|
|
35
|
+
|
|
36
|
+
return resolvedProjectId ? likec4IdFor[resolvedProjectId] : project;
|
|
37
|
+
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
// render-icon.tsx
|
|
2
|
-
// path: src/components/render-icon.tsx
|
|
3
|
-
// Shared icon renderer for both the project-model and inline-fence islands.
|
|
4
|
-
|
|
5
1
|
import type { ElementIconRenderer } from 'likec4:icons';
|
|
6
2
|
|
|
7
3
|
import { classifyIcon } from './icon-source';
|
|
8
4
|
|
|
9
|
-
/**
|
|
10
|
-
* Wrap the bundled-icon renderer so elements can also use an image URL, or a
|
|
11
|
-
* local file kept under `public/`. Anything else local has no web equivalent —
|
|
12
|
-
* warn rather than render a broken image.
|
|
13
|
-
*/
|
|
14
5
|
export function withImageIcons(packRenderer: ElementIconRenderer): ElementIconRenderer {
|
|
15
6
|
return function IconRenderer(props) {
|
|
16
7
|
const source = classifyIcon(props.node.icon);
|
package/src/components/utils.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
// utils.ts
|
|
2
|
-
// path: src/components/utils.ts
|
|
3
|
-
// Pure resolution/validation helpers for <LikeC4View>. Separated from the React
|
|
4
|
-
// component so the error-message behaviour can be tested without a DOM.
|
|
5
|
-
|
|
6
1
|
import { DEFAULT_LABELS, format, type IslandLabels } from './labels';
|
|
7
2
|
|
|
8
3
|
type ErrorLabels = Pick<
|
|
9
4
|
IslandLabels,
|
|
10
|
-
'viewIdRequired' | 'noProjects' | 'unknownProject' | 'loadFailed'
|
|
5
|
+
'viewIdRequired' | 'noProjects' | 'unknownProject' | 'projectRequired' | 'loadFailed'
|
|
11
6
|
>;
|
|
12
7
|
|
|
13
8
|
export function getAvailableProjectIds(
|
|
@@ -38,7 +33,18 @@ export function resolveProjectId(
|
|
|
38
33
|
};
|
|
39
34
|
}
|
|
40
35
|
|
|
41
|
-
const
|
|
36
|
+
const requested = projectId?.trim();
|
|
37
|
+
|
|
38
|
+
// With several projects, "the first one found" depends on folder order —
|
|
39
|
+
// a reorder would silently change the diagram — so the page has to say.
|
|
40
|
+
if (!requested && availableProjectIds.length > 1) {
|
|
41
|
+
return {
|
|
42
|
+
resolvedProjectId: null,
|
|
43
|
+
error: format(labels.projectRequired, { available: availableProjectIds.join(', ') }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resolvedProjectId = requested || availableProjectIds[0]!;
|
|
42
48
|
|
|
43
49
|
if (!availableProjectIds.includes(resolvedProjectId)) {
|
|
44
50
|
return {
|
|
@@ -56,6 +62,33 @@ export function resolveProjectId(
|
|
|
56
62
|
};
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The `import.meta.glob` key for `<LikeC4View file="…">`, or `null`. Only files
|
|
67
|
+
* under `src/` can match the glob, so a path that climbs out loads nothing.
|
|
68
|
+
*/
|
|
69
|
+
export function fileKey(file: string): string | null {
|
|
70
|
+
// Absolute paths (`/etc/x.c4`, `C:/x.c4`, `\\server\x.c4`) are never relative to src/.
|
|
71
|
+
if (/^[\\/]/.test(file) || /^[a-z]:/i.test(file)) return null;
|
|
72
|
+
|
|
73
|
+
const segments: string[] = [];
|
|
74
|
+
|
|
75
|
+
for (const segment of file.split(/[\\/]+/)) {
|
|
76
|
+
if (segment === '' || segment === '.') continue;
|
|
77
|
+
|
|
78
|
+
if (segment === '..') {
|
|
79
|
+
if (segments.length === 0) return null;
|
|
80
|
+
segments.pop();
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
segments.push(segment);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const path = segments.join('/');
|
|
88
|
+
|
|
89
|
+
return path.toLowerCase().endsWith('.c4') ? `/src/${path}` : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
59
92
|
export function toLoadErrorMessage(
|
|
60
93
|
err: unknown,
|
|
61
94
|
resolvedProjectId: string,
|
package/src/translations.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// UI strings for the diagram components, injected into Starlight's i18n system
|
|
4
|
-
// by the plugin's `i18n:setup` hook. A site overrides or adds a language the
|
|
5
|
-
// same way it does for Starlight's own strings — a `src/content/i18n/<lang>.json`
|
|
6
|
-
// containing any of these keys.
|
|
7
|
-
//
|
|
8
|
-
// Placeholders are `{name}`, not i18next's `{{name}}`: some values (the list of
|
|
9
|
-
// available projects, say) are only known in the browser, so the template has
|
|
10
|
-
// to reach the island unformatted. See `format()` in src/components/labels.ts.
|
|
1
|
+
// Placeholders are `{name}`, not i18next's `{{name}}`: some values are only
|
|
2
|
+
// known in the browser, so the template has to reach the island unformatted.
|
|
11
3
|
|
|
12
4
|
export const translations = {
|
|
13
5
|
en: {
|
|
@@ -25,6 +17,7 @@ export const translations = {
|
|
|
25
17
|
'likec4.error.viewIdRequired': 'A LikeC4 viewId is required to render the diagram.',
|
|
26
18
|
'likec4.error.noProjects': 'No LikeC4 projects are available in this workspace.',
|
|
27
19
|
'likec4.error.unknownProject': 'Unknown LikeC4 projectId "{projectId}". Available projects: {available}.',
|
|
20
|
+
'likec4.error.projectRequired': 'Several LikeC4 projects exist ({available}); pass project="…".',
|
|
28
21
|
'likec4.error.unresolvedProject': 'Unable to resolve a LikeC4 project.',
|
|
29
22
|
'likec4.error.loadFailed': 'Unable to load LikeC4 project "{projectId}".',
|
|
30
23
|
},
|