Skip to content

Commit db586b3

Browse files
authored
fix: rich editor returning empty state (#27)
* fix: rich editor returning empty state * Fix styling --------- Co-authored-by: Baspa <[email protected]>
1 parent b0c3d00 commit db586b3

File tree

1 file changed

+140
-107
lines changed

1 file changed

+140
-107
lines changed

src/Fields/RichEditor.php

Lines changed: 140 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -29,104 +29,107 @@ public static function getDefaultConfig(): array
2929

3030
public static function make(string $name, ?Field $field = null): Input
3131
{
32-
/**
33-
* @var Input $input
34-
*/
35-
$input = self::applyDefaultSettings(Input::make($name), $field);
36-
37-
$input = $input->label($field->name ?? null)
38-
->toolbarButtons([$field->config['toolbarButtons'] ?? self::getDefaultConfig()['toolbarButtons']])
39-
->disableToolbarButtons($field->config['disableToolbarButtons'] ?? self::getDefaultConfig()['disableToolbarButtons'])
32+
$input = self::createBaseInput($name, $field);
33+
$input = self::configureToolbarButtons($input, $field);
34+
$input = self::configureStateHandling($input, $name);
35+
$input = self::configureCaptions($input, $field);
36+
37+
return $input;
38+
}
39+
40+
private static function createBaseInput(string $name, ?Field $field): Input
41+
{
42+
return self::applyDefaultSettings(Input::make($name), $field)
43+
->label($field->name ?? null)
4044
->default(null)
4145
->placeholder('')
4246
->statePath($name)
4347
->live()
4448
->json(false)
4549
->beforeStateDehydrated(function () {})
46-
->saveRelationshipsUsing(function () {})
47-
->formatStateUsing(function ($state) {
48-
if (empty($state)) {
49-
return null;
50-
}
51-
52-
if (is_string($state)) {
53-
$decoded = json_decode($state, true);
54-
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
55-
return $decoded;
56-
}
57-
58-
return $state;
59-
}
60-
61-
if (is_array($state)) {
62-
if (isset($state[0]) && is_array($state[0]) && isset($state[0]['type']) && $state[0]['type'] === 'doc') {
63-
$state = $state[0];
64-
}
65-
66-
if (isset($state['content']) && is_array($state['content'])) {
67-
$content = $state['content'];
68-
if (count($content) > 0 && is_array($content[0]) && empty($content[0])) {
69-
$state['content'] = [];
70-
}
71-
}
72-
73-
if (! isset($state['type']) || $state['type'] !== 'doc') {
74-
return null;
75-
}
76-
77-
if (! isset($state['content']) || ! is_array($state['content'])) {
78-
$state['content'] = [];
79-
}
80-
81-
return $state;
82-
}
83-
84-
return null;
85-
})
86-
87-
->dehydrateStateUsing(function ($state) {
88-
if (empty($state)) {
89-
return null;
90-
}
91-
92-
if (is_string($state)) {
93-
return $state;
94-
}
95-
96-
if (is_array($state)) {
97-
if (isset($state[0]) && is_array($state[0]) && isset($state[0]['type']) && $state[0]['type'] === 'doc') {
98-
$state = $state[0];
99-
}
100-
101-
if (isset($state['content']) && is_array($state['content'])) {
102-
$content = $state['content'];
103-
if (count($content) > 0 && is_array($content[0]) && empty($content[0])) {
104-
$state['content'] = [];
105-
}
106-
}
107-
108-
if (! isset($state['type']) || $state['type'] !== 'doc') {
109-
return null;
110-
}
111-
112-
if (! isset($state['content']) || ! is_array($state['content'])) {
113-
$state['content'] = [];
114-
}
115-
116-
return $state;
117-
}
118-
119-
return null;
120-
});
50+
->saveRelationshipsUsing(function () {});
51+
}
52+
53+
private static function configureToolbarButtons(Input $input, ?Field $field): Input
54+
{
55+
$config = self::getDefaultConfig();
56+
57+
return $input
58+
->toolbarButtons([$field->config['toolbarButtons'] ?? $config['toolbarButtons']])
59+
->disableToolbarButtons($field->config['disableToolbarButtons'] ?? $config['disableToolbarButtons']);
60+
}
12161

62+
private static function configureStateHandling(Input $input, string $name): Input
63+
{
64+
return $input->formatStateUsing(function ($state) {
65+
return self::formatRichEditorState($state);
66+
});
67+
}
68+
69+
private static function configureCaptions(Input $input, ?Field $field): Input
70+
{
12271
$hideCaptions = $field->config['hideCaptions'] ?? self::getDefaultConfig()['hideCaptions'];
72+
12373
if ($hideCaptions) {
12474
$input->extraAttributes(['data-hide-captions' => 'true']);
12575
}
12676

12777
return $input;
12878
}
12979

