@carlonicora/nextjs-jsonapi 1.111.2 → 1.112.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/generate-web-module/__tests__/container.spec.js +2 -2
- package/dist/scripts/generate-web-module/__tests__/container.spec.js.map +1 -1
- package/dist/scripts/generate-web-module/__tests__/parent-detector.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/parent-detector.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/parent-detector.spec.js +17 -0
- package/dist/scripts/generate-web-module/__tests__/parent-detector.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js +1 -2
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js.map +1 -1
- package/dist/scripts/generate-web-module/__tests__/selector.spec.js +19 -14
- package/dist/scripts/generate-web-module/__tests__/selector.spec.js.map +1 -1
- package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/generator.js +7 -2
- package/dist/scripts/generate-web-module/generator.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/content.template.js +58 -8
- package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +3 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts +4 -0
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.js +55 -48
- package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/module.template.js +5 -3
- package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/table-hook.template.js +6 -6
- package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js +1 -0
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts +12 -5
- package/dist/scripts/generate-web-module/transformers/parent-detector.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/parent-detector.js +13 -13
- package/dist/scripts/generate-web-module/transformers/parent-detector.js.map +1 -1
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts +6 -3
- package/dist/scripts/generate-web-module/types/field-mapping.types.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/types/field-mapping.types.js +17 -3
- package/dist/scripts/generate-web-module/types/field-mapping.types.js.map +1 -1
- package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/utils/i18n-updater.js +25 -26
- package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -1
- package/dist/scripts/generate-web-module/validators/json-schema-validator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/validators/json-schema-validator.js +3 -2
- package/dist/scripts/generate-web-module/validators/json-schema-validator.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-web-module/__tests__/container.spec.ts +2 -2
- package/scripts/generate-web-module/__tests__/parent-detector.spec.ts +20 -0
- package/scripts/generate-web-module/__tests__/plumbing.spec.ts +1 -2
- package/scripts/generate-web-module/__tests__/selector.spec.ts +19 -14
- package/scripts/generate-web-module/generator.ts +6 -2
- package/scripts/generate-web-module/templates/components/content.template.ts +66 -8
- package/scripts/generate-web-module/templates/components/editor.template.ts +3 -1
- package/scripts/generate-web-module/templates/components/selector.template.ts +55 -48
- package/scripts/generate-web-module/templates/module.template.ts +6 -3
- package/scripts/generate-web-module/templates/table-hook.template.ts +6 -6
- package/scripts/generate-web-module/transformers/i18n-generator.ts +1 -0
- package/scripts/generate-web-module/transformers/parent-detector.ts +13 -14
- package/scripts/generate-web-module/types/field-mapping.types.ts +16 -3
- package/scripts/generate-web-module/utils/i18n-updater.ts +25 -30
- package/scripts/generate-web-module/validators/json-schema-validator.ts +3 -2
|
@@ -95,8 +95,9 @@ function validateJsonSchema(schema) {
|
|
|
95
95
|
severity: "error",
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
-
// Validate known types
|
|
99
|
-
|
|
98
|
+
// Validate known types (mirrors the backend CypherType set plus the
|
|
99
|
+
// frontend-facing "blocknote" rich-text marker)
|
|
100
|
+
const validTypes = ["string", "number", "boolean", "date", "datetime", "json", "any", "blocknote"];
|
|
100
101
|
if (field.type && !validTypes.includes(field.type)) {
|
|
101
102
|
errors.push({
|
|
102
103
|
field: `fields[${index}].type`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-schema-validator.js","sourceRoot":"","sources":["../../../../scripts/generate-web-module/validators/json-schema-validator.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAgBH,
|
|
1
|
+
{"version":3,"file":"json-schema-validator.js","sourceRoot":"","sources":["../../../../scripts/generate-web-module/validators/json-schema-validator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -14,8 +14,8 @@ describe("container template", () => {
|
|
|
14
14
|
expect(out).toContain("<WidgetContent widget={widget} />");
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it("emits an Activity tab
|
|
18
|
-
const out = generateContainerTemplate(makeFrontendData());
|
|
17
|
+
it("emits an Activity tab when containerTabs.activity is true and no Tasks tab", () => {
|
|
18
|
+
const out = generateContainerTemplate(makeFrontendData({ containerTabs: { activity: true, relations: [] } }));
|
|
19
19
|
expect(out).toContain("<ActivityFeed module={Modules.Widget} entityId={widget.id} />");
|
|
20
20
|
expect(out).not.toContain("TaskProvider");
|
|
21
21
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { detectExtendsContent } from "../transformers/parent-detector";
|
|
3
|
+
import { JsonFieldDefinition } from "../types/json-schema.interface";
|
|
4
|
+
|
|
5
|
+
const fields = (names: string[]): JsonFieldDefinition[] =>
|
|
6
|
+
names.map((name) => ({ name, type: "string", nullable: true }));
|
|
7
|
+
|
|
8
|
+
describe("detectExtendsContent", () => {
|
|
9
|
+
it("is opt-in: false when extendsContent is not set, even with a name field", () => {
|
|
10
|
+
expect(detectExtendsContent(fields(["name", "tldr", "abstract"]))).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("is true only when explicitly enabled", () => {
|
|
14
|
+
expect(detectExtendsContent(fields(["name"]), true)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("respects an explicit false", () => {
|
|
18
|
+
expect(detectExtendsContent(fields(["name", "abstract"]), false)).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -3,10 +3,9 @@ import { makeFrontendData, makeRelationship } from "./fixtures";
|
|
|
3
3
|
import { resolveRelationshipKey } from "../transformers/relationship-key";
|
|
4
4
|
|
|
5
5
|
describe("frontend plumbing", () => {
|
|
6
|
-
it("defaults displayProp to name
|
|
6
|
+
it("defaults displayProp to name", () => {
|
|
7
7
|
const d = makeFrontendData();
|
|
8
8
|
expect(d.displayProp).toBe("name");
|
|
9
|
-
expect(d.containerTabs.activity).toBe(true);
|
|
10
9
|
});
|
|
11
10
|
|
|
12
11
|
it("resolves wire key from explicit dtoKey verbatim", () => {
|
|
@@ -2,23 +2,28 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import { makeFrontendData } from "./fixtures";
|
|
3
3
|
import { generateSelectorTemplate } from "../templates/components/selector.template";
|
|
4
4
|
|
|
5
|
-
describe("selector template —
|
|
6
|
-
it("uses
|
|
5
|
+
describe("selector template — UserSelector-style UI", () => {
|
|
6
|
+
it("uses the input-styled trigger + custom search Input (mirrors UserSelector)", () => {
|
|
7
7
|
const out = generateSelectorTemplate(makeFrontendData());
|
|
8
|
-
expect(out).toContain(
|
|
9
|
-
expect(out).toContain("
|
|
10
|
-
expect(out).toContain("
|
|
11
|
-
expect(out).toContain("
|
|
12
|
-
expect(out).toContain("
|
|
13
|
-
expect(out).toContain("
|
|
14
|
-
|
|
8
|
+
expect(out).toContain("CircleX");
|
|
9
|
+
expect(out).toContain("SearchIcon");
|
|
10
|
+
expect(out).toContain("RefreshCwIcon");
|
|
11
|
+
expect(out).toContain("bg-input/20");
|
|
12
|
+
expect(out).toContain("w-(--anchor-width)");
|
|
13
|
+
expect(out).toContain("ui.search.placeholder");
|
|
14
|
+
// custom <Input> search, not the cmdk CommandInput
|
|
15
|
+
expect(out).toContain("<Input");
|
|
16
|
+
expect(out).toContain("CommandList");
|
|
17
|
+
expect(out).toContain("CommandItem");
|
|
15
18
|
});
|
|
16
19
|
|
|
17
|
-
it("drops the legacy
|
|
20
|
+
it("drops the legacy Button-combobox styling", () => {
|
|
18
21
|
const out = generateSelectorTemplate(makeFrontendData());
|
|
19
|
-
expect(out).not.toContain("
|
|
20
|
-
expect(out).not.toContain("
|
|
21
|
-
expect(out).not.toContain("
|
|
22
|
-
expect(out).not.toContain("
|
|
22
|
+
expect(out).not.toContain('role="combobox"');
|
|
23
|
+
expect(out).not.toContain("ChevronsUpDown");
|
|
24
|
+
expect(out).not.toContain("CommandInput");
|
|
25
|
+
expect(out).not.toContain("CommandEmpty");
|
|
26
|
+
expect(out).not.toContain("CommandGroup");
|
|
27
|
+
expect(out).not.toContain("w-(--radix-popover-trigger-width)");
|
|
23
28
|
});
|
|
24
29
|
});
|
|
@@ -240,7 +240,9 @@ function buildTemplateData(schema: JsonModuleDefinition, targetHasNameMap?: Map<
|
|
|
240
240
|
featureId: schema.featureId,
|
|
241
241
|
displayProp: schema.displayProp ?? (allFields.some((f) => f.name === "name") ? "name" : "id"),
|
|
242
242
|
containerTabs: {
|
|
243
|
-
|
|
243
|
+
// Opt-in: the Activity tab imports an app-level ActivityFeed feature that
|
|
244
|
+
// many apps don't have. Only emit it when the schema explicitly asks.
|
|
245
|
+
activity: schema.containerTabs?.activity === true,
|
|
244
246
|
relations: schema.containerTabs?.relations ?? [],
|
|
245
247
|
},
|
|
246
248
|
relatedInclusions: schema.inclusions?.related ?? [],
|
|
@@ -261,8 +263,10 @@ function buildTableFieldNames(schema: JsonModuleDefinition, extendsContent: bool
|
|
|
261
263
|
fieldNames.push("authors");
|
|
262
264
|
}
|
|
263
265
|
|
|
264
|
-
// Add custom fields (excluding certain ones
|
|
266
|
+
// Add custom fields (excluding certain ones, and rich-text BlockNote fields
|
|
267
|
+
// which hold stringified JSON and must not become table columns)
|
|
265
268
|
schema.fields.forEach((f) => {
|
|
269
|
+
if (f.type === "blocknote") return;
|
|
266
270
|
if (!["id", "name", "tldr", "abstract", "content", "createdAt", "updatedAt"].includes(f.name)) {
|
|
267
271
|
fieldNames.push(f.name);
|
|
268
272
|
}
|
|
@@ -40,9 +40,64 @@ export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}Cont
|
|
|
40
40
|
`;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// Rich-text (BlockNote) fields each render as their own labelled section.
|
|
44
|
+
const richFields = data.fields.filter((f) => f.isContentField);
|
|
44
45
|
const attributeElements = generateAttributeElements(data);
|
|
46
|
+
const hasAttributes = attributeElements.trim().length > 0;
|
|
47
|
+
|
|
48
|
+
// When the module has one or more BlockNote fields, render a stacked layout of
|
|
49
|
+
// labelled editors (read-only display), followed by an attribute grid if any
|
|
50
|
+
// plain fields remain.
|
|
51
|
+
if (richFields.length > 0) {
|
|
52
|
+
const richSections = richFields
|
|
53
|
+
.map(
|
|
54
|
+
(field) => ` <div className="flex flex-col gap-y-3">
|
|
55
|
+
<h3 className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
|
|
56
|
+
{t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`)}
|
|
57
|
+
</h3>
|
|
58
|
+
<Card className="flex w-full flex-col p-4">
|
|
59
|
+
<BlockNoteEditorContainer id={${camel}.id} type="${camel}" initialContent={${camel}.${field.name}} />
|
|
60
|
+
</Card>
|
|
61
|
+
</div>`,
|
|
62
|
+
)
|
|
63
|
+
.join("\n");
|
|
64
|
+
|
|
65
|
+
const attributesBlock = hasAttributes
|
|
66
|
+
? `\n <div className="flex flex-col gap-y-3">
|
|
67
|
+
<h3 className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
|
|
68
|
+
{t(\`features.${names.camelCase.toLowerCase()}.sections.details\`)}
|
|
69
|
+
</h3>
|
|
70
|
+
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
71
|
+
${attributeElements}
|
|
72
|
+
</div>
|
|
73
|
+
</div>`
|
|
74
|
+
: "";
|
|
45
75
|
|
|
76
|
+
const importLine = hasAttributes
|
|
77
|
+
? `import { AttributeElement, BlockNoteEditorContainer, Card } from "@carlonicora/nextjs-jsonapi/components";`
|
|
78
|
+
: `import { BlockNoteEditorContainer, Card } from "@carlonicora/nextjs-jsonapi/components";`;
|
|
79
|
+
|
|
80
|
+
return `"use client";
|
|
81
|
+
|
|
82
|
+
import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
83
|
+
${importLine}
|
|
84
|
+
import { useTranslations } from "next-intl";
|
|
85
|
+
|
|
86
|
+
type ${names.pascalCase}ContentProps = { ${camel}: ${names.pascalCase}Interface };
|
|
87
|
+
|
|
88
|
+
export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}ContentProps) {
|
|
89
|
+
const t = useTranslations();
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="flex flex-col gap-y-8">
|
|
93
|
+
${richSections}${attributesBlock}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Non-content modules render an AttributeElement layout of their fields.
|
|
46
101
|
return `"use client";
|
|
47
102
|
|
|
48
103
|
import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
@@ -58,7 +113,7 @@ export function ${names.pascalCase}Content({ ${camel} }: ${names.pascalCase}Cont
|
|
|
58
113
|
<div className="flex flex-col gap-y-8">
|
|
59
114
|
<div className="flex flex-col gap-y-3">
|
|
60
115
|
<h3 className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
|
|
61
|
-
{t(\`features.${names.camelCase}.sections.details\`)}
|
|
116
|
+
{t(\`features.${names.camelCase.toLowerCase()}.sections.details\`)}
|
|
62
117
|
</h3>
|
|
63
118
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
64
119
|
${attributeElements}
|
|
@@ -81,23 +136,26 @@ function generateAttributeElements(data: FrontendTemplateData): string {
|
|
|
81
136
|
const camel = names.camelCase;
|
|
82
137
|
const elements: string[] = [];
|
|
83
138
|
|
|
84
|
-
// Add custom fields (excluding name, tldr, abstract, content
|
|
85
|
-
|
|
139
|
+
// Add custom fields (excluding name, tldr, abstract, content and rich-text
|
|
140
|
+
// BlockNote fields, which are rendered as their own sections elsewhere)
|
|
141
|
+
const displayableFields = fields.filter(
|
|
142
|
+
(f) => !f.isContentField && !["name", "tldr", "abstract", "content", "id"].includes(f.name),
|
|
143
|
+
);
|
|
86
144
|
|
|
87
145
|
displayableFields.forEach((field) => {
|
|
88
146
|
if (field.type === "string") {
|
|
89
147
|
elements.push(` <AttributeElement
|
|
90
|
-
title={t(\`features.${names.camelCase}.fields.${field.name}.label\`)}
|
|
148
|
+
title={t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`)}
|
|
91
149
|
value={${camel}.${field.name}}
|
|
92
150
|
/>`);
|
|
93
151
|
} else if (field.type === "boolean") {
|
|
94
152
|
elements.push(` <AttributeElement
|
|
95
|
-
title={t(\`features.${names.camelCase}.fields.${field.name}.label\`)}
|
|
153
|
+
title={t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`)}
|
|
96
154
|
value={${camel}.${field.name} ? t(\`generic.yes\`) : t(\`generic.no\`)}
|
|
97
155
|
/>`);
|
|
98
156
|
} else if (field.type === "number") {
|
|
99
157
|
elements.push(` <AttributeElement
|
|
100
|
-
title={t(\`features.${names.camelCase}.fields.${field.name}.label\`)}
|
|
158
|
+
title={t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`)}
|
|
101
159
|
value={${camel}.${field.name}?.toString()}
|
|
102
160
|
/>`);
|
|
103
161
|
}
|
|
@@ -112,7 +170,7 @@ function generateAttributeElements(data: FrontendTemplateData): string {
|
|
|
112
170
|
const propName = toCamelCase(rel.alias || rel.variant || rel.name);
|
|
113
171
|
elements.push(` {${camel}.${propName} && (
|
|
114
172
|
<AttributeElement
|
|
115
|
-
title={t(\`
|
|
173
|
+
title={t(\`features.${names.camelCase.toLowerCase()}.relationships.${propName.toLowerCase()}.label\`)}
|
|
116
174
|
value={${camel}.${propName}.${displayProp}}
|
|
117
175
|
/>
|
|
118
176
|
)}`);
|
|
@@ -201,7 +201,9 @@ function generateImports(data: FrontendTemplateData): string {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
// Check for description/textarea fields
|
|
204
|
-
|
|
204
|
+
// Rich-text (BlockNote) fields render as FormBlockNote even when named
|
|
205
|
+
// "description", so they must not pull in FormTextarea.
|
|
206
|
+
const hasTextareaFields = fields.some((f) => !f.isContentField && (f.name === "description" || f.type === "textarea"));
|
|
205
207
|
if (hasTextareaFields) {
|
|
206
208
|
componentImports.push("FormTextarea");
|
|
207
209
|
}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Selector Template
|
|
3
3
|
*
|
|
4
4
|
* Generates {Module}Selector.tsx searchable combobox component.
|
|
5
|
+
*
|
|
6
|
+
* UI mirrors the framework's UserSelector (packages/.../features/user/components/forms/UserSelector.tsx):
|
|
7
|
+
* an input-styled trigger, a CircleX clear affordance, an `--anchor-width` popover, and a custom
|
|
8
|
+
* search Input with a SearchIcon + spinner/clear — minus the user-specific avatar.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import { FrontendTemplateData } from "../../types/template-data.interface";
|
|
@@ -21,25 +25,21 @@ export function generateSelectorTemplate(data: FrontendTemplateData): string {
|
|
|
21
25
|
return `"use client";
|
|
22
26
|
|
|
23
27
|
import {
|
|
24
|
-
Button,
|
|
25
28
|
Command,
|
|
26
|
-
CommandEmpty,
|
|
27
|
-
CommandGroup,
|
|
28
|
-
CommandInput,
|
|
29
29
|
CommandItem,
|
|
30
30
|
CommandList,
|
|
31
31
|
FormFieldWrapper,
|
|
32
|
+
Input,
|
|
32
33
|
Popover,
|
|
33
34
|
PopoverContent,
|
|
34
35
|
PopoverTrigger,
|
|
35
36
|
} from "@carlonicora/nextjs-jsonapi/components";
|
|
36
37
|
import { ${names.pascalCase}Interface } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Interface";
|
|
37
38
|
import { ${names.pascalCase}Service } from "@/features/${data.importTargetDir}/${names.kebabCase}/data/${names.pascalCase}Service";
|
|
38
|
-
import { DataListRetriever, useDataListRetriever } from "@carlonicora/nextjs-jsonapi/client";
|
|
39
|
-
import { useDebounce } from "@carlonicora/nextjs-jsonapi/client";
|
|
39
|
+
import { DataListRetriever, useDataListRetriever, useDebounce } from "@carlonicora/nextjs-jsonapi/client";
|
|
40
40
|
import { Modules } from "@carlonicora/nextjs-jsonapi/core";
|
|
41
41
|
|
|
42
|
-
import {
|
|
42
|
+
import { CircleX, RefreshCwIcon, SearchIcon, XIcon } from "lucide-react";
|
|
43
43
|
import { useTranslations } from "next-intl";
|
|
44
44
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
45
45
|
|
|
@@ -121,55 +121,62 @@ export default function ${names.pascalCase}Selector({
|
|
|
121
121
|
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
|
122
122
|
<div className="flex w-full flex-row items-center justify-between">
|
|
123
123
|
<PopoverTrigger className="w-full">
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
</
|
|
124
|
+
<div className="flex w-full flex-row items-center justify-start rounded-md">
|
|
125
|
+
{field.value ? (
|
|
126
|
+
<div className="bg-input/20 dark:bg-input/30 border-input flex h-7 w-full flex-row items-center justify-start rounded-md border px-2 py-0.5 text-sm md:text-xs/relaxed">
|
|
127
|
+
<span>{field.value?.name ?? ""}</span>
|
|
128
|
+
</div>
|
|
129
|
+
) : (
|
|
130
|
+
<div className="bg-input/20 dark:bg-input/30 border-input text-muted-foreground flex h-7 w-full flex-row items-center justify-start rounded-md border px-2 py-0.5 text-sm md:text-xs/relaxed">
|
|
131
|
+
{placeholder ?? t(\`ui.search.placeholder\`, { type: t(\`entities.${i18nKey}\`, { count: 1 }) })}
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
135
|
</PopoverTrigger>
|
|
136
136
|
{field.value && (
|
|
137
|
-
<
|
|
138
|
-
className="text-muted
|
|
137
|
+
<CircleX
|
|
138
|
+
className="text-muted hover:text-destructive ml-2 h-4 w-4 shrink-0 cursor-pointer"
|
|
139
139
|
onClick={() => set${names.pascalCase}()}
|
|
140
140
|
/>
|
|
141
141
|
)}
|
|
142
142
|
</div>
|
|
143
|
-
<PopoverContent className="w-(--
|
|
143
|
+
<PopoverContent align="start" className="w-(--anchor-width)">
|
|
144
144
|
<Command shouldFilter={false}>
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
className="hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent cursor-pointer"
|
|
167
|
-
>
|
|
168
|
-
{${names.camelCase}.${displayProp}}
|
|
169
|
-
</CommandItem>
|
|
170
|
-
))}
|
|
171
|
-
</CommandGroup>
|
|
145
|
+
<div className="relative mb-2 w-full">
|
|
146
|
+
<SearchIcon className="text-muted-foreground absolute top-2.5 left-2.5 h-4 w-4" />
|
|
147
|
+
<Input
|
|
148
|
+
placeholder={t(\`ui.search.placeholder\`, { type: t(\`entities.${i18nKey}\`, { count: 1 }) })}
|
|
149
|
+
type="text"
|
|
150
|
+
className="w-full pr-8 pl-8"
|
|
151
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
152
|
+
value={searchTerm}
|
|
153
|
+
/>
|
|
154
|
+
{isSearching ? (
|
|
155
|
+
<RefreshCwIcon className="text-muted-foreground absolute top-2.5 right-2.5 h-4 w-4 animate-spin" />
|
|
156
|
+
) : searchTermRef.current ? (
|
|
157
|
+
<XIcon
|
|
158
|
+
className={\`absolute top-2.5 right-2.5 h-4 w-4 \${searchTermRef.current ? "cursor-pointer" : "text-muted-foreground"}\`}
|
|
159
|
+
onClick={() => {
|
|
160
|
+
setSearchTerm("");
|
|
161
|
+
search("");
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
) : (
|
|
165
|
+
<></>
|
|
172
166
|
)}
|
|
167
|
+
</div>
|
|
168
|
+
<CommandList>
|
|
169
|
+
{data.data &&
|
|
170
|
+
data.data.length > 0 &&
|
|
171
|
+
(data.data as ${names.pascalCase}Interface[]).map((${names.camelCase}: ${names.pascalCase}Interface) => (
|
|
172
|
+
<CommandItem
|
|
173
|
+
className="cursor-pointer hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent"
|
|
174
|
+
key={${names.camelCase}.id}
|
|
175
|
+
onSelect={() => set${names.pascalCase}(${names.camelCase})}
|
|
176
|
+
>
|
|
177
|
+
<span className="">{${names.camelCase}.${displayProp}}</span>
|
|
178
|
+
</CommandItem>
|
|
179
|
+
))}
|
|
173
180
|
</CommandList>
|
|
174
181
|
</Command>
|
|
175
182
|
</PopoverContent>
|
|
@@ -24,6 +24,10 @@ export function generateModuleTemplate(data: FrontendTemplateData): string {
|
|
|
24
24
|
const featureImport = data.featureId ? `import { FeatureIds } from "@/enums/feature.ids";\n` : "";
|
|
25
25
|
const featureLine = data.featureId ? ` feature: FeatureIds.${data.featureId},\n` : "";
|
|
26
26
|
|
|
27
|
+
// The author/editor (users) inclusion only applies to Content-extending
|
|
28
|
+
// modules — standalone modules have no author relationship to include.
|
|
29
|
+
const usersInclusionLine = extendsContent ? `\n createJsonApiInclusion("users", [\`name\`, \`avatar\`]),` : "";
|
|
30
|
+
|
|
27
31
|
// Cross-module inclusions for related entities (optional)
|
|
28
32
|
const relatedInclusionLines = data.relatedInclusions
|
|
29
33
|
.map((inc) => ` createJsonApiInclusion("${inc.endpoint}", [${inc.fields.map((f) => `\`${f}\``).join(", ")}]),`)
|
|
@@ -38,15 +42,14 @@ ${featureImport}
|
|
|
38
42
|
export const ${names.pascalCase}Module = (factory: ModuleFactory) =>
|
|
39
43
|
factory({
|
|
40
44
|
pageUrl: "/${names.pluralKebab}",
|
|
41
|
-
name: "${
|
|
45
|
+
name: "${endpoint}",
|
|
42
46
|
model: ${names.pascalCase},
|
|
43
47
|
moduleId: "${moduleId}",
|
|
44
48
|
icon: ${DEFAULT_MODULE_ICON},
|
|
45
49
|
${featureLine} inclusions: {
|
|
46
50
|
lists: {
|
|
47
51
|
fields: [
|
|
48
|
-
createJsonApiInclusion("${endpoint}", [${listInclusionFields}])
|
|
49
|
-
createJsonApiInclusion("users", [\`name\`, \`avatar\`]),${relatedInclusionBlock}
|
|
52
|
+
createJsonApiInclusion("${endpoint}", [${listInclusionFields}]),${usersInclusionLine}${relatedInclusionBlock}
|
|
50
53
|
],
|
|
51
54
|
},
|
|
52
55
|
},
|
|
@@ -104,7 +104,7 @@ function generateFieldColumnEntries(data: FrontendTemplateData): string {
|
|
|
104
104
|
entries.push(` [${names.pascalCase}Fields.name]: () => ({
|
|
105
105
|
id: "name",
|
|
106
106
|
accessorKey: "name",
|
|
107
|
-
header: t(\`features.${names.camelCase}.fields.name.label\`),
|
|
107
|
+
header: t(\`features.${names.camelCase.toLowerCase()}.fields.name.label\`),
|
|
108
108
|
cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
|
|
109
109
|
const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
|
|
110
110
|
return (
|
|
@@ -151,7 +151,7 @@ function generateFieldColumnEntries(data: FrontendTemplateData): string {
|
|
|
151
151
|
entries.push(` [${names.pascalCase}Fields.${field.name}]: () => ({
|
|
152
152
|
id: "${field.name}",
|
|
153
153
|
accessorKey: "${field.name}",
|
|
154
|
-
header: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
|
|
154
|
+
header: t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`),
|
|
155
155
|
cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
|
|
156
156
|
const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
|
|
157
157
|
return <span>{${names.camelCase}.${field.name}}</span>;
|
|
@@ -163,7 +163,7 @@ function generateFieldColumnEntries(data: FrontendTemplateData): string {
|
|
|
163
163
|
entries.push(` [${names.pascalCase}Fields.${field.name}]: () => ({
|
|
164
164
|
id: "${field.name}",
|
|
165
165
|
accessorKey: "${field.name}",
|
|
166
|
-
header: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
|
|
166
|
+
header: t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`),
|
|
167
167
|
cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
|
|
168
168
|
const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
|
|
169
169
|
return <span>{${names.camelCase}.${field.name} ? t(\`generic.yes\`) : t(\`generic.no\`)}</span>;
|
|
@@ -175,7 +175,7 @@ function generateFieldColumnEntries(data: FrontendTemplateData): string {
|
|
|
175
175
|
entries.push(` [${names.pascalCase}Fields.${field.name}]: () => ({
|
|
176
176
|
id: "${field.name}",
|
|
177
177
|
accessorKey: "${field.name}",
|
|
178
|
-
header: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
|
|
178
|
+
header: t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`),
|
|
179
179
|
cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
|
|
180
180
|
const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
|
|
181
181
|
return <span>{${names.camelCase}.${field.name}?.toString()}</span>;
|
|
@@ -187,7 +187,7 @@ function generateFieldColumnEntries(data: FrontendTemplateData): string {
|
|
|
187
187
|
entries.push(` [${names.pascalCase}Fields.${field.name}]: () =>
|
|
188
188
|
cellDate({
|
|
189
189
|
name: "${field.name}",
|
|
190
|
-
title: t(\`features.${names.camelCase}.fields.${field.name}.label\`),
|
|
190
|
+
title: t(\`features.${names.camelCase.toLowerCase()}.fields.${field.name}.label\`),
|
|
191
191
|
}),`);
|
|
192
192
|
}
|
|
193
193
|
});
|
|
@@ -205,7 +205,7 @@ function generateFieldColumnEntries(data: FrontendTemplateData): string {
|
|
|
205
205
|
entries.push(` [${names.pascalCase}Fields.${key}]: () => ({
|
|
206
206
|
id: "${key}",
|
|
207
207
|
accessorKey: "${key}",
|
|
208
|
-
header: t(\`
|
|
208
|
+
header: t(\`features.${names.camelCase.toLowerCase()}.relationships.${key.toLowerCase()}.label\`),
|
|
209
209
|
cell: ({ row }: { row: TableContent<${names.pascalCase}Interface> }) => {
|
|
210
210
|
const ${names.camelCase}: ${names.pascalCase}Interface = row.original.jsonApiData;
|
|
211
211
|
return <span>{${accessor}}</span>;
|
|
@@ -6,24 +6,23 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { JsonFieldDefinition } from "../types/json-schema.interface";
|
|
9
|
-
import { CONTENT_INDICATOR_FIELDS } from "../types/field-mapping.types";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
* Check if a module should extend Content
|
|
11
|
+
* Check if a module should extend the Content base.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Opt-in ONLY: a module extends Content solely when the schema explicitly sets
|
|
14
|
+
* `extendsContent: true`. The previous heuristic auto-enabled it whenever a
|
|
15
|
+
* `name` (or `tldr`/`abstract`) field was present — i.e. for almost every
|
|
16
|
+
* entity — which forced apps that have no Content base feature to import one
|
|
17
|
+
* that doesn't exist. Rich-text editing no longer depends on Content: use a
|
|
18
|
+
* field of `type: "blocknote"` to get a BlockNote editor on a standalone model.
|
|
19
|
+
*
|
|
20
|
+
* @param _fields - Field definitions (unused; kept for signature stability)
|
|
21
|
+
* @param explicitValue - Explicit `extendsContent` value from the JSON schema
|
|
22
|
+
* @returns true only when explicitly opted in
|
|
17
23
|
*/
|
|
18
|
-
export function detectExtendsContent(
|
|
19
|
-
|
|
20
|
-
if (explicitValue !== undefined) {
|
|
21
|
-
return explicitValue;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Otherwise, auto-detect based on fields
|
|
25
|
-
const fieldNames = fields.map((f) => f.name);
|
|
26
|
-
return CONTENT_INDICATOR_FIELDS.some((indicator) => fieldNames.includes(indicator));
|
|
24
|
+
export function detectExtendsContent(_fields: JsonFieldDefinition[], explicitValue?: boolean): boolean {
|
|
25
|
+
return explicitValue === true;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
/**
|