diff --git a/resources/dist/build/assets/addon-Dnt1-tBd.js b/resources/dist/build/assets/addon-Dnt1-tBd.js new file mode 100644 index 0000000..da23103 --- /dev/null +++ b/resources/dist/build/assets/addon-Dnt1-tBd.js @@ -0,0 +1 @@ +const c=window.Vue,{BaseTransition:g,BaseTransitionPropsValidators:h,Comment:T,DeprecationTypes:f,EffectScope:v,ErrorCodes:b,ErrorTypeStrings:P,Fragment:R,KeepAlive:y,ReactiveEffect:w,Static:x,Suspense:M,Teleport:I,Text:F,TrackOpTypes:E,Transition:D,TransitionGroup:H,TriggerOpTypes:L,VueElement:k,__esModule:A,assertNumber:V,callWithAsyncErrorHandling:_,callWithErrorHandling:B,camelize:O,capitalize:z,cloneVNode:N,compatUtils:G,compile:U,computed:K,createApp:W,createBlock:p,createCommentVNode:j,createElementBlock:q,createElementVNode:$,createHydrationRenderer:Q,createPropsRestProxy:J,createRenderer:X,createSSRApp:Y,createSlots:Z,createStaticVNode:ee,createTextVNode:te,createVNode:oe,customRef:re,defineAsyncComponent:ie,defineComponent:ne,defineCustomElement:ae,defineEmits:se,defineExpose:le,defineModel:de,defineOptions:ce,defineProps:pe,defineSSRCustomElement:ue,defineSlots:me,devtools:Ce,effect:Se,effectScope:ge,getCurrentInstance:he,getCurrentScope:Te,getCurrentWatcher:fe,getTransitionRawChildren:ve,guardReactiveProps:be,h:Pe,handleError:Re,hasInjectionContext:ye,hydrate:we,hydrateOnIdle:xe,hydrateOnInteraction:Me,hydrateOnMediaQuery:Ie,hydrateOnVisible:Fe,initCustomFormatter:Ee,initDirectivesForSSR:De,inject:He,isMemoSame:Le,isProxy:ke,isReactive:Ae,isReadonly:Ve,isRef:_e,isRuntimeOnly:Be,isShallow:Oe,isVNode:ze,markRaw:Ne,mergeDefaults:Ge,mergeModels:Ue,mergeProps:Ke,nextTick:We,nodeOps:je,normalizeClass:qe,normalizeProps:$e,normalizeStyle:Qe,onActivated:Je,onBeforeMount:Xe,onBeforeUnmount:Ye,onBeforeUpdate:Ze,onDeactivated:et,onErrorCaptured:tt,onMounted:ot,onRenderTracked:rt,onRenderTriggered:it,onScopeDispose:nt,onServerPrefetch:at,onUnmounted:st,onUpdated:lt,onWatcherCleanup:dt,openBlock:u,patchProp:ct,popScopeId:pt,provide:ut,proxyRefs:mt,pushScopeId:Ct,queuePostFlushCb:St,reactive:gt,readonly:ht,ref:Tt,registerRuntimeCompiler:ft,render:vt,renderList:bt,renderSlot:Pt,resolveComponent:Rt,resolveDirective:yt,resolveDynamicComponent:wt,resolveFilter:xt,resolveTransitionHooks:Mt,setBlockTracking:It,setDevtoolsHook:Ft,setTransitionHooks:Et,shallowReactive:Dt,shallowReadonly:Ht,shallowRef:Lt,ssrContextKey:kt,ssrUtils:At,stop:Vt,toDisplayString:_t,toHandlerKey:Bt,toHandlers:Ot,toRaw:zt,toRef:Nt,toRefs:Gt,toValue:Ut,transformVNodeArgs:Kt,triggerRef:Wt,unref:o,useAttrs:jt,useCssModule:qt,useCssVars:$t,useHost:Qt,useId:Jt,useModel:Xt,useSSRContext:Yt,useShadowRoot:Zt,useSlots:eo,useTemplateRef:to,useTransitionState:oo,vModelCheckbox:ro,vModelDynamic:io,vModelRadio:no,vModelSelect:ao,vModelText:so,vShow:lo,version:co,warn:po,watch:uo,watchEffect:mo,watchPostEffect:Co,watchSyncEffect:So,withAsyncContext:go,withCtx:ho,withDefaults:To,withDirectives:fo,withKeys:vo,withMemo:bo,withModifiers:Po,withScopeId:Ro}=c,{Fieldtype:e,IndexFieldtype:yo,FieldtypeMixin:wo,HasActionsMixin:xo,HasInputOptionsMixin:Mo,HasPreferencesMixin:Io,IndexFieldtypeMixin:Fo,InlineEditForm:Eo,DateFormatter:Do,NumberFormatter:Ho,ItemActions:Lo,RelatedItem:ko,RestoreRevision:Ao,RevisionHistory:Vo,RevisionPreview:_o,SaveButtonOptions:Bo,SortableList:Oo,requireElevatedSession:zo,requireElevatedSessionIf:No,clone:Go,debounce:Uo,deepClone:Ko,resetValuesFromResponse:Wo}=__STATAMIC__.core,{Alert:jo,AuthCard:qo,Avatar:$o,Badge:Qo,Button:Jo,ButtonGroup:Xo,Calendar:Yo,Card:Zo,CardList:er,CardListItem:tr,CardPanel:or,CharacterCounter:rr,Checkbox:ir,CheckboxGroup:nr,CodeEditor:ar,Combobox:m,CommandPaletteItem:sr,ConfirmationModal:lr,Context:dr,ContextFooter:cr,ContextHeader:pr,ContextItem:ur,ContextLabel:mr,ContextMenu:Cr,ContextSeparator:Sr,CreateForm:gr,DatePicker:hr,DateRangePicker:Tr,Description:fr,DocsCallout:vr,DragHandle:br,Dropdown:Pr,DropdownItem:Rr,DropdownLabel:yr,DropdownMenu:wr,DropdownSeparator:xr,DropdownFooter:Mr,DropdownHeader:Ir,Editable:Fr,ErrorMessage:Er,EmptyStateItem:Dr,EmptyStateMenu:Hr,Field:Lr,Header:kr,Heading:Ar,HoverCard:Vr,Icon:_r,Input:Br,InputGroup:Or,InputGroupAppend:zr,InputGroupPrepend:Nr,Label:Gr,Listing:Ur,ListingCustomizeColumns:Kr,ListingFilters:Wr,ListingHeaderCell:jr,ListingPagination:qr,ListingPresets:$r,ListingPresetTrigger:Qr,ListingRowActions:Jr,ListingSearch:Xr,ListingTable:Yr,ListingTableBody:Zr,ListingTableHead:ei,ListingToggleAll:ti,LivePreview:oi,LivePreviewPopout:ri,MiddleEllipsis:ii,Modal:ni,ModalClose:ai,ModalTitle:si,Pagination:li,Panel:di,PanelFooter:ci,PanelHeader:pi,Popover:ui,PublishComponents:mi,PublishContainer:Ci,publishContextKey:Si,injectPublishContext:gi,PublishField:hi,PublishFields:Ti,PublishFieldsProvider:fi,PublishForm:vi,PublishLocalizations:bi,PublishSections:Pi,PublishTabs:Ri,Radio:yi,RadioGroup:wi,Select:xi,Separator:Mi,Slider:Ii,Skeleton:Fi,SplitterGroup:Ei,SplitterPanel:Di,SplitterResizeHandle:Hi,StatusIndicator:Li,Subheading:ki,Switch:Ai,TabContent:Vi,Stack:_i,StackClose:Bi,StackHeader:Oi,StackFooter:zi,StackContent:Ni,Table:Gi,TableCell:Ui,TableColumn:Ki,TableColumns:Wi,TableRow:ji,TableRows:qi,TabList:$i,TabProvider:Qi,Tabs:Ji,TabTrigger:Xi,Text:Yi,Textarea:Zi,TimePicker:en,TimezoneHoverCard:tn,Timezones:on,ToggleGroup:rn,ToggleItem:nn,Widget:an,registerIconSet:sn,registerIconSetFromStrings:ln}=__STATAMIC__.ui,C={__name:"MailerLiteFormFieldsFieldtype",props:e.props,emits:e.emits,setup(r,{expose:i,emit:n}){const a=n,s=r,{expose:l,update:d}=e.use(a,s);return i(l),(t,S)=>(u(),p(o(m),{class:"w-full",clearable:"",searchable:"","model-value":t.value,options:t.meta.options,"option-label":"label","option-value":"value","onUpdate:modelValue":o(d)},null,8,["model-value","options","onUpdate:modelValue"]))}};Statamic.booting(()=>{Statamic.$components.register("mailer_lite_form_fields-fieldtype",C)}); diff --git a/resources/dist/build/assets/addon-l0sNRNKZ.js b/resources/dist/build/assets/addon-l0sNRNKZ.js deleted file mode 100644 index 8b13789..0000000 --- a/resources/dist/build/assets/addon-l0sNRNKZ.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/dist/build/manifest.json b/resources/dist/build/manifest.json index 3de1dd2..06f944f 100644 --- a/resources/dist/build/manifest.json +++ b/resources/dist/build/manifest.json @@ -1,6 +1,6 @@ { "resources/js/addon.js": { - "file": "assets/addon-l0sNRNKZ.js", + "file": "assets/addon-Dnt1-tBd.js", "name": "addon", "src": "resources/js/addon.js", "isEntry": true diff --git a/resources/js/addon.js b/resources/js/addon.js index 459b729..36ada1c 100644 --- a/resources/js/addon.js +++ b/resources/js/addon.js @@ -1,3 +1,5 @@ -// No custom Control Panel components. MailerLite configuration lives on the -// native form configure page, injected via Form::appendConfigFields() in the -// addon service provider. +import MailerLiteFormFieldsFieldtype from './components/fieldtypes/MailerLiteFormFieldsFieldtype.vue'; + +Statamic.booting(() => { + Statamic.$components.register('mailer_lite_form_fields-fieldtype', MailerLiteFormFieldsFieldtype); +}); diff --git a/resources/js/components/fieldtypes/MailerLiteFormFieldsFieldtype.vue b/resources/js/components/fieldtypes/MailerLiteFormFieldsFieldtype.vue new file mode 100644 index 0000000..cde3740 --- /dev/null +++ b/resources/js/components/fieldtypes/MailerLiteFormFieldsFieldtype.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/Fieldtypes/MailerLiteFormFields.php b/src/Fieldtypes/MailerLiteFormFields.php new file mode 100644 index 0000000..b616ee7 --- /dev/null +++ b/src/Fieldtypes/MailerLiteFormFields.php @@ -0,0 +1,51 @@ +} + */ + public function preload(): array + { + return [ + 'options' => $this->formFieldOptions(), + ]; + } + + /** + * @return array + */ + private function formFieldOptions(): array + { + $form = request()->route('form'); + + if (is_string($form)) { + $form = Form::find($form); + } + + if (! $form instanceof FormContract) { + return []; + } + + return $form->blueprint()->fields()->all() + ->map(fn (Field $field): array => [ + 'label' => $field->display(), + 'value' => $field->handle(), + ]) + ->values() + ->all(); + } +} diff --git a/src/StatamicMailerLiteServiceProvider.php b/src/StatamicMailerLiteServiceProvider.php index 03b0de8..02718dd 100644 --- a/src/StatamicMailerLiteServiceProvider.php +++ b/src/StatamicMailerLiteServiceProvider.php @@ -3,10 +3,8 @@ namespace Concept7\StatamicMailerLite; use Concept7\StatamicMailerLite\Listeners\SubmissionCreatedListener; -use Statamic\Contracts\Forms\Form as FormContract; use Statamic\Events\SubmissionCreated; use Statamic\Facades\Form; -use Statamic\Fields\Field; use Statamic\Providers\AddonServiceProvider; class StatamicMailerLiteServiceProvider extends AddonServiceProvider @@ -24,28 +22,24 @@ class StatamicMailerLiteServiceProvider extends AddonServiceProvider public function bootAddon(): void { - // bootAddon() already runs inside Statamic's booted phase, so the form - // repository is available and config fields can be appended directly. - // Wrapping this in Statamic::booted() would queue a callback that never - // runs — runBootedCallbacks() is already mid-drain at this point. - Form::all()->each(fn (FormContract $form) => Form::appendConfigFields( - $form->handle(), + // Registered with the '*' wildcard so booting never enumerates forms — + // with statamic/eloquent-driver that would query the database, which + // may be unreachable or unmigrated while the app still has to boot + // (package:discover, Larastan analysis, first deploy). + Form::appendConfigFields( + '*', 'MailerLite', - static::configFields($form), - )); + static::configFields(), + ); } /** - * Build the MailerLite section appended to a form's native configure page. + * Build the MailerLite section appended to every form's native configure page. * * @return array> */ - public static function configFields(FormContract $form): array + public static function configFields(): array { - $formFieldOptions = $form->blueprint()->fields()->all() - ->mapWithKeys(fn (Field $field): array => [$field->handle() => $field->display()]) - ->all(); - return [ 'mailerlite_enabled' => [ 'type' => 'toggle', @@ -70,10 +64,8 @@ public static function configFields(FormContract $form): array [ 'handle' => 'form_field', 'field' => [ - 'type' => 'select', + 'type' => 'mailer_lite_form_fields', 'display' => __('Form field'), - 'options' => $formFieldOptions, - 'clearable' => false, ], ], [ diff --git a/tests/Feature/MailerLiteConfigFieldsTest.php b/tests/Feature/MailerLiteConfigFieldsTest.php index 040817f..854cf35 100644 --- a/tests/Feature/MailerLiteConfigFieldsTest.php +++ b/tests/Feature/MailerLiteConfigFieldsTest.php @@ -1,12 +1,9 @@ toHaveKeys(['mailerlite_enabled', 'mailerlite_group', 'mailerlite_field_mapping']); expect($fields['mailerlite_enabled']['type'])->toBe('toggle'); @@ -17,7 +14,7 @@ }); it('uses a single-select MailerLite field inside the mapping grid', function () { - $fields = StatamicMailerLiteServiceProvider::configFields(Form::make('contact')); + $fields = StatamicMailerLiteServiceProvider::configFields(); $mailerLiteField = collect($fields['mailerlite_field_mapping']['fields']) ->firstWhere('handle', 'mailerlite_field')['field']; @@ -26,18 +23,11 @@ expect($mailerLiteField['max_items'])->toBe(1); }); -it('offers the form fields as form_field options', function () { - $blueprint = Blueprint::makeFromFields([ - 'email' => ['type' => 'text'], - 'name' => ['type' => 'text'], - ])->setHandle('contact')->setNamespace('forms'); +it('uses the dynamic form fields fieldtype inside the mapping grid', function () { + $fields = StatamicMailerLiteServiceProvider::configFields(); - $form = Mockery::mock(FormContract::class); - $form->shouldReceive('handle')->andReturn('contact'); - $form->shouldReceive('blueprint')->andReturn($blueprint); + $formField = collect($fields['mailerlite_field_mapping']['fields']) + ->firstWhere('handle', 'form_field')['field']; - $options = collect(StatamicMailerLiteServiceProvider::configFields($form)['mailerlite_field_mapping']['fields']) - ->firstWhere('handle', 'form_field')['field']['options']; - - expect($options)->toHaveKeys(['email', 'name']); + expect($formField['type'])->toBe('mailer_lite_form_fields'); }); diff --git a/tests/Feature/MailerLiteFormFieldsFieldtypeTest.php b/tests/Feature/MailerLiteFormFieldsFieldtypeTest.php new file mode 100644 index 0000000..47df0c6 --- /dev/null +++ b/tests/Feature/MailerLiteFormFieldsFieldtypeTest.php @@ -0,0 +1,45 @@ + ['type' => 'text'], + 'name' => ['type' => 'text'], + ])->setHandle('contact')->setNamespace('forms'); + + $form = Mockery::mock(FormContract::class); + $form->shouldReceive('blueprint')->andReturn($blueprint); + + $route = new Route(['GET'], 'cp/forms/{form}/edit', []); + $route->parameters = ['form' => $form]; + + request()->setRouteResolver(fn (): Route => $route); + + $options = (new MailerLiteFormFields)->preload()['options']; + + expect(collect($options)->pluck('value')->all())->toBe([ + 'email', + 'name', + ]); + expect(collect($options)->pluck('label')->all())->toBe([ + 'Email', + 'Name', + ]); +}); + +it('preloads an empty option list when the route has no form', function () { + expect((new MailerLiteFormFields)->preload()['options'])->toBe([]); +}); + +it('preloads an empty option list when a form handle cannot be resolved', function () { + $route = new Route(['GET'], 'cp/forms/{form}/edit', []); + $route->parameters = ['form' => 'missing']; + + request()->setRouteResolver(fn (): Route => $route); + + expect((new MailerLiteFormFields)->preload()['options'])->toBe([]); +}); diff --git a/tests/Feature/SectionRegistrationTest.php b/tests/Feature/SectionRegistrationTest.php index 8ad9c39..9793585 100644 --- a/tests/Feature/SectionRegistrationTest.php +++ b/tests/Feature/SectionRegistrationTest.php @@ -3,11 +3,7 @@ use Concept7\StatamicMailerLite\StatamicMailerLiteServiceProvider; use Statamic\Facades\Form; -it('appends the MailerLite section to forms during bootAddon', function () { - $form = Form::make('contact'); - - Form::partialMock()->shouldReceive('all')->andReturn(collect([$form])); - +it('appends the MailerLite section to every form during bootAddon', function () { app()->getProvider(StatamicMailerLiteServiceProvider::class)->bootAddon(); $sections = Form::extraConfigFor('contact'); @@ -16,3 +12,9 @@ expect(array_keys($sections['mailer_lite']['fields'])) ->toBe(['mailerlite_enabled', 'mailerlite_group', 'mailerlite_field_mapping']); }); + +it('boots without enumerating forms', function () { + Form::partialMock()->shouldReceive('all')->never(); + + app()->getProvider(StatamicMailerLiteServiceProvider::class)->bootAddon(); +});