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