strapi-plugin-hubspot 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +4 -4
- package/dist/_chunks/{Forms-6EoEYzAc.mjs → Forms-BOQWely0.mjs} +240 -114
- package/dist/_chunks/{Forms-BW_zGc1P.js → Forms-mMNP7hc9.js} +238 -112
- package/dist/_chunks/{HubspotFormInput-V5w4y1fb.mjs → HubspotFormInput-DCkoavrN.mjs} +1 -1
- package/dist/_chunks/{HubspotFormInput-oMhm1UJm.js → HubspotFormInput-DI35Aqiu.js} +1 -1
- package/dist/_chunks/{HubspotObjectInput-DhsnSguG.mjs → HubspotObjectInput-BbnvhdbD.mjs} +3 -3
- package/dist/_chunks/{HubspotObjectInput-Bmo6YQgs.js → HubspotObjectInput-Cq1TuL_l.js} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-CrYP4pc0.mjs → HubspotPropertyInput-BHfpBD36.mjs} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-luFfnxqy.js → HubspotPropertyInput-BLZzsW08.js} +3 -3
- package/dist/_chunks/{Settings-B3A22i7Z.js → Settings-BAsx0LGv.js} +2 -2
- package/dist/_chunks/{Settings-CjcWj9I6.mjs → Settings-DqRpHtnT.mjs} +2 -2
- package/dist/_chunks/{en-D6GHgMOt.mjs → en-Djd3TID0.mjs} +7 -3
- package/dist/_chunks/{en-C7ls2EUl.js → en-sfzopB5K.js} +7 -3
- package/dist/_chunks/{fr-CfohMY8n.mjs → fr-C7t1elmR.mjs} +7 -3
- package/dist/_chunks/{fr-pOP8ZXqM.js → fr-sF5Josfh.js} +7 -3
- package/dist/_chunks/{index-7cL9PRh1.mjs → index-CLAy5dVN.mjs} +6 -6
- package/dist/_chunks/{index-CodpLKZx.js → index-R3ReZXF1.js} +6 -6
- package/dist/_chunks/{objectLabels-Dnj14fsp.mjs → objectLabels--zOmqsfm.mjs} +1 -1
- package/dist/_chunks/{objectLabels-CSWGIgNb.js → objectLabels-CW2iBOkD.js} +1 -1
- package/dist/_chunks/{useHubspotSchema-DiRN3HrN.js → useHubspotSchema-BRzzvs9t.js} +1 -1
- package/dist/_chunks/{useHubspotSchema-BsmQgJgx.mjs → useHubspotSchema-Bz2o1BU-.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/builder/Sortable.d.ts +25 -12
- package/dist/admin/src/builder/__tests__/relocateField.test.d.ts +1 -0
- package/dist/admin/src/builder/relocateField.d.ts +14 -0
- package/dist/server/index.js +129 -12
- package/dist/server/index.mjs +129 -12
- package/dist/server/src/formsAdmin.d.ts +8 -3
- package/dist/server/src/importHubspot.d.ts +12 -0
- package/dist/server/src/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -164,7 +164,11 @@ const fr = {
|
|
|
164
164
|
"editor.hubspot-form": "Formulaire marketing HubSpot",
|
|
165
165
|
"editor.hubspot-form-placeholder": "Choisir un formulaire du portail connecté",
|
|
166
166
|
"editor.hubspot-form-none": "Aucun — upsert CRM uniquement",
|
|
167
|
-
"editor.hubspot-form-hint": "Les envois passent par l’API Forms de HubSpot pour compter comme des conversions.
|
|
167
|
+
"editor.hubspot-form-hint": "Les envois passent par l’API Forms de HubSpot pour compter comme des conversions. Après une modification dans HubSpot, utilisez Resynchroniser — les champs mappés se mettent à jour sur place, les nouveaux s’ajoutent, vos étapes restent.",
|
|
168
|
+
"editor.resync": "Resynchroniser depuis HubSpot",
|
|
169
|
+
"editor.resynced": "Resynchronisé depuis HubSpot — {updated, plural, one {# champ mis à jour} other {# champs mis à jour}}, {added, plural, one {# ajouté} other {# ajoutés}}.",
|
|
170
|
+
"editor.resynced-gaps": "Resynchronisé depuis HubSpot — {updated, plural, one {# mis à jour} other {# mis à jour}}, {added, plural, one {# ajouté} other {# ajoutés}}, {skipped, plural, one {# écarté} other {# écartés}}.",
|
|
171
|
+
"editor.resync-error": "Resynchronisation impossible — le formulaire est-il toujours lié ?",
|
|
168
172
|
"editor.form-hint": "Sélectionnez une étape ou un champ pour l'éditer. Le endpoint public sert ce formulaire sur /api/hubspot/forms/{slug}.",
|
|
169
173
|
"panel.label": "Libellé",
|
|
170
174
|
"panel.name": "Clé du payload",
|
|
@@ -234,7 +238,7 @@ const fr = {
|
|
|
234
238
|
"forms.col-submissions": "Soumissions",
|
|
235
239
|
"forms.submissions": "Soumissions",
|
|
236
240
|
"forms.import-hubspot-title": "Importer depuis votre portail HubSpot",
|
|
237
|
-
"forms.import-hubspot-hint": "Traduit un formulaire construit dans HubSpot en brouillon ici, mapping CRM inclus. Ce que le builder ne sait pas exprimer est écarté et signalé. Ré-importer
|
|
241
|
+
"forms.import-hubspot-hint": "Traduit un formulaire construit dans HubSpot en brouillon ici, mapping CRM inclus. Ce que le builder ne sait pas exprimer est écarté et signalé. Ré-importer met à jour les champs mappés et ajoute les nouveaux.",
|
|
238
242
|
"forms.import-hubspot-source": "Formulaire HubSpot à importer",
|
|
239
243
|
"forms.hubspot-list-error": "Impossible de lister les formulaires du portail — le jeton a besoin du scope de lecture `forms`.",
|
|
240
244
|
"forms.report-title": "Importé avec des manques",
|
|
@@ -295,7 +299,7 @@ const fr = {
|
|
|
295
299
|
"company.datum.nafLabel": "Libellé NAF",
|
|
296
300
|
"company.datum.headcount": "Tranche d'effectif",
|
|
297
301
|
"editor.drag-step": "Glisser pour réordonner l'étape",
|
|
298
|
-
"editor.drag-field": "Glisser pour
|
|
302
|
+
"editor.drag-field": "Glisser pour déplacer le champ, y compris vers une autre étape"
|
|
299
303
|
};
|
|
300
304
|
export {
|
|
301
305
|
fr as default
|
|
@@ -166,7 +166,11 @@ const fr = {
|
|
|
166
166
|
"editor.hubspot-form": "Formulaire marketing HubSpot",
|
|
167
167
|
"editor.hubspot-form-placeholder": "Choisir un formulaire du portail connecté",
|
|
168
168
|
"editor.hubspot-form-none": "Aucun — upsert CRM uniquement",
|
|
169
|
-
"editor.hubspot-form-hint": "Les envois passent par l’API Forms de HubSpot pour compter comme des conversions.
|
|
169
|
+
"editor.hubspot-form-hint": "Les envois passent par l’API Forms de HubSpot pour compter comme des conversions. Après une modification dans HubSpot, utilisez Resynchroniser — les champs mappés se mettent à jour sur place, les nouveaux s’ajoutent, vos étapes restent.",
|
|
170
|
+
"editor.resync": "Resynchroniser depuis HubSpot",
|
|
171
|
+
"editor.resynced": "Resynchronisé depuis HubSpot — {updated, plural, one {# champ mis à jour} other {# champs mis à jour}}, {added, plural, one {# ajouté} other {# ajoutés}}.",
|
|
172
|
+
"editor.resynced-gaps": "Resynchronisé depuis HubSpot — {updated, plural, one {# mis à jour} other {# mis à jour}}, {added, plural, one {# ajouté} other {# ajoutés}}, {skipped, plural, one {# écarté} other {# écartés}}.",
|
|
173
|
+
"editor.resync-error": "Resynchronisation impossible — le formulaire est-il toujours lié ?",
|
|
170
174
|
"editor.form-hint": "Sélectionnez une étape ou un champ pour l'éditer. Le endpoint public sert ce formulaire sur /api/hubspot/forms/{slug}.",
|
|
171
175
|
"panel.label": "Libellé",
|
|
172
176
|
"panel.name": "Clé du payload",
|
|
@@ -236,7 +240,7 @@ const fr = {
|
|
|
236
240
|
"forms.col-submissions": "Soumissions",
|
|
237
241
|
"forms.submissions": "Soumissions",
|
|
238
242
|
"forms.import-hubspot-title": "Importer depuis votre portail HubSpot",
|
|
239
|
-
"forms.import-hubspot-hint": "Traduit un formulaire construit dans HubSpot en brouillon ici, mapping CRM inclus. Ce que le builder ne sait pas exprimer est écarté et signalé. Ré-importer
|
|
243
|
+
"forms.import-hubspot-hint": "Traduit un formulaire construit dans HubSpot en brouillon ici, mapping CRM inclus. Ce que le builder ne sait pas exprimer est écarté et signalé. Ré-importer met à jour les champs mappés et ajoute les nouveaux.",
|
|
240
244
|
"forms.import-hubspot-source": "Formulaire HubSpot à importer",
|
|
241
245
|
"forms.hubspot-list-error": "Impossible de lister les formulaires du portail — le jeton a besoin du scope de lecture `forms`.",
|
|
242
246
|
"forms.report-title": "Importé avec des manques",
|
|
@@ -297,6 +301,6 @@ const fr = {
|
|
|
297
301
|
"company.datum.nafLabel": "Libellé NAF",
|
|
298
302
|
"company.datum.headcount": "Tranche d'effectif",
|
|
299
303
|
"editor.drag-step": "Glisser pour réordonner l'étape",
|
|
300
|
-
"editor.drag-field": "Glisser pour
|
|
304
|
+
"editor.drag-field": "Glisser pour déplacer le champ, y compris vers une autre étape"
|
|
301
305
|
};
|
|
302
306
|
exports.default = fr;
|
|
@@ -25,7 +25,7 @@ const index = {
|
|
|
25
25
|
icon: PaperPlane,
|
|
26
26
|
intlLabel: { id: `${PLUGIN_ID}.menu.label`, defaultMessage: "HubSpot Forms" },
|
|
27
27
|
permissions: [{ action: `plugin::${PLUGIN_ID}.forms`, subject: null }],
|
|
28
|
-
Component: async () => (await import("./Forms-
|
|
28
|
+
Component: async () => (await import("./Forms-BOQWely0.mjs")).default
|
|
29
29
|
});
|
|
30
30
|
app.customFields.register({
|
|
31
31
|
name: "property",
|
|
@@ -40,7 +40,7 @@ const index = {
|
|
|
40
40
|
defaultMessage: "Picked from the portal's real properties"
|
|
41
41
|
},
|
|
42
42
|
components: {
|
|
43
|
-
Input: async () => import("./HubspotPropertyInput-
|
|
43
|
+
Input: async () => import("./HubspotPropertyInput-BHfpBD36.mjs")
|
|
44
44
|
},
|
|
45
45
|
// Exposed in the Content-Type Builder. Without this, `objectField` could
|
|
46
46
|
// only be set by hand-editing schema.json — which is not how anyone
|
|
@@ -103,7 +103,7 @@ const index = {
|
|
|
103
103
|
defaultMessage: "Picked from the portal's configured objects"
|
|
104
104
|
},
|
|
105
105
|
components: {
|
|
106
|
-
Input: async () => import("./HubspotObjectInput-
|
|
106
|
+
Input: async () => import("./HubspotObjectInput-BbnvhdbD.mjs")
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
app.customFields.register({
|
|
@@ -119,7 +119,7 @@ const index = {
|
|
|
119
119
|
defaultMessage: "A form built in the HubSpot form builder, stored by slug"
|
|
120
120
|
},
|
|
121
121
|
components: {
|
|
122
|
-
Input: async () => import("./HubspotFormInput-
|
|
122
|
+
Input: async () => import("./HubspotFormInput-DCkoavrN.mjs")
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
app.createSettingSection(
|
|
@@ -135,7 +135,7 @@ const index = {
|
|
|
135
135
|
// Mirrors the RBAC action registered server-side: a role without it
|
|
136
136
|
// doesn't see the link at all — the routes refuse it anyway.
|
|
137
137
|
permissions: [{ action: `plugin::${PLUGIN_ID}.settings`, subject: null }],
|
|
138
|
-
Component: async () => (await import("./Settings-
|
|
138
|
+
Component: async () => (await import("./Settings-DqRpHtnT.mjs")).default
|
|
139
139
|
}
|
|
140
140
|
]
|
|
141
141
|
);
|
|
@@ -145,7 +145,7 @@ const index = {
|
|
|
145
145
|
return Promise.all(
|
|
146
146
|
locales.map(async (locale) => {
|
|
147
147
|
try {
|
|
148
|
-
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-
|
|
148
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-Djd3TID0.mjs"), "./translations/fr.json": () => import("./fr-C7t1elmR.mjs") }), `./translations/${locale}.json`, 3);
|
|
149
149
|
return { data: prefixPluginTranslations(data), locale };
|
|
150
150
|
} catch {
|
|
151
151
|
return { data: {}, locale };
|
|
@@ -26,7 +26,7 @@ const index = {
|
|
|
26
26
|
icon: icons.PaperPlane,
|
|
27
27
|
intlLabel: { id: `${PLUGIN_ID}.menu.label`, defaultMessage: "HubSpot Forms" },
|
|
28
28
|
permissions: [{ action: `plugin::${PLUGIN_ID}.forms`, subject: null }],
|
|
29
|
-
Component: async () => (await Promise.resolve().then(() => require("./Forms-
|
|
29
|
+
Component: async () => (await Promise.resolve().then(() => require("./Forms-mMNP7hc9.js"))).default
|
|
30
30
|
});
|
|
31
31
|
app.customFields.register({
|
|
32
32
|
name: "property",
|
|
@@ -41,7 +41,7 @@ const index = {
|
|
|
41
41
|
defaultMessage: "Picked from the portal's real properties"
|
|
42
42
|
},
|
|
43
43
|
components: {
|
|
44
|
-
Input: async () => Promise.resolve().then(() => require("./HubspotPropertyInput-
|
|
44
|
+
Input: async () => Promise.resolve().then(() => require("./HubspotPropertyInput-BLZzsW08.js"))
|
|
45
45
|
},
|
|
46
46
|
// Exposed in the Content-Type Builder. Without this, `objectField` could
|
|
47
47
|
// only be set by hand-editing schema.json — which is not how anyone
|
|
@@ -104,7 +104,7 @@ const index = {
|
|
|
104
104
|
defaultMessage: "Picked from the portal's configured objects"
|
|
105
105
|
},
|
|
106
106
|
components: {
|
|
107
|
-
Input: async () => Promise.resolve().then(() => require("./HubspotObjectInput-
|
|
107
|
+
Input: async () => Promise.resolve().then(() => require("./HubspotObjectInput-Cq1TuL_l.js"))
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
110
|
app.customFields.register({
|
|
@@ -120,7 +120,7 @@ const index = {
|
|
|
120
120
|
defaultMessage: "A form built in the HubSpot form builder, stored by slug"
|
|
121
121
|
},
|
|
122
122
|
components: {
|
|
123
|
-
Input: async () => Promise.resolve().then(() => require("./HubspotFormInput-
|
|
123
|
+
Input: async () => Promise.resolve().then(() => require("./HubspotFormInput-DI35Aqiu.js"))
|
|
124
124
|
}
|
|
125
125
|
});
|
|
126
126
|
app.createSettingSection(
|
|
@@ -136,7 +136,7 @@ const index = {
|
|
|
136
136
|
// Mirrors the RBAC action registered server-side: a role without it
|
|
137
137
|
// doesn't see the link at all — the routes refuse it anyway.
|
|
138
138
|
permissions: [{ action: `plugin::${PLUGIN_ID}.settings`, subject: null }],
|
|
139
|
-
Component: async () => (await Promise.resolve().then(() => require("./Settings-
|
|
139
|
+
Component: async () => (await Promise.resolve().then(() => require("./Settings-BAsx0LGv.js"))).default
|
|
140
140
|
}
|
|
141
141
|
]
|
|
142
142
|
);
|
|
@@ -146,7 +146,7 @@ const index = {
|
|
|
146
146
|
return Promise.all(
|
|
147
147
|
locales.map(async (locale) => {
|
|
148
148
|
try {
|
|
149
|
-
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-
|
|
149
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-sfzopB5K.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("./fr-sF5Josfh.js")) }), `./translations/${locale}.json`, 3);
|
|
150
150
|
return { data: prefixPluginTranslations(data), locale };
|
|
151
151
|
} catch {
|
|
152
152
|
return { data: {}, locale };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const React = require("react");
|
|
3
3
|
const admin = require("@strapi/strapi/admin");
|
|
4
|
-
const index = require("./index-
|
|
4
|
+
const index = require("./index-R3ReZXF1.js");
|
|
5
5
|
function _interopNamespace(e) {
|
|
6
6
|
if (e && e.__esModule) return e;
|
|
7
7
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { useFetchClient } from "@strapi/strapi/admin";
|
|
3
|
-
import { P as PLUGIN_ID } from "./index-
|
|
3
|
+
import { P as PLUGIN_ID } from "./index-CLAy5dVN.mjs";
|
|
4
4
|
let cached = null;
|
|
5
5
|
let inFlight = null;
|
|
6
6
|
const listeners = /* @__PURE__ */ new Set();
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { type DragEndEvent } from "@dnd-kit/core";
|
|
3
|
+
import { arrayMove, relocateField } from "./relocateField";
|
|
4
|
+
export { arrayMove, relocateField };
|
|
5
|
+
export type { DragEndEvent };
|
|
4
6
|
/** The drag-handle props to spread on the grip element. */
|
|
5
7
|
export interface HandleProps {
|
|
6
8
|
attributes: React.HTMLAttributes<HTMLElement>;
|
|
7
9
|
listeners: Record<string, unknown> | undefined;
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
export type SortableData = {
|
|
12
|
+
type: "step";
|
|
13
|
+
} | {
|
|
14
|
+
type: "field";
|
|
15
|
+
stepId: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "container";
|
|
18
|
+
stepId: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function SortableBoard({ children, onDragEnd, }: {
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
onDragEnd: (event: DragEndEvent) => void;
|
|
23
|
+
}): React.JSX.Element;
|
|
24
|
+
export declare function SortableGroup({ ids, children, }: {
|
|
16
25
|
ids: string[];
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
}): React.JSX.Element;
|
|
28
|
+
/** Drop target for a step's field list — including when the list is empty. */
|
|
29
|
+
export declare function SortableContainer({ stepId, children, }: {
|
|
30
|
+
stepId: string;
|
|
19
31
|
children: React.ReactNode;
|
|
20
32
|
}): React.JSX.Element;
|
|
21
33
|
/** A sortable row: wraps the card, hands the grip props to its children. */
|
|
22
|
-
export declare function SortableItem({ id, children, }: {
|
|
34
|
+
export declare function SortableItem({ id, data, children, }: {
|
|
23
35
|
id: string;
|
|
36
|
+
data?: SortableData;
|
|
24
37
|
children: (handle: HandleProps) => React.ReactNode;
|
|
25
38
|
}): React.JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { arrayMove } from "@dnd-kit/sortable";
|
|
2
|
+
export { arrayMove };
|
|
3
|
+
/**
|
|
4
|
+
* Move `fieldId` onto `overId` across steps.
|
|
5
|
+
*
|
|
6
|
+
* `overId` is another field (insert at that index), a step id (append),
|
|
7
|
+
* or `container:${stepId}` (the empty-area droppable of that step).
|
|
8
|
+
*/
|
|
9
|
+
export declare function relocateField<S extends {
|
|
10
|
+
id: string;
|
|
11
|
+
fields: F[];
|
|
12
|
+
}, F extends {
|
|
13
|
+
id: string;
|
|
14
|
+
}>(steps: S[], fieldId: string, overId: string): S[];
|
package/dist/server/index.js
CHANGED
|
@@ -1778,6 +1778,65 @@ function convertHubspotForm(raw) {
|
|
|
1778
1778
|
skipped
|
|
1779
1779
|
};
|
|
1780
1780
|
}
|
|
1781
|
+
function mappingKey(field) {
|
|
1782
|
+
const mapping = field.hubspot ?? {};
|
|
1783
|
+
const property = mapping.property?.trim();
|
|
1784
|
+
if (!property) return null;
|
|
1785
|
+
return `${mapping.object?.trim() || "contact"}:${property}`;
|
|
1786
|
+
}
|
|
1787
|
+
function uniqueName(base, used) {
|
|
1788
|
+
if (!used.has(base)) return base;
|
|
1789
|
+
let n = 2;
|
|
1790
|
+
while (used.has(`${base}_${n}`)) n += 1;
|
|
1791
|
+
return `${base}_${n}`;
|
|
1792
|
+
}
|
|
1793
|
+
function mergeHubspotImport(current, incoming) {
|
|
1794
|
+
const steps = (current.steps ?? []).map((step) => ({
|
|
1795
|
+
...step,
|
|
1796
|
+
fields: [...step.fields ?? []]
|
|
1797
|
+
}));
|
|
1798
|
+
if (!steps.length) {
|
|
1799
|
+
steps.push({ id: makeId$1("stp"), fields: [] });
|
|
1800
|
+
}
|
|
1801
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1802
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
1803
|
+
for (const step of steps) {
|
|
1804
|
+
for (const field of step.fields) {
|
|
1805
|
+
usedNames.add(field.name);
|
|
1806
|
+
const key = mappingKey(field);
|
|
1807
|
+
if (key) byKey.set(key, field);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const added = [];
|
|
1811
|
+
const updated = [];
|
|
1812
|
+
const incomingFields = (incoming.steps ?? []).flatMap((step) => step.fields ?? []);
|
|
1813
|
+
for (const next of incomingFields) {
|
|
1814
|
+
const key = mappingKey(next);
|
|
1815
|
+
const existing = key ? byKey.get(key) : void 0;
|
|
1816
|
+
if (existing) {
|
|
1817
|
+
existing.label = next.label;
|
|
1818
|
+
existing.type = next.type;
|
|
1819
|
+
existing.required = next.required;
|
|
1820
|
+
existing.placeholder = next.placeholder;
|
|
1821
|
+
existing.helpText = next.helpText;
|
|
1822
|
+
if (next.options) existing.options = next.options;
|
|
1823
|
+
else delete existing.options;
|
|
1824
|
+
if (!existing.visibleIf && next.visibleIf) existing.visibleIf = next.visibleIf;
|
|
1825
|
+
updated.push(key);
|
|
1826
|
+
continue;
|
|
1827
|
+
}
|
|
1828
|
+
const field = {
|
|
1829
|
+
...next,
|
|
1830
|
+
id: makeId$1("fld"),
|
|
1831
|
+
name: uniqueName(next.name, usedNames)
|
|
1832
|
+
};
|
|
1833
|
+
usedNames.add(field.name);
|
|
1834
|
+
if (key) byKey.set(key, field);
|
|
1835
|
+
steps[steps.length - 1].fields.push(field);
|
|
1836
|
+
added.push(key || field.name);
|
|
1837
|
+
}
|
|
1838
|
+
return { definition: { version: 1, steps }, added, updated };
|
|
1839
|
+
}
|
|
1781
1840
|
async function hsGet(apiKey, path) {
|
|
1782
1841
|
const res = await fetch(`${HS_BASE$2}${path}`, {
|
|
1783
1842
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
@@ -2895,9 +2954,9 @@ function createFormsAdminController(strapi) {
|
|
|
2895
2954
|
},
|
|
2896
2955
|
/**
|
|
2897
2956
|
* Converts one portal form into a plugin form draft, in the current
|
|
2898
|
-
* locale.
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2957
|
+
* locale. Matching prefers the linked HubSpot GUID, then the slug.
|
|
2958
|
+
* Re-importing merges: mapped fields update in place, new ones append,
|
|
2959
|
+
* Strapi steps stay. Never the published version, never the portal.
|
|
2901
2960
|
*/
|
|
2902
2961
|
async runHubspotImport(ctx) {
|
|
2903
2962
|
const formId = ctx.request.body?.formId;
|
|
@@ -2916,24 +2975,81 @@ function createFormsAdminController(strapi) {
|
|
|
2916
2975
|
const { skipped, definition, ...meta } = converted;
|
|
2917
2976
|
const slug = slugify(name);
|
|
2918
2977
|
const existing = await documents().findFirst({
|
|
2978
|
+
filters: { hubspotFormId: formId }
|
|
2979
|
+
}) ?? await documents().findFirst({
|
|
2919
2980
|
filters: { slug }
|
|
2920
2981
|
});
|
|
2921
|
-
let documentId;
|
|
2922
2982
|
if (existing) {
|
|
2923
|
-
await documents().
|
|
2983
|
+
const current = await documents().findOne({
|
|
2924
2984
|
documentId: existing.documentId,
|
|
2925
2985
|
locale: ctx.query.locale,
|
|
2926
|
-
|
|
2986
|
+
status: "draft"
|
|
2927
2987
|
});
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2988
|
+
const merged = mergeHubspotImport(current?.definition ?? EMPTY_DEFINITION, definition);
|
|
2989
|
+
await documents().update({
|
|
2990
|
+
documentId: existing.documentId,
|
|
2931
2991
|
locale: ctx.query.locale,
|
|
2932
|
-
data: {
|
|
2992
|
+
data: {
|
|
2993
|
+
hubspotFormId: meta.hubspotFormId ?? formId,
|
|
2994
|
+
definition: merged.definition
|
|
2995
|
+
}
|
|
2933
2996
|
});
|
|
2934
|
-
|
|
2997
|
+
ctx.body = {
|
|
2998
|
+
documentId: existing.documentId,
|
|
2999
|
+
skipped,
|
|
3000
|
+
added: merged.added,
|
|
3001
|
+
updated: merged.updated
|
|
3002
|
+
};
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
const created = await documents().create({
|
|
3006
|
+
locale: ctx.query.locale,
|
|
3007
|
+
data: { ...meta, name, slug, definition }
|
|
3008
|
+
});
|
|
3009
|
+
ctx.body = { documentId: created.documentId, skipped, added: [], updated: [] };
|
|
3010
|
+
},
|
|
3011
|
+
/**
|
|
3012
|
+
* Pull the linked HubSpot marketing form back into this draft: mapped
|
|
3013
|
+
* fields update in place, new ones append, Strapi steps and extras stay.
|
|
3014
|
+
*/
|
|
3015
|
+
async resync(ctx) {
|
|
3016
|
+
const entry = await documents().findOne({
|
|
3017
|
+
documentId: ctx.params.documentId,
|
|
3018
|
+
locale: ctx.query.locale,
|
|
3019
|
+
status: "draft"
|
|
3020
|
+
});
|
|
3021
|
+
if (!entry) ctx.throw(404, "Form not found");
|
|
3022
|
+
const guid = typeof entry.hubspotFormId === "string" ? entry.hubspotFormId.trim() : "";
|
|
3023
|
+
if (!isFormGuid(guid)) {
|
|
3024
|
+
ctx.throw(400, "This form is not linked to a HubSpot marketing form.");
|
|
3025
|
+
}
|
|
3026
|
+
const { apiKey } = await resolveApiKey(strapi);
|
|
3027
|
+
if (!apiKey) ctx.throw(400, "No HubSpot API key configured");
|
|
3028
|
+
let converted;
|
|
3029
|
+
try {
|
|
3030
|
+
converted = convertHubspotForm(await fetchHubspotForm(apiKey, guid));
|
|
3031
|
+
} catch (err) {
|
|
3032
|
+
strapi.log.warn(`[hubspot] form resync failed — ${err.message}`);
|
|
3033
|
+
ctx.throw(502, err.message || "Cannot reach HubSpot");
|
|
3034
|
+
return;
|
|
2935
3035
|
}
|
|
2936
|
-
|
|
3036
|
+
const merged = mergeHubspotImport(entry.definition ?? EMPTY_DEFINITION, converted.definition);
|
|
3037
|
+
await documents().update({
|
|
3038
|
+
documentId: ctx.params.documentId,
|
|
3039
|
+
locale: ctx.query.locale,
|
|
3040
|
+
data: { definition: merged.definition }
|
|
3041
|
+
});
|
|
3042
|
+
const form = await documents().findOne({
|
|
3043
|
+
documentId: ctx.params.documentId,
|
|
3044
|
+
locale: ctx.query.locale,
|
|
3045
|
+
status: "draft"
|
|
3046
|
+
});
|
|
3047
|
+
ctx.body = {
|
|
3048
|
+
form,
|
|
3049
|
+
added: merged.added,
|
|
3050
|
+
updated: merged.updated,
|
|
3051
|
+
skipped: converted.skipped
|
|
3052
|
+
};
|
|
2937
3053
|
},
|
|
2938
3054
|
/**
|
|
2939
3055
|
* Copies a form (every locale of its draft) into a new draft document —
|
|
@@ -3472,6 +3588,7 @@ const routes = {
|
|
|
3472
3588
|
adminRoute("POST", "/builder/forms/:documentId/unpublish", "formsAdmin.unpublish", [FORMS_ACTION]),
|
|
3473
3589
|
adminRoute("DELETE", "/builder/forms/:documentId", "formsAdmin.remove", [FORMS_ACTION]),
|
|
3474
3590
|
adminRoute("POST", "/builder/forms/:documentId/duplicate", "formsAdmin.duplicate", [FORMS_ACTION]),
|
|
3591
|
+
adminRoute("POST", "/builder/forms/:documentId/resync", "formsAdmin.resync", [FORMS_ACTION]),
|
|
3475
3592
|
// No FORMS_ACTION: editors pick forms from the Content Manager, like
|
|
3476
3593
|
// they pick properties — the builder itself stays gated.
|
|
3477
3594
|
adminRoute("GET", "/forms-options", "formsAdmin.options"),
|
package/dist/server/index.mjs
CHANGED
|
@@ -1777,6 +1777,65 @@ function convertHubspotForm(raw) {
|
|
|
1777
1777
|
skipped
|
|
1778
1778
|
};
|
|
1779
1779
|
}
|
|
1780
|
+
function mappingKey(field) {
|
|
1781
|
+
const mapping = field.hubspot ?? {};
|
|
1782
|
+
const property = mapping.property?.trim();
|
|
1783
|
+
if (!property) return null;
|
|
1784
|
+
return `${mapping.object?.trim() || "contact"}:${property}`;
|
|
1785
|
+
}
|
|
1786
|
+
function uniqueName(base, used) {
|
|
1787
|
+
if (!used.has(base)) return base;
|
|
1788
|
+
let n = 2;
|
|
1789
|
+
while (used.has(`${base}_${n}`)) n += 1;
|
|
1790
|
+
return `${base}_${n}`;
|
|
1791
|
+
}
|
|
1792
|
+
function mergeHubspotImport(current, incoming) {
|
|
1793
|
+
const steps = (current.steps ?? []).map((step) => ({
|
|
1794
|
+
...step,
|
|
1795
|
+
fields: [...step.fields ?? []]
|
|
1796
|
+
}));
|
|
1797
|
+
if (!steps.length) {
|
|
1798
|
+
steps.push({ id: makeId$1("stp"), fields: [] });
|
|
1799
|
+
}
|
|
1800
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1801
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
1802
|
+
for (const step of steps) {
|
|
1803
|
+
for (const field of step.fields) {
|
|
1804
|
+
usedNames.add(field.name);
|
|
1805
|
+
const key = mappingKey(field);
|
|
1806
|
+
if (key) byKey.set(key, field);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
const added = [];
|
|
1810
|
+
const updated = [];
|
|
1811
|
+
const incomingFields = (incoming.steps ?? []).flatMap((step) => step.fields ?? []);
|
|
1812
|
+
for (const next of incomingFields) {
|
|
1813
|
+
const key = mappingKey(next);
|
|
1814
|
+
const existing = key ? byKey.get(key) : void 0;
|
|
1815
|
+
if (existing) {
|
|
1816
|
+
existing.label = next.label;
|
|
1817
|
+
existing.type = next.type;
|
|
1818
|
+
existing.required = next.required;
|
|
1819
|
+
existing.placeholder = next.placeholder;
|
|
1820
|
+
existing.helpText = next.helpText;
|
|
1821
|
+
if (next.options) existing.options = next.options;
|
|
1822
|
+
else delete existing.options;
|
|
1823
|
+
if (!existing.visibleIf && next.visibleIf) existing.visibleIf = next.visibleIf;
|
|
1824
|
+
updated.push(key);
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1827
|
+
const field = {
|
|
1828
|
+
...next,
|
|
1829
|
+
id: makeId$1("fld"),
|
|
1830
|
+
name: uniqueName(next.name, usedNames)
|
|
1831
|
+
};
|
|
1832
|
+
usedNames.add(field.name);
|
|
1833
|
+
if (key) byKey.set(key, field);
|
|
1834
|
+
steps[steps.length - 1].fields.push(field);
|
|
1835
|
+
added.push(key || field.name);
|
|
1836
|
+
}
|
|
1837
|
+
return { definition: { version: 1, steps }, added, updated };
|
|
1838
|
+
}
|
|
1780
1839
|
async function hsGet(apiKey, path) {
|
|
1781
1840
|
const res = await fetch(`${HS_BASE$2}${path}`, {
|
|
1782
1841
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
@@ -2894,9 +2953,9 @@ function createFormsAdminController(strapi) {
|
|
|
2894
2953
|
},
|
|
2895
2954
|
/**
|
|
2896
2955
|
* Converts one portal form into a plugin form draft, in the current
|
|
2897
|
-
* locale.
|
|
2898
|
-
*
|
|
2899
|
-
*
|
|
2956
|
+
* locale. Matching prefers the linked HubSpot GUID, then the slug.
|
|
2957
|
+
* Re-importing merges: mapped fields update in place, new ones append,
|
|
2958
|
+
* Strapi steps stay. Never the published version, never the portal.
|
|
2900
2959
|
*/
|
|
2901
2960
|
async runHubspotImport(ctx) {
|
|
2902
2961
|
const formId = ctx.request.body?.formId;
|
|
@@ -2915,24 +2974,81 @@ function createFormsAdminController(strapi) {
|
|
|
2915
2974
|
const { skipped, definition, ...meta } = converted;
|
|
2916
2975
|
const slug = slugify(name);
|
|
2917
2976
|
const existing = await documents().findFirst({
|
|
2977
|
+
filters: { hubspotFormId: formId }
|
|
2978
|
+
}) ?? await documents().findFirst({
|
|
2918
2979
|
filters: { slug }
|
|
2919
2980
|
});
|
|
2920
|
-
let documentId;
|
|
2921
2981
|
if (existing) {
|
|
2922
|
-
await documents().
|
|
2982
|
+
const current = await documents().findOne({
|
|
2923
2983
|
documentId: existing.documentId,
|
|
2924
2984
|
locale: ctx.query.locale,
|
|
2925
|
-
|
|
2985
|
+
status: "draft"
|
|
2926
2986
|
});
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2987
|
+
const merged = mergeHubspotImport(current?.definition ?? EMPTY_DEFINITION, definition);
|
|
2988
|
+
await documents().update({
|
|
2989
|
+
documentId: existing.documentId,
|
|
2930
2990
|
locale: ctx.query.locale,
|
|
2931
|
-
data: {
|
|
2991
|
+
data: {
|
|
2992
|
+
hubspotFormId: meta.hubspotFormId ?? formId,
|
|
2993
|
+
definition: merged.definition
|
|
2994
|
+
}
|
|
2932
2995
|
});
|
|
2933
|
-
|
|
2996
|
+
ctx.body = {
|
|
2997
|
+
documentId: existing.documentId,
|
|
2998
|
+
skipped,
|
|
2999
|
+
added: merged.added,
|
|
3000
|
+
updated: merged.updated
|
|
3001
|
+
};
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
const created = await documents().create({
|
|
3005
|
+
locale: ctx.query.locale,
|
|
3006
|
+
data: { ...meta, name, slug, definition }
|
|
3007
|
+
});
|
|
3008
|
+
ctx.body = { documentId: created.documentId, skipped, added: [], updated: [] };
|
|
3009
|
+
},
|
|
3010
|
+
/**
|
|
3011
|
+
* Pull the linked HubSpot marketing form back into this draft: mapped
|
|
3012
|
+
* fields update in place, new ones append, Strapi steps and extras stay.
|
|
3013
|
+
*/
|
|
3014
|
+
async resync(ctx) {
|
|
3015
|
+
const entry = await documents().findOne({
|
|
3016
|
+
documentId: ctx.params.documentId,
|
|
3017
|
+
locale: ctx.query.locale,
|
|
3018
|
+
status: "draft"
|
|
3019
|
+
});
|
|
3020
|
+
if (!entry) ctx.throw(404, "Form not found");
|
|
3021
|
+
const guid = typeof entry.hubspotFormId === "string" ? entry.hubspotFormId.trim() : "";
|
|
3022
|
+
if (!isFormGuid(guid)) {
|
|
3023
|
+
ctx.throw(400, "This form is not linked to a HubSpot marketing form.");
|
|
3024
|
+
}
|
|
3025
|
+
const { apiKey } = await resolveApiKey(strapi);
|
|
3026
|
+
if (!apiKey) ctx.throw(400, "No HubSpot API key configured");
|
|
3027
|
+
let converted;
|
|
3028
|
+
try {
|
|
3029
|
+
converted = convertHubspotForm(await fetchHubspotForm(apiKey, guid));
|
|
3030
|
+
} catch (err) {
|
|
3031
|
+
strapi.log.warn(`[hubspot] form resync failed — ${err.message}`);
|
|
3032
|
+
ctx.throw(502, err.message || "Cannot reach HubSpot");
|
|
3033
|
+
return;
|
|
2934
3034
|
}
|
|
2935
|
-
|
|
3035
|
+
const merged = mergeHubspotImport(entry.definition ?? EMPTY_DEFINITION, converted.definition);
|
|
3036
|
+
await documents().update({
|
|
3037
|
+
documentId: ctx.params.documentId,
|
|
3038
|
+
locale: ctx.query.locale,
|
|
3039
|
+
data: { definition: merged.definition }
|
|
3040
|
+
});
|
|
3041
|
+
const form = await documents().findOne({
|
|
3042
|
+
documentId: ctx.params.documentId,
|
|
3043
|
+
locale: ctx.query.locale,
|
|
3044
|
+
status: "draft"
|
|
3045
|
+
});
|
|
3046
|
+
ctx.body = {
|
|
3047
|
+
form,
|
|
3048
|
+
added: merged.added,
|
|
3049
|
+
updated: merged.updated,
|
|
3050
|
+
skipped: converted.skipped
|
|
3051
|
+
};
|
|
2936
3052
|
},
|
|
2937
3053
|
/**
|
|
2938
3054
|
* Copies a form (every locale of its draft) into a new draft document —
|
|
@@ -3471,6 +3587,7 @@ const routes = {
|
|
|
3471
3587
|
adminRoute("POST", "/builder/forms/:documentId/unpublish", "formsAdmin.unpublish", [FORMS_ACTION]),
|
|
3472
3588
|
adminRoute("DELETE", "/builder/forms/:documentId", "formsAdmin.remove", [FORMS_ACTION]),
|
|
3473
3589
|
adminRoute("POST", "/builder/forms/:documentId/duplicate", "formsAdmin.duplicate", [FORMS_ACTION]),
|
|
3590
|
+
adminRoute("POST", "/builder/forms/:documentId/resync", "formsAdmin.resync", [FORMS_ACTION]),
|
|
3474
3591
|
// No FORMS_ACTION: editors pick forms from the Content Manager, like
|
|
3475
3592
|
// they pick properties — the builder itself stays gated.
|
|
3476
3593
|
adminRoute("GET", "/forms-options", "formsAdmin.options"),
|