PluginProbe
The Innovative Form Builder – IvyForms / 1.4.1
The Innovative Form Builder – IvyForms v1.4.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / assets / js / admin / admin.ts

admin.ts in The Innovative Form Builder – IvyForms 1.4.1, at frontend/src/assets/js/admin/admin.ts

301 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* eslint no-unused-vars: "off" */
2 import type { Component } from 'vue'
3 import * as Vue from 'vue'
4 import { createApp } from 'vue'
5 import { createPinia } from 'pinia'
6 import App from '../../../views/admin/App.vue'
7 import EntryPage from '@/views/admin/entries/entry-page/EntryPage.vue'
8 import router from '@/router'
9 import '@/assets/scss/main.scss'
10 import 'element-plus/dist/index.css'
11 import { useLabelsStore } from '@/stores/labels'
12 import { useLabels } from '@/composables/useLabels'
13 import api, { dispatchIvyReady, installIvyGlobals } from '@/composables/IvyFormAPI'
14 import { useProFeatures } from '@/composables/useProFeatures'
15 import { useFormBuilder } from '@/stores/useFormBuilder'
16 import { useEntry } from '@/stores/useEntry'
17 import { useNavigation } from '@/composables/useNavigation'
18 import { registerLiteComponents } from '@/composables/registerLiteComponents'
19 import { registerLiteFieldOptions } from '@/views/admin/form-builder/form-builder-page/form-builder/panel/tabs/field-options/registerLiteFieldOptions'
20 import { registerLiteStyleSectionPromos } from '@/views/admin/form-builder/form-builder-page/form-builder/panel/tabs/style-controls/registerLiteStyleSectionPromos'
21 import * as LiteTypes from '@/types/index'
22 import type { IvyProFeatures } from '@/types/global'
23 import { useWcagColors } from '@/composables/useWcagColors'
24 import { useSettingsStore } from '@/stores/useSettingsStore'
25 import IvyMessage from '@/views/_components/message/ivyMessage'
26 import { useApiClient } from '@/composables/useApiClient'
27 import { useActionEntityStore } from '@/stores/actionEntityStore'
28 import { useUnsavedChangesStore } from '@/stores/useUnsavedChangesStore'
29 import { useProUpgradeDialogStore } from '@/stores/proUpgradeDialogStore'
30 import { useGlobalState } from '@/stores/useGlobalState'
31 import { denormalizeFieldsForApi } from '@/utils/fieldsNormalization'
32 import { countryList } from '@/constants/countries'
33 import { isSecurityField } from '@/constants/securityFields'
34 import { useProFeatureUpgrade } from '@/composables/useProFeatureUpgrade'
35 import { useConfirmationMultiSetting } from '@/stores/useConfirmationMultiSetting'
36 import { useGoogleSheetsFormIntegrations } from '@/integrations/google-sheets/composables/useGoogleSheetsFormIntegrations'
37 import {
38 registerFormBuilderExtension,
39 getFormBuilderExtensions,
40 } from '@/extensions/formBuilderExtensions'
41 import { useAllEntries } from '@/stores/useAllEntries'
42 import { getPreviewFieldComponent } from '@/composables/useFieldPreviewComponents'
43 import { normalizeFieldsForBuilder } from '@/utils/fieldsNormalization'
44 import { registerLitePublicFieldComponents } from '@/entries/publicFieldComponentRegistry'
45 import { registerGoogleSheetsAdmin } from '@/integrations/google-sheets/registerGoogleSheetsAdmin'
46
47 // Note: Window interface extensions are now defined in src/types/global.d.ts
48 // No need to duplicate declarations here
49
50 const app = createApp(App)
51 const pinia = createPinia()
52 app.use(pinia)
53 app.use(router)
54
55 // Shared settings store: must exist before mount so Pro views (e.g. Zapier) can read the same
56 // `settings/all` payload as the rest of admin via `window.IvyForms.settingsStore`.
57 const settingsStore = useSettingsStore()
58
59 // Initialize the labels store with WordPress data
60 const labelsStore = useLabelsStore()
61 labelsStore.initialize()
62
63 // Make labels globally available to all components
64 app.config.globalProperties.$labels = useLabels()
65
66 // Expose IvyFormsApi and Lite globals for extenders
67 ;(function exposeGlobals() {
68 // Ensure the API is present on window
69 installIvyGlobals()
70 const w = window
71 const { getLabel } = useLabels()
72 const formBuilder = useFormBuilder()
73 const globalState = useGlobalState()
74
75 // Proxy to resolve labels dynamically via getLabel
76 const labelsProxy = new Proxy(
77 {},
78 {
79 get: (_t, key: string | symbol) => {
80 try {
81 return getLabel(String(key))
82 } catch {
83 return String(key)
84 }
85 },
86 },
87 )
88
89 w.IvyForms = w.IvyForms || { api, hooks: api.hooks, fields: api.fields }
90 w.IvyForms.api = api
91 // Always sync to canonical api.hooks so third-party registrations land on the same registry.
92 w.IvyForms.hooks = api.hooks
93
94 // Ensure hook registration triggers reactive updates in Lite components
95 if (w.IvyForms.hooks === api.hooks && api.hooks.triggerUpdate) {
96 w.IvyForms.hooks.triggerUpdate = api.hooks.triggerUpdate.bind(api.hooks)
97 }
98
99 w.IvyForms.fields = api.fields
100 // Expose label helpers for Pro plugin and other extenders
101 w.IvyForms.getLabel = getLabel
102 w.IvyForms.labels = labelsProxy
103 // Set WordPress admin URL
104 w.IvyForms.adminUrl = w.wpIvyApiSettings?.root?.replace(/\/wp-json\/?$/, '') + '/wp-admin/'
105 // Expose Vue for pro plugins with guaranteed compatibility
106 w.IvyForms.Vue = Vue
107 //w.Vue = w.Vue || Vue // Global fallback for compatibility with other plugins
108 // Expose IvyMessage utility for Pro plugin
109 w.IvyForms.IvyMessage = IvyMessage
110 // Expose useApiClient for Pro plugin (call it once to prevent tree-shaking)
111 w.IvyForms.useApiClient = useApiClient
112 // Shared Google Sheets form-builder CRUD for Lite single + Pro multi shells
113 w.IvyForms.useGoogleSheetsFormIntegrations = useGoogleSheetsFormIntegrations
114 // Expose stores for Pro plugin to use dialogs and entity actions
115 w.IvyForms.useActionEntityStore = useActionEntityStore
116 // Expose the actual store instance (not the function) so Pro uses the same instance
117 w.IvyForms.unsavedChangesStore = useUnsavedChangesStore()
118 w.IvyForms.useProUpgradeDialogStore = useProUpgradeDialogStore
119 // Expose the form builder store so external components can read/write fields
120 w.IvyForms.formBuilder = formBuilder
121 w.IvyForms.registerFormBuilderExtension = registerFormBuilderExtension
122 w.IvyForms.getFormBuilderExtensions = getFormBuilderExtensions
123 w.IvyForms.useEntry = useEntry
124 // Expose global state for Pro plugins to set page title and active page
125 w.IvyForms.globalState = globalState
126 // Expose shared types for Pro plugin and other extenders
127 w.IvyForms.types = LiteTypes
128 // Expose shared utility helpers for Pro plugin and other extenders
129 const { showUpgradeDialog } = useProFeatureUpgrade()
130 w.IvyForms.helpers = {
131 isSecurityField,
132 showUpgradeDialog,
133 getPreviewFieldComponent: getPreviewFieldComponent as (fieldType: string) => unknown,
134 normalizeFieldsForBuilder: normalizeFieldsForBuilder as (fields: unknown[]) => unknown[],
135 denormalizeFieldsForApi,
136 countryList,
137 registerLitePublicFieldComponents,
138 }
139 w.IvyForms.confirmationMulti = {
140 refreshTable: async (formId: number) => {
141 await useConfirmationMultiSetting().searchConfirmations({ formId })
142 },
143 }
144
145 // Expose entry stores so Pro can read/refresh entries (add-entry is a Pro feature).
146 w.IvyForms.useAllEntries = useAllEntries
147 w.IvyForms.useEntry = useEntry
148 // Create a callable components registry:
149 // - Use as function: w.IvyForms.components('MyComp', Comp)
150 // - Or as object map: w.IvyForms.components.MyComp
151 // - Or helper alias: w.IvyForms.registerComponent('MyComp', Comp)
152 type ComponentRegistry = {
153 (name?: string, component?: Component): Component | ComponentRegistry
154 [key: string]: Component | unknown
155 }
156 const componentsRegistry = ((name?: string, component?: Component) => {
157 if (name && component) {
158 app.component(name, component)
159 ;(componentsRegistry as Record<string, Component>)[name] = component
160 return component
161 }
162 // If only name is provided, return the component
163 if (name) {
164 return (componentsRegistry as Record<string, Component>)[name]
165 }
166 return componentsRegistry
167 }) as ComponentRegistry
168
169 // Register all Lite components for Pro plugin access
170 registerLiteComponents(componentsRegistry, app)
171
172 w.IvyForms.components = componentsRegistry
173 w.IvyForms.registerComponent = (name: string, component: Component) =>
174 componentsRegistry(name, component)
175 w.IvyForms.registerComponent('EntryPage', EntryPage)
176
177 // Expose router for Pro plugins to add routes dynamically
178 w.IvyForms._router = router
179
180 // Initialize pro features bridge if not already provided by Pro plugin
181 if (!w.IvyForms.pro) {
182 const pro = useProFeatures()
183 // Fire fetch; backend route may 404 in Lite-only context which is fine.
184 pro
185 .fetch()
186 .then(() => {
187 window.dispatchEvent(new CustomEvent('ivyforms:pro_features_loaded'))
188 })
189 .catch(() => {})
190 w.IvyForms.pro = {
191 get loaded() {
192 return pro.loaded.value
193 },
194 get loading() {
195 return pro.loading.value
196 },
197 get plan() {
198 return pro.plan.value
199 },
200 get upgradeMap() {
201 return pro.upgradeMap.value
202 },
203 get groups() {
204 return pro.groups.value
205 },
206 get license() {
207 return pro.license.value
208 },
209 get features(): Record<string, boolean> {
210 const rawFeatures =
211 (pro as { data?: { value?: { features?: Record<string, unknown> } } }).data?.value
212 ?.features || {}
213 // Convert unknown to boolean
214 const booleanFeatures: Record<string, boolean> = {}
215 for (const key in rawFeatures) {
216 booleanFeatures[key] = !!rawFeatures[key]
217 }
218 return booleanFeatures
219 },
220 hasFeature: (slug: string) => pro.hasFeature(slug),
221 fetch: (opts?: Record<string, unknown>) => pro.fetch(opts),
222 refresh: () => pro.refresh(),
223 }
224 }
225
226 // Simple onProReady queue so other scripts can safely register logic dependent on features
227 if (!w.IvyForms.onProReady) {
228 w.IvyForms._proReadyQueue = []
229 w.IvyForms.onProReady = (cb: (pro: IvyProFeatures) => void) => {
230 try {
231 if (w.IvyForms.pro?.loaded) return cb(w.IvyForms.pro)
232 w.IvyForms._proReadyQueue?.push(cb)
233 } catch {
234 /* noop */
235 }
236 }
237 window.addEventListener('ivyforms:pro_features_loaded', () => {
238 const q = Array.isArray(w.IvyForms._proReadyQueue) ? w.IvyForms._proReadyQueue : []
239 while (q.length) {
240 const fn = q.shift()
241 try {
242 if (fn) {
243 fn(w.IvyForms.pro)
244 }
245 } catch {
246 /* ignore */
247 }
248 }
249 })
250 }
251
252 // Convenience global helper (returns boolean) – safe to call anytime
253 if (!w.IvyForms.hasProFeature) {
254 w.IvyForms.hasProFeature = (slug: string): boolean => {
255 try {
256 if (!w.IvyForms.pro) return false
257 if (!w.IvyForms.pro.loaded) return false // treat not-yet-loaded as false for simplicity
258 return !!w.IvyForms.pro.hasFeature?.(slug)
259 } catch {
260 return false
261 }
262 }
263 }
264
265 // Pinia settings store (same instance as Lite admin) — Pro reads `integrations.*` here.
266 w.IvyForms.settingsStore = settingsStore
267 })()
268
269 // Register Google Sheets after hooks + router are exposed
270 registerGoogleSheetsAdmin()
271
272 // Register Lite field options
273 registerLiteFieldOptions()
274
275 // Register Lite advanced style section promos (replaced by Pro via hook when available)
276 registerLiteStyleSectionPromos()
277
278 // Bind navigation helper under app context so useRouter() resolves correctly.
279 app.runWithContext(() => {
280 window.IvyForms.navigateToAdminPage = useNavigation().navigateToAdminPage
281 })
282
283 app.mount('#ivyforms-app')
284
285 // Load settings from backend first, then initialize WCAG colors
286 settingsStore
287 .loadAllSettings()
288 .then(() => {
289 // Initialize WCAG colors watcher globally after settings are loaded
290 const { startWatching } = useWcagColors()
291 startWatching()
292 })
293 .catch(() => {
294 // Still start watcher even if settings fail to load (will use default value)
295 const { startWatching } = useWcagColors()
296 startWatching()
297 })
298
299 // Signal readiness for any external extenders waiting on Lite
300 dispatchIvyReady({ scope: 'admin' })
301