Skip to content

Commit 0c54319

Browse files
committed
Merge branch 'main' into feat/conditional-fields
2 parents 0cbe588 + 23cb006 commit 0c54319

File tree

5 files changed

+345
-34
lines changed

5 files changed

+345
-34
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: PR Auto Assignment and Labeling
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited]
6+
7+
jobs:
8+
auto-assign-and-label:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
pull-requests: write
12+
issues: write
13+
steps:
14+
- name: Get branch name
15+
id: branch
16+
run: echo "branch_name=${GITHUB_HEAD_REF}" >> $GITHUB_OUTPUT
17+
18+
- name: Determine label based on branch prefix or author
19+
id: label
20+
run: |
21+
# Check if it's a Dependabot PR
22+
if [[ "${{ github.actor }}" == "dependabot[bot]" ]]; then
23+
echo "label=dependencies" >> $GITHUB_OUTPUT
24+
# Check branch prefixes
25+
elif [[ "${{ github.head_ref }}" == feature/* ]] || [[ "${{ github.head_ref }}" == feat/* ]]; then
26+
echo "label=enhancement" >> $GITHUB_OUTPUT
27+
elif [[ "${{ github.head_ref }}" == fix/* ]]; then
28+
echo "label=fix" >> $GITHUB_OUTPUT
29+
elif [[ "${{ github.head_ref }}" == docs/* ]]; then
30+
echo "label=documentation" >> $GITHUB_OUTPUT
31+
else
32+
echo "label=" >> $GITHUB_OUTPUT
33+
fi
34+
35+
- name: Add label to PR
36+
if: steps.label.outputs.label != ''
37+
uses: actions/github-script@v7
38+
with:
39+
script: |
40+
github.rest.issues.addLabels({
41+
issue_number: context.issue.number,
42+
owner: context.repo.owner,
43+
repo: context.repo.repo,
44+
labels: ['${{ steps.label.outputs.label }}']
45+
})
46+
47+
- name: Assign reviewer and assignee
48+
uses: actions/github-script@v7
49+
with:
50+
script: |
51+
const assignee = 'baspa';
52+
53+
// Always assign as assignee (you can be assigned to your own PR)
54+
github.rest.issues.addAssignees({
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
issue_number: context.issue.number,
58+
assignees: [assignee]
59+
});
60+
console.log(`Assigned ${assignee} as assignee`);
61+
62+
// Only assign reviewer if they are not the PR author
63+
if (context.payload.pull_request.user.login !== assignee) {
64+
github.rest.pulls.requestReviewers({
65+
owner: context.repo.owner,
66+
repo: context.repo.repo,
67+
pull_number: context.issue.number,
68+
reviewers: [assignee]
69+
});
70+
console.log(`Assigned ${assignee} as reviewer`);
71+
} else {
72+
console.log(`Skipping reviewer assignment - ${assignee} is the PR author`);
73+
}

src/Concerns/HasSelectableValues.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ protected function selectableValuesFormFields(string $type, string $label, strin
212212
$set("config.{$type}", is_array($value) ? $value : (is_bool($value) ? [] : [$value]));
213213
})
214214
->label(__('Type'))
215+
->in(['array', 'relationship', ''])
216+
->nullable()
215217
->live(),
216218
// Array options
217219
$arrayComponent::make('config.options')

src/Fields/RichEditor.php

Lines changed: 160 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,63 +35,189 @@ public static function getDefaultConfig(): array
3535

3636
public static function make(string $name, ?Field $field = null): Input
3737
{
38+
$input = self::createBaseInput($name, $field);
39+
$input = self::configureToolbarButtons($input, $field);
40+
$input = self::configureStateHandling($input, $name);
41+
$input = self::configureCaptions($input, $field);
3842

39-
$input = self::applyDefaultSettings(Input::make($name), $field);
43+
return $input;
44+
}
45+
46+
private static function createBaseInput(string $name, ?Field $field): Input
47+
{
48+
return self::applyDefaultSettings(Input::make($name), $field)
49+
->label($field->name ?? null)
50+
->default(null)
51+
->placeholder('')
52+
->statePath($name)
53+
->live()
54+
->json(false)
55+
->beforeStateDehydrated(function () {})
56+
->saveRelationshipsUsing(function () {});
57+
}
4058

41-
$input = $input->label($field->name ?? null)
42-
->toolbarButtons([$field->config['toolbarButtons'] ?? self::getDefaultConfig()['toolbarButtons']])
43-
->disableToolbarButtons($field->config['disableToolbarButtons'] ?? self::getDefaultConfig()['disableToolbarButtons']);
59+
private static function configureToolbarButtons(Input $input, ?Field $field): Input
60+
{
61+
$config = self::getDefaultConfig();
62+
63+
return $input
64+
->toolbarButtons([$field->config['toolbarButtons'] ?? $config['toolbarButtons']])
65+
->disableToolbarButtons($field->config['disableToolbarButtons'] ?? $config['disableToolbarButtons']);
66+
}
4467

45-
// Add data attribute for hiding captions if enabled
68+
private static function configureStateHandling(Input $input, string $name): Input
69+
{
70+
return $input->formatStateUsing(function ($state) {
71+
return self::formatRichEditorState($state);
72+
});
73+
}
74+
75+
private static function configureCaptions(Input $input, ?Field $field): Input
76+
{
4677
$hideCaptions = $field->config['hideCaptions'] ?? self::getDefaultConfig()['hideCaptions'];
78+
4779
if ($hideCaptions) {
4880
$input->extraAttributes(['data-hide-captions' => 'true']);
4981
}
5082

51-
// Add content processing to automatically clean HTML
52-
$autoCleanContent = $field->config['autoCleanContent'] ?? self::getDefaultConfig()['autoCleanContent'];
83+
return $input;
84+
}
85+
86+
private static function formatRichEditorState($state)
87+
{
88+
if (empty($state)) {
89+
return null;
90+
}
91+
92+
// If it's already a string (HTML), return it as is
93+
if (is_string($state)) {
94+
return $state;
95+
}
5396

54-
if ($autoCleanContent) {
55-
$options = [
56-
'preserveCustomCaptions' => $field->config['preserveCustomCaptions'] ?? self::getDefaultConfig()['preserveCustomCaptions'],
57-
];
97+
// If it's an array (JSON format), handle it
98+
if (is_array($state)) {
99+
return self::formatJsonState($state);
100+
}
58101

59-
// Clean content when state is updated (including file uploads)
60-
$input->afterStateUpdated(function ($state) use ($options) {
61-
if (! empty($state)) {
62-
return ContentCleaningService::cleanHtmlContent($state, $options);
63-
}
102+
return null;
103+
}
104+
105+
private static function formatJsonState(array $state): ?array
106+
{
107+
// Handle nested doc structure
108+
if (isset($state[0]) && is_array($state[0]) && isset($state[0]['type']) && $state[0]['type'] === 'doc') {
109+
$state = $state[0];
110+
}
64111

65-
return $state;
66-
});
112+
// Clean up empty content arrays
113+
if (isset($state['content']) && is_array($state['content'])) {
114+
$state = self::cleanContentArray($state);
115+
}
67116

68-
// Ensure cleaned content is saved to database
69-
$input->dehydrateStateUsing(function ($state) use ($options) {
70-
if (! empty($state)) {
71-
return ContentCleaningService::cleanHtmlContent($state, $options);
72-
}
117+
// Validate doc structure
118+
if (! isset($state['type']) || $state['type'] !== 'doc') {
119+
return null;
120+
}
73121

74-
return $state;
75-
});
122+
if (! isset($state['content']) || ! is_array($state['content'])) {
123+
$state['content'] = [];
76124
}
77125

78-
return $input;
126+
return $state;
127+
}
128+
129+
private static function cleanContentArray(array $state): array
130+
{
131+
$content = $state['content'];
132+
if (count($content) > 0 && is_array($content[0]) && empty($content[0])) {
133+
$state['content'] = [];
134+
}
135+
136+
return $state;
137+
}
138+
139+
public static function cleanRichEditorState($state, array $options = [])
140+
{
141+
if (empty($state)) {
142+
return '';
143+
}
144+
145+
$cleanedState = ContentCleaningService::cleanContent($state, $options);
146+
147+
return $cleanedState;
79148
}
80149

81150
public static function mutateBeforeSaveCallback($record, $field, array $data): array
82151
{
83-
$autoCleanContent = $field->config['autoCleanContent'] ?? self::getDefaultConfig()['autoCleanContent'];
152+
$data = self::ensureRichEditorDataFormat($record, $field, $data);
153+
154+
if (self::shouldAutoCleanContent($field)) {
155+
$data = self::applyContentCleaning($record, $field, $data);
156+
}
157+
158+
return $data;
159+
}
160+
161+
private static function shouldAutoCleanContent($field): bool
162+
{
163+
return $field->config['autoCleanContent'] ?? self::getDefaultConfig()['autoCleanContent'];
164+
}
84165

85-
if ($autoCleanContent && isset($data['values'][$field->ulid])) {
86-
Log::info('RichEditor mutateBeforeSaveCallback before cleaning:', ['content' => $data['values'][$field->ulid]]);
166+
private static function applyContentCleaning($record, $field, array $data): array
167+
{
168+
$options = self::getCleaningOptions($field);
87169

88-
$options = [
89-
'preserveCustomCaptions' => $field->config['preserveCustomCaptions'] ?? self::getDefaultConfig()['preserveCustomCaptions'],
90-
];
170+
if (isset($data['values'][$field->ulid])) {
171+
// Called from ContentResource
172+
$data['values'][$field->ulid] = self::cleanRichEditorState($data['values'][$field->ulid], $options);
173+
} elseif (isset($data[$record->valueColumn][$field->ulid])) {
174+
// Called from CanMapDynamicFields trait
175+
$data[$record->valueColumn][$field->ulid] = self::cleanRichEditorState($data[$record->valueColumn][$field->ulid], $options);
176+
}
177+
178+
return $data;
179+
}
180+
181+
private static function getCleaningOptions($field): array
182+
{
183+
return [
184+
'preserveCustomCaptions' => $field->config['preserveCustomCaptions'] ?? self::getDefaultConfig()['preserveCustomCaptions'],
185+
];
186+
}
91187

92-
$data['values'][$field->ulid] = ContentCleaningService::cleanHtmlContent($data['values'][$field->ulid], $options);
188+
private static function ensureRichEditorDataFormat($record, $field, array $data): array
189+
{
190+
$data = self::normalizeContentResourceValue($data, $field);
191+
$data = self::normalizeDynamicFieldValue($record, $data, $field);
192+
193+
return $data;
194+
}
195+
196+
private static function normalizeContentResourceValue(array $data, $field): array
197+
{
198+
if (isset($data['values'][$field->ulid]) && empty($data['values'][$field->ulid])) {
199+
$data['values'][$field->ulid] = '';
200+
}
201+
202+
return $data;
203+
}
204+
205+
private static function normalizeDynamicFieldValue($record, array $data, $field): array
206+
{
207+
if (isset($data[$record->valueColumn][$field->ulid]) && empty($data[$record->valueColumn][$field->ulid])) {
208+
$data[$record->valueColumn][$field->ulid] = '';
209+
}
210+
211+
return $data;
212+
}
213+
214+
public static function mutateFormDataCallback($record, $field, array $data): array
215+
{
216+
// Get the raw value from the database without JSON decoding
217+
$rawValue = $record->values()->where('field_ulid', $field->ulid)->first()?->value;
93218

94-
Log::info('RichEditor mutateBeforeSaveCallback after cleaning:', ['content' => $data['values'][$field->ulid]]);
219+
if ($rawValue !== null) {
220+
$data[$record->valueColumn][$field->ulid] = $rawValue;
95221
}
96222

97223
return $data;

src/Filament/RelationManagers/FieldsRelationManager.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function form(Schema $schema): Schema
4343
Section::make('Field')
4444
->columns(2)
4545
->columnSpanFull()
46+
->columns(2)
4647
->schema([
4748
TextInput::make('name')
4849
->label(__('Name'))

0 commit comments

Comments
 (0)