80+
private static function formatRichEditorState($state)
81+
{
82+
if (empty($state)) {
83+
return null;
84+
}
85+
86+
// If it's already a string (HTML), return it as is
87+
if (is_string($state)) {
88+
return $state;
89+
}
90+
91+
// If it's an array (JSON format), handle it
92+
if (is_array($state)) {
93+
return self::formatJsonState($state);
94+
}
95+
96+
return null;
97+
}
98+
99+
private static function formatJsonState(array $state): ?array
100+
{
101+
// Handle nested doc structure
102+
if (isset($state[0]) && is_array($state[0]) && isset($state[0]['type']) && $state[0]['type'] === 'doc') {
103+
$state = $state[0];
104+
}
105+
106+
// Clean up empty content arrays
107+
if (isset($state['content']) && is_array($state['content'])) {
108+
$state = self::cleanContentArray($state);
109+
}
110+
111+
// Validate doc structure
112+
if (! isset($state['type']) || $state['type'] !== 'doc') {
113+
return null;
114+
}
115+
116+
if (! isset($state['content']) || ! is_array($state['content'])) {
117+
$state['content'] = [];
118+
}
119+
120+
return $state;
121+
}
122+
123+
private static function cleanContentArray(array $state): array
124+
{
125+
$content = $state['content'];
126+
if (count($content) > 0 && is_array($content[0]) && empty($content[0])) {
127+
$state['content'] = [];
128+
}
129+
130+
return $state;
131+
}
132+
130133
public static function cleanRichEditorState($state, array $options = [])
131134
{
132135
if (empty($state)) {
@@ -142,45 +145,75 @@ public static function mutateBeforeSaveCallback($record, $field, array $data): a
142145
{
143146
$data = self::ensureRichEditorDataFormat($record, $field, $data);
144147

145-
$autoCleanContent = $field->config['autoCleanContent'] ?? self::getDefaultConfig()['autoCleanContent'];
146-
147-
if ($autoCleanContent) {
148-
$options = [
149-
'preserveCustomCaptions' => $field->config['preserveCustomCaptions'] ?? self::getDefaultConfig()['preserveCustomCaptions'],
150-
];
151-
152-
// Handle different data structures from different callers
153-
if (isset($data['values'][$field->ulid])) {
154-
// Called from ContentResource
155-
$data['values'][$field->ulid] = self::cleanRichEditorState($data['values'][$field->ulid], $options);
156-
} elseif (isset($data[$record->valueColumn][$field->ulid])) {
157-
// Called from CanMapDynamicFields trait
158-
$data[$record->valueColumn][$field->ulid] = self::cleanRichEditorState($data[$record->valueColumn][$field->ulid], $options);
159-
}
148+
if (self::shouldAutoCleanContent($field)) {
149+
$data = self::applyContentCleaning($record, $field, $data);
160150
}
161151

162152
return $data;
163153
}
164154

165-
private static function ensureRichEditorDataFormat($record, $field, array $data): array
155+
private static function shouldAutoCleanContent($field): bool
156+
{
157+
return $field->config['autoCleanContent'] ?? self::getDefaultConfig()['autoCleanContent'];
158+
}
159+
160+
private static function applyContentCleaning($record, $field, array $data): array
166161
{
162+
$options = self::getCleaningOptions($field);
163+
167164
if (isset($data['values'][$field->ulid])) {
168-
$value = $data['values'][$field->ulid];
169-
if (empty($value)) {
170-
$data['values'][$field->ulid] = '';
171-
}
165+
// Called from ContentResource
166+
$data['values'][$field->ulid] = self::cleanRichEditorState($data['values'][$field->ulid], $options);
172167
} elseif (isset($data[$record->valueColumn][$field->ulid])) {
173-
$value = $data[$record->valueColumn][$field->ulid];
174-
if (empty($value)) {
175-
$data[$record->valueColumn][$field->ulid] = '';
176-
}
168+
// Called from CanMapDynamicFields trait
169+
$data[$record->valueColumn][$field->ulid] = self::cleanRichEditorState($data[$record->valueColumn][$field->ulid], $options);
170+
}
171+
172+
return $data;
173+
}
174+
175+
private static function getCleaningOptions($field): array
176+
{
177+
return [
178+
'preserveCustomCaptions' => $field->config['preserveCustomCaptions'] ?? self::getDefaultConfig()['preserveCustomCaptions'],
179+
];
180+
}
181+
182+
private static function ensureRichEditorDataFormat($record, $field, array $data): array
183+
{
184+
$data = self::normalizeContentResourceValue($data, $field);
185+
$data = self::normalizeDynamicFieldValue($record, $data, $field);
186+
187+
return $data;
188+
}
189+
190+
private static function normalizeContentResourceValue(array $data, $field): array
191+
{
192+
if (isset($data['values'][$field->ulid]) && empty($data['values'][$field->ulid])) {
193+
$data['values'][$field->ulid] = '';
194+
}
195+
196+
return $data;
197+
}
198+
199+
private static function normalizeDynamicFieldValue($record, array $data, $field): array
200+
{
201+
if (isset($data[$record->valueColumn][$field->ulid]) && empty($data[$record->valueColumn][$field->ulid])) {
202+
$data[$record->valueColumn][$field->ulid] = '';
177203
}
178204

179205
return $data;
180206
}
181207

182208
public static function mutateFormDataCallback($record, $field, array $data): array
183209
{
210+
// Get the raw value from the database without JSON decoding
211+
$rawValue = $record->values()->where('field_ulid', $field->ulid)->first()?->value;
212+
213+
if ($rawValue !== null) {
214+
$data[$record->valueColumn][$field->ulid] = $rawValue;
215+
}
216+
184217
return $data;
185218
}
186219

0 commit comments

Comments
 (0)