@fnd-platform/cms 1.0.0-alpha.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/LICENSE +21 -0
- package/README.md +283 -0
- package/lib/cms-project.d.ts +127 -0
- package/lib/cms-project.d.ts.map +1 -0
- package/lib/cms-project.js +343 -0
- package/lib/cms-project.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +20 -0
- package/lib/index.js.map +1 -0
- package/lib/options.d.ts +59 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +3 -0
- package/lib/options.js.map +1 -0
- package/lib/templates/admin-breadcrumbs.d.ts +13 -0
- package/lib/templates/admin-breadcrumbs.d.ts.map +1 -0
- package/lib/templates/admin-breadcrumbs.js +80 -0
- package/lib/templates/admin-breadcrumbs.js.map +1 -0
- package/lib/templates/admin-content-route.d.ts +18 -0
- package/lib/templates/admin-content-route.d.ts.map +1 -0
- package/lib/templates/admin-content-route.js +100 -0
- package/lib/templates/admin-content-route.js.map +1 -0
- package/lib/templates/admin-content-type-route.d.ts +9 -0
- package/lib/templates/admin-content-type-route.d.ts.map +1 -0
- package/lib/templates/admin-content-type-route.js +96 -0
- package/lib/templates/admin-content-type-route.js.map +1 -0
- package/lib/templates/admin-header.d.ts +13 -0
- package/lib/templates/admin-header.d.ts.map +1 -0
- package/lib/templates/admin-header.js +123 -0
- package/lib/templates/admin-header.js.map +1 -0
- package/lib/templates/admin-index.d.ts +9 -0
- package/lib/templates/admin-index.d.ts.map +1 -0
- package/lib/templates/admin-index.js +60 -0
- package/lib/templates/admin-index.js.map +1 -0
- package/lib/templates/admin-layout.d.ts +10 -0
- package/lib/templates/admin-layout.d.ts.map +1 -0
- package/lib/templates/admin-layout.js +46 -0
- package/lib/templates/admin-layout.js.map +1 -0
- package/lib/templates/admin-sidebar.d.ts +13 -0
- package/lib/templates/admin-sidebar.d.ts.map +1 -0
- package/lib/templates/admin-sidebar.js +149 -0
- package/lib/templates/admin-sidebar.js.map +1 -0
- package/lib/templates/content-editor.d.ts +10 -0
- package/lib/templates/content-editor.d.ts.map +1 -0
- package/lib/templates/content-editor.js +354 -0
- package/lib/templates/content-editor.js.map +1 -0
- package/lib/templates/content-schema.d.ts +10 -0
- package/lib/templates/content-schema.d.ts.map +1 -0
- package/lib/templates/content-schema.js +274 -0
- package/lib/templates/content-schema.js.map +1 -0
- package/lib/templates/content-table.d.ts +13 -0
- package/lib/templates/content-table.d.ts.map +1 -0
- package/lib/templates/content-table.js +177 -0
- package/lib/templates/content-table.js.map +1 -0
- package/lib/templates/content-types-examples.d.ts +19 -0
- package/lib/templates/content-types-examples.d.ts.map +1 -0
- package/lib/templates/content-types-examples.js +275 -0
- package/lib/templates/content-types-examples.js.map +1 -0
- package/lib/templates/content-types-registry.d.ts +10 -0
- package/lib/templates/content-types-registry.d.ts.map +1 -0
- package/lib/templates/content-types-registry.js +87 -0
- package/lib/templates/content-types-registry.js.map +1 -0
- package/lib/templates/content-types.d.ts +10 -0
- package/lib/templates/content-types.d.ts.map +1 -0
- package/lib/templates/content-types.js +384 -0
- package/lib/templates/content-types.js.map +1 -0
- package/lib/templates/dashboard-stats.d.ts +13 -0
- package/lib/templates/dashboard-stats.d.ts.map +1 -0
- package/lib/templates/dashboard-stats.js +117 -0
- package/lib/templates/dashboard-stats.js.map +1 -0
- package/lib/templates/editor/index.d.ts +6 -0
- package/lib/templates/editor/index.d.ts.map +1 -0
- package/lib/templates/editor/index.js +21 -0
- package/lib/templates/editor/index.js.map +1 -0
- package/lib/templates/editor/rich-text-editor.d.ts +7 -0
- package/lib/templates/editor/rich-text-editor.d.ts.map +1 -0
- package/lib/templates/editor/rich-text-editor.js +115 -0
- package/lib/templates/editor/rich-text-editor.js.map +1 -0
- package/lib/templates/editor/toolbar.d.ts +7 -0
- package/lib/templates/editor/toolbar.d.ts.map +1 -0
- package/lib/templates/editor/toolbar.js +272 -0
- package/lib/templates/editor/toolbar.js.map +1 -0
- package/lib/templates/form-fields/boolean-field.d.ts +7 -0
- package/lib/templates/form-fields/boolean-field.d.ts.map +1 -0
- package/lib/templates/form-fields/boolean-field.js +76 -0
- package/lib/templates/form-fields/boolean-field.js.map +1 -0
- package/lib/templates/form-fields/date-field.d.ts +7 -0
- package/lib/templates/form-fields/date-field.d.ts.map +1 -0
- package/lib/templates/form-fields/date-field.js +61 -0
- package/lib/templates/form-fields/date-field.js.map +1 -0
- package/lib/templates/form-fields/datetime-field.d.ts +7 -0
- package/lib/templates/form-fields/datetime-field.d.ts.map +1 -0
- package/lib/templates/form-fields/datetime-field.js +87 -0
- package/lib/templates/form-fields/datetime-field.js.map +1 -0
- package/lib/templates/form-fields/index.d.ts +23 -0
- package/lib/templates/form-fields/index.d.ts.map +1 -0
- package/lib/templates/form-fields/index.js +275 -0
- package/lib/templates/form-fields/index.js.map +1 -0
- package/lib/templates/form-fields/media-field.d.ts +10 -0
- package/lib/templates/form-fields/media-field.d.ts.map +1 -0
- package/lib/templates/form-fields/media-field.js +225 -0
- package/lib/templates/form-fields/media-field.js.map +1 -0
- package/lib/templates/form-fields/multiselect-field.d.ts +7 -0
- package/lib/templates/form-fields/multiselect-field.d.ts.map +1 -0
- package/lib/templates/form-fields/multiselect-field.js +121 -0
- package/lib/templates/form-fields/multiselect-field.js.map +1 -0
- package/lib/templates/form-fields/number-field.d.ts +7 -0
- package/lib/templates/form-fields/number-field.d.ts.map +1 -0
- package/lib/templates/form-fields/number-field.js +87 -0
- package/lib/templates/form-fields/number-field.js.map +1 -0
- package/lib/templates/form-fields/reference-field.d.ts +9 -0
- package/lib/templates/form-fields/reference-field.d.ts.map +1 -0
- package/lib/templates/form-fields/reference-field.js +145 -0
- package/lib/templates/form-fields/reference-field.js.map +1 -0
- package/lib/templates/form-fields/richtext-field.d.ts +9 -0
- package/lib/templates/form-fields/richtext-field.d.ts.map +1 -0
- package/lib/templates/form-fields/richtext-field.js +60 -0
- package/lib/templates/form-fields/richtext-field.js.map +1 -0
- package/lib/templates/form-fields/select-field.d.ts +7 -0
- package/lib/templates/form-fields/select-field.d.ts.map +1 -0
- package/lib/templates/form-fields/select-field.js +70 -0
- package/lib/templates/form-fields/select-field.js.map +1 -0
- package/lib/templates/form-fields/slug-field.d.ts +7 -0
- package/lib/templates/form-fields/slug-field.d.ts.map +1 -0
- package/lib/templates/form-fields/slug-field.js +143 -0
- package/lib/templates/form-fields/slug-field.js.map +1 -0
- package/lib/templates/form-fields/tags-field.d.ts +7 -0
- package/lib/templates/form-fields/tags-field.d.ts.map +1 -0
- package/lib/templates/form-fields/tags-field.js +172 -0
- package/lib/templates/form-fields/tags-field.js.map +1 -0
- package/lib/templates/form-fields/text-field.d.ts +7 -0
- package/lib/templates/form-fields/text-field.d.ts.map +1 -0
- package/lib/templates/form-fields/text-field.js +63 -0
- package/lib/templates/form-fields/text-field.js.map +1 -0
- package/lib/templates/form-fields/textarea-field.d.ts +7 -0
- package/lib/templates/form-fields/textarea-field.d.ts.map +1 -0
- package/lib/templates/form-fields/textarea-field.js +64 -0
- package/lib/templates/form-fields/textarea-field.js.map +1 -0
- package/lib/templates/index.d.ts +34 -0
- package/lib/templates/index.d.ts.map +1 -0
- package/lib/templates/index.js +92 -0
- package/lib/templates/index.js.map +1 -0
- package/lib/templates/media/index.d.ts +12 -0
- package/lib/templates/media/index.d.ts.map +1 -0
- package/lib/templates/media/index.js +50 -0
- package/lib/templates/media/index.js.map +1 -0
- package/lib/templates/media/media-api.d.ts +13 -0
- package/lib/templates/media/media-api.d.ts.map +1 -0
- package/lib/templates/media/media-api.js +274 -0
- package/lib/templates/media/media-api.js.map +1 -0
- package/lib/templates/media/media-grid.d.ts +14 -0
- package/lib/templates/media/media-grid.d.ts.map +1 -0
- package/lib/templates/media/media-grid.js +314 -0
- package/lib/templates/media/media-grid.js.map +1 -0
- package/lib/templates/media/media-library-route.d.ts +13 -0
- package/lib/templates/media/media-library-route.d.ts.map +1 -0
- package/lib/templates/media/media-library-route.js +105 -0
- package/lib/templates/media/media-library-route.js.map +1 -0
- package/lib/templates/media/media-picker.d.ts +13 -0
- package/lib/templates/media/media-picker.d.ts.map +1 -0
- package/lib/templates/media/media-picker.js +152 -0
- package/lib/templates/media/media-picker.js.map +1 -0
- package/lib/templates/media/media-uploader.d.ts +14 -0
- package/lib/templates/media/media-uploader.d.ts.map +1 -0
- package/lib/templates/media/media-uploader.js +318 -0
- package/lib/templates/media/media-uploader.js.map +1 -0
- package/lib/templates/recent-content.d.ts +13 -0
- package/lib/templates/recent-content.d.ts.map +1 -0
- package/lib/templates/recent-content.js +138 -0
- package/lib/templates/recent-content.js.map +1 -0
- package/lib/templates/slug-utils.d.ts +10 -0
- package/lib/templates/slug-utils.d.ts.map +1 -0
- package/lib/templates/slug-utils.js +194 -0
- package/lib/templates/slug-utils.js.map +1 -0
- package/lib/templates/ui-avatar.d.ts +8 -0
- package/lib/templates/ui-avatar.d.ts.map +1 -0
- package/lib/templates/ui-avatar.js +60 -0
- package/lib/templates/ui-avatar.js.map +1 -0
- package/lib/templates/ui-badge.d.ts +8 -0
- package/lib/templates/ui-badge.d.ts.map +1 -0
- package/lib/templates/ui-badge.js +52 -0
- package/lib/templates/ui-badge.js.map +1 -0
- package/lib/templates/ui-dialog.d.ts +10 -0
- package/lib/templates/ui-dialog.d.ts.map +1 -0
- package/lib/templates/ui-dialog.js +134 -0
- package/lib/templates/ui-dialog.js.map +1 -0
- package/lib/templates/ui-dropdown-menu.d.ts +8 -0
- package/lib/templates/ui-dropdown-menu.d.ts.map +1 -0
- package/lib/templates/ui-dropdown-menu.js +210 -0
- package/lib/templates/ui-dropdown-menu.js.map +1 -0
- package/lib/templates/ui-popover.d.ts +8 -0
- package/lib/templates/ui-popover.d.ts.map +1 -0
- package/lib/templates/ui-popover.js +43 -0
- package/lib/templates/ui-popover.js.map +1 -0
- package/lib/templates/ui-progress.d.ts +10 -0
- package/lib/templates/ui-progress.d.ts.map +1 -0
- package/lib/templates/ui-progress.js +40 -0
- package/lib/templates/ui-progress.js.map +1 -0
- package/lib/templates/ui-table.d.ts +8 -0
- package/lib/templates/ui-table.d.ts.map +1 -0
- package/lib/templates/ui-table.js +129 -0
- package/lib/templates/ui-table.js.map +1 -0
- package/lib/templates/ui-tabs.d.ts +10 -0
- package/lib/templates/ui-tabs.d.ts.map +1 -0
- package/lib/templates/ui-tabs.js +67 -0
- package/lib/templates/ui-tabs.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getContentTypesTemplate = getContentTypesTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the content types definition template.
|
|
6
|
+
*
|
|
7
|
+
* This template provides the `defineContentType()` helper function and
|
|
8
|
+
* TypeScript interfaces for defining custom content structures.
|
|
9
|
+
*
|
|
10
|
+
* @returns Template string for app/lib/content-types.ts
|
|
11
|
+
*/
|
|
12
|
+
function getContentTypesTemplate() {
|
|
13
|
+
return `/**
|
|
14
|
+
* Content Type Definition System
|
|
15
|
+
*
|
|
16
|
+
* This module provides type-safe content type definitions for the CMS.
|
|
17
|
+
* Use \`defineContentType()\` to create custom content structures with
|
|
18
|
+
* fields, validation, and relationships.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Field Type Definitions
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base field definition shared by all field types.
|
|
27
|
+
*/
|
|
28
|
+
export interface BaseFieldDefinition {
|
|
29
|
+
/** Unique field identifier (used as form field name) */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Display label for the field */
|
|
32
|
+
label: string;
|
|
33
|
+
/** Optional help text shown below the field */
|
|
34
|
+
description?: string;
|
|
35
|
+
/** Whether the field is required */
|
|
36
|
+
required?: boolean;
|
|
37
|
+
/** Whether the field should be hidden in the editor */
|
|
38
|
+
hidden?: boolean;
|
|
39
|
+
/** Default value for the field */
|
|
40
|
+
defaultValue?: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Text field for single-line text input.
|
|
45
|
+
*/
|
|
46
|
+
export interface TextFieldDefinition extends BaseFieldDefinition {
|
|
47
|
+
type: 'text';
|
|
48
|
+
/** Placeholder text */
|
|
49
|
+
placeholder?: string;
|
|
50
|
+
/** Minimum character length */
|
|
51
|
+
minLength?: number;
|
|
52
|
+
/** Maximum character length */
|
|
53
|
+
maxLength?: number;
|
|
54
|
+
/** Regex pattern for validation */
|
|
55
|
+
pattern?: string;
|
|
56
|
+
defaultValue?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Textarea field for multi-line text input.
|
|
61
|
+
*/
|
|
62
|
+
export interface TextareaFieldDefinition extends BaseFieldDefinition {
|
|
63
|
+
type: 'textarea';
|
|
64
|
+
/** Placeholder text */
|
|
65
|
+
placeholder?: string;
|
|
66
|
+
/** Number of visible rows */
|
|
67
|
+
rows?: number;
|
|
68
|
+
/** Minimum character length */
|
|
69
|
+
minLength?: number;
|
|
70
|
+
/** Maximum character length */
|
|
71
|
+
maxLength?: number;
|
|
72
|
+
defaultValue?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Rich text field for HTML content editing.
|
|
77
|
+
*/
|
|
78
|
+
export interface RichtextFieldDefinition extends BaseFieldDefinition {
|
|
79
|
+
type: 'richtext';
|
|
80
|
+
/** Placeholder text */
|
|
81
|
+
placeholder?: string;
|
|
82
|
+
defaultValue?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Slug field for URL-friendly identifiers.
|
|
87
|
+
*/
|
|
88
|
+
export interface SlugFieldDefinition extends BaseFieldDefinition {
|
|
89
|
+
type: 'slug';
|
|
90
|
+
/** Field name to generate slug from (usually 'title') */
|
|
91
|
+
sourceField: string;
|
|
92
|
+
/** Prefix for the slug (e.g., '/blog/') */
|
|
93
|
+
prefix?: string;
|
|
94
|
+
defaultValue?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Media field for image/file uploads.
|
|
99
|
+
*/
|
|
100
|
+
export interface MediaFieldDefinition extends BaseFieldDefinition {
|
|
101
|
+
type: 'media';
|
|
102
|
+
/** Accepted file types (e.g., ['image/*', 'application/pdf']) */
|
|
103
|
+
accept?: string[];
|
|
104
|
+
/** Maximum file size in bytes */
|
|
105
|
+
maxSize?: number;
|
|
106
|
+
/** Allow multiple files */
|
|
107
|
+
multiple?: boolean;
|
|
108
|
+
defaultValue?: string | string[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Select field for single option selection.
|
|
113
|
+
*/
|
|
114
|
+
export interface SelectFieldDefinition extends BaseFieldDefinition {
|
|
115
|
+
type: 'select';
|
|
116
|
+
/** Available options */
|
|
117
|
+
options: Array<{ value: string; label: string }>;
|
|
118
|
+
/** Placeholder text */
|
|
119
|
+
placeholder?: string;
|
|
120
|
+
defaultValue?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Multi-select field for multiple option selection.
|
|
125
|
+
*/
|
|
126
|
+
export interface MultiselectFieldDefinition extends BaseFieldDefinition {
|
|
127
|
+
type: 'multiselect';
|
|
128
|
+
/** Available options */
|
|
129
|
+
options: Array<{ value: string; label: string }>;
|
|
130
|
+
/** Minimum selections required */
|
|
131
|
+
min?: number;
|
|
132
|
+
/** Maximum selections allowed */
|
|
133
|
+
max?: number;
|
|
134
|
+
defaultValue?: string[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Date field for date selection.
|
|
139
|
+
*/
|
|
140
|
+
export interface DateFieldDefinition extends BaseFieldDefinition {
|
|
141
|
+
type: 'date';
|
|
142
|
+
/** Minimum date (ISO string) */
|
|
143
|
+
min?: string;
|
|
144
|
+
/** Maximum date (ISO string) */
|
|
145
|
+
max?: string;
|
|
146
|
+
defaultValue?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Datetime field for date and time selection.
|
|
151
|
+
*/
|
|
152
|
+
export interface DatetimeFieldDefinition extends BaseFieldDefinition {
|
|
153
|
+
type: 'datetime';
|
|
154
|
+
/** Minimum datetime (ISO string) */
|
|
155
|
+
min?: string;
|
|
156
|
+
/** Maximum datetime (ISO string) */
|
|
157
|
+
max?: string;
|
|
158
|
+
defaultValue?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Boolean field for true/false toggle.
|
|
163
|
+
*/
|
|
164
|
+
export interface BooleanFieldDefinition extends BaseFieldDefinition {
|
|
165
|
+
type: 'boolean';
|
|
166
|
+
defaultValue?: boolean;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Number field for numeric input.
|
|
171
|
+
*/
|
|
172
|
+
export interface NumberFieldDefinition extends BaseFieldDefinition {
|
|
173
|
+
type: 'number';
|
|
174
|
+
/** Placeholder text */
|
|
175
|
+
placeholder?: string;
|
|
176
|
+
/** Minimum value */
|
|
177
|
+
min?: number;
|
|
178
|
+
/** Maximum value */
|
|
179
|
+
max?: number;
|
|
180
|
+
/** Step increment */
|
|
181
|
+
step?: number;
|
|
182
|
+
/** Allow decimal values */
|
|
183
|
+
decimal?: boolean;
|
|
184
|
+
defaultValue?: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Tags field for tag input with add/remove.
|
|
189
|
+
*/
|
|
190
|
+
export interface TagsFieldDefinition extends BaseFieldDefinition {
|
|
191
|
+
type: 'tags';
|
|
192
|
+
/** Placeholder text */
|
|
193
|
+
placeholder?: string;
|
|
194
|
+
/** Predefined suggestions */
|
|
195
|
+
suggestions?: string[];
|
|
196
|
+
/** Minimum tags required */
|
|
197
|
+
min?: number;
|
|
198
|
+
/** Maximum tags allowed */
|
|
199
|
+
max?: number;
|
|
200
|
+
defaultValue?: string[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Reference field for linking to other content.
|
|
205
|
+
*/
|
|
206
|
+
export interface ReferenceFieldDefinition extends BaseFieldDefinition {
|
|
207
|
+
type: 'reference';
|
|
208
|
+
/** Content type(s) that can be referenced */
|
|
209
|
+
contentTypes: string[];
|
|
210
|
+
/** Allow multiple references */
|
|
211
|
+
multiple?: boolean;
|
|
212
|
+
defaultValue?: string | string[];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Union type for all field definitions.
|
|
217
|
+
*/
|
|
218
|
+
export type FieldDefinition =
|
|
219
|
+
| TextFieldDefinition
|
|
220
|
+
| TextareaFieldDefinition
|
|
221
|
+
| RichtextFieldDefinition
|
|
222
|
+
| SlugFieldDefinition
|
|
223
|
+
| MediaFieldDefinition
|
|
224
|
+
| SelectFieldDefinition
|
|
225
|
+
| MultiselectFieldDefinition
|
|
226
|
+
| DateFieldDefinition
|
|
227
|
+
| DatetimeFieldDefinition
|
|
228
|
+
| BooleanFieldDefinition
|
|
229
|
+
| NumberFieldDefinition
|
|
230
|
+
| TagsFieldDefinition
|
|
231
|
+
| ReferenceFieldDefinition;
|
|
232
|
+
|
|
233
|
+
// ============================================================================
|
|
234
|
+
// Content Type Definition
|
|
235
|
+
// ============================================================================
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Status configuration for content items.
|
|
239
|
+
*/
|
|
240
|
+
export interface ContentStatus {
|
|
241
|
+
/** Status identifier */
|
|
242
|
+
value: string;
|
|
243
|
+
/** Display label */
|
|
244
|
+
label: string;
|
|
245
|
+
/** Badge color variant */
|
|
246
|
+
color: 'default' | 'secondary' | 'destructive' | 'success' | 'warning';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Default content statuses.
|
|
251
|
+
*/
|
|
252
|
+
export const DEFAULT_STATUSES: ContentStatus[] = [
|
|
253
|
+
{ value: 'draft', label: 'Draft', color: 'secondary' },
|
|
254
|
+
{ value: 'published', label: 'Published', color: 'success' },
|
|
255
|
+
{ value: 'archived', label: 'Archived', color: 'default' },
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Content type definition.
|
|
260
|
+
*/
|
|
261
|
+
export interface ContentType<TFields extends readonly FieldDefinition[] = readonly FieldDefinition[]> {
|
|
262
|
+
/** Unique content type identifier (e.g., 'blog-post') */
|
|
263
|
+
name: string;
|
|
264
|
+
/** Display label (e.g., 'Blog Posts') */
|
|
265
|
+
label: string;
|
|
266
|
+
/** Singular label (e.g., 'Blog Post') */
|
|
267
|
+
singularLabel: string;
|
|
268
|
+
/** Optional description */
|
|
269
|
+
description?: string;
|
|
270
|
+
/** Lucide icon name for the sidebar */
|
|
271
|
+
icon: string;
|
|
272
|
+
/** Field definitions */
|
|
273
|
+
fields: TFields;
|
|
274
|
+
/** Available statuses */
|
|
275
|
+
statuses: ContentStatus[];
|
|
276
|
+
/** Field to use as the title in lists */
|
|
277
|
+
titleField: string;
|
|
278
|
+
/** Optional field to display as subtitle in lists */
|
|
279
|
+
subtitleField?: string;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Options for defining a content type.
|
|
284
|
+
*/
|
|
285
|
+
export interface ContentTypeOptions<TFields extends readonly FieldDefinition[] = readonly FieldDefinition[]> {
|
|
286
|
+
/** Unique content type identifier (e.g., 'blog-post') */
|
|
287
|
+
name: string;
|
|
288
|
+
/** Display label (e.g., 'Blog Posts') */
|
|
289
|
+
label: string;
|
|
290
|
+
/** Singular label (e.g., 'Blog Post') - defaults to label without trailing 's' */
|
|
291
|
+
singularLabel?: string;
|
|
292
|
+
/** Optional description */
|
|
293
|
+
description?: string;
|
|
294
|
+
/** Lucide icon name for the sidebar (default: 'FileText') */
|
|
295
|
+
icon?: string;
|
|
296
|
+
/** Field definitions */
|
|
297
|
+
fields: TFields;
|
|
298
|
+
/** Available statuses (default: draft, published, archived) */
|
|
299
|
+
statuses?: ContentStatus[];
|
|
300
|
+
/** Field to use as the title in lists (default: first text field or 'title') */
|
|
301
|
+
titleField?: string;
|
|
302
|
+
/** Optional field to display as subtitle in lists */
|
|
303
|
+
subtitleField?: string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Creates a typed content type definition.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* \`\`\`typescript
|
|
311
|
+
* const blogPost = defineContentType({
|
|
312
|
+
* name: 'blog-post',
|
|
313
|
+
* label: 'Blog Posts',
|
|
314
|
+
* icon: 'FileText',
|
|
315
|
+
* fields: [
|
|
316
|
+
* { type: 'text', name: 'title', label: 'Title', required: true },
|
|
317
|
+
* { type: 'slug', name: 'slug', label: 'URL Slug', sourceField: 'title' },
|
|
318
|
+
* { type: 'richtext', name: 'content', label: 'Content' },
|
|
319
|
+
* ] as const,
|
|
320
|
+
* });
|
|
321
|
+
* \`\`\`
|
|
322
|
+
*/
|
|
323
|
+
export function defineContentType<TFields extends readonly FieldDefinition[]>(
|
|
324
|
+
options: ContentTypeOptions<TFields>
|
|
325
|
+
): ContentType<TFields> {
|
|
326
|
+
// Find first text field for default title
|
|
327
|
+
const firstTextField = options.fields.find((f) => f.type === 'text');
|
|
328
|
+
const defaultTitleField = firstTextField?.name ?? 'title';
|
|
329
|
+
|
|
330
|
+
// Generate singular label from plural
|
|
331
|
+
const defaultSingularLabel = options.label.endsWith('s')
|
|
332
|
+
? options.label.slice(0, -1)
|
|
333
|
+
: options.label;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
name: options.name,
|
|
337
|
+
label: options.label,
|
|
338
|
+
singularLabel: options.singularLabel ?? defaultSingularLabel,
|
|
339
|
+
description: options.description,
|
|
340
|
+
icon: options.icon ?? 'FileText',
|
|
341
|
+
fields: options.fields,
|
|
342
|
+
statuses: options.statuses ?? DEFAULT_STATUSES,
|
|
343
|
+
titleField: options.titleField ?? defaultTitleField,
|
|
344
|
+
subtitleField: options.subtitleField,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// Type Utilities
|
|
350
|
+
// ============================================================================
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Extract the data type from a field definition.
|
|
354
|
+
*/
|
|
355
|
+
export type FieldDataType<T extends FieldDefinition> =
|
|
356
|
+
T extends TextFieldDefinition ? string :
|
|
357
|
+
T extends TextareaFieldDefinition ? string :
|
|
358
|
+
T extends RichtextFieldDefinition ? string :
|
|
359
|
+
T extends SlugFieldDefinition ? string :
|
|
360
|
+
T extends MediaFieldDefinition ? (T['multiple'] extends true ? string[] : string) :
|
|
361
|
+
T extends SelectFieldDefinition ? string :
|
|
362
|
+
T extends MultiselectFieldDefinition ? string[] :
|
|
363
|
+
T extends DateFieldDefinition ? string :
|
|
364
|
+
T extends DatetimeFieldDefinition ? string :
|
|
365
|
+
T extends BooleanFieldDefinition ? boolean :
|
|
366
|
+
T extends NumberFieldDefinition ? number :
|
|
367
|
+
T extends TagsFieldDefinition ? string[] :
|
|
368
|
+
T extends ReferenceFieldDefinition ? (T['multiple'] extends true ? string[] : string) :
|
|
369
|
+
unknown;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Infer content data type from a content type definition.
|
|
373
|
+
*/
|
|
374
|
+
export type ContentData<T extends ContentType> = {
|
|
375
|
+
id: string;
|
|
376
|
+
status: string;
|
|
377
|
+
createdAt: string;
|
|
378
|
+
updatedAt: string;
|
|
379
|
+
} & {
|
|
380
|
+
[K in T['fields'][number] as K['name']]: FieldDataType<K>;
|
|
381
|
+
};
|
|
382
|
+
`;
|
|
383
|
+
}
|
|
384
|
+
//# sourceMappingURL=content-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-types.js","sourceRoot":"","sources":["../../src/templates/content-types.ts"],"names":[],"mappings":";;AAQA,0DAmXC;AA3XD;;;;;;;GAOG;AACH,SAAgB,uBAAuB;IACrC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiXR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the dashboard stats component template.
|
|
3
|
+
*
|
|
4
|
+
* This component displays:
|
|
5
|
+
* - Total content count
|
|
6
|
+
* - Published content count
|
|
7
|
+
* - Draft content count
|
|
8
|
+
* - Content breakdown by type
|
|
9
|
+
*
|
|
10
|
+
* @returns Template string for app/components/admin/dashboard-stats.tsx
|
|
11
|
+
*/
|
|
12
|
+
export declare function getDashboardStatsTemplate(): string;
|
|
13
|
+
//# sourceMappingURL=dashboard-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-stats.d.ts","sourceRoot":"","sources":["../../src/templates/dashboard-stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAqGlD"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getDashboardStatsTemplate = getDashboardStatsTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the dashboard stats component template.
|
|
6
|
+
*
|
|
7
|
+
* This component displays:
|
|
8
|
+
* - Total content count
|
|
9
|
+
* - Published content count
|
|
10
|
+
* - Draft content count
|
|
11
|
+
* - Content breakdown by type
|
|
12
|
+
*
|
|
13
|
+
* @returns Template string for app/components/admin/dashboard-stats.tsx
|
|
14
|
+
*/
|
|
15
|
+
function getDashboardStatsTemplate() {
|
|
16
|
+
return `import { FileText, CheckCircle, Clock, Files } from 'lucide-react';
|
|
17
|
+
import { Card, CardContent, CardHeader, CardTitle } from '~/components/ui/card';
|
|
18
|
+
|
|
19
|
+
export interface ContentStats {
|
|
20
|
+
total: number;
|
|
21
|
+
published: number;
|
|
22
|
+
draft: number;
|
|
23
|
+
byType: Array<{
|
|
24
|
+
type: string;
|
|
25
|
+
label: string;
|
|
26
|
+
count: number;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface DashboardStatsProps {
|
|
31
|
+
stats: ContentStats;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface StatCardProps {
|
|
35
|
+
title: string;
|
|
36
|
+
value: number;
|
|
37
|
+
icon: React.ReactNode;
|
|
38
|
+
description?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function StatCard({ title, value, icon, description }: StatCardProps) {
|
|
42
|
+
return (
|
|
43
|
+
<Card>
|
|
44
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
45
|
+
<CardTitle className="text-sm font-medium">{title}</CardTitle>
|
|
46
|
+
{icon}
|
|
47
|
+
</CardHeader>
|
|
48
|
+
<CardContent>
|
|
49
|
+
<div className="text-2xl font-bold">{value}</div>
|
|
50
|
+
{description && (
|
|
51
|
+
<p className="text-xs text-muted-foreground">{description}</p>
|
|
52
|
+
)}
|
|
53
|
+
</CardContent>
|
|
54
|
+
</Card>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function DashboardStats({ stats }: DashboardStatsProps) {
|
|
59
|
+
return (
|
|
60
|
+
<div className="space-y-6">
|
|
61
|
+
{/* Main Stats Grid */}
|
|
62
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
63
|
+
<StatCard
|
|
64
|
+
title="Total Content"
|
|
65
|
+
value={stats.total}
|
|
66
|
+
icon={<Files className="h-4 w-4 text-muted-foreground" />}
|
|
67
|
+
description="All content items"
|
|
68
|
+
/>
|
|
69
|
+
<StatCard
|
|
70
|
+
title="Published"
|
|
71
|
+
value={stats.published}
|
|
72
|
+
icon={<CheckCircle className="h-4 w-4 text-green-500" />}
|
|
73
|
+
description="Live content"
|
|
74
|
+
/>
|
|
75
|
+
<StatCard
|
|
76
|
+
title="Drafts"
|
|
77
|
+
value={stats.draft}
|
|
78
|
+
icon={<Clock className="h-4 w-4 text-yellow-500" />}
|
|
79
|
+
description="Work in progress"
|
|
80
|
+
/>
|
|
81
|
+
<StatCard
|
|
82
|
+
title="Content Types"
|
|
83
|
+
value={stats.byType.length}
|
|
84
|
+
icon={<FileText className="h-4 w-4 text-muted-foreground" />}
|
|
85
|
+
description="Configured types"
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Content by Type */}
|
|
90
|
+
{stats.byType.length > 0 && (
|
|
91
|
+
<Card>
|
|
92
|
+
<CardHeader>
|
|
93
|
+
<CardTitle className="text-lg">Content by Type</CardTitle>
|
|
94
|
+
</CardHeader>
|
|
95
|
+
<CardContent>
|
|
96
|
+
<div className="space-y-4">
|
|
97
|
+
{stats.byType.map((item) => (
|
|
98
|
+
<div key={item.type} className="flex items-center justify-between">
|
|
99
|
+
<div className="flex items-center gap-2">
|
|
100
|
+
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
101
|
+
<span className="text-sm font-medium">{item.label}</span>
|
|
102
|
+
</div>
|
|
103
|
+
<span className="text-sm text-muted-foreground">
|
|
104
|
+
{item.count} item{item.count !== 1 ? 's' : ''}
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
</CardContent>
|
|
110
|
+
</Card>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=dashboard-stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-stats.js","sourceRoot":"","sources":["../../src/templates/dashboard-stats.ts"],"names":[],"mappings":";;AAWA,8DAqGC;AAhHD;;;;;;;;;;GAUG;AACH,SAAgB,yBAAyB;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmGR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/editor/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getRichTextEditorTemplate = exports.getEditorToolbarTemplate = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Editor component templates for rich text editing.
|
|
6
|
+
*/
|
|
7
|
+
var toolbar_1 = require('./toolbar');
|
|
8
|
+
Object.defineProperty(exports, 'getEditorToolbarTemplate', {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function () {
|
|
11
|
+
return toolbar_1.getEditorToolbarTemplate;
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
var rich_text_editor_1 = require('./rich-text-editor');
|
|
15
|
+
Object.defineProperty(exports, 'getRichTextEditorTemplate', {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () {
|
|
18
|
+
return rich_text_editor_1.getRichTextEditorTemplate;
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/editor/index.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,qCAAqD;AAA5C,mHAAA,wBAAwB,OAAA;AACjC,uDAA+D;AAAtD,6HAAA,yBAAyB,OAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-text-editor.d.ts","sourceRoot":"","sources":["../../../src/templates/editor/rich-text-editor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAyGlD"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getRichTextEditorTemplate = getRichTextEditorTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the rich text editor component template.
|
|
6
|
+
*
|
|
7
|
+
* @returns Template string for app/components/editor/rich-text-editor.tsx
|
|
8
|
+
*/
|
|
9
|
+
function getRichTextEditorTemplate() {
|
|
10
|
+
return `import * as React from 'react';
|
|
11
|
+
import { useEditor, EditorContent } from '@tiptap/react';
|
|
12
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
13
|
+
import Link from '@tiptap/extension-link';
|
|
14
|
+
import Image from '@tiptap/extension-image';
|
|
15
|
+
import { EditorToolbar } from './toolbar';
|
|
16
|
+
import { cn } from '~/lib/utils';
|
|
17
|
+
|
|
18
|
+
export interface RichTextEditorProps {
|
|
19
|
+
value: string;
|
|
20
|
+
onChange: (value: string) => void;
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
error?: boolean;
|
|
24
|
+
onImageSelect?: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function RichTextEditor({
|
|
28
|
+
value,
|
|
29
|
+
onChange,
|
|
30
|
+
placeholder = 'Start writing...',
|
|
31
|
+
disabled = false,
|
|
32
|
+
error = false,
|
|
33
|
+
onImageSelect,
|
|
34
|
+
}: RichTextEditorProps) {
|
|
35
|
+
const editor = useEditor({
|
|
36
|
+
extensions: [
|
|
37
|
+
StarterKit.configure({
|
|
38
|
+
heading: {
|
|
39
|
+
levels: [1, 2, 3],
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
Link.configure({
|
|
43
|
+
openOnClick: false,
|
|
44
|
+
HTMLAttributes: {
|
|
45
|
+
class: 'text-primary underline underline-offset-4',
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
Image.configure({
|
|
49
|
+
HTMLAttributes: {
|
|
50
|
+
class: 'max-w-full h-auto rounded-md',
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
content: value,
|
|
55
|
+
editable: !disabled,
|
|
56
|
+
onUpdate: ({ editor }) => {
|
|
57
|
+
onChange(editor.getHTML());
|
|
58
|
+
},
|
|
59
|
+
editorProps: {
|
|
60
|
+
attributes: {
|
|
61
|
+
class: cn(
|
|
62
|
+
'prose prose-sm dark:prose-invert max-w-none min-h-[200px] p-3 focus:outline-none',
|
|
63
|
+
'[&_ol]:list-decimal [&_ul]:list-disc',
|
|
64
|
+
'[&_h1]:text-2xl [&_h1]:font-bold [&_h1]:mb-4',
|
|
65
|
+
'[&_h2]:text-xl [&_h2]:font-semibold [&_h2]:mb-3',
|
|
66
|
+
'[&_h3]:text-lg [&_h3]:font-medium [&_h3]:mb-2',
|
|
67
|
+
'[&_p]:mb-3 [&_p]:leading-relaxed',
|
|
68
|
+
'[&_pre]:bg-muted [&_pre]:p-3 [&_pre]:rounded-md',
|
|
69
|
+
'[&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded',
|
|
70
|
+
'[&_blockquote]:border-l-4 [&_blockquote]:border-border [&_blockquote]:pl-4 [&_blockquote]:italic'
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Sync value changes from parent
|
|
77
|
+
React.useEffect(() => {
|
|
78
|
+
if (editor && value !== editor.getHTML()) {
|
|
79
|
+
editor.commands.setContent(value, false);
|
|
80
|
+
}
|
|
81
|
+
}, [editor, value]);
|
|
82
|
+
|
|
83
|
+
// Update editable state
|
|
84
|
+
React.useEffect(() => {
|
|
85
|
+
if (editor) {
|
|
86
|
+
editor.setEditable(!disabled);
|
|
87
|
+
}
|
|
88
|
+
}, [editor, disabled]);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
className={cn(
|
|
93
|
+
'rounded-md border bg-background',
|
|
94
|
+
error && 'border-destructive',
|
|
95
|
+
disabled && 'opacity-50 cursor-not-allowed'
|
|
96
|
+
)}
|
|
97
|
+
>
|
|
98
|
+
<EditorToolbar
|
|
99
|
+
editor={editor}
|
|
100
|
+
onImageSelect={onImageSelect}
|
|
101
|
+
disabled={disabled}
|
|
102
|
+
/>
|
|
103
|
+
<EditorContent
|
|
104
|
+
editor={editor}
|
|
105
|
+
className={cn(
|
|
106
|
+
'min-h-[200px]',
|
|
107
|
+
disabled && 'pointer-events-none'
|
|
108
|
+
)}
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=rich-text-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rich-text-editor.js","sourceRoot":"","sources":["../../../src/templates/editor/rich-text-editor.ts"],"names":[],"mappings":";;AAKA,8DAyGC;AA9GD;;;;GAIG;AACH,SAAgB,yBAAyB;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuGR,CAAC;AACF,CAAC"}
|