@elytracms/studio 0.0.86 → 0.0.87
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/convex/asset-revalidate.test.ts +186 -0
- package/convex/assets.ts +53 -19
- package/convex/cms.ts +22 -0
- package/convex/delivery.ts +33 -116
- package/convex/functions.ts +3 -3
- package/convex/rail-write-parity.test.ts +180 -0
- package/convex/redirects.ts +33 -11
- package/convex/revalidate.ts +7 -5
- package/convex/triggers.ts +53 -3
- package/dist/components/app-views/default-modules.d.ts +20 -20
- package/dist/components/app-views/default-modules.js +27 -48
- package/dist/components/app-views/default-modules.js.map +1 -1
- package/dist/components/app-views/studio-config.d.ts +5 -6
- package/dist/components/app-views/studio-config.js.map +1 -1
- package/dist/components/assets/asset-metadata.d.ts +28 -19
- package/dist/components/assets/asset-metadata.js +24 -31
- package/dist/components/assets/asset-metadata.js.map +1 -1
- package/dist/components/assets/asset-selection.d.ts +24 -5
- package/dist/components/assets/asset-selection.js +39 -9
- package/dist/components/assets/asset-selection.js.map +1 -1
- package/dist/components/assets/assets-collection-view-source.d.ts +17 -14
- package/dist/components/assets/assets-collection-view-source.js +25 -31
- package/dist/components/assets/assets-collection-view-source.js.map +1 -1
- package/dist/components/builder/blocks-panel.js +3 -3
- package/dist/components/builder/blocks-panel.js.map +1 -1
- package/dist/components/builder/builder-store.d.ts +25 -1
- package/dist/components/builder/builder-store.js +3 -2
- package/dist/components/builder/builder-store.js.map +1 -1
- package/dist/components/builder/canvas-chrome-css.js +0 -17
- package/dist/components/builder/canvas-chrome-css.js.map +1 -1
- package/dist/components/builder/canvas-chrome.d.ts +10 -12
- package/dist/components/builder/canvas-chrome.js +12 -13
- package/dist/components/builder/canvas-chrome.js.map +1 -1
- package/dist/components/builder/canvas-dnd.d.ts +8 -0
- package/dist/components/builder/canvas-dnd.js +5 -3
- package/dist/components/builder/canvas-dnd.js.map +1 -1
- package/dist/components/builder/canvas-render-context.d.ts +12 -24
- package/dist/components/builder/canvas-render-context.js +15 -44
- package/dist/components/builder/canvas-render-context.js.map +1 -1
- package/dist/components/builder/canvas.js +5 -11
- package/dist/components/builder/canvas.js.map +1 -1
- package/dist/components/builder/clipboard-actions.d.ts +14 -6
- package/dist/components/builder/clipboard-actions.js +29 -20
- package/dist/components/builder/clipboard-actions.js.map +1 -1
- package/dist/components/builder/composition-field-editor.d.ts +19 -26
- package/dist/components/builder/composition-field-editor.js +30 -52
- package/dist/components/builder/composition-field-editor.js.map +1 -1
- package/dist/components/builder/insert-context.d.ts +3 -1
- package/dist/components/builder/insert-context.js +15 -10
- package/dist/components/builder/insert-context.js.map +1 -1
- package/dist/components/builder/page-tree.d.ts +29 -0
- package/dist/components/builder/page-tree.js +56 -58
- package/dist/components/builder/page-tree.js.map +1 -1
- package/dist/components/builder/reuse.js +5 -2
- package/dist/components/builder/reuse.js.map +1 -1
- package/dist/components/cms/asset-field.js +20 -32
- package/dist/components/cms/asset-field.js.map +1 -1
- package/dist/components/cms/cms-composition-validators.js +11 -2
- package/dist/components/cms/cms-composition-validators.js.map +1 -1
- package/dist/components/cms/editable-cell.js +5 -2
- package/dist/components/cms/editable-cell.js.map +1 -1
- package/dist/components/cms/use-document-versions.js +1 -0
- package/dist/components/cms/use-document-versions.js.map +1 -1
- package/dist/components/cms/versions-model.d.ts +0 -12
- package/dist/components/cms/versions-model.js +3 -15
- package/dist/components/cms/versions-model.js.map +1 -1
- package/dist/components/components/component-detail.js +1 -1
- package/dist/components/components/component-detail.js.map +1 -1
- package/dist/components/components/transfer-dialog.js +2 -4
- package/dist/components/components/transfer-dialog.js.map +1 -1
- package/dist/components/components/transfer.d.ts +13 -10
- package/dist/components/components/transfer.js +21 -19
- package/dist/components/components/transfer.js.map +1 -1
- package/dist/components/project-session.d.ts +9 -0
- package/dist/components/project-session.js +1 -1
- package/dist/components/project-session.js.map +1 -1
- package/dist/components/redirects/redirects-collection-view-source.d.ts +17 -2
- package/dist/components/redirects/redirects-collection-view-source.js +20 -6
- package/dist/components/redirects/redirects-collection-view-source.js.map +1 -1
- package/dist/components/section-surface.js +3 -2
- package/dist/components/section-surface.js.map +1 -1
- package/dist/components/settings-form.js +1 -2
- package/dist/components/settings-form.js.map +1 -1
- package/dist/components/studio-context.js +1 -18
- package/dist/components/studio-context.js.map +1 -1
- package/dist/components/studio-project-screen.js +3 -5
- package/dist/components/studio-project-screen.js.map +1 -1
- package/dist/lib/ai/apply-content.js +73 -29
- package/dist/lib/ai/apply-content.js.map +1 -1
- package/dist/lib/ai/constraints.d.ts +35 -0
- package/dist/lib/ai/constraints.js +104 -4
- package/dist/lib/ai/constraints.js.map +1 -1
- package/dist/lib/ai/route.js +4 -1
- package/dist/lib/ai/route.js.map +1 -1
- package/dist/lib/ai/shape.d.ts +3 -2
- package/dist/lib/ai/shape.js +16 -24
- package/dist/lib/ai/shape.js.map +1 -1
- package/dist/lib/ai/types.d.ts +8 -0
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ai/update-fields.js +33 -12
- package/dist/lib/ai/update-fields.js.map +1 -1
- package/dist/lib/assets/asset-placements.d.ts +35 -0
- package/dist/lib/assets/asset-placements.js +74 -0
- package/dist/lib/assets/asset-placements.js.map +1 -0
- package/dist/lib/assets/backfill-thumbs.d.ts +9 -8
- package/dist/lib/assets/backfill-thumbs.js +13 -25
- package/dist/lib/assets/backfill-thumbs.js.map +1 -1
- package/dist/lib/clipboard/paste-issues.d.ts +17 -15
- package/dist/lib/clipboard/paste-issues.js +8 -45
- package/dist/lib/clipboard/paste-issues.js.map +1 -1
- package/dist/lib/clipboard/paste-target.d.ts +25 -1
- package/dist/lib/clipboard/paste-target.js +13 -6
- package/dist/lib/clipboard/paste-target.js.map +1 -1
- package/dist/lib/cms/published-documents.js +1 -0
- package/dist/lib/cms/published-documents.js.map +1 -1
- package/dist/lib/cms-views/view-model.d.ts +3 -3
- package/dist/lib/data/index.d.ts +0 -3
- package/dist/lib/data/index.js +4 -6
- package/dist/lib/data/index.js.map +1 -1
- package/dist/lib/dnd/drop-rules.d.ts +45 -2
- package/dist/lib/dnd/drop-rules.js +64 -9
- package/dist/lib/dnd/drop-rules.js.map +1 -1
- package/dist/lib/engine-version.d.ts +1 -1
- package/dist/lib/engine-version.js +1 -1
- package/dist/lib/engine-version.js.map +1 -1
- package/dist/lib/i18n/messages.de.js +4 -56
- package/dist/lib/i18n/messages.de.js.map +1 -1
- package/dist/lib/i18n/messages.en.d.ts +4 -53
- package/dist/lib/i18n/messages.en.js +8 -57
- package/dist/lib/i18n/messages.en.js.map +1 -1
- package/dist/lib/locale/fallback.js +8 -2
- package/dist/lib/locale/fallback.js.map +1 -1
- package/dist/lib/locale/index.d.ts +0 -1
- package/dist/lib/locale/index.js +0 -1
- package/dist/lib/locale/index.js.map +1 -1
- package/dist/lib/locale/locale-store.d.ts +10 -3
- package/dist/lib/locale/locale-store.js +12 -5
- package/dist/lib/locale/locale-store.js.map +1 -1
- package/dist/lib/operations/session-hydration.d.ts +9 -5
- package/dist/lib/operations/session-hydration.js +10 -10
- package/dist/lib/operations/session-hydration.js.map +1 -1
- package/dist/lib/persistence/convex-adapter.js +63 -75
- package/dist/lib/persistence/convex-adapter.js.map +1 -1
- package/dist/lib/persona/labels.d.ts +0 -2
- package/dist/lib/persona/labels.js +1 -7
- package/dist/lib/persona/labels.js.map +1 -1
- package/dist/lib/registry/index.d.ts +4 -2
- package/dist/lib/registry/index.js +26 -21
- package/dist/lib/registry/index.js.map +1 -1
- package/dist/lib/registry/project-components.js +0 -3
- package/dist/lib/registry/project-components.js.map +1 -1
- package/dist/lib/routes/route-issues.js +4 -5
- package/dist/lib/routes/route-issues.js.map +1 -1
- package/dist/lib/theme.js +3 -2
- package/dist/lib/theme.js.map +1 -1
- package/dist/lib/webhooks/tags.js +11 -14
- package/dist/lib/webhooks/tags.js.map +1 -1
- package/dist/lib/workspace/index.d.ts +1 -1
- package/dist/lib/workspace/index.js +1 -1
- package/dist/lib/workspace/index.js.map +1 -1
- package/dist/previews/enumerate.d.ts +27 -9
- package/dist/previews/enumerate.js +27 -6
- package/dist/previews/enumerate.js.map +1 -1
- package/dist/previews/manifest-format.d.ts +9 -0
- package/dist/previews/manifest-format.js.map +1 -1
- package/dist/router/match.d.ts +0 -2
- package/dist/router/match.js +0 -2
- package/dist/router/match.js.map +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/package.json +2 -2
- package/src/styles/studio.css +5 -4
- package/src/styles/tokens.css +72 -55
- package/convex/content.ts +0 -291
- package/dist/components/collab/collab-context.d.ts +0 -27
- package/dist/components/collab/collab-context.js +0 -39
- package/dist/components/collab/collab-context.js.map +0 -1
- package/dist/components/collab/collab-seed.d.ts +0 -15
- package/dist/components/collab/collab-seed.js +0 -20
- package/dist/components/collab/collab-seed.js.map +0 -1
- package/dist/components/extensions/extensions-view.d.ts +0 -7
- package/dist/components/extensions/extensions-view.js +0 -127
- package/dist/components/extensions/extensions-view.js.map +0 -1
- package/dist/lib/bindings/section-bindings.d.ts +0 -24
- package/dist/lib/bindings/section-bindings.js +0 -9
- package/dist/lib/bindings/section-bindings.js.map +0 -1
- package/dist/lib/data/data-store.d.ts +0 -98
- package/dist/lib/data/data-store.js +0 -211
- package/dist/lib/data/data-store.js.map +0 -1
- package/dist/lib/data/providers.d.ts +0 -143
- package/dist/lib/data/providers.js +0 -255
- package/dist/lib/data/providers.js.map +0 -1
- package/dist/lib/data/sample-capture.d.ts +0 -30
- package/dist/lib/data/sample-capture.js +0 -70
- package/dist/lib/data/sample-capture.js.map +0 -1
- package/dist/lib/data/sources-store.d.ts +0 -42
- package/dist/lib/data/sources-store.js +0 -120
- package/dist/lib/data/sources-store.js.map +0 -1
- package/dist/lib/locale/binding-fallback.d.ts +0 -35
- package/dist/lib/locale/binding-fallback.js +0 -73
- package/dist/lib/locale/binding-fallback.js.map +0 -1
- package/dist/lib/starters/kit-session.d.ts +0 -5
- package/dist/lib/starters/kit-session.js +0 -17
- package/dist/lib/starters/kit-session.js.map +0 -1
package/convex/content.ts
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { documentKey, documentSchema, unfoldValue } from '@elytracms/core/cms-core'
|
|
2
|
-
import type {
|
|
3
|
-
CmsDocument,
|
|
4
|
-
CmsValidationIssue,
|
|
5
|
-
CollectionDef,
|
|
6
|
-
Locale,
|
|
7
|
-
LocaleConfig,
|
|
8
|
-
RedirectRecord,
|
|
9
|
-
RouteRecord,
|
|
10
|
-
} from '@elytracms/core/cms-core'
|
|
11
|
-
import { createContentClient, createContentContext, isDocumentHistory, rawDocument } from '@elytracms/core/content'
|
|
12
|
-
import type {
|
|
13
|
-
ContentClient,
|
|
14
|
-
ContentContext,
|
|
15
|
-
ContentDocumentSource,
|
|
16
|
-
ContentLookup,
|
|
17
|
-
Perspective,
|
|
18
|
-
} from '@elytracms/core/content'
|
|
19
|
-
import type { AssetRecord } from '@elytracms/core/persistence'
|
|
20
|
-
import type { QueryCtx } from './functions'
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* EC-145 — the Convex-side content kit (vision AD-3/AD-4).
|
|
24
|
-
*
|
|
25
|
-
* Custom Convex functions are the query escape hatch ("our query language is
|
|
26
|
-
* TypeScript") — but only if developers never touch raw tables. `createContent`
|
|
27
|
-
* gives a Convex query function the same delivery surface as the Next-side
|
|
28
|
-
* accessors: `get`, `list`, `resolvePage`, plus the `raw` escape hatch, all
|
|
29
|
-
* with ambient `{ locale, perspective }` context.
|
|
30
|
-
*
|
|
31
|
-
* Byte-equivalence with the Next side is guaranteed by construction: the kit
|
|
32
|
-
* loads rows through `ctx.db`, builds the same synchronous `ContentLookup`
|
|
33
|
-
* that `@elytracms/next`'s snapshot builds from a `PersistenceAdapter`, and then
|
|
34
|
-
* runs the EXACT pure functions from `@elytracms/core/content`
|
|
35
|
-
* (`createContentClient` → `resolveDocument`, `query.ts` validation, cache
|
|
36
|
-
* tags). Nothing about resolution semantics is reimplemented here.
|
|
37
|
-
*
|
|
38
|
-
* Loading strategy (honest about v1 scale): the project's schema, documents,
|
|
39
|
-
* and asset metadata are prefetched ONCE per `createContent` call via the
|
|
40
|
-
* indexed `by_project` / `by_project_asset` scans, then every kit call is
|
|
41
|
-
* synchronous over memory — exactly like `createContentSnapshot` on the Next
|
|
42
|
-
* side. Filters and sorts therefore execute in-function over an indexed
|
|
43
|
-
* by-project fetch; see `contentIndexes.ts` for the index requirements this
|
|
44
|
-
* implies and the documented field-level-index upgrade path.
|
|
45
|
-
*
|
|
46
|
-
* Perspective isolation: the lookup holds the raw stored rows (draft state and
|
|
47
|
-
* all), and EVERY read path through `@elytracms/core/content` — `resolveDocument`,
|
|
48
|
-
* relation population, `listDocuments` filtering, slug fallback, and the `raw`
|
|
49
|
-
* escape hatch — applies the EC-142 perspective gate (`selectPerspective`)
|
|
50
|
-
* before any value is exposed. The kit never reads field values around that
|
|
51
|
-
* gate, so a `published` kit cannot observe draft values. (Pre-filtering draft
|
|
52
|
-
* rows out of the lookup would be redundant *and* would change structured
|
|
53
|
-
* issue messages — e.g. `unknown-relation-target` reasons — breaking
|
|
54
|
-
* byte-equivalence with the Next-side accessors.)
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
const DEFAULT_LOCALES: LocaleConfig = { default: 'en', locales: ['en'] }
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Structured load-time problems (stored blobs that fail their canonical Zod
|
|
61
|
-
* schema, unparsable graph revisions). Never thrown — the kit degrades to the
|
|
62
|
-
* valid subset and reports what it skipped, mirroring the package-wide
|
|
63
|
-
* "structured issues, never crashes" stance.
|
|
64
|
-
*
|
|
65
|
-
* `invalid-stored-schema` is retained for wire/back-compat but is no longer
|
|
66
|
-
* produced: CMS collections are config-owned (EC-183, AD-11) and supplied by the
|
|
67
|
-
* caller (already validated), so there is no stored schema blob to reject.
|
|
68
|
-
*/
|
|
69
|
-
export type ContentKitIssueCode = 'invalid-stored-schema' | 'invalid-stored-document'
|
|
70
|
-
|
|
71
|
-
export interface ContentKitIssue {
|
|
72
|
-
code: ContentKitIssueCode
|
|
73
|
-
message: string
|
|
74
|
-
meta?: Record<string, unknown>
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface CreateContentOptions {
|
|
78
|
-
projectId: string
|
|
79
|
-
/** Draft or published perspective (EC-021); ambient for every kit call. */
|
|
80
|
-
perspective: Perspective
|
|
81
|
-
/** Requested locale; defaults to the project's default locale. */
|
|
82
|
-
locale?: Locale
|
|
83
|
-
/** Override the locale config derived from the project settings. */
|
|
84
|
-
locales?: LocaleConfig
|
|
85
|
-
/**
|
|
86
|
-
* CMS collections (EC-183, AD-11), sourced from `elytra.config.ts` by the
|
|
87
|
-
* caller — the backend no longer stores a schema. Defaults to none (the kit
|
|
88
|
-
* then resolves documents with no collection structure).
|
|
89
|
-
*/
|
|
90
|
-
collections?: readonly CollectionDef[]
|
|
91
|
-
/** Route records served by `resolvePage` (EC-019). */
|
|
92
|
-
routes?: readonly RouteRecord[]
|
|
93
|
-
// Redirects are no longer an option (EC-303): they are editorial DATA, read from
|
|
94
|
-
// the `redirects` table inside `createContent`, not passed by the caller.
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface ContentKit {
|
|
98
|
-
/** The ambient `{ locale, perspective }` context every call resolves under. */
|
|
99
|
-
readonly context: ContentContext
|
|
100
|
-
/** Structured load-time issues (invalid stored blobs); never thrown. */
|
|
101
|
-
readonly loadIssues: readonly ContentKitIssue[]
|
|
102
|
-
/** Structural route issues from router construction (conflicts, loops). */
|
|
103
|
-
readonly routerIssues: readonly CmsValidationIssue[]
|
|
104
|
-
/** Delivery-shaped single-document accessor — same semantics as Next-side `getDocument`. */
|
|
105
|
-
readonly get: ContentClient['getDocument']
|
|
106
|
-
/** Delivery-shaped list accessor — same filter vocabulary as EC-143. */
|
|
107
|
-
readonly list: ContentClient['listDocuments']
|
|
108
|
-
/** Route → page/document resolution (EC-019), when routes were provided. */
|
|
109
|
-
readonly resolvePage: ContentClient['resolvePage']
|
|
110
|
-
/**
|
|
111
|
-
* Escape hatch (EC-142 `rawDocument`): the stored row, untouched — no locale
|
|
112
|
-
* fallback, no population, no asset resolution. The perspective gate still
|
|
113
|
-
* applies: a `published` kit never sees draft rows, even raw.
|
|
114
|
-
*/
|
|
115
|
-
raw(collection: string, id: string): CmsDocument | null
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Create the server-side content kit for one Convex query invocation.
|
|
120
|
-
*
|
|
121
|
-
* Usage inside a Convex query function:
|
|
122
|
-
* ```ts
|
|
123
|
-
* const content = await createContent(ctx, { projectId, perspective: 'published' })
|
|
124
|
-
* const { document: post } = content.get<Post>('post', slug)
|
|
125
|
-
* ```
|
|
126
|
-
*/
|
|
127
|
-
export async function createContent(
|
|
128
|
-
ctx: QueryCtx,
|
|
129
|
-
options: CreateContentOptions,
|
|
130
|
-
): Promise<ContentKit> {
|
|
131
|
-
const { projectId, perspective } = options
|
|
132
|
-
const routes = options.routes ?? []
|
|
133
|
-
const loadIssues: ContentKitIssue[] = []
|
|
134
|
-
|
|
135
|
-
// Redirects are editorial DATA now (EC-303), read from the `redirects` table —
|
|
136
|
-
// only ENABLED entries resolve. (Config redirects are retired; static infra
|
|
137
|
-
// redirects stay native.) Projected to the routing `RedirectRecord` shape.
|
|
138
|
-
const redirectRows = await ctx.db.query('redirects').withIndex('by_redirect').collect()
|
|
139
|
-
const redirects: RedirectRecord[] = redirectRows
|
|
140
|
-
.filter((row) => row.enabled)
|
|
141
|
-
.map((row) => ({ id: row.redirectId, from: row.from, to: row.to, permanent: row.permanent }))
|
|
142
|
-
|
|
143
|
-
// --- Prefetch (the only async part; everything below is synchronous) -----
|
|
144
|
-
|
|
145
|
-
// Config-first identity (EC-182/EC-183, AD-11): locales AND collections are
|
|
146
|
-
// supplied by the caller (sourced from `elytra.config.ts`); the backend has
|
|
147
|
-
// no `projects` row and no `cmsSchemas` table — it stores documents only.
|
|
148
|
-
const locales = options.locales ?? DEFAULT_LOCALES
|
|
149
|
-
const collectionDefs: CollectionDef[] = options.collections ? [...options.collections] : []
|
|
150
|
-
|
|
151
|
-
// One indexed scan over the project's documents (v1 scale; see module doc).
|
|
152
|
-
// A row's `document` is the continuous working **draft**; `publishedVersion`
|
|
153
|
-
// (when set) pins which recorded version is live — the Sanity model (EC-224).
|
|
154
|
-
const documentRows = await ctx.db.query('cmsDocuments').collect()
|
|
155
|
-
|
|
156
|
-
// The recorded version snapshots, indexed by (collection, docId, version), so
|
|
157
|
-
// a published read can serve the *pinned* version rather than the live draft.
|
|
158
|
-
const versionsByKey = new Map<string, Map<number, CmsDocument>>()
|
|
159
|
-
for (const row of await ctx.db.query('documentVersions').collect()) {
|
|
160
|
-
// EC-260: unfold the folded storage form before parsing — the kit resolves over
|
|
161
|
-
// STRUCTURED values, exactly like the Next-side snapshot (byte-equivalence).
|
|
162
|
-
const parsed = documentSchema.safeParse(unfoldValue(row.snapshot))
|
|
163
|
-
if (!parsed.success) continue
|
|
164
|
-
const key = `${row.collection}:${row.docId}`
|
|
165
|
-
let byVersion = versionsByKey.get(key)
|
|
166
|
-
if (!byVersion) {
|
|
167
|
-
byVersion = new Map()
|
|
168
|
-
versionsByKey.set(key, byVersion)
|
|
169
|
-
}
|
|
170
|
-
byVersion.set(row.version, parsed.data)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Hybrid source per row (EC-224 A1a): a `publishedVersion` pointer yields a
|
|
174
|
-
// `DocumentHistory` (draft = the live row, published = the pinned snapshot),
|
|
175
|
-
// so the published perspective serves the recorded version, decoupled from
|
|
176
|
-
// the continuously-edited draft. An unpinned row stays a bare `CmsDocument`
|
|
177
|
-
// (the EC-142 gate reads its `state`) — backward-compatible: pre-pointer data
|
|
178
|
-
// resolves exactly as before.
|
|
179
|
-
const parsedSources: { key: string; source: ContentDocumentSource }[] = []
|
|
180
|
-
for (const row of documentRows) {
|
|
181
|
-
// EC-260: unfold the folded storage form before parsing (see the versions loop).
|
|
182
|
-
const parsed = documentSchema.safeParse(unfoldValue(row.document))
|
|
183
|
-
if (!parsed.success) {
|
|
184
|
-
loadIssues.push({
|
|
185
|
-
code: 'invalid-stored-document',
|
|
186
|
-
message: `Stored document "${row.collection}:${row.docId}" does not match the canonical document schema and was skipped.`,
|
|
187
|
-
meta: { projectId, collection: row.collection, docId: row.docId },
|
|
188
|
-
})
|
|
189
|
-
continue
|
|
190
|
-
}
|
|
191
|
-
const draft = parsed.data
|
|
192
|
-
const key = documentKey(draft)
|
|
193
|
-
if (row.publishedVersion === undefined) {
|
|
194
|
-
parsedSources.push({ key, source: draft })
|
|
195
|
-
continue
|
|
196
|
-
}
|
|
197
|
-
// Pinned: serve the recorded snapshot for `published`. A dangling pin
|
|
198
|
-
// (version missing/invalid) under-serves to `null` — never the live draft.
|
|
199
|
-
const published = versionsByKey.get(`${row.collection}:${row.docId}`)?.get(row.publishedVersion)
|
|
200
|
-
parsedSources.push({ key, source: { draft, published, versions: [] } })
|
|
201
|
-
}
|
|
202
|
-
// Deterministic iteration order regardless of row creation order.
|
|
203
|
-
parsedSources.sort((a, b) => (a.key < b.key ? -1 : 1))
|
|
204
|
-
|
|
205
|
-
const assetRows = await ctx.db.query('assets').withIndex('by_asset').collect()
|
|
206
|
-
const assetRecords: AssetRecord[] = []
|
|
207
|
-
for (const row of assetRows) {
|
|
208
|
-
// EC-151 stores a stable serve URL on upload; older rows fall back to a
|
|
209
|
-
// live storage lookup, then to the blob adapter's placeholder convention.
|
|
210
|
-
const url = row.url ?? (await ctx.storage.getUrl(row.storageKey)) ?? undefined
|
|
211
|
-
assetRecords.push({
|
|
212
|
-
id: row.assetId,
|
|
213
|
-
// Single-tenant: rows no longer store projectId; the kit's `projectId`
|
|
214
|
-
// option (the constant slug) satisfies the AssetRecord shape (EC-179).
|
|
215
|
-
projectId,
|
|
216
|
-
filename: row.filename,
|
|
217
|
-
contentType: row.contentType,
|
|
218
|
-
byteSize: row.byteSize,
|
|
219
|
-
...(row.width !== undefined ? { width: row.width } : {}),
|
|
220
|
-
...(row.height !== undefined ? { height: row.height } : {}),
|
|
221
|
-
...(row.alt !== undefined ? { alt: row.alt } : {}),
|
|
222
|
-
// EC-301: focalPoint (EC-230) and the schema-driven metadata bag must reach
|
|
223
|
-
// delivery — both were dropped here, so a studio-set hotspot/metadata never
|
|
224
|
-
// made it into the LIVE snapshot (only the studio canvas, which reads records
|
|
225
|
-
// directly, showed them).
|
|
226
|
-
...(row.focalPoint !== undefined ? { focalPoint: row.focalPoint } : {}),
|
|
227
|
-
...(row.meta !== undefined ? { meta: row.meta } : {}),
|
|
228
|
-
storageKey: row.storageKey,
|
|
229
|
-
...(url !== undefined ? { url } : {}),
|
|
230
|
-
createdAt: row.createdAt,
|
|
231
|
-
updatedAt: row.updatedAt,
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// EC-287: there is no project graph. Pages are `page`-collection documents,
|
|
236
|
-
// resolved by `resolvePage` from the document lookup + routes — no graph load.
|
|
237
|
-
|
|
238
|
-
// --- Index into the synchronous lookup the pure accessors read through ---
|
|
239
|
-
|
|
240
|
-
const collections = new Map<string, CollectionDef>()
|
|
241
|
-
for (const collection of collectionDefs) collections.set(collection.id, collection)
|
|
242
|
-
|
|
243
|
-
const documents = new Map<string, ContentDocumentSource>()
|
|
244
|
-
const byCollection = new Map<string, ContentDocumentSource[]>()
|
|
245
|
-
for (const { key, source } of parsedSources) {
|
|
246
|
-
documents.set(key, source)
|
|
247
|
-
const collection = isDocumentHistory(source) ? source.draft.collection : source.collection
|
|
248
|
-
const list = byCollection.get(collection) ?? []
|
|
249
|
-
list.push(source)
|
|
250
|
-
byCollection.set(collection, list)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const assets = new Map<string, AssetRecord>()
|
|
254
|
-
for (const record of assetRecords) assets.set(record.id, record)
|
|
255
|
-
|
|
256
|
-
const lookup: ContentLookup = {
|
|
257
|
-
collection: (id) => collections.get(id),
|
|
258
|
-
document: (ref) => documents.get(documentKey(ref)),
|
|
259
|
-
documents: (collectionId) => byCollection.get(collectionId) ?? [],
|
|
260
|
-
asset: (id) => assets.get(id),
|
|
261
|
-
// Same convention as `ConvexBlobStorage.url`: stored serve URL when
|
|
262
|
-
// available, otherwise the explicit unresolved-placeholder scheme.
|
|
263
|
-
assetUrl: (asset) => asset.url ?? `convex://storage/${asset.storageKey}`,
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const context = createContentContext({
|
|
267
|
-
locale: options.locale ?? locales.default,
|
|
268
|
-
perspective,
|
|
269
|
-
locales,
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
// The EXACT pure client the Next-side accessors run — this is what makes
|
|
273
|
-
// results byte-equivalent across runtimes for the same inputs.
|
|
274
|
-
const client = createContentClient({ lookup, context, routes, redirects })
|
|
275
|
-
|
|
276
|
-
const raw = (collection: string, id: string): CmsDocument | null => {
|
|
277
|
-
const source = documents.get(documentKey({ collection, id }))
|
|
278
|
-
if (!source) return null
|
|
279
|
-
return rawDocument(context, source)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
context,
|
|
284
|
-
loadIssues,
|
|
285
|
-
routerIssues: client.routerIssues,
|
|
286
|
-
get: client.getDocument,
|
|
287
|
-
list: client.listDocuments,
|
|
288
|
-
resolvePage: client.resolvePage,
|
|
289
|
-
raw,
|
|
290
|
-
}
|
|
291
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import { Collaboration } from '@elytracms/core/collaboration';
|
|
3
|
-
import type { Actor, PreviewMode } from '@elytracms/core/collaboration';
|
|
4
|
-
/**
|
|
5
|
-
* Per-project collaboration session (Phase 10). Holds one {@link Collaboration} store seeded with
|
|
6
|
-
* an initial version + presence for the current user, and exposes a `mutate` helper that re-renders
|
|
7
|
-
* consumers after an imperative store change. Non-persistent (session): it is the builder canvas's
|
|
8
|
-
* session-scoped presence/perspective store. EC-287 retired the project graph and its publish path,
|
|
9
|
-
* so the seed no longer reflects a pinned graph revision; content publishing is per document
|
|
10
|
-
* (EC-224), surfaced by the CMS document Versions pane.
|
|
11
|
-
*/
|
|
12
|
-
export interface CollabStore {
|
|
13
|
-
collab: Collaboration;
|
|
14
|
-
projectId: string;
|
|
15
|
-
me: Actor;
|
|
16
|
-
/** Draft vs published perspective the canvas/preview renders (EC-123). */
|
|
17
|
-
previewMode: PreviewMode;
|
|
18
|
-
setPreviewMode: (mode: PreviewMode) => void;
|
|
19
|
-
/** Run an imperative store mutation, then re-render consumers. */
|
|
20
|
-
mutate: <T>(fn: () => T) => T;
|
|
21
|
-
}
|
|
22
|
-
declare const useCollab: () => CollabStore;
|
|
23
|
-
export declare function CollabProvider({ projectId, children, }: {
|
|
24
|
-
projectId: string;
|
|
25
|
-
children: ReactNode;
|
|
26
|
-
}): import("react").JSX.Element;
|
|
27
|
-
export { useCollab };
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useReducer, useRef } from 'react';
|
|
3
|
-
import { Collaboration, createSequentialClock, createSequentialIds } from '@elytracms/core/collaboration';
|
|
4
|
-
import { createRequiredContext } from '@elytracms/studio/lib/required-context';
|
|
5
|
-
import { seedCollabSession } from './collab-seed.js';
|
|
6
|
-
const { Context: CollabContext, useRequired: useCollab } = createRequiredContext('Collab');
|
|
7
|
-
const ME = { id: 'u-you', name: 'You' };
|
|
8
|
-
export function CollabProvider({ projectId, children, }) {
|
|
9
|
-
const [tick, bump] = useReducer((n) => n + 1, 0);
|
|
10
|
-
const [previewMode, setPreviewModeState] = useReducer((_, next) => next, 'draft');
|
|
11
|
-
const ref = useRef(null);
|
|
12
|
-
if (ref.current === null) {
|
|
13
|
-
const collab = new Collaboration({ clock: createSequentialClock(), ids: createSequentialIds() });
|
|
14
|
-
// Seed one empty Initial version + presence for the current user (EC-122/126).
|
|
15
|
-
seedCollabSession(collab, projectId, { author: ME.name });
|
|
16
|
-
collab.presence.join(projectId, ME);
|
|
17
|
-
ref.current = collab;
|
|
18
|
-
}
|
|
19
|
-
const store = useMemo(() => {
|
|
20
|
-
const collab = ref.current;
|
|
21
|
-
return {
|
|
22
|
-
collab,
|
|
23
|
-
projectId,
|
|
24
|
-
me: ME,
|
|
25
|
-
previewMode,
|
|
26
|
-
setPreviewMode: setPreviewModeState,
|
|
27
|
-
mutate: (fn) => {
|
|
28
|
-
const result = fn();
|
|
29
|
-
bump();
|
|
30
|
-
return result;
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
// `tick` (bumped by `mutate`) and `previewMode` are in deps so consumers
|
|
34
|
-
// re-render after an imperative store mutation or a preview change.
|
|
35
|
-
}, [projectId, previewMode, tick]);
|
|
36
|
-
return _jsx(CollabContext.Provider, { value: store, children: children });
|
|
37
|
-
}
|
|
38
|
-
export { useCollab };
|
|
39
|
-
//# sourceMappingURL=collab-context.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"collab-context.js","sourceRoot":"","sources":["../../../src/components/collab/collab-context.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAEzG,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAqBjD,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,qBAAqB,CAAc,QAAQ,CAAC,CAAA;AAEvG,MAAM,EAAE,GAAU,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AAE9C,MAAM,UAAU,cAAc,CAAC,EAC7B,SAAS,EACT,QAAQ,GAIT;IACC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACxD,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,GAAG,UAAU,CACnD,CAAC,CAAc,EAAE,IAAiB,EAAE,EAAE,CAAC,IAAI,EAC3C,OAAsB,CACvB,CAAA;IAED,MAAM,GAAG,GAAG,MAAM,CAAuB,IAAI,CAAC,CAAA;IAC9C,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,GAAG,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAA;QAChG,+EAA+E;QAC/E,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;QACzD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACnC,GAAG,CAAC,OAAO,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAc,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAQ,CAAA;QAC3B,OAAO;YACL,MAAM;YACN,SAAS;YACT,EAAE,EAAE,EAAE;YACN,WAAW;YACX,cAAc,EAAE,mBAAmB;YACnC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;gBACb,MAAM,MAAM,GAAG,EAAE,EAAE,CAAA;gBACnB,IAAI,EAAE,CAAA;gBACN,OAAO,MAAM,CAAA;YACf,CAAC;SACF,CAAA;QACD,yEAAyE;QACzE,oEAAoE;IACtE,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC,CAAA;IAElC,OAAO,KAAC,aAAa,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAA0B,CAAA;AAClF,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,CAAA","sourcesContent":["import { useMemo, useReducer, useRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { Collaboration, createSequentialClock, createSequentialIds } from '@elytracms/core/collaboration'\nimport type { Actor, PreviewMode } from '@elytracms/core/collaboration'\nimport { createRequiredContext } from '@elytracms/studio/lib/required-context'\nimport { seedCollabSession } from './collab-seed'\n\n/**\n * Per-project collaboration session (Phase 10). Holds one {@link Collaboration} store seeded with\n * an initial version + presence for the current user, and exposes a `mutate` helper that re-renders\n * consumers after an imperative store change. Non-persistent (session): it is the builder canvas's\n * session-scoped presence/perspective store. EC-287 retired the project graph and its publish path,\n * so the seed no longer reflects a pinned graph revision; content publishing is per document\n * (EC-224), surfaced by the CMS document Versions pane.\n */\nexport interface CollabStore {\n collab: Collaboration\n projectId: string\n me: Actor\n /** Draft vs published perspective the canvas/preview renders (EC-123). */\n previewMode: PreviewMode\n setPreviewMode: (mode: PreviewMode) => void\n /** Run an imperative store mutation, then re-render consumers. */\n mutate: <T>(fn: () => T) => T\n}\n\nconst { Context: CollabContext, useRequired: useCollab } = createRequiredContext<CollabStore>('Collab')\n\nconst ME: Actor = { id: 'u-you', name: 'You' }\n\nexport function CollabProvider({\n projectId,\n children,\n}: {\n projectId: string\n children: ReactNode\n}) {\n const [tick, bump] = useReducer((n: number) => n + 1, 0)\n const [previewMode, setPreviewModeState] = useReducer(\n (_: PreviewMode, next: PreviewMode) => next,\n 'draft' as PreviewMode,\n )\n\n const ref = useRef<Collaboration | null>(null)\n if (ref.current === null) {\n const collab = new Collaboration({ clock: createSequentialClock(), ids: createSequentialIds() })\n // Seed one empty Initial version + presence for the current user (EC-122/126).\n seedCollabSession(collab, projectId, { author: ME.name })\n collab.presence.join(projectId, ME)\n ref.current = collab\n }\n\n const store = useMemo<CollabStore>(() => {\n const collab = ref.current!\n return {\n collab,\n projectId,\n me: ME,\n previewMode,\n setPreviewMode: setPreviewModeState,\n mutate: (fn) => {\n const result = fn()\n bump()\n return result\n },\n }\n // `tick` (bumped by `mutate`) and `previewMode` are in deps so consumers\n // re-render after an imperative store mutation or a preview change.\n }, [projectId, previewMode, tick])\n\n return <CollabContext.Provider value={store}>{children}</CollabContext.Provider>\n}\n\nexport { useCollab }\n"]}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { Collaboration } from '@elytracms/core/collaboration';
|
|
2
|
-
/**
|
|
3
|
-
* Seed a fresh per-project {@link Collaboration} session.
|
|
4
|
-
*
|
|
5
|
-
* EC-287: the project graph and its publish path are retired (the DB holds
|
|
6
|
-
* content only), so a session no longer reflects a pinned graph revision. The
|
|
7
|
-
* collaboration store stays the builder's session-scoped presence/perspective
|
|
8
|
-
* store; it seeds one empty "Initial" version so `versions.latest()` is defined,
|
|
9
|
-
* with no graph snapshot and nothing published. Content publishing is per
|
|
10
|
-
* document (EC-224), surfaced by the CMS document Versions pane — not here.
|
|
11
|
-
*/
|
|
12
|
-
export declare function seedCollabSession(collab: Collaboration, projectId: string, options: {
|
|
13
|
-
/** Author recorded on the Initial version. */
|
|
14
|
-
author: string;
|
|
15
|
-
}): void;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seed a fresh per-project {@link Collaboration} session.
|
|
3
|
-
*
|
|
4
|
-
* EC-287: the project graph and its publish path are retired (the DB holds
|
|
5
|
-
* content only), so a session no longer reflects a pinned graph revision. The
|
|
6
|
-
* collaboration store stays the builder's session-scoped presence/perspective
|
|
7
|
-
* store; it seeds one empty "Initial" version so `versions.latest()` is defined,
|
|
8
|
-
* with no graph snapshot and nothing published. Content publishing is per
|
|
9
|
-
* document (EC-224), surfaced by the CMS document Versions pane — not here.
|
|
10
|
-
*/
|
|
11
|
-
export function seedCollabSession(collab, projectId, options) {
|
|
12
|
-
collab.versions.create(projectId, {
|
|
13
|
-
snapshot: '',
|
|
14
|
-
label: 'Initial',
|
|
15
|
-
message: 'Project opened.',
|
|
16
|
-
author: options.author,
|
|
17
|
-
origin: 'import',
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=collab-seed.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"collab-seed.js","sourceRoot":"","sources":["../../../src/components/collab/collab-seed.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAqB,EACrB,SAAiB,EACjB,OAGC;IAED,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE;QAChC,QAAQ,EAAE,EAAE;QACZ,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,iBAAiB;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { Collaboration } from '@elytracms/core/collaboration'\n\n/**\n * Seed a fresh per-project {@link Collaboration} session.\n *\n * EC-287: the project graph and its publish path are retired (the DB holds\n * content only), so a session no longer reflects a pinned graph revision. The\n * collaboration store stays the builder's session-scoped presence/perspective\n * store; it seeds one empty \"Initial\" version so `versions.latest()` is defined,\n * with no graph snapshot and nothing published. Content publishing is per\n * document (EC-224), surfaced by the CMS document Versions pane — not here.\n */\nexport function seedCollabSession(\n collab: Collaboration,\n projectId: string,\n options: {\n /** Author recorded on the Initial version. */\n author: string\n },\n): void {\n collab.versions.create(projectId, {\n snapshot: '',\n label: 'Initial',\n message: 'Project opened.',\n author: options.author,\n origin: 'import',\n })\n}\n"]}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Extensions surface (Phase 11, EC-130–137). A live, agent-inspectable view over the
|
|
3
|
-
* extensibility layer: browse a public + private marketplace, install plugins into a host, and
|
|
4
|
-
* inspect what each surface contributes (components, field types, data sources, asset storage,
|
|
5
|
-
* export targets, hooks). All state is deterministic — driven by `@elytracms/core/plugins` fixtures.
|
|
6
|
-
*/
|
|
7
|
-
export declare function ExtensionsView(): import("react").JSX.Element;
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useReducer, useState } from 'react';
|
|
3
|
-
import { Boxes, Check, Database, Download, HardDrive, Lock, Package, Puzzle, Search, Share2, LayoutTemplate, Type, Webhook, } from 'lucide-react';
|
|
4
|
-
import { BUILTIN_FIELD_TYPES, BUILTIN_SOURCE_KINDS, HOOK_POINTS, MarketplaceClient, PluginHost, privateMarketplace, publicMarketplace, } from '@elytracms/core/plugins';
|
|
5
|
-
import { Badge, Button, Code, Heading, Input, Row, Spacer, Stack, Text } from '@elytracms/studio/ui';
|
|
6
|
-
import { translate } from '@elytracms/studio/lib/i18n';
|
|
7
|
-
import { cn } from '@elytracms/studio/lib/utils';
|
|
8
|
-
/**
|
|
9
|
-
* The Extensions surface (Phase 11, EC-130–137). A live, agent-inspectable view over the
|
|
10
|
-
* extensibility layer: browse a public + private marketplace, install plugins into a host, and
|
|
11
|
-
* inspect what each surface contributes (components, field types, data sources, asset storage,
|
|
12
|
-
* export targets, hooks). All state is deterministic — driven by `@elytracms/core/plugins` fixtures.
|
|
13
|
-
*/
|
|
14
|
-
export function ExtensionsView() {
|
|
15
|
-
// One host for the session; PluginHost mutates in place, so a bump reducer drives re-render.
|
|
16
|
-
const [host] = useState(() => new PluginHost());
|
|
17
|
-
const [, bump] = useReducer((n) => n + 1, 0);
|
|
18
|
-
const [tab, setTab] = useState('marketplace');
|
|
19
|
-
const [token, setToken] = useState('');
|
|
20
|
-
const [query, setQuery] = useState('');
|
|
21
|
-
const [results, setResults] = useState({});
|
|
22
|
-
const client = useMemo(() => new MarketplaceClient([publicMarketplace, privateMarketplace], token.trim() ? { 'acme-private': token.trim() } : {}), [token]);
|
|
23
|
-
const hits = client.search(query.trim() ? { query: query.trim() } : {});
|
|
24
|
-
const privateUnlocked = client.accessibleRegistries().some((r) => r.id === 'acme-private');
|
|
25
|
-
const installedCount = host.list().length;
|
|
26
|
-
function install(listing) {
|
|
27
|
-
const result = client.install(host, listing.id);
|
|
28
|
-
setResults((prev) => ({ ...prev, [listing.id]: result }));
|
|
29
|
-
bump();
|
|
30
|
-
}
|
|
31
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(ExtensionsHeader, { tab: tab, onTab: setTab, installedCount: installedCount, marketCount: hits.length }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto px-4 py-5 sm:px-6", children: _jsx("div", { className: "mx-auto w-full max-w-5xl", children: tab === 'marketplace' ? (_jsx(MarketplaceTab, { hits: hits, query: query, onQuery: setQuery, token: token, onToken: setToken, privateUnlocked: privateUnlocked, isInstalled: (l) => host.isInstalled(l.plugin.id), onInstall: install, results: results })) : tab === 'installed' ? (_jsx(InstalledTab, { installed: host.list() })) : (_jsx(RegisteredTab, { host: host })) }) })] }));
|
|
32
|
-
}
|
|
33
|
-
// --- Header + tabs ----------------------------------------------------------
|
|
34
|
-
function ExtensionsHeader({ tab, onTab, installedCount, marketCount, }) {
|
|
35
|
-
const tabs = [
|
|
36
|
-
{ id: 'marketplace', label: translate('extensions.tab.marketplace'), count: marketCount },
|
|
37
|
-
{ id: 'installed', label: translate('extensions.tab.installed'), count: installedCount },
|
|
38
|
-
{ id: 'registered', label: translate('extensions.tab.registered') },
|
|
39
|
-
];
|
|
40
|
-
return (_jsx("div", { className: "shrink-0 border-b border-border bg-gradient-to-b from-primary/[0.04] to-transparent px-4 py-3 sm:px-6", children: _jsxs(Row, { gap: "3", children: [_jsx("span", { className: "grid size-8 shrink-0 place-items-center rounded-lg bg-primary/15 text-primary", children: _jsx(Puzzle, { className: "size-4" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { size: "sm", weight: "semibold", children: translate('extensions.header.title') }), _jsx(Text, { size: "xs", tone: "subtle", children: translate('extensions.header.subtitle') })] }), _jsx(Spacer, {}), _jsx(Row, { gap: "1", className: "rounded-md bg-muted/50 p-0.5", children: tabs.map((t) => (_jsxs("button", { type: "button", "aria-pressed": tab === t.id, onClick: () => onTab(t.id), className: cn('flex items-center gap-1.5 rounded-[5px] px-3 py-1 text-sm font-medium transition-colors', tab === t.id ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'), children: [t.label, t.count !== undefined ? (_jsx("span", { className: "rounded-full bg-muted px-1.5 text-[0.7rem] font-semibold tabular-nums text-muted-foreground", children: t.count })) : null] }, t.id))) })] }) }));
|
|
41
|
-
}
|
|
42
|
-
// --- Marketplace ------------------------------------------------------------
|
|
43
|
-
function MarketplaceTab({ hits, query, onQuery, token, onToken, privateUnlocked, isInstalled, onInstall, results, }) {
|
|
44
|
-
return (_jsxs(Stack, { gap: "6", children: [_jsxs(Row, { gap: "3", wrap: true, children: [_jsxs("div", { className: "relative min-w-0 flex-1", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: query, onChange: (e) => onQuery(e.target.value), placeholder: translate('extensions.search.placeholder'), className: "pl-8", "aria-label": translate('extensions.search.aria') })] }), _jsxs(Row, { gap: "2", className: "shrink-0", children: [_jsx(Lock, { className: cn('size-3.5', privateUnlocked ? 'text-success' : 'text-muted-foreground') }), _jsx(Input, { value: token, onChange: (e) => onToken(e.target.value), placeholder: translate('extensions.token.placeholder'), "aria-label": translate('extensions.token.placeholder'), className: "w-52" }), privateUnlocked ? _jsx(Badge, { variant: "success", children: translate('extensions.token.unlocked') }) : null] })] }), !privateUnlocked ? (_jsxs(Text, { size: "xs", tone: "subtle", children: ["A private registry is available. Enter its access token (hint: ", _jsx(Code, { tone: "muted", children: "acme-internal-token" }), ") to reveal private listings."] })) : null, hits.length === 0 ? (_jsx(EmptyHint, { icon: _jsx(Search, { className: "size-5" }), title: translate('extensions.empty.noMatch.title'), body: translate('extensions.empty.noMatch.body') })) : (_jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: hits.map(({ registryId, listing }) => (_jsx(ListingCard, { listing: listing, registryId: registryId, installed: isInstalled(listing), result: results[listing.id], onInstall: () => onInstall(listing) }, listing.id))) }))] }));
|
|
45
|
-
}
|
|
46
|
-
function ListingCard({ listing, registryId, installed, result, onInstall, }) {
|
|
47
|
-
const failed = result && !result.ok;
|
|
48
|
-
return (_jsxs("div", { className: "flex flex-col rounded-xl border border-border bg-card/60 p-4 transition-colors hover:border-primary/40", children: [_jsxs(Row, { gap: "2", children: [_jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-muted text-foreground/80", children: _jsx(KindIcon, { kind: listing.kind }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "truncate", children: listing.name }), _jsxs(Row, { gap: "1", className: "text-muted-foreground", children: [_jsx(KindBadge, { kind: listing.kind }), _jsxs(Code, { tone: "muted", children: ["v", listing.version] })] })] }), _jsx(Spacer, {}), registryId === 'acme-private' ? (_jsxs(Badge, { variant: "outline", children: [_jsx(Lock, { className: "size-3" }), " private"] })) : null] }), _jsx(Text, { size: "xs", tone: "muted", className: "mt-3 line-clamp-3 min-h-0 flex-1", children: listing.description ?? translate('extensions.listing.noDescription') }), listing.tags && listing.tags.length > 0 ? (_jsx(Row, { gap: "1", wrap: true, className: "mt-3", children: listing.tags.map((tag) => (_jsx("span", { className: "rounded-full bg-muted/70 px-2 py-0.5 text-[0.7rem] text-muted-foreground", children: tag }, tag))) })) : null, _jsxs(Row, { gap: "2", className: "mt-4", children: [_jsx(Text, { size: "xs", tone: "subtle", className: "truncate", children: listing.author ?? translate('extensions.listing.unknownAuthor') }), _jsx(Spacer, {}), installed ? (_jsxs(Button, { size: "sm", variant: "ghost", disabled: true, children: [_jsx(Check, {}), " ", translate('extensions.action.installed')] })) : (_jsxs(Button, { size: "sm", variant: failed ? 'destructive' : 'default', onClick: onInstall, children: [_jsx(Download, {}), " ", failed ? translate('common.retry') : translate('extensions.action.install')] }))] }), failed ? (_jsx(Text, { size: "xs", className: "mt-2 text-destructive", children: result?.blocked === 'access-denied'
|
|
49
|
-
? translate('extensions.error.accessDenied')
|
|
50
|
-
: result?.issues[0]?.message ?? translate('extensions.error.installFailed') })) : null] }));
|
|
51
|
-
}
|
|
52
|
-
// --- Installed --------------------------------------------------------------
|
|
53
|
-
function InstalledTab({ installed }) {
|
|
54
|
-
if (installed.length === 0) {
|
|
55
|
-
return (_jsx(EmptyHint, { icon: _jsx(Boxes, { className: "size-5" }), title: translate('extensions.empty.installed.title'), body: translate('extensions.empty.installed.body') }));
|
|
56
|
-
}
|
|
57
|
-
return (_jsx(Stack, { gap: "3", children: installed.map((plugin) => (_jsxs("div", { className: "rounded-xl border border-border bg-card/60 p-4", children: [_jsxs(Row, { gap: "2", children: [_jsx(Package, { className: "size-4 text-primary" }), _jsx(Text, { size: "sm", weight: "semibold", children: plugin.name }), _jsxs(Code, { tone: "muted", children: ["v", plugin.version] }), _jsx(Spacer, {}), _jsx(Text, { size: "xs", tone: "subtle", children: plugin.author ?? '' })] }), plugin.description ? (_jsx(Text, { size: "xs", tone: "muted", className: "mt-2", children: plugin.description })) : null, _jsxs(Row, { gap: "2", wrap: true, className: "mt-3", children: [_jsx(ContributionChip, { icon: _jsx(Boxes, {}), label: translate('extensions.contrib.components'), count: plugin.contributes.components }), _jsx(ContributionChip, { icon: _jsx(Type, {}), label: translate('extensions.contrib.fieldTypes'), count: plugin.contributes.fieldTypes }), _jsx(ContributionChip, { icon: _jsx(Database, {}), label: translate('extensions.contrib.dataSources'), count: plugin.contributes.dataSources }), _jsx(ContributionChip, { icon: _jsx(HardDrive, {}), label: translate('extensions.contrib.assetStorage'), count: plugin.contributes.assetStorage }), _jsx(ContributionChip, { icon: _jsx(Share2, {}), label: translate('extensions.contrib.exportTargets'), count: plugin.contributes.exportTargets }), _jsx(ContributionChip, { icon: _jsx(LayoutTemplate, {}), label: translate('extensions.contrib.templates'), count: plugin.contributes.templates }), _jsx(ContributionChip, { icon: _jsx(Webhook, {}), label: translate('extensions.contrib.hooks'), count: plugin.contributes.hooks })] })] }, plugin.id))) }));
|
|
58
|
-
}
|
|
59
|
-
function ContributionChip({ icon, label, count }) {
|
|
60
|
-
if (count === 0)
|
|
61
|
-
return null;
|
|
62
|
-
return (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-md border border-border/60 bg-muted/40 px-2 py-1 text-xs text-muted-foreground", children: [_jsx("span", { className: "grid size-3.5 place-items-center text-primary [&_svg]:size-3.5", children: icon }), _jsx("span", { className: "font-semibold tabular-nums text-foreground", children: count }), label] }));
|
|
63
|
-
}
|
|
64
|
-
// --- Registered (inspect) ---------------------------------------------------
|
|
65
|
-
function RegisteredTab({ host }) {
|
|
66
|
-
const hookPoints = host.hooks.points();
|
|
67
|
-
return (_jsxs(Stack, { gap: "6", children: [_jsx(Text, { size: "xs", tone: "subtle", children: translate('extensions.registered.intro') }), _jsx(InspectGroup, { icon: _jsx(Boxes, {}), title: translate('extensions.registered.components.title'), sub: "base.* + project + vendor packages", children: _jsx(ChipRow, { items: host.components.projectComponents().map((m) => m.id), empty: translate('extensions.registered.components.empty') }) }), _jsx(InspectGroup, { icon: _jsx(Type, {}), title: translate('extensions.registered.fieldTypes.title'), children: _jsx(ChipRow, { items: [...BUILTIN_FIELD_TYPES], tone: "builtin", suffix: host.fieldTypes.list().map((f) => f.kind) }) }), _jsx(InspectGroup, { icon: _jsx(Database, {}), title: translate('extensions.registered.dataSources.title'), children: _jsx(ChipRow, { items: [...BUILTIN_SOURCE_KINDS], tone: "builtin", suffix: host.dataSources.list().map((a) => a.kind) }) }), _jsx(InspectGroup, { icon: _jsx(HardDrive, {}), title: translate('extensions.registered.assetStorage.title'), children: _jsx(ChipRow, { items: host.assetStorage.list().map((a) => `${a.kind} · ${a.title}`), empty: translate('extensions.registered.assetStorage.empty') }) }), _jsx(InspectGroup, { icon: _jsx(Share2, {}), title: translate('extensions.registered.exportTargets.title'), children: _jsx(ChipRow, { items: ['next-app-router · reference'], tone: "builtin", suffix: host.exportTargets.list().map((t) => `${t.id} · ${t.title}`) }) }), _jsx(InspectGroup, { icon: _jsx(Webhook, {}), title: translate('extensions.registered.hookPoints.title'), children: _jsx(ChipRow, { items: [...HOOK_POINTS], tone: "builtin", suffix: hookPoints.flatMap((p) => host.hooks.list(p).map((t) => `${p}${t.plugin ? ` ← ${t.plugin}` : ''}`)) }) })] }));
|
|
68
|
-
}
|
|
69
|
-
function InspectGroup({ icon, title, sub, children, }) {
|
|
70
|
-
return (_jsxs(Stack, { gap: "2", children: [_jsxs(Row, { gap: "2", children: [_jsx("span", { className: "grid size-5 place-items-center text-primary [&_svg]:size-4", children: icon }), _jsx(Text, { size: "sm", weight: "semibold", children: title }), sub ? (_jsx(Text, { size: "xs", tone: "subtle", children: sub })) : null] }), children] }));
|
|
71
|
-
}
|
|
72
|
-
function ChipRow({ items, suffix, tone, empty, }) {
|
|
73
|
-
const all = [...items.map((v) => ({ v, builtin: tone === 'builtin' })), ...(suffix ?? []).map((v) => ({ v, builtin: false }))];
|
|
74
|
-
if (all.length === 0) {
|
|
75
|
-
return (_jsx(Text, { size: "xs", tone: "subtle", children: empty ?? translate('common.none') }));
|
|
76
|
-
}
|
|
77
|
-
return (_jsx(Row, { gap: "1", wrap: true, children: all.map(({ v, builtin }) => (_jsx("span", { className: cn('rounded-md border px-2 py-0.5 font-mono text-xs', builtin
|
|
78
|
-
? 'border-border/60 bg-muted/40 text-muted-foreground'
|
|
79
|
-
: 'border-primary/30 bg-primary/10 text-primary'), children: v }, v))) }));
|
|
80
|
-
}
|
|
81
|
-
// --- Shared bits -------------------------------------------------------------
|
|
82
|
-
function EmptyHint({ icon, title, body }) {
|
|
83
|
-
return (_jsxs(Stack, { gap: "2", align: "center", className: "rounded-xl border border-dashed border-border/70 px-6 py-16 text-center", children: [_jsx("span", { className: "grid size-10 place-items-center rounded-full bg-muted text-muted-foreground", children: icon }), _jsx(Heading, { level: 3, size: "h4", children: title }), _jsx(Text, { size: "sm", tone: "muted", className: "max-w-sm", children: body })] }));
|
|
84
|
-
}
|
|
85
|
-
const KIND_LABEL_KEY = {
|
|
86
|
-
plugin: 'extensions.kind.plugin',
|
|
87
|
-
'component-package': 'extensions.kind.componentPackage',
|
|
88
|
-
'field-type': 'extensions.kind.fieldType',
|
|
89
|
-
'data-source': 'extensions.kind.dataSource',
|
|
90
|
-
'asset-storage': 'extensions.kind.assetStorage',
|
|
91
|
-
'export-target': 'extensions.kind.exportTarget',
|
|
92
|
-
template: 'extensions.kind.template',
|
|
93
|
-
};
|
|
94
|
-
function KindBadge({ kind }) {
|
|
95
|
-
const variant = kind === 'plugin'
|
|
96
|
-
? 'primary'
|
|
97
|
-
: kind === 'field-type'
|
|
98
|
-
? 'success'
|
|
99
|
-
: kind === 'data-source'
|
|
100
|
-
? 'warning'
|
|
101
|
-
: kind === 'template'
|
|
102
|
-
? 'info'
|
|
103
|
-
: 'outline';
|
|
104
|
-
return _jsx(Badge, { variant: variant, children: translate(KIND_LABEL_KEY[kind]) });
|
|
105
|
-
}
|
|
106
|
-
function KindIcon({ kind }) {
|
|
107
|
-
const className = 'size-4';
|
|
108
|
-
switch (kind) {
|
|
109
|
-
case 'plugin':
|
|
110
|
-
return _jsx(Puzzle, { className: className });
|
|
111
|
-
case 'component-package':
|
|
112
|
-
return _jsx(Boxes, { className: className });
|
|
113
|
-
case 'field-type':
|
|
114
|
-
return _jsx(Type, { className: className });
|
|
115
|
-
case 'data-source':
|
|
116
|
-
return _jsx(Database, { className: className });
|
|
117
|
-
case 'asset-storage':
|
|
118
|
-
return _jsx(HardDrive, { className: className });
|
|
119
|
-
case 'export-target':
|
|
120
|
-
return _jsx(Share2, { className: className });
|
|
121
|
-
case 'template':
|
|
122
|
-
return _jsx(LayoutTemplate, { className: className });
|
|
123
|
-
default:
|
|
124
|
-
return _jsx(Package, { className: className });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
//# sourceMappingURL=extensions-view.js.map
|