@qwanyx/ai-editor 1.3.13 → 1.4.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.
Files changed (49) hide show
  1. package/dist/components/PageEditor.d.ts +10 -0
  2. package/dist/components/PageEditor.d.ts.map +1 -0
  3. package/dist/components/PageEditor.js +294 -0
  4. package/dist/components/PageViewer.d.ts +8 -0
  5. package/dist/components/PageViewer.d.ts.map +1 -0
  6. package/dist/components/PageViewer.js +38 -0
  7. package/dist/components/RichTextEditor.d.ts +5 -1
  8. package/dist/components/RichTextEditor.d.ts.map +1 -1
  9. package/dist/components/RichTextEditor.js +11 -2
  10. package/dist/components/RichTextViewer.d.ts +3 -1
  11. package/dist/components/RichTextViewer.d.ts.map +1 -1
  12. package/dist/components/RichTextViewer.js +7 -2
  13. package/dist/components/Section.d.ts +10 -0
  14. package/dist/components/Section.d.ts.map +1 -0
  15. package/dist/components/Section.js +117 -0
  16. package/dist/components/SmartEditor.d.ts +58 -0
  17. package/dist/components/SmartEditor.d.ts.map +1 -0
  18. package/dist/components/SmartEditor.js +123 -0
  19. package/dist/context/PageContext.d.ts +35 -0
  20. package/dist/context/PageContext.d.ts.map +1 -0
  21. package/dist/context/PageContext.js +277 -0
  22. package/dist/index.d.ts +18 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +57 -1
  25. package/dist/layouts/GalleryLayout.d.ts +5 -0
  26. package/dist/layouts/GalleryLayout.d.ts.map +1 -0
  27. package/dist/layouts/GalleryLayout.js +262 -0
  28. package/dist/layouts/OgilvyLayout.d.ts +5 -0
  29. package/dist/layouts/OgilvyLayout.d.ts.map +1 -0
  30. package/dist/layouts/OgilvyLayout.js +301 -0
  31. package/dist/layouts/RichTextLayout.d.ts +7 -0
  32. package/dist/layouts/RichTextLayout.d.ts.map +1 -0
  33. package/dist/layouts/RichTextLayout.js +47 -0
  34. package/dist/layouts/index.d.ts +31 -0
  35. package/dist/layouts/index.d.ts.map +1 -0
  36. package/dist/layouts/index.js +73 -0
  37. package/dist/types/index.d.ts +5 -0
  38. package/dist/types/index.d.ts.map +1 -0
  39. package/dist/types/index.js +20 -0
  40. package/dist/types/page.d.ts +158 -0
  41. package/dist/types/page.d.ts.map +1 -0
  42. package/dist/types/page.js +54 -0
  43. package/dist/utils/format.d.ts +71 -0
  44. package/dist/utils/format.d.ts.map +1 -0
  45. package/dist/utils/format.js +190 -0
  46. package/dist/utils/index.d.ts +5 -0
  47. package/dist/utils/index.d.ts.map +1 -0
  48. package/dist/utils/index.js +20 -0
  49. package/package.json +1 -1
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createDefaultGalleryContent = createDefaultGalleryContent;
5
+ exports.GalleryLayout = GalleryLayout;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const index_1 = require("./index");
9
+ // ============================================
10
+ // Styles
11
+ // ============================================
12
+ const styles = {
13
+ container: {
14
+ width: '100%',
15
+ },
16
+ // Grid layout
17
+ gridContainer: {
18
+ display: 'flex',
19
+ flexWrap: 'wrap',
20
+ },
21
+ gridItem: {
22
+ overflow: 'hidden',
23
+ },
24
+ gridImage: {
25
+ width: '100%',
26
+ height: '100%',
27
+ objectFit: 'cover',
28
+ display: 'block',
29
+ },
30
+ // Masonry layout
31
+ masonryContainer: {
32
+ columnFill: 'balance',
33
+ },
34
+ masonryItem: {
35
+ breakInside: 'avoid',
36
+ marginBottom: '8px',
37
+ },
38
+ masonryImage: {
39
+ width: '100%',
40
+ height: 'auto',
41
+ display: 'block',
42
+ },
43
+ // Caption
44
+ caption: {
45
+ fontSize: '0.75rem',
46
+ color: '#666',
47
+ textAlign: 'center',
48
+ padding: '4px 0',
49
+ backgroundColor: 'rgba(255,255,255,0.9)',
50
+ },
51
+ // Edit mode
52
+ addButton: {
53
+ display: 'flex',
54
+ alignItems: 'center',
55
+ justifyContent: 'center',
56
+ padding: '2rem',
57
+ border: '2px dashed #ccc',
58
+ borderRadius: '8px',
59
+ backgroundColor: '#f9f9f9',
60
+ cursor: 'pointer',
61
+ color: '#666',
62
+ fontSize: '0.875rem',
63
+ marginTop: '1rem',
64
+ },
65
+ editOverlay: {
66
+ position: 'absolute',
67
+ top: 0,
68
+ left: 0,
69
+ right: 0,
70
+ bottom: 0,
71
+ backgroundColor: 'rgba(0,0,0,0.5)',
72
+ display: 'flex',
73
+ alignItems: 'center',
74
+ justifyContent: 'center',
75
+ opacity: 0,
76
+ transition: 'opacity 0.2s',
77
+ },
78
+ editButton: {
79
+ padding: '0.5rem',
80
+ backgroundColor: 'white',
81
+ border: 'none',
82
+ borderRadius: '4px',
83
+ cursor: 'pointer',
84
+ margin: '0 2px',
85
+ },
86
+ imageWrapper: {
87
+ position: 'relative',
88
+ },
89
+ };
90
+ // ============================================
91
+ // Default Content
92
+ // ============================================
93
+ function createDefaultGalleryContent() {
94
+ return {
95
+ images: [],
96
+ imageWidth: 200,
97
+ layout: 'grid',
98
+ alignment: 'left',
99
+ gap: 8,
100
+ showCaptions: false,
101
+ };
102
+ }
103
+ // ============================================
104
+ // Helper: Get alignment CSS
105
+ // ============================================
106
+ function getAlignmentStyle(alignment) {
107
+ switch (alignment) {
108
+ case 'left':
109
+ return { justifyContent: 'flex-start' };
110
+ case 'center':
111
+ return { justifyContent: 'center' };
112
+ case 'right':
113
+ return { justifyContent: 'flex-end' };
114
+ case 'distribute':
115
+ return { justifyContent: 'space-between' };
116
+ default:
117
+ return { justifyContent: 'flex-start' };
118
+ }
119
+ }
120
+ function ImageItem({ image, index, isEditing, showCaption, onDelete, onEdit, style, imageStyle, }) {
121
+ const [isHovered, setIsHovered] = (0, react_1.useState)(false);
122
+ return ((0, jsx_runtime_1.jsxs)("div", { style: { ...styles.imageWrapper, ...style }, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [(0, jsx_runtime_1.jsx)("img", { src: image.src, alt: image.alt || '', style: imageStyle }), showCaption && image.caption && ((0, jsx_runtime_1.jsx)("div", { style: styles.caption, children: image.caption })), isEditing && ((0, jsx_runtime_1.jsxs)("div", { style: { ...styles.editOverlay, opacity: isHovered ? 1 : 0 }, children: [(0, jsx_runtime_1.jsx)("button", { style: styles.editButton, onClick: () => onEdit(index), title: "Modifier", children: "\u270F\uFE0F" }), (0, jsx_runtime_1.jsx)("button", { style: styles.editButton, onClick: () => onDelete(index), title: "Supprimer", children: "\uD83D\uDDD1\uFE0F" })] }))] }));
123
+ }
124
+ function AddImageDialog({ onAdd, onClose, editImage }) {
125
+ const [url, setUrl] = (0, react_1.useState)(editImage?.src || '');
126
+ const [alt, setAlt] = (0, react_1.useState)(editImage?.alt || '');
127
+ const [caption, setCaption] = (0, react_1.useState)(editImage?.caption || '');
128
+ const handleSubmit = () => {
129
+ if (url.trim()) {
130
+ onAdd({ src: url.trim(), alt: alt.trim(), caption: caption.trim() });
131
+ }
132
+ };
133
+ return ((0, jsx_runtime_1.jsx)("div", { style: {
134
+ position: 'fixed',
135
+ top: 0,
136
+ left: 0,
137
+ right: 0,
138
+ bottom: 0,
139
+ backgroundColor: 'rgba(0,0,0,0.5)',
140
+ display: 'flex',
141
+ alignItems: 'center',
142
+ justifyContent: 'center',
143
+ zIndex: 1000,
144
+ }, onClick: onClose, children: (0, jsx_runtime_1.jsxs)("div", { style: {
145
+ backgroundColor: 'white',
146
+ padding: '1.5rem',
147
+ borderRadius: '8px',
148
+ minWidth: '400px',
149
+ maxWidth: '90%',
150
+ }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("h3", { style: { margin: '0 0 1rem 0' }, children: editImage ? 'Modifier l\'image' : 'Ajouter une image' }), (0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: '1rem' }, children: [(0, jsx_runtime_1.jsx)("label", { style: { display: 'block', marginBottom: '0.25rem', fontSize: '0.875rem' }, children: "URL de l'image *" }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: url, onChange: (e) => setUrl(e.target.value), placeholder: "https://...", style: {
151
+ width: '100%',
152
+ padding: '0.5rem',
153
+ border: '1px solid #ccc',
154
+ borderRadius: '4px',
155
+ } })] }), url && ((0, jsx_runtime_1.jsx)("img", { src: url, alt: "Preview", style: {
156
+ maxWidth: '100%',
157
+ maxHeight: '150px',
158
+ marginBottom: '1rem',
159
+ display: 'block',
160
+ borderRadius: '4px',
161
+ }, onError: (e) => {
162
+ e.target.style.display = 'none';
163
+ } })), (0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: '1rem' }, children: [(0, jsx_runtime_1.jsx)("label", { style: { display: 'block', marginBottom: '0.25rem', fontSize: '0.875rem' }, children: "Texte alternatif" }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: alt, onChange: (e) => setAlt(e.target.value), placeholder: "Description de l'image", style: {
164
+ width: '100%',
165
+ padding: '0.5rem',
166
+ border: '1px solid #ccc',
167
+ borderRadius: '4px',
168
+ } })] }), (0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: '1rem' }, children: [(0, jsx_runtime_1.jsx)("label", { style: { display: 'block', marginBottom: '0.25rem', fontSize: '0.875rem' }, children: "L\u00E9gende" }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: caption, onChange: (e) => setCaption(e.target.value), placeholder: "L\u00E9gende affich\u00E9e sous l'image", style: {
169
+ width: '100%',
170
+ padding: '0.5rem',
171
+ border: '1px solid #ccc',
172
+ borderRadius: '4px',
173
+ } })] }), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }, children: [(0, jsx_runtime_1.jsx)("button", { onClick: onClose, style: {
174
+ padding: '0.5rem 1rem',
175
+ border: '1px solid #ccc',
176
+ borderRadius: '4px',
177
+ backgroundColor: 'white',
178
+ cursor: 'pointer',
179
+ }, children: "Annuler" }), (0, jsx_runtime_1.jsx)("button", { onClick: handleSubmit, disabled: !url.trim(), style: {
180
+ padding: '0.5rem 1rem',
181
+ border: 'none',
182
+ borderRadius: '4px',
183
+ backgroundColor: url.trim() ? '#007bff' : '#ccc',
184
+ color: 'white',
185
+ cursor: url.trim() ? 'pointer' : 'not-allowed',
186
+ }, children: editImage ? 'Modifier' : 'Ajouter' })] })] }) }));
187
+ }
188
+ // ============================================
189
+ // Main Layout Component
190
+ // ============================================
191
+ function GalleryLayout({ section, isEditing, onContentChange, }) {
192
+ const content = section.content;
193
+ const [showAddDialog, setShowAddDialog] = (0, react_1.useState)(false);
194
+ const [editingIndex, setEditingIndex] = (0, react_1.useState)(null);
195
+ const gap = content.gap || 8;
196
+ const imageWidth = content.imageWidth || 200;
197
+ const handleAddImage = (0, react_1.useCallback)((image) => {
198
+ if (editingIndex !== null) {
199
+ // Editing existing image
200
+ const newImages = [...content.images];
201
+ newImages[editingIndex] = image;
202
+ onContentChange({ ...content, images: newImages });
203
+ setEditingIndex(null);
204
+ }
205
+ else {
206
+ // Adding new image
207
+ onContentChange({ ...content, images: [...content.images, image] });
208
+ }
209
+ setShowAddDialog(false);
210
+ }, [content, onContentChange, editingIndex]);
211
+ const handleDeleteImage = (0, react_1.useCallback)((index) => {
212
+ const newImages = content.images.filter((_, i) => i !== index);
213
+ onContentChange({ ...content, images: newImages });
214
+ }, [content, onContentChange]);
215
+ const handleEditImage = (0, react_1.useCallback)((index) => {
216
+ setEditingIndex(index);
217
+ setShowAddDialog(true);
218
+ }, []);
219
+ // Grid Layout
220
+ const renderGrid = () => ((0, jsx_runtime_1.jsx)("div", { style: {
221
+ ...styles.gridContainer,
222
+ gap: `${gap}px`,
223
+ ...getAlignmentStyle(content.alignment),
224
+ }, children: content.images.map((image, index) => ((0, jsx_runtime_1.jsx)(ImageItem, { image: image, index: index, isEditing: isEditing, showCaption: content.showCaptions || false, onDelete: handleDeleteImage, onEdit: handleEditImage, style: {
225
+ ...styles.gridItem,
226
+ width: `${imageWidth}px`,
227
+ height: `${imageWidth}px`,
228
+ }, imageStyle: styles.gridImage }, index))) }));
229
+ // Masonry Layout
230
+ const renderMasonry = () => {
231
+ // Calculate column count based on container width
232
+ // We'll use CSS columns for true masonry effect
233
+ const columnWidth = imageWidth + gap;
234
+ return ((0, jsx_runtime_1.jsx)("div", { style: {
235
+ ...styles.masonryContainer,
236
+ columnWidth: `${imageWidth}px`,
237
+ columnGap: `${gap}px`,
238
+ }, children: content.images.map((image, index) => ((0, jsx_runtime_1.jsx)(ImageItem, { image: image, index: index, isEditing: isEditing, showCaption: content.showCaptions || false, onDelete: handleDeleteImage, onEdit: handleEditImage, style: {
239
+ ...styles.masonryItem,
240
+ marginBottom: `${gap}px`,
241
+ }, imageStyle: styles.masonryImage }, index))) }));
242
+ };
243
+ return ((0, jsx_runtime_1.jsxs)("div", { style: styles.container, children: [content.images.length === 0 && !isEditing ? ((0, jsx_runtime_1.jsx)("div", { style: { textAlign: 'center', padding: '2rem', color: '#999' }, children: "Aucune image" })) : (content.layout === 'masonry' ? renderMasonry() : renderGrid()), isEditing && ((0, jsx_runtime_1.jsx)("div", { style: styles.addButton, onClick: () => {
244
+ setEditingIndex(null);
245
+ setShowAddDialog(true);
246
+ }, children: "+ Ajouter une image" })), showAddDialog && ((0, jsx_runtime_1.jsx)(AddImageDialog, { onAdd: handleAddImage, onClose: () => {
247
+ setShowAddDialog(false);
248
+ setEditingIndex(null);
249
+ }, editImage: editingIndex !== null ? content.images[editingIndex] : undefined }))] }));
250
+ }
251
+ // ============================================
252
+ // Register Layout
253
+ // ============================================
254
+ (0, index_1.registerLayout)({
255
+ type: 'gallery',
256
+ name: 'Galerie',
257
+ description: 'Grille d\'images responsive avec modes grid et masonry',
258
+ icon: '🖼️',
259
+ component: GalleryLayout,
260
+ defaultContent: createDefaultGalleryContent,
261
+ category: 'media',
262
+ });
@@ -0,0 +1,5 @@
1
+ import { OgilvyContent } from '../types/page';
2
+ import { LayoutComponentProps } from './index';
3
+ export declare function createDefaultOgilvyContent(): OgilvyContent;
4
+ export declare function OgilvyLayout({ section, isEditing, onContentChange, }: LayoutComponentProps<OgilvyContent>): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=OgilvyLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OgilvyLayout.d.ts","sourceRoot":"","sources":["../../src/layouts/OgilvyLayout.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,aAAa,EAId,MAAM,eAAe,CAAA;AACtB,OAAO,EAAkB,oBAAoB,EAAE,MAAM,SAAS,CAAA;AA0H9D,wBAAgB,0BAA0B,IAAI,aAAa,CAe1D;AAiUD,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,SAAS,EACT,eAAe,GAChB,EAAE,oBAAoB,CAAC,aAAa,CAAC,2CA8GrC"}
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createDefaultOgilvyContent = createDefaultOgilvyContent;
5
+ exports.OgilvyLayout = OgilvyLayout;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const page_1 = require("../types/page");
9
+ const index_1 = require("./index");
10
+ const RichTextEditor_1 = require("../components/RichTextEditor");
11
+ const RichTextViewer_1 = require("../components/RichTextViewer");
12
+ // ============================================
13
+ // Styles
14
+ // ============================================
15
+ const styles = {
16
+ container: {
17
+ display: 'flex',
18
+ flexDirection: 'column',
19
+ gap: '1.5rem',
20
+ fontFamily: 'Georgia, serif',
21
+ },
22
+ imageContainer: {
23
+ width: '100%',
24
+ position: 'relative',
25
+ },
26
+ image: {
27
+ width: '100%',
28
+ height: 'auto',
29
+ display: 'block',
30
+ },
31
+ imagePlaceholder: {
32
+ width: '100%',
33
+ aspectRatio: '16/9',
34
+ backgroundColor: '#f0f0f0',
35
+ display: 'flex',
36
+ alignItems: 'center',
37
+ justifyContent: 'center',
38
+ color: '#999',
39
+ fontSize: '1rem',
40
+ cursor: 'pointer',
41
+ border: '2px dashed #ccc',
42
+ borderRadius: '4px',
43
+ },
44
+ legend: {
45
+ fontSize: '0.85rem',
46
+ color: '#666',
47
+ marginTop: '0.5rem',
48
+ fontStyle: 'italic',
49
+ },
50
+ titleContainer: {
51
+ textAlign: 'center',
52
+ },
53
+ title: {
54
+ fontSize: '2.5rem',
55
+ fontWeight: 700,
56
+ margin: 0,
57
+ lineHeight: 1.2,
58
+ },
59
+ titleInput: {
60
+ fontSize: '2.5rem',
61
+ fontWeight: 700,
62
+ margin: 0,
63
+ lineHeight: 1.2,
64
+ border: 'none',
65
+ outline: 'none',
66
+ width: '100%',
67
+ textAlign: 'center',
68
+ fontFamily: 'inherit',
69
+ backgroundColor: 'transparent',
70
+ },
71
+ subtitle: {
72
+ fontSize: '1.25rem',
73
+ fontWeight: 400,
74
+ color: '#555',
75
+ margin: '0.5rem 0 0 0',
76
+ fontStyle: 'italic',
77
+ },
78
+ subtitleInput: {
79
+ fontSize: '1.25rem',
80
+ fontWeight: 400,
81
+ color: '#555',
82
+ margin: '0.5rem 0 0 0',
83
+ fontStyle: 'italic',
84
+ border: 'none',
85
+ outline: 'none',
86
+ width: '100%',
87
+ textAlign: 'center',
88
+ fontFamily: 'inherit',
89
+ backgroundColor: 'transparent',
90
+ },
91
+ columnsContainer: {
92
+ display: 'grid',
93
+ gap: '2rem',
94
+ },
95
+ column: {
96
+ lineHeight: 1.7,
97
+ fontSize: '1rem',
98
+ textAlign: 'justify',
99
+ },
100
+ dropCap: {
101
+ float: 'left',
102
+ fontSize: '3em',
103
+ lineHeight: 1,
104
+ marginRight: '0.1em',
105
+ marginTop: '0.1em',
106
+ },
107
+ footnote: {
108
+ fontSize: '0.85rem',
109
+ color: '#666',
110
+ borderTop: '1px solid #ddd',
111
+ paddingTop: '1rem',
112
+ marginTop: '1rem',
113
+ },
114
+ editableField: {
115
+ border: '1px solid transparent',
116
+ borderRadius: '4px',
117
+ transition: 'border-color 0.2s, background-color 0.2s',
118
+ },
119
+ editableFieldHover: {
120
+ borderColor: '#007bff',
121
+ backgroundColor: 'rgba(0, 123, 255, 0.05)',
122
+ },
123
+ };
124
+ // ============================================
125
+ // Default Content
126
+ // ============================================
127
+ function createDefaultOgilvyContent() {
128
+ return {
129
+ image: {
130
+ src: '',
131
+ altText: '',
132
+ },
133
+ legend: '',
134
+ legendAsterisk: false,
135
+ title: 'Titre de la section',
136
+ subtitle: 'Sous-titre descriptif',
137
+ bodyText: '',
138
+ columns: 2,
139
+ dropCap: { ...page_1.DEFAULT_DROP_CAP },
140
+ footnote: '',
141
+ };
142
+ }
143
+ function EditableText({ value, onChange, isEditing, placeholder = 'Cliquez pour éditer...', style = {}, inputStyle = {}, multiline = false, as: Component = 'p', }) {
144
+ const [isHovered, setIsHovered] = (0, react_1.useState)(false);
145
+ if (!isEditing) {
146
+ return (0, jsx_runtime_1.jsx)(Component, { style: style, children: value || placeholder });
147
+ }
148
+ const combinedStyle = {
149
+ ...style,
150
+ ...inputStyle,
151
+ ...(isHovered ? styles.editableFieldHover : {}),
152
+ };
153
+ if (multiline) {
154
+ return ((0, jsx_runtime_1.jsx)("textarea", { value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, style: {
155
+ ...combinedStyle,
156
+ resize: 'none',
157
+ minHeight: '100px',
158
+ }, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false) }));
159
+ }
160
+ return ((0, jsx_runtime_1.jsx)("input", { type: "text", value: value, onChange: (e) => onChange(e.target.value), placeholder: placeholder, style: combinedStyle, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false) }));
161
+ }
162
+ function ImagePicker({ image, onChange, isEditing }) {
163
+ const [showDialog, setShowDialog] = (0, react_1.useState)(false);
164
+ const [tempUrl, setTempUrl] = (0, react_1.useState)(image.src);
165
+ const handleClick = () => {
166
+ if (isEditing) {
167
+ setTempUrl(image.src);
168
+ setShowDialog(true);
169
+ }
170
+ };
171
+ const handleSave = () => {
172
+ onChange({ ...image, src: tempUrl });
173
+ setShowDialog(false);
174
+ };
175
+ if (!image.src) {
176
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: styles.imagePlaceholder, onClick: handleClick, children: isEditing ? 'Cliquez pour ajouter une image' : 'Aucune image' }), showDialog && ((0, jsx_runtime_1.jsx)(ImageDialog, { url: tempUrl, onUrlChange: setTempUrl, onSave: handleSave, onCancel: () => setShowDialog(false) }))] }));
177
+ }
178
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: styles.imageContainer, children: (0, jsx_runtime_1.jsx)("img", { src: image.src, alt: image.altText || '', style: {
179
+ ...styles.image,
180
+ cursor: isEditing ? 'pointer' : 'default',
181
+ }, onClick: handleClick }) }), showDialog && ((0, jsx_runtime_1.jsx)(ImageDialog, { url: tempUrl, onUrlChange: setTempUrl, onSave: handleSave, onCancel: () => setShowDialog(false) }))] }));
182
+ }
183
+ function ImageDialog({ url, onUrlChange, onSave, onCancel }) {
184
+ return ((0, jsx_runtime_1.jsx)("div", { style: {
185
+ position: 'fixed',
186
+ top: 0,
187
+ left: 0,
188
+ right: 0,
189
+ bottom: 0,
190
+ backgroundColor: 'rgba(0,0,0,0.5)',
191
+ display: 'flex',
192
+ alignItems: 'center',
193
+ justifyContent: 'center',
194
+ zIndex: 1000,
195
+ }, onClick: onCancel, children: (0, jsx_runtime_1.jsxs)("div", { style: {
196
+ backgroundColor: 'white',
197
+ padding: '1.5rem',
198
+ borderRadius: '8px',
199
+ minWidth: '400px',
200
+ }, onClick: (e) => e.stopPropagation(), children: [(0, jsx_runtime_1.jsx)("h3", { style: { margin: '0 0 1rem 0' }, children: "URL de l'image" }), (0, jsx_runtime_1.jsx)("input", { type: "text", value: url, onChange: (e) => onUrlChange(e.target.value), placeholder: "https://...", style: {
201
+ width: '100%',
202
+ padding: '0.5rem',
203
+ marginBottom: '1rem',
204
+ border: '1px solid #ccc',
205
+ borderRadius: '4px',
206
+ } }), url && ((0, jsx_runtime_1.jsx)("img", { src: url, alt: "Preview", style: {
207
+ maxWidth: '100%',
208
+ maxHeight: '200px',
209
+ marginBottom: '1rem',
210
+ display: 'block',
211
+ } })), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }, children: [(0, jsx_runtime_1.jsx)("button", { onClick: onCancel, style: {
212
+ padding: '0.5rem 1rem',
213
+ border: '1px solid #ccc',
214
+ borderRadius: '4px',
215
+ backgroundColor: 'white',
216
+ cursor: 'pointer',
217
+ }, children: "Annuler" }), (0, jsx_runtime_1.jsx)("button", { onClick: onSave, style: {
218
+ padding: '0.5rem 1rem',
219
+ border: 'none',
220
+ borderRadius: '4px',
221
+ backgroundColor: '#007bff',
222
+ color: 'white',
223
+ cursor: 'pointer',
224
+ }, children: "Enregistrer" })] })] }) }));
225
+ }
226
+ function DropCapText({ text, dropCap }) {
227
+ if (!text || !dropCap.enabled) {
228
+ return (0, jsx_runtime_1.jsx)("div", { style: styles.column, children: text });
229
+ }
230
+ const firstChar = text.charAt(0);
231
+ const restOfText = text.slice(1);
232
+ const dropCapStyle = {
233
+ ...styles.dropCap,
234
+ fontSize: `${dropCap.fontSize || 3}em`,
235
+ fontFamily: dropCap.fontFamily || 'inherit',
236
+ color: dropCap.color || 'inherit',
237
+ };
238
+ if (dropCap.type === 'icon' && dropCap.iconUrl) {
239
+ return ((0, jsx_runtime_1.jsxs)("div", { style: styles.column, children: [(0, jsx_runtime_1.jsx)("img", { src: dropCap.iconUrl, alt: "", style: {
240
+ float: 'left',
241
+ width: `${dropCap.iconSize || 48}px`,
242
+ height: `${dropCap.iconSize || 48}px`,
243
+ marginRight: '0.5em',
244
+ marginTop: '0.1em',
245
+ } }), text] }));
246
+ }
247
+ return ((0, jsx_runtime_1.jsxs)("div", { style: styles.column, children: [(0, jsx_runtime_1.jsx)("span", { style: dropCapStyle, children: firstChar }), restOfText] }));
248
+ }
249
+ function Columns({ text, columns, dropCap }) {
250
+ // Pour l'instant, on divise simplement le texte en paragraphes
251
+ // TODO: Intégrer RichTextEditor pour l'édition du corps de texte
252
+ const paragraphs = text.split('\n\n').filter(Boolean);
253
+ // Distribuer les paragraphes dans les colonnes
254
+ const columnTexts = Array(columns).fill('');
255
+ paragraphs.forEach((para, idx) => {
256
+ columnTexts[idx % columns] += (columnTexts[idx % columns] ? '\n\n' : '') + para;
257
+ });
258
+ return ((0, jsx_runtime_1.jsx)("div", { style: {
259
+ ...styles.columnsContainer,
260
+ gridTemplateColumns: `repeat(${columns}, 1fr)`,
261
+ }, children: columnTexts.map((colText, idx) => ((0, jsx_runtime_1.jsx)("div", { children: idx === 0 ? ((0, jsx_runtime_1.jsx)(DropCapText, { text: colText, dropCap: dropCap })) : ((0, jsx_runtime_1.jsx)("div", { style: styles.column, children: colText })) }, idx))) }));
262
+ }
263
+ // ============================================
264
+ // Main Layout Component
265
+ // ============================================
266
+ function OgilvyLayout({ section, isEditing, onContentChange, }) {
267
+ const content = section.content;
268
+ const updateField = (0, react_1.useCallback)((field, value) => {
269
+ onContentChange({
270
+ ...content,
271
+ [field]: value,
272
+ });
273
+ }, [content, onContentChange]);
274
+ return ((0, jsx_runtime_1.jsxs)("div", { style: styles.container, children: [(0, jsx_runtime_1.jsx)(ImagePicker, { image: content.image, onChange: (img) => updateField('image', img), isEditing: isEditing }), (content.legend || isEditing) && ((0, jsx_runtime_1.jsxs)("div", { style: styles.legend, children: [(0, jsx_runtime_1.jsx)(EditableText, { value: content.legend, onChange: (v) => updateField('legend', v), isEditing: isEditing, placeholder: "L\u00E9gende de l'image...", style: styles.legend }), content.legendAsterisk && '*'] })), (0, jsx_runtime_1.jsxs)("div", { style: styles.titleContainer, children: [(0, jsx_runtime_1.jsx)(EditableText, { value: content.title, onChange: (v) => updateField('title', v), isEditing: isEditing, placeholder: "Titre", style: styles.title, inputStyle: styles.titleInput, as: "h1" }), (0, jsx_runtime_1.jsx)(EditableText, { value: content.subtitle, onChange: (v) => updateField('subtitle', v), isEditing: isEditing, placeholder: "Sous-titre", style: styles.subtitle, inputStyle: styles.subtitleInput, as: "h2" })] }), (0, jsx_runtime_1.jsxs)("div", { className: content.dropCap.enabled ? 'ogilvy-drop-cap' : '', style: {
275
+ ['--drop-cap-size']: `${content.dropCap.fontSize || 3}em`,
276
+ ['--drop-cap-color']: content.dropCap.color || 'inherit',
277
+ ['--drop-cap-font']: content.dropCap.fontFamily || 'Georgia, serif',
278
+ }, children: [(0, jsx_runtime_1.jsx)("style", { children: `
279
+ .ogilvy-drop-cap .editor-paragraph:first-of-type::first-letter {
280
+ float: left;
281
+ font-size: var(--drop-cap-size, 3em);
282
+ font-family: var(--drop-cap-font, Georgia, serif);
283
+ color: var(--drop-cap-color, inherit);
284
+ line-height: 0.8;
285
+ margin-right: 0.1em;
286
+ margin-top: 0.1em;
287
+ }
288
+ ` }), isEditing ? ((0, jsx_runtime_1.jsx)(RichTextEditor_1.RichTextEditor, { initialContent: content.bodyText, onChange: (_html, json) => updateField('bodyText', json), placeholder: "Corps du texte...", columns: content.columns, minHeight: content.columnLines ? `calc(${content.columnLines * 1.7}em + 60px)` : "200px", autoGrow: !content.columnLines })) : ((0, jsx_runtime_1.jsx)(RichTextViewer_1.RichTextViewer, { content: content.bodyText, columns: content.columns }))] }), (content.footnote || (isEditing && content.legendAsterisk)) && ((0, jsx_runtime_1.jsx)("div", { style: styles.footnote, children: (0, jsx_runtime_1.jsx)(EditableText, { value: content.footnote || '', onChange: (v) => updateField('footnote', v), isEditing: isEditing, placeholder: "* Note explicative...", style: styles.footnote }) }))] }));
289
+ }
290
+ // ============================================
291
+ // Register Layout
292
+ // ============================================
293
+ (0, index_1.registerLayout)({
294
+ type: 'ogilvy',
295
+ name: 'Ogilvy',
296
+ description: 'Image hero + légende + titre + sous-titre + texte en colonnes avec lettrine',
297
+ icon: '📰',
298
+ component: OgilvyLayout,
299
+ defaultContent: createDefaultOgilvyContent,
300
+ category: 'editorial',
301
+ });
@@ -0,0 +1,7 @@
1
+ import { LayoutComponentProps } from './index';
2
+ export interface RichTextContent {
3
+ lexicalState: string;
4
+ }
5
+ export declare function createDefaultRichTextContent(): RichTextContent;
6
+ export declare function RichTextLayout({ section, isEditing, onContentChange, }: LayoutComponentProps<RichTextContent>): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=RichTextLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RichTextLayout.d.ts","sourceRoot":"","sources":["../../src/layouts/RichTextLayout.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAkB,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAQ9D,MAAM,WAAW,eAAe;IAE9B,YAAY,EAAE,MAAM,CAAA;CACrB;AAMD,wBAAgB,4BAA4B,IAAI,eAAe,CAI9D;AAMD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,SAAS,EACT,eAAe,GAChB,EAAE,oBAAoB,CAAC,eAAe,CAAC,2CA6BvC"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.createDefaultRichTextContent = createDefaultRichTextContent;
5
+ exports.RichTextLayout = RichTextLayout;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const index_1 = require("./index");
9
+ const RichTextEditor_1 = require("../components/RichTextEditor");
10
+ const RichTextViewer_1 = require("../components/RichTextViewer");
11
+ // ============================================
12
+ // Default Content
13
+ // ============================================
14
+ function createDefaultRichTextContent() {
15
+ return {
16
+ lexicalState: '',
17
+ };
18
+ }
19
+ // ============================================
20
+ // Layout Component
21
+ // ============================================
22
+ function RichTextLayout({ section, isEditing, onContentChange, }) {
23
+ const content = section.content;
24
+ const handleChange = (0, react_1.useCallback)((_html, json) => {
25
+ onContentChange({
26
+ lexicalState: json,
27
+ });
28
+ }, [onContentChange]);
29
+ if (!isEditing) {
30
+ // Mode lecture: utiliser RichTextViewer
31
+ return ((0, jsx_runtime_1.jsx)(RichTextViewer_1.RichTextViewer, { content: content.lexicalState }));
32
+ }
33
+ // Mode édition: utiliser RichTextEditor
34
+ return ((0, jsx_runtime_1.jsx)(RichTextEditor_1.RichTextEditor, { initialContent: content.lexicalState, onChange: handleChange, placeholder: "Commencez \u00E0 \u00E9crire..." }));
35
+ }
36
+ // ============================================
37
+ // Register Layout
38
+ // ============================================
39
+ (0, index_1.registerLayout)({
40
+ type: 'text-only', // On utilise 'text-only' qui est déjà dans les types
41
+ name: 'Texte riche',
42
+ description: 'Éditeur de texte riche simple (compatible avec l\'ancien format)',
43
+ icon: '📝',
44
+ component: RichTextLayout,
45
+ defaultContent: createDefaultRichTextContent,
46
+ category: 'editorial',
47
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Layout Registry
3
+ * Centralise tous les layouts disponibles pour les sections
4
+ */
5
+ import React from 'react';
6
+ import { LayoutType, Section } from '../types/page';
7
+ export interface LayoutComponentProps<T = unknown> {
8
+ section: Section<T>;
9
+ isEditing: boolean;
10
+ onContentChange: (content: T) => void;
11
+ }
12
+ export interface LayoutRegistryEntry<T = unknown> {
13
+ type: LayoutType;
14
+ name: string;
15
+ description: string;
16
+ icon?: string;
17
+ component: React.ComponentType<LayoutComponentProps<T>>;
18
+ defaultContent: () => T;
19
+ category: 'editorial' | 'media' | 'grid' | 'special';
20
+ }
21
+ export declare function registerLayout<T>(entry: LayoutRegistryEntry<T>): void;
22
+ export declare function getLayout(type: LayoutType): LayoutRegistryEntry | undefined;
23
+ export declare function getAllLayouts(): LayoutRegistryEntry[];
24
+ export declare function getLayoutsByCategory(category: LayoutRegistryEntry['category']): LayoutRegistryEntry[];
25
+ export declare function getLayoutComponent(type: LayoutType): React.ComponentType<LayoutComponentProps> | null;
26
+ export declare function getLayoutDefaultContent<T>(type: LayoutType): T | null;
27
+ export * from './OgilvyLayout';
28
+ export * from './RichTextLayout';
29
+ export * from './GalleryLayout';
30
+ export declare function initializeLayouts(): void;
31
+ //# sourceMappingURL=index.d.ts.map