mdx-artifacts 0.1.1 → 0.2.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/README.md +224 -60
- package/README.zh-CN.md +299 -42
- package/agents/AGENTS.snippet.md +21 -10
- package/artifact-docs/examples/commentable-feedback.mdx +76 -70
- package/artifact-docs/examples/decision-matrix.mdx +122 -50
- package/artifact-docs/examples/layout-composition.mdx +106 -128
- package/artifact-docs/examples/streamlit-style-mixed.mdx +100 -85
- package/dist/lib/cli/{build.js → commands/build.js} +2 -2
- package/dist/lib/cli/{components.js → commands/components.js} +19 -3
- package/dist/lib/cli/{dev.js → commands/dev.js} +2 -2
- package/dist/lib/cli/commands/interactions.d.ts +2 -0
- package/dist/lib/cli/commands/interactions.js +280 -0
- package/dist/lib/cli/commands/review.d.ts +1 -0
- package/dist/lib/cli/commands/review.js +171 -0
- package/dist/lib/cli/commands/scaffold.d.ts +16 -0
- package/dist/lib/cli/commands/scaffold.js +440 -0
- package/dist/lib/cli/commands/validate.d.ts +18 -0
- package/dist/lib/cli/commands/validate.js +311 -0
- package/dist/lib/cli/config/config.d.ts +2 -0
- package/dist/lib/cli/{config.js → config/config.js} +3 -2
- package/dist/lib/cli/{types.d.ts → config/types.d.ts} +2 -1
- package/dist/lib/cli/{vite-artifact.d.ts → dev-server/vite-artifact.d.ts} +3 -3
- package/dist/lib/cli/dev-server/vite-artifact.js +415 -0
- package/dist/lib/cli/diagnostics/diagnostics.d.ts +11 -0
- package/dist/lib/cli/diagnostics/diagnostics.js +1 -0
- package/dist/lib/cli/index.js +39 -18
- package/dist/lib/cli/mdx/sortable-list.d.ts +14 -0
- package/dist/lib/cli/mdx/sortable-list.js +520 -0
- package/dist/lib/cli/resources/resource-policy.d.ts +15 -0
- package/dist/lib/cli/resources/resource-policy.js +46 -0
- package/dist/lib/cli/resources/safe-path.d.ts +13 -0
- package/dist/lib/cli/resources/safe-path.js +55 -0
- package/dist/lib/cli/services/interaction-service.d.ts +40 -0
- package/dist/lib/cli/services/interaction-service.js +226 -0
- package/dist/lib/cli/services/review.d.ts +43 -0
- package/dist/lib/cli/{review.js → services/review.js} +34 -172
- package/dist/lib/react/{components → composites/comparison-set}/ComparisonSet.d.ts +1 -1
- package/dist/lib/react/{components → composites/comparison-set}/ComparisonSet.js +3 -3
- package/dist/lib/react/composites/comparison-set/index.d.ts +2 -0
- package/dist/lib/react/composites/comparison-set/index.js +1 -0
- package/dist/lib/react/composites/content-set/ContentItem.d.ts +37 -0
- package/dist/lib/react/composites/content-set/ContentItem.js +49 -0
- package/dist/lib/react/composites/content-set/index.d.ts +2 -0
- package/dist/lib/react/composites/content-set/index.js +1 -0
- package/dist/lib/react/{components → composites/export-panel}/ExportPanel.js +2 -2
- package/dist/lib/react/composites/export-panel/index.d.ts +2 -0
- package/dist/lib/react/composites/export-panel/index.js +1 -0
- package/dist/lib/react/{components → composites/section}/Section.js +1 -1
- package/dist/lib/react/composites/section/index.d.ts +2 -0
- package/dist/lib/react/composites/section/index.js +1 -0
- package/dist/lib/react/index.d.ts +36 -31
- package/dist/lib/react/index.js +18 -15
- package/dist/lib/react/interactions/artifact-state/index.d.ts +1 -0
- package/dist/lib/react/interactions/artifact-state/index.js +1 -0
- package/dist/lib/react/{components → interactions/comments}/Comments.d.ts +2 -2
- package/dist/lib/react/{components → interactions/comments}/Comments.js +3 -3
- package/dist/lib/react/interactions/comments/index.d.ts +1 -0
- package/dist/lib/react/interactions/comments/index.js +1 -0
- package/dist/lib/react/interactions/sortable-list/SortableList.d.ts +29 -0
- package/dist/lib/react/interactions/sortable-list/SortableList.js +282 -0
- package/dist/lib/react/interactions/sortable-list/index.d.ts +1 -0
- package/dist/lib/react/interactions/sortable-list/index.js +1 -0
- package/dist/lib/react/layout/layout-primitives/index.d.ts +2 -0
- package/dist/lib/react/layout/layout-primitives/index.js +1 -0
- package/dist/lib/react/legacy/LegacyContentComponents.d.ts +65 -0
- package/dist/lib/react/legacy/LegacyContentComponents.js +26 -0
- package/dist/lib/react/mdx-components.d.ts +5 -0
- package/dist/lib/react/mdx-components.js +38 -0
- package/dist/lib/react/{components → primitives/annotated-code}/AnnotatedCode.d.ts +1 -1
- package/dist/lib/react/{components → primitives/annotated-code}/AnnotatedCode.js +5 -5
- package/dist/lib/react/primitives/annotated-code/index.d.ts +2 -0
- package/dist/lib/react/primitives/annotated-code/index.js +1 -0
- package/dist/lib/react/primitives/callout/Callout.d.ts +11 -0
- package/dist/lib/react/{components → primitives/callout}/Callout.js +9 -6
- package/dist/lib/react/primitives/callout/index.d.ts +2 -0
- package/dist/lib/react/primitives/callout/index.js +1 -0
- package/dist/lib/react/primitives/code-block/CodeBlock.d.ts +20 -0
- package/dist/lib/react/primitives/code-block/CodeBlock.js +32 -0
- package/dist/lib/react/primitives/code-block/index.d.ts +2 -0
- package/dist/lib/react/primitives/code-block/index.js +1 -0
- package/dist/lib/react/primitives/code-surface/CodeSurface.d.ts +11 -0
- package/dist/lib/react/primitives/code-surface/CodeSurface.js +34 -0
- package/dist/lib/react/primitives/code-surface/index.d.ts +2 -0
- package/dist/lib/react/primitives/code-surface/index.js +1 -0
- package/dist/lib/react/primitives/diff-block/DiffBlock.js +25 -0
- package/dist/lib/react/primitives/diff-block/index.d.ts +2 -0
- package/dist/lib/react/primitives/diff-block/index.js +1 -0
- package/dist/lib/react/{components → primitives/inline-text}/InlineText.d.ts +4 -2
- package/dist/lib/react/primitives/inline-text/InlineText.js +28 -0
- package/dist/lib/react/primitives/inline-text/index.d.ts +2 -0
- package/dist/lib/react/primitives/inline-text/index.js +1 -0
- package/dist/lib/react/primitives/markdown-body/MarkdownBody.d.ts +9 -0
- package/dist/lib/react/primitives/markdown-body/MarkdownBody.js +49 -0
- package/dist/lib/react/primitives/markdown-body/index.d.ts +2 -0
- package/dist/lib/react/primitives/markdown-body/index.js +1 -0
- package/dist/lib/react/primitives/severity-badge/index.d.ts +2 -0
- package/dist/lib/react/primitives/severity-badge/index.js +1 -0
- package/dist/lib/react/registry.d.ts +10 -0
- package/dist/lib/react/registry.js +505 -210
- package/dist/lib/react/styles.css +490 -38
- package/docs/cli-structure.md +141 -0
- package/docs/component-protocol.md +199 -33
- package/docs/component-taxonomy.md +40 -4
- package/docs/design.md +42 -21
- package/docs/design.zh-CN.md +41 -21
- package/docs/naming.md +17 -7
- package/docs/releasing.md +132 -0
- package/docs/testing.md +35 -10
- package/package.json +9 -7
- package/dist/lib/cli/config.d.ts +0 -2
- package/dist/lib/cli/review.d.ts +0 -33
- package/dist/lib/cli/scaffold.d.ts +0 -1
- package/dist/lib/cli/scaffold.js +0 -56
- package/dist/lib/cli/validate.d.ts +0 -6
- package/dist/lib/cli/validate.js +0 -79
- package/dist/lib/cli/vite-artifact.js +0 -237
- package/dist/lib/react/components/Callout.d.ts +0 -9
- package/dist/lib/react/components/CodeBlock.d.ts +0 -10
- package/dist/lib/react/components/CodeBlock.js +0 -28
- package/dist/lib/react/components/DecisionMatrix.d.ts +0 -16
- package/dist/lib/react/components/DecisionMatrix.js +0 -27
- package/dist/lib/react/components/DiffBlock.js +0 -24
- package/dist/lib/react/components/InlineText.js +0 -18
- package/dist/lib/react/components/MarkdownBody.d.ts +0 -7
- package/dist/lib/react/components/MarkdownBody.js +0 -36
- package/dist/lib/react/components/OptionGrid.d.ts +0 -13
- package/dist/lib/react/components/OptionGrid.js +0 -21
- /package/dist/lib/cli/{build.d.ts → commands/build.d.ts} +0 -0
- /package/dist/lib/cli/{components.d.ts → commands/components.d.ts} +0 -0
- /package/dist/lib/cli/{dev.d.ts → commands/dev.d.ts} +0 -0
- /package/dist/lib/cli/{types.js → config/types.js} +0 -0
- /package/dist/lib/cli/{artifact-state.d.ts → state/artifact-state.d.ts} +0 -0
- /package/dist/lib/cli/{artifact-state.js → state/artifact-state.js} +0 -0
- /package/dist/lib/react/{components → composites/export-panel}/ExportPanel.d.ts +0 -0
- /package/dist/lib/react/{components → composites/section}/Section.d.ts +0 -0
- /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.d.ts +0 -0
- /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.js +0 -0
- /package/dist/lib/react/{components → layout/layout-primitives}/Layout.d.ts +0 -0
- /package/dist/lib/react/{components → layout/layout-primitives}/Layout.js +0 -0
- /package/dist/lib/react/{components → primitives/diff-block}/DiffBlock.d.ts +0 -0
- /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.d.ts +0 -0
- /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.js +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createArtifactMeta, createArtifactRoute, readArtifactState, writeArtifactState } from "../state/artifact-state.js";
|
|
4
|
+
import { loadConfig } from "../config/config.js";
|
|
5
|
+
import { addSortableListItem as addSortableListItemSource, extractSortableListSeeds, promoteSortableListOrder, removeSortableListItem as removeSortableListItemSource, updateSortableListItem as updateSortableListItemSource } from "../mdx/sortable-list.js";
|
|
6
|
+
export async function inspectInteractionService(projectRoot, input, id) {
|
|
7
|
+
const artifact = await createArtifactFromInput(projectRoot, input);
|
|
8
|
+
const meta = await createArtifactMeta(projectRoot, artifact);
|
|
9
|
+
const state = await readArtifactState(artifact);
|
|
10
|
+
const source = await readFile(artifact.sourcePath, "utf8");
|
|
11
|
+
const component = findSortableList(source, id);
|
|
12
|
+
const persistedOrder = readPersistedOrder(state.interactions[id]);
|
|
13
|
+
const resolvedOrder = resolveOrder(component, persistedOrder);
|
|
14
|
+
const warnings = createInspectWarnings(id, resolvedOrder.staleIds);
|
|
15
|
+
return {
|
|
16
|
+
component,
|
|
17
|
+
order: resolvedOrder,
|
|
18
|
+
state: {
|
|
19
|
+
path: meta.statePath,
|
|
20
|
+
exists: meta.stateExists
|
|
21
|
+
},
|
|
22
|
+
source: {
|
|
23
|
+
path: meta.sourcePath
|
|
24
|
+
},
|
|
25
|
+
warnings
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function setInteractionOrderService(projectRoot, input, id, orderedIds) {
|
|
29
|
+
const { artifact, component } = await loadSortableList(projectRoot, input, id);
|
|
30
|
+
validateOrderedIds(component, orderedIds);
|
|
31
|
+
const state = await readArtifactState(artifact);
|
|
32
|
+
const interaction = createSortableListInteraction(component, orderedIds);
|
|
33
|
+
const nextState = await writeArtifactState(projectRoot, artifact, {
|
|
34
|
+
...state,
|
|
35
|
+
interactions: {
|
|
36
|
+
...state.interactions,
|
|
37
|
+
[id]: interaction
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return createMutationResult("set-order", artifact, id, nextState, { orderedIds });
|
|
41
|
+
}
|
|
42
|
+
export async function resetInteractionService(projectRoot, input, id) {
|
|
43
|
+
const { artifact } = await loadSortableList(projectRoot, input, id);
|
|
44
|
+
const state = await readArtifactState(artifact);
|
|
45
|
+
const { [id]: _removed, ...interactions } = state.interactions;
|
|
46
|
+
const nextState = await writeArtifactState(projectRoot, artifact, {
|
|
47
|
+
...state,
|
|
48
|
+
interactions
|
|
49
|
+
});
|
|
50
|
+
return createMutationResult("reset", artifact, id, nextState);
|
|
51
|
+
}
|
|
52
|
+
export async function promoteInteractionService(projectRoot, input, id) {
|
|
53
|
+
const { artifact, component, source } = await loadSortableList(projectRoot, input, id);
|
|
54
|
+
const state = await readArtifactState(artifact);
|
|
55
|
+
const orderedIds = readPersistedOrder(state.interactions[id]);
|
|
56
|
+
if (!orderedIds) {
|
|
57
|
+
return createMutationResult("promote", artifact, id, state, { status: "noop", reason: "no runtime overlay" });
|
|
58
|
+
}
|
|
59
|
+
validateOrderedIds(component, orderedIds);
|
|
60
|
+
const nextSource = promoteSortableListOrder(source, id, orderedIds);
|
|
61
|
+
await writeFile(artifact.sourcePath, nextSource, "utf8");
|
|
62
|
+
const { [id]: _removed, ...interactions } = state.interactions;
|
|
63
|
+
const nextState = await writeArtifactState(projectRoot, artifact, {
|
|
64
|
+
...state,
|
|
65
|
+
interactions
|
|
66
|
+
});
|
|
67
|
+
return createMutationResult("promote", artifact, id, nextState, { orderedIds });
|
|
68
|
+
}
|
|
69
|
+
export async function addInteractionItemService(projectRoot, input, id, item, options = {}) {
|
|
70
|
+
const { artifact, source } = await loadSortableList(projectRoot, input, id);
|
|
71
|
+
const nextSource = addSortableListItemSource(source, id, item, options);
|
|
72
|
+
const nextComponent = readSortableListFromSource(nextSource, id);
|
|
73
|
+
await writeFile(artifact.sourcePath, nextSource, "utf8");
|
|
74
|
+
const nextState = await syncInteractionOverlay(projectRoot, artifact, id, nextComponent);
|
|
75
|
+
return createMutationResult("add-item", artifact, id, nextState, { itemId: item.id });
|
|
76
|
+
}
|
|
77
|
+
export async function removeInteractionItemService(projectRoot, input, id, itemId) {
|
|
78
|
+
const { artifact, source } = await loadSortableList(projectRoot, input, id);
|
|
79
|
+
const nextSource = removeSortableListItemSource(source, id, itemId);
|
|
80
|
+
const nextComponent = readSortableListFromSource(nextSource, id);
|
|
81
|
+
await writeFile(artifact.sourcePath, nextSource, "utf8");
|
|
82
|
+
const nextState = await syncInteractionOverlay(projectRoot, artifact, id, nextComponent);
|
|
83
|
+
return createMutationResult("remove-item", artifact, id, nextState, { itemId });
|
|
84
|
+
}
|
|
85
|
+
export async function updateInteractionItemService(projectRoot, input, id, itemId, patch) {
|
|
86
|
+
const { artifact, source } = await loadSortableList(projectRoot, input, id);
|
|
87
|
+
const nextSource = updateSortableListItemSource(source, id, itemId, patch);
|
|
88
|
+
const nextComponent = readSortableListFromSource(nextSource, id);
|
|
89
|
+
await writeFile(artifact.sourcePath, nextSource, "utf8");
|
|
90
|
+
const nextState = await syncInteractionOverlay(projectRoot, artifact, id, nextComponent);
|
|
91
|
+
return createMutationResult("update-item", artifact, id, nextState, { itemId });
|
|
92
|
+
}
|
|
93
|
+
async function createArtifactFromInput(projectRoot, input) {
|
|
94
|
+
const config = await loadConfig(projectRoot);
|
|
95
|
+
const mdxPath = path.resolve(projectRoot, input);
|
|
96
|
+
return createArtifactRoute(projectRoot, mdxPath, config.docsDir);
|
|
97
|
+
}
|
|
98
|
+
async function loadSortableList(projectRoot, input, id) {
|
|
99
|
+
const artifact = await createArtifactFromInput(projectRoot, input);
|
|
100
|
+
const source = await readFile(artifact.sourcePath, "utf8");
|
|
101
|
+
const component = findSortableList(source, id);
|
|
102
|
+
return { artifact, component, source };
|
|
103
|
+
}
|
|
104
|
+
function findSortableList(source, id) {
|
|
105
|
+
const seeds = extractSortableListSeeds(source);
|
|
106
|
+
const component = seeds.find((seed) => seed.id === id);
|
|
107
|
+
if (!component) {
|
|
108
|
+
const availableIds = seeds.map((seed) => seed.id);
|
|
109
|
+
throw new Error(availableIds.length > 0
|
|
110
|
+
? `SortableList not found: ${id}. Available SortableList ids: ${availableIds.join(", ")}.`
|
|
111
|
+
: `SortableList not found: ${id}.`);
|
|
112
|
+
}
|
|
113
|
+
return component;
|
|
114
|
+
}
|
|
115
|
+
function validateOrderedIds(component, orderedIds) {
|
|
116
|
+
const itemIds = component.items.map((item) => item.id);
|
|
117
|
+
const expectedIds = new Set(itemIds);
|
|
118
|
+
const seenIds = new Set();
|
|
119
|
+
const duplicateIds = new Set();
|
|
120
|
+
const unknownIds = new Set();
|
|
121
|
+
for (const orderedId of orderedIds) {
|
|
122
|
+
if (seenIds.has(orderedId)) {
|
|
123
|
+
duplicateIds.add(orderedId);
|
|
124
|
+
}
|
|
125
|
+
seenIds.add(orderedId);
|
|
126
|
+
if (!expectedIds.has(orderedId)) {
|
|
127
|
+
unknownIds.add(orderedId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const missingIds = itemIds.filter((itemId) => !seenIds.has(itemId));
|
|
131
|
+
const errors = [
|
|
132
|
+
duplicateIds.size > 0 ? `duplicate ids: ${Array.from(duplicateIds).join(", ")}` : undefined,
|
|
133
|
+
unknownIds.size > 0 ? `unknown ids: ${Array.from(unknownIds).join(", ")}` : undefined,
|
|
134
|
+
missingIds.length > 0 ? `missing ids: ${missingIds.join(", ")}` : undefined
|
|
135
|
+
].filter(Boolean);
|
|
136
|
+
if (errors.length > 0) {
|
|
137
|
+
throw new Error(`Invalid orderedIds for SortableList ${component.id}: ${errors.join("; ")}.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function syncInteractionOverlay(projectRoot, artifact, id, component) {
|
|
141
|
+
const state = await readArtifactState(artifact);
|
|
142
|
+
const orderedIds = readPersistedOrder(state.interactions[id]);
|
|
143
|
+
if (!orderedIds) {
|
|
144
|
+
return state;
|
|
145
|
+
}
|
|
146
|
+
const nextOrder = resolveOrder(component, orderedIds).orderedIds;
|
|
147
|
+
return writeArtifactState(projectRoot, artifact, {
|
|
148
|
+
...state,
|
|
149
|
+
interactions: {
|
|
150
|
+
...state.interactions,
|
|
151
|
+
[id]: createSortableListInteraction(component, nextOrder)
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function readSortableListFromSource(source, id) {
|
|
156
|
+
const component = extractSortableListSeeds(source).find((seed) => seed.id === id);
|
|
157
|
+
if (!component) {
|
|
158
|
+
throw new Error(`SortableList not found: ${id}.`);
|
|
159
|
+
}
|
|
160
|
+
return component;
|
|
161
|
+
}
|
|
162
|
+
function createSortableListInteraction(component, orderedIds) {
|
|
163
|
+
return {
|
|
164
|
+
type: "sortable-list",
|
|
165
|
+
id: component.id,
|
|
166
|
+
title: component.title,
|
|
167
|
+
orderedIds,
|
|
168
|
+
orderedItems: orderedIds.map((itemId) => {
|
|
169
|
+
const item = component.items.find((candidate) => candidate.id === itemId);
|
|
170
|
+
if (!item) {
|
|
171
|
+
throw new Error(`SortableList item not found: ${itemId}`);
|
|
172
|
+
}
|
|
173
|
+
return item;
|
|
174
|
+
}),
|
|
175
|
+
updatedAt: new Date().toISOString()
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function readPersistedOrder(interaction) {
|
|
179
|
+
if (!isRecord(interaction) || !Array.isArray(interaction.orderedIds)) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const orderedIds = interaction.orderedIds.filter((itemId) => typeof itemId === "string");
|
|
183
|
+
return orderedIds.length > 0 ? orderedIds : undefined;
|
|
184
|
+
}
|
|
185
|
+
function resolveOrder(component, persistedOrder) {
|
|
186
|
+
const itemIds = component.items.map((item) => item.id);
|
|
187
|
+
if (!persistedOrder) {
|
|
188
|
+
return {
|
|
189
|
+
source: "mdx",
|
|
190
|
+
orderedIds: itemIds,
|
|
191
|
+
staleIds: [],
|
|
192
|
+
appendedIds: []
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const knownIds = new Set(itemIds);
|
|
196
|
+
const staleIds = persistedOrder.filter((itemId) => !knownIds.has(itemId));
|
|
197
|
+
const retainedIds = persistedOrder.filter((itemId) => knownIds.has(itemId));
|
|
198
|
+
const retainedIdSet = new Set(retainedIds);
|
|
199
|
+
const appendedIds = itemIds.filter((itemId) => !retainedIdSet.has(itemId));
|
|
200
|
+
return {
|
|
201
|
+
source: "state",
|
|
202
|
+
orderedIds: [...retainedIds, ...appendedIds],
|
|
203
|
+
staleIds,
|
|
204
|
+
appendedIds
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function createInspectWarnings(id, staleIds) {
|
|
208
|
+
if (staleIds.length === 0) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
return [`SortableList ${id} state contains stale orderedIds ignored by inspect: ${staleIds.join(", ")}.`];
|
|
212
|
+
}
|
|
213
|
+
function createMutationResult(action, artifact, id, state, extra = {}) {
|
|
214
|
+
return {
|
|
215
|
+
action,
|
|
216
|
+
component: id,
|
|
217
|
+
sourcePath: artifact.sourceRelativePath,
|
|
218
|
+
statePath: artifact.stateRelativePath,
|
|
219
|
+
state,
|
|
220
|
+
status: extra.status ?? "ok",
|
|
221
|
+
...extra
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function isRecord(value) {
|
|
225
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
226
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type ReviewReply = {
|
|
2
|
+
threadId: string;
|
|
3
|
+
body: string;
|
|
4
|
+
};
|
|
5
|
+
export type ReviewAddOptions = {
|
|
6
|
+
anchorId: string;
|
|
7
|
+
body: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
};
|
|
10
|
+
export type ReviewReplyOptions = {
|
|
11
|
+
replies: ReviewReply[];
|
|
12
|
+
status?: string;
|
|
13
|
+
};
|
|
14
|
+
export type ReviewAddResult = {
|
|
15
|
+
anchorId: string;
|
|
16
|
+
statePath: string;
|
|
17
|
+
threadId: string;
|
|
18
|
+
};
|
|
19
|
+
export type ReviewReplyResult = {
|
|
20
|
+
messageRecords: Array<{
|
|
21
|
+
threadId: string;
|
|
22
|
+
messageId: string;
|
|
23
|
+
}>;
|
|
24
|
+
statePath: string;
|
|
25
|
+
};
|
|
26
|
+
export type ReviewValidationResult = {
|
|
27
|
+
anchorCount: number;
|
|
28
|
+
missingThreads: ReviewMissingThread[];
|
|
29
|
+
statePath: string;
|
|
30
|
+
threadCount: number;
|
|
31
|
+
};
|
|
32
|
+
export type ReviewMissingThread = {
|
|
33
|
+
anchorId: string;
|
|
34
|
+
threadId: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
status?: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function addReviewThreadService(projectRoot: string, input: string, options: ReviewAddOptions): Promise<ReviewAddResult>;
|
|
39
|
+
export declare function replyToReviewThreadService(projectRoot: string, input: string, options: ReviewReply & {
|
|
40
|
+
status?: string;
|
|
41
|
+
}): Promise<ReviewReplyResult>;
|
|
42
|
+
export declare function replyToReviewThreadsService(projectRoot: string, input: string, options: ReviewReplyOptions): Promise<ReviewReplyResult>;
|
|
43
|
+
export declare function validateReviewStateService(projectRoot: string, input: string): Promise<ReviewValidationResult>;
|
|
@@ -1,31 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
export async function
|
|
6
|
-
const [subcommand] = args;
|
|
7
|
-
if (subcommand === "add") {
|
|
8
|
-
const options = parseReviewAddArgs(args.slice(1));
|
|
9
|
-
console.log(await addReviewThread(projectRoot, options.input, options));
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
if (subcommand === "reply") {
|
|
13
|
-
const options = parseReviewReplyArgs(args.slice(1));
|
|
14
|
-
console.log(await replyToReviewThreads(projectRoot, options.input, options));
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
if (subcommand === "validate") {
|
|
18
|
-
const input = parseReviewValidateArgs(args.slice(1));
|
|
19
|
-
const result = await validateReviewState(projectRoot, input);
|
|
20
|
-
console.log(result.output);
|
|
21
|
-
if (result.missingThreads.length > 0) {
|
|
22
|
-
process.exitCode = 1;
|
|
23
|
-
}
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
throw new Error("review requires a subcommand. Use `artifact-kit review add <file.mdx>`, `artifact-kit review reply <file.mdx>`, or `artifact-kit review validate <file.mdx>`.");
|
|
27
|
-
}
|
|
28
|
-
export async function addReviewThread(projectRoot, input, options) {
|
|
3
|
+
import { loadConfig } from "../config/config.js";
|
|
4
|
+
import { createArtifactRoute, readArtifactState, writeArtifactState } from "../state/artifact-state.js";
|
|
5
|
+
export async function addReviewThreadService(projectRoot, input, options) {
|
|
29
6
|
const artifact = await createArtifactFromInput(projectRoot, input);
|
|
30
7
|
const state = await readArtifactState(artifact);
|
|
31
8
|
const source = await readFile(artifact.sourcePath, "utf8");
|
|
@@ -49,15 +26,19 @@ export async function addReviewThread(projectRoot, input, options) {
|
|
|
49
26
|
...state,
|
|
50
27
|
threads: [...state.threads, thread]
|
|
51
28
|
});
|
|
52
|
-
return
|
|
29
|
+
return {
|
|
30
|
+
anchorId: options.anchorId,
|
|
31
|
+
statePath: artifact.stateRelativePath,
|
|
32
|
+
threadId
|
|
33
|
+
};
|
|
53
34
|
}
|
|
54
|
-
export async function
|
|
55
|
-
return
|
|
35
|
+
export async function replyToReviewThreadService(projectRoot, input, options) {
|
|
36
|
+
return replyToReviewThreadsService(projectRoot, input, {
|
|
56
37
|
replies: [{ threadId: options.threadId, body: options.body }],
|
|
57
38
|
...(options.status ? { status: options.status } : {})
|
|
58
39
|
});
|
|
59
40
|
}
|
|
60
|
-
export async function
|
|
41
|
+
export async function replyToReviewThreadsService(projectRoot, input, options) {
|
|
61
42
|
const artifact = await createArtifactFromInput(projectRoot, input);
|
|
62
43
|
const state = await readArtifactState(artifact);
|
|
63
44
|
const repliesByThread = groupRepliesByThread(options.replies);
|
|
@@ -90,14 +71,12 @@ export async function replyToReviewThreads(projectRoot, input, options) {
|
|
|
90
71
|
...state,
|
|
91
72
|
threads
|
|
92
73
|
});
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
...messageRecords.map((record) => `- thread: ${record.threadId} message: ${record.messageId}`)
|
|
98
|
-
].join("\n");
|
|
74
|
+
return {
|
|
75
|
+
messageRecords,
|
|
76
|
+
statePath: artifact.stateRelativePath
|
|
77
|
+
};
|
|
99
78
|
}
|
|
100
|
-
export async function
|
|
79
|
+
export async function validateReviewStateService(projectRoot, input) {
|
|
101
80
|
const artifact = await createArtifactFromInput(projectRoot, input);
|
|
102
81
|
const state = await readArtifactState(artifact);
|
|
103
82
|
const source = await readFile(artifact.sourcePath, "utf8");
|
|
@@ -116,32 +95,10 @@ export async function validateReviewState(projectRoot, input) {
|
|
|
116
95
|
}
|
|
117
96
|
];
|
|
118
97
|
});
|
|
119
|
-
const output = missingThreads.length === 0
|
|
120
|
-
? [
|
|
121
|
-
`review validate ok`,
|
|
122
|
-
`state: ${artifact.stateRelativePath}`,
|
|
123
|
-
`anchors: ${anchorIds.size}`,
|
|
124
|
-
`threads: ${threads.length}`
|
|
125
|
-
].join("\n")
|
|
126
|
-
: [
|
|
127
|
-
`review validate failed`,
|
|
128
|
-
`state: ${artifact.stateRelativePath}`,
|
|
129
|
-
`anchors: ${anchorIds.size}`,
|
|
130
|
-
`threads: ${threads.length}`,
|
|
131
|
-
`missing: ${missingThreads.length}`,
|
|
132
|
-
...missingThreads.map((thread) => [
|
|
133
|
-
`- thread: ${thread.threadId}`,
|
|
134
|
-
`anchorId: ${thread.anchorId}`,
|
|
135
|
-
thread.status ? `status: ${thread.status}` : undefined,
|
|
136
|
-
thread.title ? `title: ${thread.title}` : undefined
|
|
137
|
-
]
|
|
138
|
-
.filter(Boolean)
|
|
139
|
-
.join(" "))
|
|
140
|
-
].join("\n");
|
|
141
98
|
return {
|
|
142
99
|
anchorCount: anchorIds.size,
|
|
143
100
|
missingThreads,
|
|
144
|
-
|
|
101
|
+
statePath: artifact.stateRelativePath,
|
|
145
102
|
threadCount: threads.length
|
|
146
103
|
};
|
|
147
104
|
}
|
|
@@ -150,116 +107,6 @@ async function createArtifactFromInput(projectRoot, input) {
|
|
|
150
107
|
const mdxPath = path.resolve(projectRoot, input);
|
|
151
108
|
return createArtifactRoute(projectRoot, mdxPath, config.docsDir);
|
|
152
109
|
}
|
|
153
|
-
function parseReviewReplyArgs(args) {
|
|
154
|
-
const [input, ...rest] = args;
|
|
155
|
-
if (!input) {
|
|
156
|
-
throw new Error("review reply requires a .mdx file path.");
|
|
157
|
-
}
|
|
158
|
-
const { replies, status } = parseReviewReplyOptions(rest);
|
|
159
|
-
return {
|
|
160
|
-
input,
|
|
161
|
-
replies,
|
|
162
|
-
...(status ? { status } : {})
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
function parseReviewValidateArgs(args) {
|
|
166
|
-
const [input, ...rest] = args;
|
|
167
|
-
if (!input) {
|
|
168
|
-
throw new Error("review validate requires a .mdx file path.");
|
|
169
|
-
}
|
|
170
|
-
if (rest.length > 0) {
|
|
171
|
-
throw new Error(`Unexpected review validate argument: ${rest[0]}`);
|
|
172
|
-
}
|
|
173
|
-
return input;
|
|
174
|
-
}
|
|
175
|
-
function parseReviewAddArgs(args) {
|
|
176
|
-
const [input, ...rest] = args;
|
|
177
|
-
if (!input) {
|
|
178
|
-
throw new Error("review add requires a .mdx file path.");
|
|
179
|
-
}
|
|
180
|
-
const options = parseKeyValueOptions(rest, ["anchor", "body", "title"]);
|
|
181
|
-
const anchorId = options.get("anchor");
|
|
182
|
-
const body = options.get("body");
|
|
183
|
-
const title = options.get("title");
|
|
184
|
-
if (!anchorId) {
|
|
185
|
-
throw new Error("review add requires --anchor <anchorId>.");
|
|
186
|
-
}
|
|
187
|
-
if (!body || !body.trim()) {
|
|
188
|
-
throw new Error("review add requires --body <message>.");
|
|
189
|
-
}
|
|
190
|
-
return {
|
|
191
|
-
input,
|
|
192
|
-
anchorId,
|
|
193
|
-
body: body.trim(),
|
|
194
|
-
...(title ? { title } : {})
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function parseKeyValueOptions(args, allowedKeys) {
|
|
198
|
-
const options = new Map();
|
|
199
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
200
|
-
const name = args[index];
|
|
201
|
-
if (!name?.startsWith("--")) {
|
|
202
|
-
throw new Error(`Unexpected review argument: ${name}`);
|
|
203
|
-
}
|
|
204
|
-
const key = name.slice(2);
|
|
205
|
-
const value = args[index + 1];
|
|
206
|
-
if (!allowedKeys.includes(key)) {
|
|
207
|
-
throw new Error(`Unknown review option: --${key}.`);
|
|
208
|
-
}
|
|
209
|
-
if (!value || value.startsWith("--")) {
|
|
210
|
-
throw new Error(`Missing value for --${key}.`);
|
|
211
|
-
}
|
|
212
|
-
options.set(key, value);
|
|
213
|
-
index += 1;
|
|
214
|
-
}
|
|
215
|
-
return options;
|
|
216
|
-
}
|
|
217
|
-
function parseReviewReplyOptions(args) {
|
|
218
|
-
const replies = [];
|
|
219
|
-
let status;
|
|
220
|
-
let threadId;
|
|
221
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
222
|
-
const name = args[index];
|
|
223
|
-
if (!name?.startsWith("--")) {
|
|
224
|
-
throw new Error(`Unexpected review reply argument: ${name}`);
|
|
225
|
-
}
|
|
226
|
-
const key = name.slice(2);
|
|
227
|
-
const value = args[index + 1];
|
|
228
|
-
if (!value || value.startsWith("--")) {
|
|
229
|
-
throw new Error(`Missing value for --${key}.`);
|
|
230
|
-
}
|
|
231
|
-
if (key === "thread") {
|
|
232
|
-
if (threadId) {
|
|
233
|
-
throw new Error(`Missing --body for --thread ${threadId}.`);
|
|
234
|
-
}
|
|
235
|
-
threadId = value;
|
|
236
|
-
}
|
|
237
|
-
else if (key === "body") {
|
|
238
|
-
if (!threadId) {
|
|
239
|
-
throw new Error("review reply requires --thread before --body.");
|
|
240
|
-
}
|
|
241
|
-
if (!value.trim()) {
|
|
242
|
-
throw new Error("review reply requires --body <message>.");
|
|
243
|
-
}
|
|
244
|
-
replies.push({ threadId, body: value.trim() });
|
|
245
|
-
threadId = undefined;
|
|
246
|
-
}
|
|
247
|
-
else if (key === "status") {
|
|
248
|
-
status = value;
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
throw new Error(`Unknown review reply option: --${key}.`);
|
|
252
|
-
}
|
|
253
|
-
index += 1;
|
|
254
|
-
}
|
|
255
|
-
if (threadId) {
|
|
256
|
-
throw new Error(`Missing --body for --thread ${threadId}.`);
|
|
257
|
-
}
|
|
258
|
-
if (replies.length === 0) {
|
|
259
|
-
throw new Error("review reply requires at least one --thread <threadId> --body <message> pair.");
|
|
260
|
-
}
|
|
261
|
-
return { replies, status };
|
|
262
|
-
}
|
|
263
110
|
function groupRepliesByThread(replies) {
|
|
264
111
|
const repliesByThread = new Map();
|
|
265
112
|
for (const reply of replies) {
|
|
@@ -306,8 +153,7 @@ function extractReviewAnchorIds(source) {
|
|
|
306
153
|
anchorIds.add(match[2]);
|
|
307
154
|
}
|
|
308
155
|
}
|
|
309
|
-
|
|
310
|
-
addArrayChildAnchors(source, anchorIds, "OptionGrid", "options");
|
|
156
|
+
addCompoundChildAnchors(source, anchorIds, "ContentSet", "Item");
|
|
311
157
|
addArrayChildAnchors(source, anchorIds, "AnnotatedCode", "annotations", { addCodeChild: true });
|
|
312
158
|
addComparisonSetChildAnchors(source, anchorIds);
|
|
313
159
|
return anchorIds;
|
|
@@ -332,6 +178,22 @@ function addArrayChildAnchors(source, anchorIds, componentName, arrayPropName, o
|
|
|
332
178
|
}
|
|
333
179
|
}
|
|
334
180
|
}
|
|
181
|
+
function addCompoundChildAnchors(source, anchorIds, componentName, childName) {
|
|
182
|
+
const componentPattern = new RegExp(`<${componentName}\\b[\\s\\S]*?</${componentName}>`, "g");
|
|
183
|
+
const childPattern = new RegExp(`<${componentName}\\.${childName}\\b[^>]*\\sid\\s*=\\s*["']([^"']+)["'][^>]*>`, "g");
|
|
184
|
+
for (const match of source.matchAll(componentPattern)) {
|
|
185
|
+
const componentSource = match[0];
|
|
186
|
+
const parentId = extractJsxStringProp(componentSource, "id");
|
|
187
|
+
if (!parentId) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
for (const childMatch of componentSource.matchAll(childPattern)) {
|
|
191
|
+
if (childMatch[1]) {
|
|
192
|
+
anchorIds.add(`${parentId}.${childMatch[1]}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
335
197
|
function addComparisonSetChildAnchors(source, anchorIds) {
|
|
336
198
|
for (const match of source.matchAll(/<ComparisonSet\b[\s\S]*?<\/ComparisonSet>/g)) {
|
|
337
199
|
const componentSource = match[0];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { CommentTarget } from "../../interactions/comments/Comments.js";
|
|
4
|
+
import { Grid } from "../../layout/layout-primitives/Layout.js";
|
|
5
|
+
import { InlineText } from "../../primitives/inline-text/InlineText.js";
|
|
6
6
|
const ComparisonSetIdContext = createContext(undefined);
|
|
7
7
|
function ComparisonSetRoot({ id, title, children, columns = 2, gap = "md", collapseAt = "md", className }) {
|
|
8
8
|
const targetId = id ?? `comparison:${slugify(title)}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ComparisonSet } from "./ComparisonSet.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type ContentItemTone = "neutral" | "info" | "positive" | "warning" | "danger" | "accent";
|
|
3
|
+
export type ContentItemEmphasis = "default" | "primary" | "subtle";
|
|
4
|
+
export type ContentSetLayout = "grid" | "stack";
|
|
5
|
+
export type ContentSetColumns = 2 | 3 | 4 | 5;
|
|
6
|
+
export type ContentSetSurface = "plain" | "subtle" | "outlined";
|
|
7
|
+
export type ContentItemProps = {
|
|
8
|
+
id?: string;
|
|
9
|
+
title: string;
|
|
10
|
+
badge?: string;
|
|
11
|
+
summary?: string;
|
|
12
|
+
icon?: ReactNode;
|
|
13
|
+
tone?: ContentItemTone;
|
|
14
|
+
emphasis?: ContentItemEmphasis;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
className?: string;
|
|
17
|
+
};
|
|
18
|
+
export type ContentSetProps = {
|
|
19
|
+
id?: string;
|
|
20
|
+
title: string;
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
icon?: ReactNode;
|
|
23
|
+
layout?: ContentSetLayout;
|
|
24
|
+
columns?: ContentSetColumns;
|
|
25
|
+
surface?: ContentSetSurface;
|
|
26
|
+
tone?: ContentItemTone;
|
|
27
|
+
emphasis?: ContentItemEmphasis;
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
export type ContentSetItemProps = ContentItemProps;
|
|
31
|
+
export declare function ContentItem({ id, title, badge, summary, icon, tone, emphasis, children, className }: ContentItemProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
declare function ContentSetRoot({ id, title, children, icon, layout, columns, surface, tone, emphasis, className }: ContentSetProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
declare function ContentSetItem({ id, title, badge, summary, icon, tone, emphasis, children, className }: ContentSetItemProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export declare const ContentSet: typeof ContentSetRoot & {
|
|
35
|
+
Item: typeof ContentSetItem;
|
|
36
|
+
};
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { CommentTarget } from "../../interactions/comments/Comments.js";
|
|
4
|
+
import { InlineText } from "../../primitives/inline-text/InlineText.js";
|
|
5
|
+
const ContentSetContext = createContext(null);
|
|
6
|
+
export function ContentItem({ id, title, badge, summary, icon, tone = "neutral", emphasis = "default", children, className }) {
|
|
7
|
+
return (_jsx(ContentItemCard, { badge: badge, className: className, description: "ContentItem component", emphasis: emphasis, icon: icon, summary: summary, targetId: id ?? `content:${slugify(title)}`, title: title, tone: tone, children: children }));
|
|
8
|
+
}
|
|
9
|
+
function ContentSetRoot({ id, title, children, icon, layout = "grid", columns = 3, surface = "plain", tone, emphasis, className }) {
|
|
10
|
+
const targetId = id ?? `content-set:${slugify(title)}`;
|
|
11
|
+
return (_jsx(CommentTarget, { className: classNames("ak-comment-target-section", className), description: "ContentSet component", targetId: targetId, title: title, children: _jsxs("section", { className: classNames("ak-section", "ak-content-set", `ak-content-set-${layout}`, `ak-content-set-surface-${surface}`), children: [_jsxs("div", { className: "ak-section-header", children: [_jsx("p", { className: "ak-eyebrow", children: "Content Set" }), _jsxs("div", { className: "ak-content-set-title-row", children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "ak-content-set-icon", children: icon })) : null, _jsx(InlineText, { as: "h2", variant: "title", children: title })] })] }), _jsx("div", { className: classNames("ak-content-set-items", `ak-content-set-items-${layout}`), style: cssVars({ "--ak-content-set-columns": String(columns) }), children: _jsx(ContentSetContext.Provider, { value: { id, title, tone, emphasis }, children: children }) })] }) }));
|
|
12
|
+
}
|
|
13
|
+
function ContentSetItem({ id, title, badge, summary, icon, tone, emphasis, children, className }) {
|
|
14
|
+
const context = useContext(ContentSetContext);
|
|
15
|
+
const resolvedTone = tone ?? context?.tone ?? "neutral";
|
|
16
|
+
const resolvedEmphasis = emphasis ?? context?.emphasis ?? "default";
|
|
17
|
+
const targetId = createItemTargetId(context?.id, context?.title, id, title);
|
|
18
|
+
return (_jsx(ContentItemCard, { badge: badge, className: className, description: context ? `ContentSet item in ${context.title}` : "ContentSet item", emphasis: resolvedEmphasis, icon: icon, summary: summary, targetId: targetId, title: title, tone: resolvedTone, children: children }));
|
|
19
|
+
}
|
|
20
|
+
function ContentItemCard({ badge, children, className, description, emphasis, icon, summary, targetId, title, tone }) {
|
|
21
|
+
return (_jsx(CommentTarget, { className: classNames("ak-comment-target-card", className), description: description, targetId: targetId, title: title, children: _jsxs("article", { className: classNames("ak-content-item", `ak-content-item-tone-${tone}`, `ak-content-item-emphasis-${emphasis}`), children: [_jsxs("div", { className: "ak-content-item-header", children: [_jsxs("div", { className: "ak-content-item-title-row", children: [icon ? (_jsx("span", { "aria-hidden": "true", className: "ak-content-item-icon", children: icon })) : null, _jsx(InlineText, { as: "h3", variant: "subtitle", children: title })] }), badge ? _jsx("span", { className: "ak-content-item-badge", children: badge }) : null] }), summary ? (_jsx(InlineText, { as: "p", className: "ak-content-item-summary", children: summary })) : null, children ? _jsx("div", { className: "ak-content-item-body", children: children }) : null] }) }));
|
|
22
|
+
}
|
|
23
|
+
export const ContentSet = Object.assign(ContentSetRoot, {
|
|
24
|
+
Item: ContentSetItem
|
|
25
|
+
});
|
|
26
|
+
function createItemTargetId(parentId, parentTitle, itemId, itemTitle) {
|
|
27
|
+
if (parentId) {
|
|
28
|
+
return `${parentId}.${itemId ?? slugify(itemTitle)}`;
|
|
29
|
+
}
|
|
30
|
+
if (parentTitle) {
|
|
31
|
+
return itemId ?? `content-set:${slugify(parentTitle)}:${slugify(itemTitle)}`;
|
|
32
|
+
}
|
|
33
|
+
return itemId ?? `content:${slugify(itemTitle)}`;
|
|
34
|
+
}
|
|
35
|
+
function cssVars(values) {
|
|
36
|
+
return values;
|
|
37
|
+
}
|
|
38
|
+
function classNames(...values) {
|
|
39
|
+
return values.filter(Boolean).join(" ");
|
|
40
|
+
}
|
|
41
|
+
function slugify(value) {
|
|
42
|
+
const slug = value
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/[`*_~[\]()]/g, "")
|
|
45
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
46
|
+
.replace(/^-+|-+$/g, "")
|
|
47
|
+
.slice(0, 64);
|
|
48
|
+
return slug || "item";
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ContentItem, ContentSet } from "./ContentItem.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as Tabs from "@radix-ui/react-tabs";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
|
-
import { serializeCommentsToMarkdown, useOptionalCommentExportValue } from "
|
|
5
|
-
import { InlineText } from "
|
|
4
|
+
import { serializeCommentsToMarkdown, useOptionalCommentExportValue } from "../../interactions/comments/Comments.js";
|
|
5
|
+
import { InlineText } from "../../primitives/inline-text/InlineText.js";
|
|
6
6
|
export function ExportPanel({ title = "Export Result", formats = ["markdown", "json"], value }) {
|
|
7
7
|
const [format, setFormat] = useState(formats[0] ?? "markdown");
|
|
8
8
|
const [source, setSource] = useState("result");
|