@betterstart/cli 0.1.70 → 0.1.71
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/cli.js +207 -130
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6969,6 +6969,180 @@ ${hasDraft ? ` <Button
|
|
|
6969
6969
|
// src/generators/form/form-single.ts
|
|
6970
6970
|
import fs21 from "fs";
|
|
6971
6971
|
import path21 from "path";
|
|
6972
|
+
function parseCardGroups(allFormFields) {
|
|
6973
|
+
const groups = [];
|
|
6974
|
+
let currentFields = [];
|
|
6975
|
+
let nextTitle = "";
|
|
6976
|
+
let nextDescription = "";
|
|
6977
|
+
for (const field of allFormFields) {
|
|
6978
|
+
if (field.type === "break") {
|
|
6979
|
+
if (currentFields.length > 0) {
|
|
6980
|
+
groups.push(buildGroup(currentFields, nextTitle, nextDescription));
|
|
6981
|
+
}
|
|
6982
|
+
nextTitle = field.label || "";
|
|
6983
|
+
nextDescription = field.hint || "";
|
|
6984
|
+
currentFields = [];
|
|
6985
|
+
continue;
|
|
6986
|
+
}
|
|
6987
|
+
if (field.type === "separator") continue;
|
|
6988
|
+
currentFields.push(field);
|
|
6989
|
+
}
|
|
6990
|
+
if (currentFields.length > 0) {
|
|
6991
|
+
groups.push(buildGroup(currentFields, nextTitle, nextDescription));
|
|
6992
|
+
}
|
|
6993
|
+
return groups;
|
|
6994
|
+
}
|
|
6995
|
+
function buildGroup(fields, titleOverride, description) {
|
|
6996
|
+
const firstField = fields[0];
|
|
6997
|
+
const title = titleOverride || firstField.label || firstField.name;
|
|
6998
|
+
const varPrefix = toCamelCase(firstField.name);
|
|
6999
|
+
return {
|
|
7000
|
+
title,
|
|
7001
|
+
description,
|
|
7002
|
+
varPrefix,
|
|
7003
|
+
componentName: `${toPascalCase(firstField.name)}Card`,
|
|
7004
|
+
fields,
|
|
7005
|
+
flatFields: flattenFields(fields).filter((f) => !isLayoutField(f.type))
|
|
7006
|
+
};
|
|
7007
|
+
}
|
|
7008
|
+
function analyzeGroup(fields) {
|
|
7009
|
+
const tabFieldNames = /* @__PURE__ */ new Set();
|
|
7010
|
+
for (const f of fields) {
|
|
7011
|
+
if (f.type === "tabs" && f.tabs) {
|
|
7012
|
+
for (const tab of f.tabs) {
|
|
7013
|
+
if (tab.fields) for (const tf of tab.fields) tabFieldNames.add(tf.name);
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
7017
|
+
const relFields = collectRelationshipFields(fields, tabFieldNames);
|
|
7018
|
+
const listFieldsWithNested = [];
|
|
7019
|
+
function collectLists(flds) {
|
|
7020
|
+
for (const f of flds) {
|
|
7021
|
+
if (f.type === "list" && f.fields && f.fields.length > 0 && !f.hidden) {
|
|
7022
|
+
listFieldsWithNested.push(f);
|
|
7023
|
+
}
|
|
7024
|
+
if (f.type === "group" && f.fields) collectLists(f.fields);
|
|
7025
|
+
if (f.type === "tabs" && f.tabs) {
|
|
7026
|
+
for (const tab of f.tabs) {
|
|
7027
|
+
if (tab.fields) collectLists(tab.fields);
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
collectLists(fields);
|
|
7033
|
+
const hasTabsField = fields.some((f) => f.type === "tabs");
|
|
7034
|
+
const tabsField = fields.find((f) => f.type === "tabs");
|
|
7035
|
+
const firstTabName = tabsField?.tabs?.[0]?.name || "";
|
|
7036
|
+
return { relFields, listFieldsWithNested, hasTabsField, tabsField, firstTabName, tabFieldNames };
|
|
7037
|
+
}
|
|
7038
|
+
function buildGroupFieldsJSX(group3, analysis, skipLabel) {
|
|
7039
|
+
const indent = " ";
|
|
7040
|
+
return group3.fields.map((f) => {
|
|
7041
|
+
if (f.type === "tabs" && f.tabs) {
|
|
7042
|
+
const tabsList = f.tabs.map((t) => ` <TabsTrigger value="${t.name}">${t.label}</TabsTrigger>`).join("\n");
|
|
7043
|
+
const tabsContent = f.tabs.map((t) => {
|
|
7044
|
+
const tabFields = (t.fields || []).map((tf) => generateFieldJSX2(tf, " ")).join("\n");
|
|
7045
|
+
return ` <TabsContent value="${t.name}" className="space-y-6">
|
|
7046
|
+
${tabFields}
|
|
7047
|
+
</TabsContent>`;
|
|
7048
|
+
}).join("\n");
|
|
7049
|
+
return `${indent}<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
7050
|
+
<TabsList>
|
|
7051
|
+
${tabsList}
|
|
7052
|
+
</TabsList>
|
|
7053
|
+
${tabsContent}
|
|
7054
|
+
${indent}</Tabs>`;
|
|
7055
|
+
}
|
|
7056
|
+
if (analysis.tabFieldNames.has(f.name)) return "";
|
|
7057
|
+
return generateFieldJSX2(f, indent, { skipLabel });
|
|
7058
|
+
}).filter(Boolean).join("\n");
|
|
7059
|
+
}
|
|
7060
|
+
function buildGroupRelState(analysis) {
|
|
7061
|
+
return analysis.relFields.map((f) => {
|
|
7062
|
+
const relPlural = toPascalCase(pluralize(f.relationship || ""));
|
|
7063
|
+
return ` const [${f.name}Open, set${toPascalCase(f.name)}Open] = React.useState(false)
|
|
7064
|
+
const { data: ${f.relationship}Data } = use${relPlural}()`;
|
|
7065
|
+
}).filter((v, i, a) => a.indexOf(v) === i).join("\n");
|
|
7066
|
+
}
|
|
7067
|
+
function buildGroupFieldArrayHooks(analysis) {
|
|
7068
|
+
return analysis.listFieldsWithNested.map((field) => {
|
|
7069
|
+
const pascalFieldName = toPascalCase(field.name);
|
|
7070
|
+
return ` const [${field.name}Expanded, set${pascalFieldName}Expanded] = React.useState<string | undefined>(undefined)
|
|
7071
|
+
const ${field.name}FieldArray = useFieldArray({
|
|
7072
|
+
control: form.control,
|
|
7073
|
+
name: '${field.name}'
|
|
7074
|
+
})`;
|
|
7075
|
+
}).join("\n");
|
|
7076
|
+
}
|
|
7077
|
+
function generateCardComponent(group3, schema, analysis) {
|
|
7078
|
+
const singular = singularize(schema.name);
|
|
7079
|
+
const Singular = toPascalCase(singular);
|
|
7080
|
+
const zodFields = buildZodFields2(group3.flatFields);
|
|
7081
|
+
const defaultValues = buildDefaultValues2(group3.flatFields);
|
|
7082
|
+
const isSingleField = group3.fields.length === 1 && group3.title === (group3.fields[0].label || group3.fields[0].name);
|
|
7083
|
+
const fieldsJSX = buildGroupFieldsJSX(group3, analysis, isSingleField);
|
|
7084
|
+
const relState = buildGroupRelState(analysis);
|
|
7085
|
+
const fieldArrayHooks = buildGroupFieldArrayHooks(analysis);
|
|
7086
|
+
const needsReact = analysis.relFields.length > 0 || analysis.listFieldsWithNested.length > 0;
|
|
7087
|
+
const descriptionLine = group3.description ? `
|
|
7088
|
+
<CardDescription>${group3.description}</CardDescription>` : "";
|
|
7089
|
+
return `const ${group3.varPrefix}Schema = z.object({
|
|
7090
|
+
${zodFields}
|
|
7091
|
+
})
|
|
7092
|
+
|
|
7093
|
+
function ${group3.componentName}({ initialData }: { initialData?: ${Singular}Data | null }) {
|
|
7094
|
+
const queryClient = useQueryClient()${analysis.hasTabsField ? `
|
|
7095
|
+
const [activeTab, setActiveTab] = useQueryState('tab', { defaultValue: '${analysis.firstTabName}' })` : ""}${relState ? `
|
|
7096
|
+
${relState}` : ""}
|
|
7097
|
+
|
|
7098
|
+
const mutation = useMutation({
|
|
7099
|
+
mutationFn: (data: Upsert${Singular}Input) => upsert${Singular}(data),
|
|
7100
|
+
onSuccess: () => {
|
|
7101
|
+
toast.success('${group3.title} saved')
|
|
7102
|
+
queryClient.invalidateQueries({ queryKey: ['${schema.name}'] })
|
|
7103
|
+
},
|
|
7104
|
+
onError: (error: Error) => {
|
|
7105
|
+
toast.error(error.message || 'Failed to save')
|
|
7106
|
+
}
|
|
7107
|
+
})
|
|
7108
|
+
|
|
7109
|
+
const isPending = mutation.isPending
|
|
7110
|
+
|
|
7111
|
+
const form = useForm<z.infer<typeof ${group3.varPrefix}Schema>>({
|
|
7112
|
+
resolver: zodResolver(${group3.varPrefix}Schema),
|
|
7113
|
+
defaultValues: {
|
|
7114
|
+
${defaultValues}
|
|
7115
|
+
}
|
|
7116
|
+
})
|
|
7117
|
+
${fieldArrayHooks ? `
|
|
7118
|
+
${fieldArrayHooks}
|
|
7119
|
+
` : ""}
|
|
7120
|
+
return (
|
|
7121
|
+
<Form {...form}>
|
|
7122
|
+
<form onSubmit={form.handleSubmit((values) => {
|
|
7123
|
+
const cleaned = Object.fromEntries(
|
|
7124
|
+
Object.entries(values).map(([key, value]) => [key, value === undefined ? '' : value])
|
|
7125
|
+
)
|
|
7126
|
+
mutation.mutate(cleaned as Upsert${Singular}Input)
|
|
7127
|
+
})}>
|
|
7128
|
+
<Card>
|
|
7129
|
+
<CardHeader>
|
|
7130
|
+
<CardTitle>${group3.title}</CardTitle>${descriptionLine}
|
|
7131
|
+
</CardHeader>
|
|
7132
|
+
<CardContent className="space-y-6">
|
|
7133
|
+
${fieldsJSX}
|
|
7134
|
+
</CardContent>
|
|
7135
|
+
<CardFooter>
|
|
7136
|
+
<Button type="submit" disabled={isPending} size="sm">
|
|
7137
|
+
{isPending ? 'Saving...' : 'Save'}
|
|
7138
|
+
</Button>
|
|
7139
|
+
</CardFooter>
|
|
7140
|
+
</Card>
|
|
7141
|
+
</form>
|
|
7142
|
+
</Form>
|
|
7143
|
+
)
|
|
7144
|
+
}`;
|
|
7145
|
+
}
|
|
6972
7146
|
function generateSingleForm(schema, cwd, pagesDir, options = {}) {
|
|
6973
7147
|
const entityDir = path21.join(cwd, pagesDir, schema.name);
|
|
6974
7148
|
const formFilePath = path21.join(entityDir, `${schema.name}-form.tsx`);
|
|
@@ -6980,15 +7154,7 @@ function generateSingleForm(schema, cwd, pagesDir, options = {}) {
|
|
|
6980
7154
|
const allFormFields = schema.fields.filter(
|
|
6981
7155
|
(f) => !f.primaryKey && f.name !== "createdAt" && f.name !== "updatedAt"
|
|
6982
7156
|
);
|
|
6983
|
-
const
|
|
6984
|
-
const tabFieldNames = /* @__PURE__ */ new Set();
|
|
6985
|
-
for (const f of schema.fields) {
|
|
6986
|
-
if (f.type === "tabs" && f.tabs) {
|
|
6987
|
-
for (const tab of f.tabs) {
|
|
6988
|
-
if (tab.fields) for (const tf of tab.fields) tabFieldNames.add(tf.name);
|
|
6989
|
-
}
|
|
6990
|
-
}
|
|
6991
|
-
}
|
|
7157
|
+
const cardGroups = parseCardGroups(allFormFields);
|
|
6992
7158
|
const hasBoolean = hasFieldType(schema.fields, "boolean");
|
|
6993
7159
|
const hasImage = hasFieldType(schema.fields, "image");
|
|
6994
7160
|
const hasVideo = hasFieldType(schema.fields, "video");
|
|
@@ -7003,48 +7169,20 @@ function generateSingleForm(schema, cwd, pagesDir, options = {}) {
|
|
|
7003
7169
|
const hasSeparator = hasFieldType(schema.fields, "separator");
|
|
7004
7170
|
const hasRelationship = hasFieldType(schema.fields, "relationship");
|
|
7005
7171
|
const hasTabsField = schema.fields.some((f) => f.type === "tabs");
|
|
7006
|
-
const
|
|
7007
|
-
const firstTabName = tabsField?.tabs?.[0]?.name || "";
|
|
7008
|
-
const mainRelFields = collectRelationshipFields(schema.fields, tabFieldNames);
|
|
7009
|
-
const listFieldsWithNested = [];
|
|
7010
|
-
function collectListFieldsSingle(fields) {
|
|
7011
|
-
for (const f of fields) {
|
|
7012
|
-
if (f.type === "list" && f.fields && f.fields.length > 0 && !f.hidden) {
|
|
7013
|
-
listFieldsWithNested.push(f);
|
|
7014
|
-
}
|
|
7015
|
-
if (f.type === "group" && f.fields) collectListFieldsSingle(f.fields);
|
|
7016
|
-
if (f.type === "tabs" && f.tabs) {
|
|
7017
|
-
for (const tab of f.tabs) {
|
|
7018
|
-
if (tab.fields) collectListFieldsSingle(tab.fields);
|
|
7019
|
-
}
|
|
7020
|
-
}
|
|
7021
|
-
}
|
|
7022
|
-
}
|
|
7023
|
-
collectListFieldsSingle(allFormFields);
|
|
7172
|
+
const flatFields = flattenFields(allFormFields);
|
|
7024
7173
|
const hasList = hasFieldType(schema.fields, "list");
|
|
7025
|
-
const hasNestedList = listFieldsWithNested.length > 0;
|
|
7174
|
+
const hasNestedList = cardGroups.some((g) => analyzeGroup(g.fields).listFieldsWithNested.length > 0);
|
|
7026
7175
|
const hasSimpleList = hasList && flatFields.some((f) => f.type === "list" && (!f.fields || f.fields.length === 0));
|
|
7027
|
-
const
|
|
7028
|
-
const
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
return ` <TabsContent value="${t.name}" className="space-y-6 p-6 rounded-2xl border bg-card">
|
|
7035
|
-
${tabFields}
|
|
7036
|
-
</TabsContent>`;
|
|
7037
|
-
}).join("\n");
|
|
7038
|
-
return ` <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
|
7039
|
-
<TabsList>
|
|
7040
|
-
${tabsList}
|
|
7041
|
-
</TabsList>
|
|
7042
|
-
${tabsContent}
|
|
7043
|
-
</Tabs>`;
|
|
7176
|
+
const allRelFields = [];
|
|
7177
|
+
for (const g of cardGroups) {
|
|
7178
|
+
const a = analyzeGroup(g.fields);
|
|
7179
|
+
for (const rf of a.relFields) {
|
|
7180
|
+
if (!allRelFields.some((e) => e.relationship === rf.relationship)) {
|
|
7181
|
+
allRelFields.push(rf);
|
|
7182
|
+
}
|
|
7044
7183
|
}
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
}).filter(Boolean).join("\n");
|
|
7184
|
+
}
|
|
7185
|
+
const needsReact = allRelFields.length > 0 || hasNestedList;
|
|
7048
7186
|
const uiImports = buildUiImports({
|
|
7049
7187
|
hasBoolean,
|
|
7050
7188
|
hasTextarea,
|
|
@@ -7063,30 +7201,29 @@ ${tabsContent}
|
|
|
7063
7201
|
hasSimpleList,
|
|
7064
7202
|
hasNestedList
|
|
7065
7203
|
});
|
|
7204
|
+
uiImports.push(`import {
|
|
7205
|
+
Card,
|
|
7206
|
+
CardContent,
|
|
7207
|
+
CardDescription,
|
|
7208
|
+
CardFooter,
|
|
7209
|
+
CardHeader,
|
|
7210
|
+
CardTitle
|
|
7211
|
+
} from '@cms/components/ui/card'`);
|
|
7066
7212
|
const lucideIcons = [];
|
|
7067
7213
|
if (hasRelationship) lucideIcons.push("Check", "ChevronsUpDown");
|
|
7068
7214
|
if (hasNestedList) {
|
|
7069
7215
|
if (!lucideIcons.includes("Plus")) lucideIcons.push("Plus");
|
|
7070
7216
|
if (!lucideIcons.includes("X")) lucideIcons.push("X");
|
|
7071
7217
|
}
|
|
7072
|
-
const relHookImports =
|
|
7218
|
+
const relHookImports = allRelFields.map((f) => {
|
|
7073
7219
|
const relPlural = toPascalCase(pluralize(f.relationship || ""));
|
|
7074
7220
|
return `import { use${relPlural} } from '@cms/hooks/use-${f.relationship}'`;
|
|
7075
7221
|
}).filter((v, i, a) => a.indexOf(v) === i).join("\n");
|
|
7076
|
-
const
|
|
7077
|
-
const
|
|
7078
|
-
return
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
const fieldArrayHooks = listFieldsWithNested.map((field) => {
|
|
7082
|
-
const pascalFieldName = toPascalCase(field.name);
|
|
7083
|
-
return ` const [${field.name}Expanded, set${pascalFieldName}Expanded] = React.useState<string | undefined>(undefined)
|
|
7084
|
-
const ${field.name}FieldArray = useFieldArray({
|
|
7085
|
-
control: form.control,
|
|
7086
|
-
name: '${field.name}'
|
|
7087
|
-
})`;
|
|
7088
|
-
}).join("\n");
|
|
7089
|
-
const needsReact = mainRelFields.length > 0 || hasNestedList;
|
|
7222
|
+
const cardComponents = cardGroups.map((group3) => {
|
|
7223
|
+
const analysis = analyzeGroup(group3.fields);
|
|
7224
|
+
return generateCardComponent(group3, schema, analysis);
|
|
7225
|
+
});
|
|
7226
|
+
const cardRenders = cardGroups.map((g) => ` <${g.componentName} initialData={initialData} />`).join("\n");
|
|
7090
7227
|
const content = `'use client'
|
|
7091
7228
|
${needsReact ? "\nimport * as React from 'react'" : ""}
|
|
7092
7229
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|
@@ -7103,75 +7240,17 @@ import type {
|
|
|
7103
7240
|
} from '@cms/actions/${schema.name}'
|
|
7104
7241
|
import { upsert${Singular} } from '@cms/actions/${schema.name}'
|
|
7105
7242
|
|
|
7106
|
-
|
|
7107
|
-
${zodFields}
|
|
7108
|
-
})
|
|
7109
|
-
|
|
7110
|
-
export type FormValues = z.infer<typeof formSchema>
|
|
7243
|
+
${cardComponents.join("\n\n")}
|
|
7111
7244
|
|
|
7112
7245
|
interface ${Singular}FormProps {
|
|
7113
7246
|
initialData?: ${Singular}Data | null
|
|
7114
7247
|
}
|
|
7115
7248
|
|
|
7116
7249
|
export function ${Singular}Form({ initialData }: ${Singular}FormProps) {
|
|
7117
|
-
const queryClient = useQueryClient()${hasTabsField ? `
|
|
7118
|
-
const [activeTab, setActiveTab] = useQueryState('tab', { defaultValue: '${firstTabName}' })` : ""}${relState ? `
|
|
7119
|
-
${relState}` : ""}
|
|
7120
|
-
|
|
7121
|
-
const upsertMutation = useMutation({
|
|
7122
|
-
mutationFn: (data: Upsert${Singular}Input) => upsert${Singular}(data),
|
|
7123
|
-
onSuccess: () => {
|
|
7124
|
-
toast.success('${schema.label} saved successfully')
|
|
7125
|
-
queryClient.invalidateQueries({ queryKey: ['${schema.name}'] })
|
|
7126
|
-
},
|
|
7127
|
-
onError: (error: Error) => {
|
|
7128
|
-
toast.error(error.message || 'Failed to save ${schema.label.toLowerCase()}')
|
|
7129
|
-
}
|
|
7130
|
-
})
|
|
7131
|
-
|
|
7132
|
-
const isPending = upsertMutation.isPending
|
|
7133
|
-
|
|
7134
|
-
const form = useForm<FormValues>({
|
|
7135
|
-
resolver: zodResolver(formSchema),
|
|
7136
|
-
defaultValues: {
|
|
7137
|
-
${defaultValues}
|
|
7138
|
-
}
|
|
7139
|
-
})
|
|
7140
|
-
${fieldArrayHooks ? `
|
|
7141
|
-
${fieldArrayHooks}
|
|
7142
|
-
` : ""}
|
|
7143
|
-
function onSubmit(values: FormValues) {
|
|
7144
|
-
const cleanedValues = Object.fromEntries(
|
|
7145
|
-
Object.entries(values).map(([key, value]) => [key, value === undefined ? '' : value])
|
|
7146
|
-
) as FormValues
|
|
7147
|
-
|
|
7148
|
-
upsertMutation.mutate(cleanedValues as Upsert${Singular}Input)
|
|
7149
|
-
}
|
|
7150
|
-
|
|
7151
7250
|
return (
|
|
7152
|
-
<
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
const firstError = Object.values(errors)[0]
|
|
7156
|
-
if (firstError?.message) {
|
|
7157
|
-
toast.error(String(firstError.message))
|
|
7158
|
-
} else {
|
|
7159
|
-
toast.error('Please fix the form errors before submitting')
|
|
7160
|
-
}
|
|
7161
|
-
})} className="space-y-6">
|
|
7162
|
-
<div className="space-y-6 p-6 rounded-2xl border bg-card">
|
|
7163
|
-
${formFieldsJSX}
|
|
7164
|
-
</div>
|
|
7165
|
-
|
|
7166
|
-
<div className="flex items-center fixed bottom-0 md:left-[calc(var(--sidebar-width))] w-screen md:w-[calc(100svw-var(--sidebar-width)-4px)] right-0 bg-secondary border-t">
|
|
7167
|
-
<div className="flex mx-auto py-4 w-full max-w-5xl items-center justify-end gap-2">
|
|
7168
|
-
<Button type="submit" disabled={isPending} size="lg">
|
|
7169
|
-
{isPending ? 'Saving...' : 'Save'}
|
|
7170
|
-
</Button>
|
|
7171
|
-
</div>
|
|
7172
|
-
</div>
|
|
7173
|
-
</form>
|
|
7174
|
-
</Form>
|
|
7251
|
+
<div className="space-y-6">
|
|
7252
|
+
${cardRenders}
|
|
7253
|
+
</div>
|
|
7175
7254
|
)
|
|
7176
7255
|
}
|
|
7177
7256
|
`;
|
|
@@ -9998,14 +10077,12 @@ function defaultSettingsSchema() {
|
|
|
9998
10077
|
name: "settings",
|
|
9999
10078
|
type: "single",
|
|
10000
10079
|
label: "Settings",
|
|
10001
|
-
description: "General
|
|
10080
|
+
description: "General Settings",
|
|
10002
10081
|
icon: "Settings",
|
|
10003
10082
|
fields: [
|
|
10004
10083
|
{ name: "siteName", type: "string", label: "Site Name", default: "BetterStart" },
|
|
10005
|
-
{ name: "
|
|
10006
|
-
{ name: "
|
|
10007
|
-
{ name: "logo", type: "image", label: "Logo" },
|
|
10008
|
-
{ name: "favicon", type: "image", label: "Favicon" }
|
|
10084
|
+
{ name: "break1", type: "break" },
|
|
10085
|
+
{ name: "logo", type: "image", label: "Logo" }
|
|
10009
10086
|
]
|
|
10010
10087
|
},
|
|
10011
10088
|
null,
|