@knkcs/contenthub-ui 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/dist/{branch-content-fields-WBpviLQg.js → branch-content-fields-LyF6sD2e.js} +2 -2
- package/dist/{branch-content-fields-WBpviLQg.js.map → branch-content-fields-LyF6sD2e.js.map} +1 -1
- package/dist/{content-library-B2eauvHt.js → content-library-v0mjVD4J.js} +452 -405
- package/dist/content-library-v0mjVD4J.js.map +1 -0
- package/dist/{heads-Brd2Gtjm.js → heads-dBmqNcOB.js} +12 -12
- package/dist/heads-dBmqNcOB.js.map +1 -0
- package/dist/index.d.ts +68 -13
- package/dist/index.js +2349 -2306
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +65 -13
- package/dist/testing.js +494 -467
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
- package/dist/content-library-B2eauvHt.js.map +0 -1
- package/dist/heads-Brd2Gtjm.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/knkCS/contenthub/compare/contenthub-ui-v0.2.0...contenthub-ui-v0.3.0) (2026-10-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **catalog:** name Blueprints and filter by them from contenthub with GetBlueprints ([#247](https://github.com/knkCS/contenthub/issues/247)) ([2b99f96](https://github.com/knkCS/contenthub/commit/2b99f9699f768ac0352dbd5a3aaaa84b8cdfc9bc))
|
|
9
|
+
|
|
10
|
+
## [0.2.0](https://github.com/knkCS/contenthub/compare/contenthub-ui-v0.1.0...contenthub-ui-v0.2.0) (2026-10-13)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **catalog:** serve New Content's Blueprints and Releases from contenthub ([#243](https://github.com/knkCS/contenthub/issues/243)) ([4c0558b](https://github.com/knkCS/contenthub/commit/4c0558b895f1550449a5edebc0eb5642743b8614))
|
|
16
|
+
|
|
3
17
|
## 0.1.0 (2026-10-08)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -13,11 +13,11 @@ The package exports screens only, never routes:
|
|
|
13
13
|
- `BranchEditor`: one Content inside a Change Set at one Branch (`changeSetId`, `contentId`, `branchId`), its header tabs chosen by `?tab=`. The Content tab renders the Blueprint Release's form through fieldkit's `SpecForm`, with knkeditor for rich text, both loaded lazily; reference fields search contenthub, media fields use the `MediaSource` port. The forms above the tabs are the drafts (fieldkit's "Consumer-owned Save across unmounting tabs"): the Content's, and the Release tab's Planned Release (Validity Period, supersedes, Release Spec values; the Change Set's proposals beside it, read-only). The header's one Save persists every changed tab: one `SaveRevision` on the head the editor loaded, with a field mask of what changed, then `PlanRelease` or `UpdatePlannedRelease`; a refused save sends no plan, and a refused plan leaves only the Release tab changed. A moved head is refused with Reload, never merged; the leave guard needs a data router. Compare shows the head against its fork or Main, References its outgoing edges and outdated pins. The rail offers Update from Main and the merge resolution it may leave, Compare to the fork, Preview, Append a Branch (`onOpenBranch` opens it) and Drop Chain (`onDeleted` leaves), and shows the fork Release, the effective date, the Chain position and the head. It needs `@knkcs/fieldkit` (0.18), `@knkcms/knkeditor-editor` and `@knkcms/knkeditor-vocabulary` (GitHub Packages) as peers.
|
|
14
14
|
- `MediaSource`, the port a host fills with mediahub, in fieldkit's media-adapter shape (ADR 0020). Without it a media field offers nothing to pick.
|
|
15
15
|
- `StatusSource`, the port a host fills with statushub for Change Set status labels (ADR 0020). Without it the screens show the bare slug and offer no status picker.
|
|
16
|
-
- `BlueprintSource
|
|
16
|
+
- `BlueprintSource`: content Blueprints and their Releases, for New Content, Blueprint names and the Library's Blueprint filter, which offers the Blueprints of the listed Content too, archived ones included (#241). Not a port a host fills: contenthub serves them itself (`BlueprintCatalogService`), because blueprinthub is its direct dependency (ADR 0020 as amended, #238), so every screen reads them through its `ContenthubClient`. A host may pass its own, which only tests need. A Release contenthub refuses is offered disabled in New Content, with its reasons.
|
|
17
17
|
- `ContenthubClient`, the port every screen takes, and `createConnectContenthubClient({ baseUrl, fetch })`, its Connect implementation. `baseUrl` is `""` when contenthub serves the page, the mount prefix (`/api/contenthub`) under core.
|
|
18
18
|
- `ContenthubPathsProvider` and `ContenthubPaths`: where the host mounts the screens, for the breadcrumbs and links they draw. `contenthubSettings` is the settings page, whose tabs are `?tab=`; `contenthubSettingsPath(paths, tab)` builds a link to one.
|
|
19
19
|
- `contenthubTranslations`, the `{ namespace, resources }` a host merges (en and de), with `CONTENTHUB_UI_NS` for its namespace list (core ADR 0005).
|
|
20
|
-
- `@knkcs/contenthub-ui/testing`: `createStubContenthubClient()`, an in-memory workspace that applies the service's rules (with `publishBlockers` for the blockers it can't derive, `authzMode`, `collectionGrants`, `memberIds` and `admin` for restriction and grants, and `withheld` for Change Sets holding work the caller can't see), its fixtures, `stubBlocker()`, `createStubStatusSource()` over statushub's seed catalogue, `createStubBlueprintSource()` over a few Blueprints and their Releases, and `stubIdentity()` for anker's `TestHost`.
|
|
20
|
+
- `@knkcs/contenthub-ui/testing`: `createStubContenthubClient()`, an in-memory workspace that applies the service's rules (with `publishBlockers` for the blockers it can't derive, `authzMode`, `collectionGrants`, `memberIds` and `admin` for restriction and grants, and `withheld` for Change Sets holding work the caller can't see), its fixtures, `stubBlocker()`, `createStubStatusSource()` over statushub's seed catalogue, `createStubBlueprintSource()`, a `BlueprintSource` override over a few Blueprints and their Releases (the stub client serves its own, with `blueprints`, an `archived` one named but never offered for New Content, and `blueprintReleases`), and `stubIdentity()` for anker's `TestHost`.
|
|
21
21
|
|
|
22
22
|
Screens read who is looking through anker's host contract (`useHostIdentity`) and report their title, breadcrumbs and actions through it, so a host needs `HostProvider` (or `AppShell`), a TanStack `QueryClientProvider`, a router and i18next with the package's translations.
|
|
23
23
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as e } from "./content-library-
|
|
1
|
+
import { r as e } from "./content-library-v0mjVD4J.js";
|
|
2
2
|
import { useMemo as t } from "react";
|
|
3
3
|
import { jsx as n } from "react/jsx-runtime";
|
|
4
4
|
import { FieldKitProvider as r, SpecForm as i } from "@knkcs/fieldkit/renderer";
|
|
@@ -83,4 +83,4 @@ function d({ client: e, changeSetId: a, branchId: o, fields: s, parts: c, labels
|
|
|
83
83
|
//#endregion
|
|
84
84
|
export { d as default };
|
|
85
85
|
|
|
86
|
-
//# sourceMappingURL=branch-content-fields-
|
|
86
|
+
//# sourceMappingURL=branch-content-fields-LyF6sD2e.js.map
|
package/dist/{branch-content-fields-WBpviLQg.js.map → branch-content-fields-LyF6sD2e.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-content-fields-
|
|
1
|
+
{"version":3,"file":"branch-content-fields-LyF6sD2e.js","names":["contentLabel","MAX_PAGE","itemOf","data","id","blueprintId","branchAdapters","wanted","page","page_size","list","client","query","blueprintIds","excluded","excludeIds","seen","items","c","start","total","ids","detail","contentId","mediaSource","file","filter","plugins","builtInFieldTypes","p","knkRichTextPlugin","BranchContentFields","adapters","useMemo","changeSetId","FieldKitProvider","SpecForm","fields","branchId"],"sources":["../src/screens/branch-adapters.ts","../src/screens/branch-content-fields.tsx"],"sourcesContent":["// fieldkit's adapters for the Branch editor (ADR 0020): references from\n// contenthub's own reads, media through the host's MediaSource port. Types\n// only from fieldkit, so this module adds nothing to the root bundle.\nimport type { FieldKitAdapters, PinTarget, ReferenceItem } from \"@knkcs/fieldkit/renderer\";\nimport type { ContenthubClient } from \"../client/contenthub-client\";\nimport type { MediaSource } from \"../types\";\nimport { contentLabel } from \"./content-library\";\n\n/** The most rows one search asks for: ListContents' own cap. */\nconst MAX_PAGE = 200;\n\n/** What a picker shows for a Content: its label, or its id when its data has none. */\nfunction itemOf(id: string, blueprintId: string, data: Record<string, unknown>): ReferenceItem {\n\treturn { id, display_name: contentLabel(data) || id, blueprint_id: blueprintId };\n}\n\n/**\n * The adapters a Branch's form gets: `reference` searches contenthub's\n * Editor Search on Main (which reads open Branch heads too, so Content born\n * in an open Change Set is found), names a referenced Content from its read\n * and offers its Releases to pin; `media` is the host's MediaSource, absent\n * when the host has none, which leaves a media field nothing to pick.\n */\nexport function branchAdapters({\n\tclient,\n\tchangeSetId,\n\tmediaSource,\n}: {\n\tclient: ContenthubClient;\n\tchangeSetId: string;\n\tmediaSource?: MediaSource;\n}): FieldKitAdapters {\n\treturn {\n\t\treference: {\n\t\t\tasync search({ query, blueprintIds, excludeIds, page, page_size }) {\n\t\t\t\tconst wanted = Math.min(MAX_PAGE, Math.max(1, page) * Math.max(1, page_size));\n\t\t\t\t// Every target Blueprint at once: the service narrows the page, so\n\t\t\t\t// it holds only what the Field may point at, and paging stays exact.\n\t\t\t\tconst list = await client.listContents({\n\t\t\t\t\tsearch: query.trim() || undefined,\n\t\t\t\t\tblueprintIds: blueprintIds.length > 0 ? blueprintIds : undefined,\n\t\t\t\t\tpageSize: wanted,\n\t\t\t\t});\n\t\t\t\tconst excluded = new Set(excludeIds ?? []);\n\t\t\t\tconst seen = new Set<string>();\n\t\t\t\tconst items: ReferenceItem[] = [];\n\t\t\t\t// A search lists a Content once per head that matched: one row each.\n\t\t\t\tfor (const c of list.contents) {\n\t\t\t\t\tif (seen.has(c.id) || excluded.has(c.id)) continue;\n\t\t\t\t\tseen.add(c.id);\n\t\t\t\t\titems.push(itemOf(c.id, c.blueprintId, c.data));\n\t\t\t\t}\n\t\t\t\tconst start = (Math.max(1, page) - 1) * page_size;\n\t\t\t\t// The total is exact on the last page; otherwise there is at least one more.\n\t\t\t\tconst total = list.nextPageToken ? items.length + 1 : items.length;\n\t\t\t\treturn { items: items.slice(start, start + page_size), total };\n\t\t\t},\n\t\t\tasync fetch(ids) {\n\t\t\t\treturn Promise.all(\n\t\t\t\t\tids.map(async (id) => {\n\t\t\t\t\t\t// Published Content reads on Main; Content born in this\n\t\t\t\t\t\t// Change Set exists only there.\n\t\t\t\t\t\tconst detail = await client\n\t\t\t\t\t\t\t.getContent({ id })\n\t\t\t\t\t\t\t.catch(() => client.getContent({ id, changeSetId }))\n\t\t\t\t\t\t\t.catch(() => undefined);\n\t\t\t\t\t\treturn detail\n\t\t\t\t\t\t\t? itemOf(id, detail.content.blueprintId, detail.content.data)\n\t\t\t\t\t\t\t: { id, display_name: id };\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t},\n\t\t\t// A Pin names a Release (the one pinning kind fieldkit has), newest first.\n\t\t\tasync listPinTargets(contentId) {\n\t\t\t\tconst timeline = await client.getTimeline(contentId);\n\t\t\t\treturn [...timeline.entries].reverse().map(\n\t\t\t\t\t(e): PinTarget => ({\n\t\t\t\t\t\tid: e.releaseId,\n\t\t\t\t\t\tlabel: `#${e.ordinal}`,\n\t\t\t\t\t\tdescription: e.validityPeriod\n\t\t\t\t\t\t\t? `${e.validityPeriod.firstDay} – ${e.validityPeriod.lastDay || \"…\"}`\n\t\t\t\t\t\t\t: e.publishTime.slice(0, 10),\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t\tmedia: mediaSource\n\t\t\t? {\n\t\t\t\t\tupload: (file) => mediaSource.upload(file),\n\t\t\t\t\tbrowse: (filter) => mediaSource.browse(filter),\n\t\t\t\t}\n\t\t\t: undefined,\n\t};\n}\n","// fieldkit's renderer with knkeditor for rich text, behind a lazily loaded\n// module: both load the first time a Branch's Content tab is shown, never\n// with the rest of the package (spec #56; branch-editor-bundle.test.ts).\nimport { FieldKitProvider, SpecForm, type SpecFormLabels } from \"@knkcs/fieldkit/renderer\";\nimport { knkRichTextPlugin } from \"@knkcs/fieldkit/rich-text\";\nimport { builtInFieldTypes, type FieldTypePlugin, type Schema } from \"@knkcs/fieldkit/schema\";\nimport { useMemo } from \"react\";\nimport type { ContenthubClient } from \"../client/contenthub-client\";\nimport type { MediaSource, ResolvedField } from \"../types\";\nimport { branchAdapters } from \"./branch-adapters\";\n\n// rich_text through knkeditor (fieldkit ADR-0026); every other type as built in.\nconst plugins: FieldTypePlugin[] = builtInFieldTypes.map((p) =>\n\tp.id === \"rich_text\" ? (knkRichTextPlugin as FieldTypePlugin) : p,\n);\n\nexport interface BranchContentFieldsProps {\n\tclient: ContenthubClient;\n\tchangeSetId: string;\n\t/** The record the form holds, so SpecForm's remembered section is this Branch's. */\n\tbranchId: string;\n\tfields: ResolvedField[];\n\t/** The Resolved Release's parts: the Text Types its rich_text fields pin. */\n\tparts: Record<string, Record<string, unknown>>;\n\tlabels: SpecFormLabels;\n\treadOnly: boolean;\n\tmediaSource?: MediaSource;\n}\n\n/**\n * The Branch's data as the Blueprint Release lays it out, editable: its\n * sections, orientation and cards as fieldkit draws them, over the form the\n * editor holds above its tabs (fieldkit's spec-form.mdx, \"A Consumer-owned\n * Save across unmounting tabs\"). SpecForm keeps no draft of its own.\n */\nexport default function BranchContentFields({\n\tclient,\n\tchangeSetId,\n\tbranchId,\n\tfields,\n\tparts,\n\tlabels,\n\treadOnly,\n\tmediaSource,\n}: BranchContentFieldsProps) {\n\tconst adapters = useMemo(\n\t\t() => branchAdapters({ client, changeSetId, mediaSource }),\n\t\t[client, changeSetId, mediaSource],\n\t);\n\treturn (\n\t\t<FieldKitProvider plugins={plugins} adapters={adapters} parts={parts}>\n\t\t\t<SpecForm\n\t\t\t\tschema={fields as unknown as Schema}\n\t\t\t\tlabels={labels}\n\t\t\t\treadOnly={readOnly}\n\t\t\t\trecordKey={branchId}\n\t\t\t/>\n\t\t</FieldKitProvider>\n\t);\n}\n"],"mappings":";;;;;;;AASA,IAAMC,IAAW;AAGjB,SAASC,EAAO,GAAY,GAAqB,GAA8C;CAC9F,OAAO;EAAE;EAAI,cAAcF,EAAaG,CAAI,KAAKC;EAAI,cAAcC;CAAY;AAChF;AASA,SAAgBC,EAAe,EAC9B,WACA,gBACA,kBAKoB;CACpB,OAAO;EACN,WAAW;GACV,MAAM,OAAO,EAAE,UAAO,iBAAc,eAAY,SAAM,gBAAa;IAClE,IAAMC,IAAS,KAAK,IAAIN,GAAU,KAAK,IAAI,GAAGO,CAAI,IAAI,KAAK,IAAI,GAAGC,CAAS,CAAC,GAGtEC,IAAO,MAAMC,EAAO,aAAa;KACtC,QAAQC,EAAM,KAAK,KAAK,KAAA;KACxB,cAAcC,EAAa,SAAS,IAAIA,IAAe,KAAA;KACvD,UAAUN;IACX,CAAC,GACKO,IAAW,IAAI,IAAIC,KAAc,CAAC,CAAC,GACnCC,oBAAO,IAAI,IAAY,GACvBC,IAAyB,CAAC;IAEhC,KAAK,IAAMC,KAAKR,EAAK,UACpB,AAAIM,EAAK,IAAIE,EAAE,EAAE,KAAKJ,EAAS,IAAII,EAAE,EAAE,MACvCF,EAAK,IAAIE,EAAE,EAAE,GACbD,EAAM,KAAKf,EAAOgB,EAAE,IAAIA,EAAE,aAAaA,EAAE,IAAI,CAAC;IAE/C,IAAMC,KAAS,KAAK,IAAI,GAAGX,CAAI,IAAI,KAAKC,GAElCW,IAAQV,EAAK,gBAAgBO,EAAM,SAAS,IAAIA,EAAM;IAC5D,OAAO;KAAE,OAAOA,EAAM,MAAME,GAAOA,IAAQV,CAAS;KAAG;IAAM;GAC9D;GACA,MAAM,MAAM,GAAK;IAChB,OAAO,QAAQ,IACdY,EAAI,IAAI,OAAO,MAAO;KAGrB,IAAMC,IAAS,MAAMX,EACnB,WAAW,EAAE,MAAG,CAAC,CAAC,CAClB,YAAYA,EAAO,WAAW;MAAE;MAAI;KAAY,CAAC,CAAC,CAAC,CACnD,YAAY,KAAA,CAAS;KACvB,OAAOW,IACJpB,EAAOE,GAAIkB,EAAO,QAAQ,aAAaA,EAAO,QAAQ,IAAI,IAC1D;MAAE;MAAI,cAAclB;KAAG;IAC3B,CAAC,CACF;GACD;GAEA,MAAM,eAAe,GAAW;IAE/B,OAAO,CAAC,IAAG,MADYO,EAAO,YAAYY,CAAS,EAAA,CAC/B,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,KACrC,OAAkB;KAClB,IAAI,EAAE;KACN,OAAO,IAAI,EAAE;KACb,aAAa,EAAE,iBACZ,GAAG,EAAE,eAAe,SAAS,KAAK,EAAE,eAAe,WAAW,QAC9D,EAAE,YAAY,MAAM,GAAG,EAAE;IAC7B,EACD;GACD;EACD;EACA,OAAOC,IACJ;GACA,SAAS,MAASA,EAAY,OAAOC,CAAI;GACzC,SAAS,MAAWD,EAAY,OAAOE,CAAM;EAC9C,IACC,KAAA;CACJ;AACD;;;ACjFA,IAAMC,IAA6BC,EAAkB,KAAK,MACzDC,EAAE,OAAO,cAAeC,IAAwCD,CACjE;AAqBA,SAAwBE,EAAoB,EAC3C,WACA,gBACA,aACA,WACA,UACA,WACA,aACA,kBAC4B;CAC5B,IAAMC,IAAWC,QACV3B,EAAe;EAAE;EAAQ;EAAa;CAAY,CAAC,GACzD;EAACK;EAAQuB;EAAaV;CAAW,CAClC;CACA,OACC,kBAACW,GAAD;EAA2B;EAAmB;EAAiB;EAC9D,UAAA,kBAACC,GAAD;GACC,QAAQC;GACA;GACE;GACV,WAAWC;EACX,CAAA;CACgB,CAAA;AAEpB"}
|