strapi-plugin-hubspot 0.6.1 → 0.8.0
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 +32 -0
- package/dist/_chunks/{Forms-B2y_0-x4.mjs → Forms-77Mornxt.mjs} +115 -18
- package/dist/_chunks/{Forms-CaT0YEJo.js → Forms-B8kkPY0c.js} +133 -36
- package/dist/_chunks/HubspotFormInput-BVknnlmR.mjs +61 -0
- package/dist/_chunks/HubspotFormInput-kx3LlQYF.js +79 -0
- package/dist/_chunks/{HubspotObjectInput-DdLAUh0N.mjs → HubspotObjectInput-B-Y1PZ6c.mjs} +4 -3
- package/dist/_chunks/{HubspotObjectInput-Crpcpa8T.js → HubspotObjectInput-BOO1PvOj.js} +8 -7
- package/dist/_chunks/{HubspotPropertyInput-k7wMSmeF.mjs → HubspotPropertyInput-Drxn_Ddt.mjs} +4 -3
- package/dist/_chunks/{HubspotPropertyInput-SnJMFsII.js → HubspotPropertyInput-awfD1opH.js} +18 -17
- package/dist/_chunks/{Settings-BPAAD-QW.js → Settings-BT1NhvxO.js} +16 -15
- package/dist/_chunks/{Settings-BMOmj4NJ.mjs → Settings-DrujhMxS.mjs} +3 -2
- package/dist/_chunks/{en-C_-N8cjv.js → en-B8adhJhO.js} +17 -3
- package/dist/_chunks/{en-CJ9wYHgN.mjs → en-BYKKi1LK.mjs} +17 -3
- package/dist/_chunks/{fr-BHObPoUp.js → fr-DnKtGXaB.js} +17 -3
- package/dist/_chunks/{fr-DttDn-2Y.mjs → fr-DzgArgi3.mjs} +17 -3
- package/dist/_chunks/{objectLabels-C9ocOgVY.mjs → index-CEh8vkxY.mjs} +0 -14
- package/dist/_chunks/{objectLabels-VTt_-qTZ.js → index-DkTxsEqL.js} +0 -14
- package/dist/_chunks/{index-foVSGFWb.mjs → index-LrCSAHoH.mjs} +21 -5
- package/dist/_chunks/{index-D_CPrIHe.js → index-P7p8ASb6.js} +21 -5
- package/dist/_chunks/objectLabels-Dn-zriuZ.js +15 -0
- package/dist/_chunks/objectLabels-yNrnTOBo.mjs +16 -0
- package/dist/_chunks/{useHubspotSchema-B8SctALV.js → useHubspotSchema-BottzdRR.js} +1 -1
- package/dist/_chunks/{useHubspotSchema-Du3rLY_O.mjs → useHubspotSchema-DccG2KbZ.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/builder/types.d.ts +1 -0
- package/dist/admin/src/components/HubspotFormInput.d.ts +29 -0
- package/dist/server/index.js +100 -8
- package/dist/server/index.mjs +100 -8
- package/dist/server/src/content-types.d.ts +3 -0
- package/dist/server/src/forms.d.ts +2 -0
- package/dist/server/src/formsAdmin.d.ts +13 -0
- package/dist/server/src/importLegacy.d.ts +1 -0
- package/dist/server/src/index.d.ts +5 -0
- package/dist/shared/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const React = require("react");
|
|
3
|
-
const index = require("./index-D_CPrIHe.js");
|
|
4
3
|
function _interopNamespace(e) {
|
|
5
4
|
if (e && e.__esModule) return e;
|
|
6
5
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -426,17 +425,4 @@ createFormattedComponent("formatList");
|
|
|
426
425
|
createFormattedComponent("formatDisplayName");
|
|
427
426
|
createFormattedDateTimePartsComponent("formatDate");
|
|
428
427
|
createFormattedDateTimePartsComponent("formatTime");
|
|
429
|
-
const STANDARD = /* @__PURE__ */ new Set([
|
|
430
|
-
"contact",
|
|
431
|
-
"company",
|
|
432
|
-
"deal",
|
|
433
|
-
"ticket",
|
|
434
|
-
"product",
|
|
435
|
-
"line_item",
|
|
436
|
-
"quote"
|
|
437
|
-
]);
|
|
438
|
-
function objectLabel(intl, object) {
|
|
439
|
-
return STANDARD.has(object) ? intl.formatMessage({ id: index.getTranslation(`objects.${object}`), defaultMessage: object }) : object;
|
|
440
|
-
}
|
|
441
|
-
exports.objectLabel = objectLabel;
|
|
442
428
|
exports.useIntl = useIntl;
|
|
@@ -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-77Mornxt.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-Drxn_Ddt.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,23 @@ 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-B-Y1PZ6c.mjs")
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
app.customFields.register({
|
|
110
|
+
name: "form-picker",
|
|
111
|
+
pluginId: PLUGIN_ID,
|
|
112
|
+
type: "string",
|
|
113
|
+
intlLabel: {
|
|
114
|
+
id: `${PLUGIN_ID}.form-picker.label`,
|
|
115
|
+
defaultMessage: "HubSpot form"
|
|
116
|
+
},
|
|
117
|
+
intlDescription: {
|
|
118
|
+
id: `${PLUGIN_ID}.form-picker.description`,
|
|
119
|
+
defaultMessage: "A form built in the HubSpot form builder, stored by slug"
|
|
120
|
+
},
|
|
121
|
+
components: {
|
|
122
|
+
Input: async () => import("./HubspotFormInput-BVknnlmR.mjs")
|
|
107
123
|
}
|
|
108
124
|
});
|
|
109
125
|
app.createSettingSection(
|
|
@@ -119,7 +135,7 @@ const index = {
|
|
|
119
135
|
// Mirrors the RBAC action registered server-side: a role without it
|
|
120
136
|
// doesn't see the link at all — the routes refuse it anyway.
|
|
121
137
|
permissions: [{ action: `plugin::${PLUGIN_ID}.settings`, subject: null }],
|
|
122
|
-
Component: async () => (await import("./Settings-
|
|
138
|
+
Component: async () => (await import("./Settings-DrujhMxS.mjs")).default
|
|
123
139
|
}
|
|
124
140
|
]
|
|
125
141
|
);
|
|
@@ -129,7 +145,7 @@ const index = {
|
|
|
129
145
|
return Promise.all(
|
|
130
146
|
locales.map(async (locale) => {
|
|
131
147
|
try {
|
|
132
|
-
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-BYKKi1LK.mjs"), "./translations/fr.json": () => import("./fr-DzgArgi3.mjs") }), `./translations/${locale}.json`, 3);
|
|
133
149
|
return { data: prefixPluginTranslations(data), locale };
|
|
134
150
|
} catch {
|
|
135
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-B8kkPY0c.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-awfD1opH.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,23 @@ 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-BOO1PvOj.js"))
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
app.customFields.register({
|
|
111
|
+
name: "form-picker",
|
|
112
|
+
pluginId: PLUGIN_ID,
|
|
113
|
+
type: "string",
|
|
114
|
+
intlLabel: {
|
|
115
|
+
id: `${PLUGIN_ID}.form-picker.label`,
|
|
116
|
+
defaultMessage: "HubSpot form"
|
|
117
|
+
},
|
|
118
|
+
intlDescription: {
|
|
119
|
+
id: `${PLUGIN_ID}.form-picker.description`,
|
|
120
|
+
defaultMessage: "A form built in the HubSpot form builder, stored by slug"
|
|
121
|
+
},
|
|
122
|
+
components: {
|
|
123
|
+
Input: async () => Promise.resolve().then(() => require("./HubspotFormInput-kx3LlQYF.js"))
|
|
108
124
|
}
|
|
109
125
|
});
|
|
110
126
|
app.createSettingSection(
|
|
@@ -120,7 +136,7 @@ const index = {
|
|
|
120
136
|
// Mirrors the RBAC action registered server-side: a role without it
|
|
121
137
|
// doesn't see the link at all — the routes refuse it anyway.
|
|
122
138
|
permissions: [{ action: `plugin::${PLUGIN_ID}.settings`, subject: null }],
|
|
123
|
-
Component: async () => (await Promise.resolve().then(() => require("./Settings-
|
|
139
|
+
Component: async () => (await Promise.resolve().then(() => require("./Settings-BT1NhvxO.js"))).default
|
|
124
140
|
}
|
|
125
141
|
]
|
|
126
142
|
);
|
|
@@ -130,7 +146,7 @@ const index = {
|
|
|
130
146
|
return Promise.all(
|
|
131
147
|
locales.map(async (locale) => {
|
|
132
148
|
try {
|
|
133
|
-
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-B8adhJhO.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("./fr-DnKtGXaB.js")) }), `./translations/${locale}.json`, 3);
|
|
134
150
|
return { data: prefixPluginTranslations(data), locale };
|
|
135
151
|
} catch {
|
|
136
152
|
return { data: {}, locale };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const index = require("./index-P7p8ASb6.js");
|
|
3
|
+
const STANDARD = /* @__PURE__ */ new Set([
|
|
4
|
+
"contact",
|
|
5
|
+
"company",
|
|
6
|
+
"deal",
|
|
7
|
+
"ticket",
|
|
8
|
+
"product",
|
|
9
|
+
"line_item",
|
|
10
|
+
"quote"
|
|
11
|
+
]);
|
|
12
|
+
function objectLabel(intl, object) {
|
|
13
|
+
return STANDARD.has(object) ? intl.formatMessage({ id: index.getTranslation(`objects.${object}`), defaultMessage: object }) : object;
|
|
14
|
+
}
|
|
15
|
+
exports.objectLabel = objectLabel;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { g as getTranslation } from "./index-LrCSAHoH.mjs";
|
|
2
|
+
const STANDARD = /* @__PURE__ */ new Set([
|
|
3
|
+
"contact",
|
|
4
|
+
"company",
|
|
5
|
+
"deal",
|
|
6
|
+
"ticket",
|
|
7
|
+
"product",
|
|
8
|
+
"line_item",
|
|
9
|
+
"quote"
|
|
10
|
+
]);
|
|
11
|
+
function objectLabel(intl, object) {
|
|
12
|
+
return STANDARD.has(object) ? intl.formatMessage({ id: getTranslation(`objects.${object}`), defaultMessage: object }) : object;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
objectLabel as o
|
|
16
|
+
};
|
|
@@ -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-P7p8ASb6.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-LrCSAHoH.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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
interface InputProps {
|
|
3
|
+
name: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
onChange: (event: {
|
|
6
|
+
target: {
|
|
7
|
+
name: string;
|
|
8
|
+
value: string;
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
}) => void;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
labelAction?: React.ReactNode;
|
|
16
|
+
hint?: React.ReactNode;
|
|
17
|
+
label?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Picker over the forms built in the HubSpot form builder, for host content
|
|
21
|
+
* that references one (a hero block, a landing…). Stores the form's slug as a
|
|
22
|
+
* plain string: the reference survives the plugin being uninstalled, and a
|
|
23
|
+
* fetch failure degrades to a free-text input rather than blocking the editor.
|
|
24
|
+
*
|
|
25
|
+
* An unpublished form stays selectable — an editor can wire the block before
|
|
26
|
+
* publishing — but is flagged, since the public API only serves published ones.
|
|
27
|
+
*/
|
|
28
|
+
declare const HubspotFormInput: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
|
|
29
|
+
export default HubspotFormInput;
|
package/dist/server/index.js
CHANGED
|
@@ -397,6 +397,7 @@ const contentTypes = {
|
|
|
397
397
|
nextLabel: localizedString(),
|
|
398
398
|
submitLabel: localizedString(),
|
|
399
399
|
successMessage: localizedString("text"),
|
|
400
|
+
class: { type: "string" },
|
|
400
401
|
definition: {
|
|
401
402
|
type: "json",
|
|
402
403
|
required: true,
|
|
@@ -573,6 +574,7 @@ function publicForm(entry) {
|
|
|
573
574
|
nextLabel: entry.nextLabel ?? null,
|
|
574
575
|
submitLabel: entry.submitLabel ?? null,
|
|
575
576
|
successMessage: entry.successMessage ?? null,
|
|
577
|
+
class: entry.class ?? null,
|
|
576
578
|
locale: entry.locale ?? null,
|
|
577
579
|
steps: (entry.definition?.steps ?? []).map((step) => ({
|
|
578
580
|
...step,
|
|
@@ -932,6 +934,7 @@ function convertLegacyForm(entry, map = {}) {
|
|
|
932
934
|
nextLabel: entry.nextLabel ?? null,
|
|
933
935
|
submitLabel: entry.submitLabel ?? null,
|
|
934
936
|
successMessage: entry.successMessage ?? null,
|
|
937
|
+
class: entry.class ?? null,
|
|
935
938
|
definition: { version: 1, steps }
|
|
936
939
|
};
|
|
937
940
|
}
|
|
@@ -943,7 +946,8 @@ const WRITABLE = [
|
|
|
943
946
|
"subtitle",
|
|
944
947
|
"nextLabel",
|
|
945
948
|
"submitLabel",
|
|
946
|
-
"successMessage"
|
|
949
|
+
"successMessage",
|
|
950
|
+
"class"
|
|
947
951
|
];
|
|
948
952
|
const slugify = (value) => value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "form";
|
|
949
953
|
function createFormsAdminController(strapi) {
|
|
@@ -962,6 +966,15 @@ function createFormsAdminController(strapi) {
|
|
|
962
966
|
return null;
|
|
963
967
|
}
|
|
964
968
|
}
|
|
969
|
+
async function uniqueSlug(name) {
|
|
970
|
+
const base = slugify(name);
|
|
971
|
+
let slug = base;
|
|
972
|
+
for (let i = 2; ; i += 1) {
|
|
973
|
+
const clash = await documents().findFirst({ filters: { slug } });
|
|
974
|
+
if (!clash) return slug;
|
|
975
|
+
slug = `${base}-${i}`;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
965
978
|
function pickWritable(body) {
|
|
966
979
|
const data = {};
|
|
967
980
|
for (const key of WRITABLE) {
|
|
@@ -1027,13 +1040,7 @@ function createFormsAdminController(strapi) {
|
|
|
1027
1040
|
const body = ctx.request.body ?? {};
|
|
1028
1041
|
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
1029
1042
|
if (!name) ctx.throw(400, "A form needs a name");
|
|
1030
|
-
const
|
|
1031
|
-
let slug = base;
|
|
1032
|
-
for (let i = 2; ; i += 1) {
|
|
1033
|
-
const clash = await documents().findFirst({ filters: { slug } });
|
|
1034
|
-
if (!clash) break;
|
|
1035
|
-
slug = `${base}-${i}`;
|
|
1036
|
-
}
|
|
1043
|
+
const slug = await uniqueSlug(name);
|
|
1037
1044
|
let definition;
|
|
1038
1045
|
try {
|
|
1039
1046
|
definition = requireValidDefinition(ctx, body.definition);
|
|
@@ -1094,6 +1101,31 @@ function createFormsAdminController(strapi) {
|
|
|
1094
1101
|
});
|
|
1095
1102
|
ctx.body = { ok: true };
|
|
1096
1103
|
},
|
|
1104
|
+
/**
|
|
1105
|
+
* Feeds the `hubspot.form-picker` custom field: every form of the builder
|
|
1106
|
+
* as { name, slug, published }. Open to any authenticated admin — like the
|
|
1107
|
+
* properties route — because editors pick forms from the Content Manager,
|
|
1108
|
+
* not from the builder. `name`/`slug` are shared across locales.
|
|
1109
|
+
*/
|
|
1110
|
+
async options(ctx) {
|
|
1111
|
+
const drafts = await documents().findMany({
|
|
1112
|
+
fields: ["name", "slug"],
|
|
1113
|
+
sort: "name:asc",
|
|
1114
|
+
status: "draft"
|
|
1115
|
+
});
|
|
1116
|
+
const published = await documents().findMany({
|
|
1117
|
+
fields: ["slug"],
|
|
1118
|
+
status: "published"
|
|
1119
|
+
});
|
|
1120
|
+
const publishedSlugs = new Set(published.map((e) => e.slug));
|
|
1121
|
+
ctx.body = {
|
|
1122
|
+
forms: drafts.filter((e) => e.slug).map((e) => ({
|
|
1123
|
+
name: e.name ?? e.slug,
|
|
1124
|
+
slug: e.slug,
|
|
1125
|
+
published: publishedSlugs.has(e.slug)
|
|
1126
|
+
}))
|
|
1127
|
+
};
|
|
1128
|
+
},
|
|
1097
1129
|
/** Entries of the configured legacy content type, offered for import. */
|
|
1098
1130
|
async listSources(ctx) {
|
|
1099
1131
|
const config2 = strapi.plugin("hubspot").config("forms", {}).import;
|
|
@@ -1177,6 +1209,57 @@ function createFormsAdminController(strapi) {
|
|
|
1177
1209
|
if (!targetId) ctx.throw(404, "Source entry not found");
|
|
1178
1210
|
ctx.body = { documentId: targetId, locales: imported };
|
|
1179
1211
|
},
|
|
1212
|
+
/**
|
|
1213
|
+
* Copies a form (every locale of its draft) into a new draft document —
|
|
1214
|
+
* the quickest way to A/B a variant or to start from an existing form.
|
|
1215
|
+
* Internal step/field ids are kept: they only need uniqueness per form.
|
|
1216
|
+
*/
|
|
1217
|
+
async duplicate(ctx) {
|
|
1218
|
+
const sourceId = ctx.params.documentId;
|
|
1219
|
+
let locales = [void 0];
|
|
1220
|
+
try {
|
|
1221
|
+
const found = await strapi.plugin("i18n").service("locales").find();
|
|
1222
|
+
if (Array.isArray(found) && found.length) {
|
|
1223
|
+
locales = [...found].sort((a, b) => Number(b.isDefault ?? false) - Number(a.isDefault ?? false)).map((l) => l.code);
|
|
1224
|
+
}
|
|
1225
|
+
} catch {
|
|
1226
|
+
}
|
|
1227
|
+
let targetId = null;
|
|
1228
|
+
let slug = null;
|
|
1229
|
+
for (const locale of locales) {
|
|
1230
|
+
const source = await documents().findOne({
|
|
1231
|
+
documentId: sourceId,
|
|
1232
|
+
locale,
|
|
1233
|
+
status: "draft"
|
|
1234
|
+
});
|
|
1235
|
+
if (!source) continue;
|
|
1236
|
+
const name = `${source.name} (2)`;
|
|
1237
|
+
slug ??= await uniqueSlug(name);
|
|
1238
|
+
const data = {
|
|
1239
|
+
name,
|
|
1240
|
+
slug,
|
|
1241
|
+
title: source.title,
|
|
1242
|
+
subtitle: source.subtitle,
|
|
1243
|
+
nextLabel: source.nextLabel,
|
|
1244
|
+
submitLabel: source.submitLabel,
|
|
1245
|
+
successMessage: source.successMessage,
|
|
1246
|
+
class: source.class,
|
|
1247
|
+
definition: source.definition
|
|
1248
|
+
};
|
|
1249
|
+
if (targetId) {
|
|
1250
|
+
const { slug: _slug, name: _name, ...localized } = data;
|
|
1251
|
+
await documents().update({ documentId: targetId, locale, data: localized });
|
|
1252
|
+
} else {
|
|
1253
|
+
const created = await documents().create({
|
|
1254
|
+
locale,
|
|
1255
|
+
data
|
|
1256
|
+
});
|
|
1257
|
+
targetId = created.documentId;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (!targetId) ctx.throw(404, "Form not found");
|
|
1261
|
+
ctx.body = { documentId: targetId };
|
|
1262
|
+
},
|
|
1180
1263
|
async remove(ctx) {
|
|
1181
1264
|
await documents().delete({
|
|
1182
1265
|
documentId: ctx.params.documentId
|
|
@@ -1477,6 +1560,10 @@ const routes = {
|
|
|
1477
1560
|
adminRoute("POST", "/builder/forms/:documentId/publish", "formsAdmin.publish", [FORMS_ACTION]),
|
|
1478
1561
|
adminRoute("POST", "/builder/forms/:documentId/unpublish", "formsAdmin.unpublish", [FORMS_ACTION]),
|
|
1479
1562
|
adminRoute("DELETE", "/builder/forms/:documentId", "formsAdmin.remove", [FORMS_ACTION]),
|
|
1563
|
+
adminRoute("POST", "/builder/forms/:documentId/duplicate", "formsAdmin.duplicate", [FORMS_ACTION]),
|
|
1564
|
+
// No FORMS_ACTION: editors pick forms from the Content Manager, like
|
|
1565
|
+
// they pick properties — the builder itself stays gated.
|
|
1566
|
+
adminRoute("GET", "/forms-options", "formsAdmin.options"),
|
|
1480
1567
|
adminRoute("GET", "/builder/import/sources", "formsAdmin.listSources", [FORMS_ACTION]),
|
|
1481
1568
|
adminRoute("POST", "/builder/import", "formsAdmin.runImport", [FORMS_ACTION])
|
|
1482
1569
|
]
|
|
@@ -1512,6 +1599,11 @@ const index = {
|
|
|
1512
1599
|
plugin: "hubspot",
|
|
1513
1600
|
type: "string"
|
|
1514
1601
|
});
|
|
1602
|
+
strapi.customFields.register({
|
|
1603
|
+
name: "form-picker",
|
|
1604
|
+
plugin: "hubspot",
|
|
1605
|
+
type: "string"
|
|
1606
|
+
});
|
|
1515
1607
|
},
|
|
1516
1608
|
async bootstrap({ strapi }) {
|
|
1517
1609
|
await strapi.service("admin::permission").actionProvider.registerMany([
|
package/dist/server/index.mjs
CHANGED
|
@@ -396,6 +396,7 @@ const contentTypes = {
|
|
|
396
396
|
nextLabel: localizedString(),
|
|
397
397
|
submitLabel: localizedString(),
|
|
398
398
|
successMessage: localizedString("text"),
|
|
399
|
+
class: { type: "string" },
|
|
399
400
|
definition: {
|
|
400
401
|
type: "json",
|
|
401
402
|
required: true,
|
|
@@ -572,6 +573,7 @@ function publicForm(entry) {
|
|
|
572
573
|
nextLabel: entry.nextLabel ?? null,
|
|
573
574
|
submitLabel: entry.submitLabel ?? null,
|
|
574
575
|
successMessage: entry.successMessage ?? null,
|
|
576
|
+
class: entry.class ?? null,
|
|
575
577
|
locale: entry.locale ?? null,
|
|
576
578
|
steps: (entry.definition?.steps ?? []).map((step) => ({
|
|
577
579
|
...step,
|
|
@@ -931,6 +933,7 @@ function convertLegacyForm(entry, map = {}) {
|
|
|
931
933
|
nextLabel: entry.nextLabel ?? null,
|
|
932
934
|
submitLabel: entry.submitLabel ?? null,
|
|
933
935
|
successMessage: entry.successMessage ?? null,
|
|
936
|
+
class: entry.class ?? null,
|
|
934
937
|
definition: { version: 1, steps }
|
|
935
938
|
};
|
|
936
939
|
}
|
|
@@ -942,7 +945,8 @@ const WRITABLE = [
|
|
|
942
945
|
"subtitle",
|
|
943
946
|
"nextLabel",
|
|
944
947
|
"submitLabel",
|
|
945
|
-
"successMessage"
|
|
948
|
+
"successMessage",
|
|
949
|
+
"class"
|
|
946
950
|
];
|
|
947
951
|
const slugify = (value) => value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "form";
|
|
948
952
|
function createFormsAdminController(strapi) {
|
|
@@ -961,6 +965,15 @@ function createFormsAdminController(strapi) {
|
|
|
961
965
|
return null;
|
|
962
966
|
}
|
|
963
967
|
}
|
|
968
|
+
async function uniqueSlug(name) {
|
|
969
|
+
const base = slugify(name);
|
|
970
|
+
let slug = base;
|
|
971
|
+
for (let i = 2; ; i += 1) {
|
|
972
|
+
const clash = await documents().findFirst({ filters: { slug } });
|
|
973
|
+
if (!clash) return slug;
|
|
974
|
+
slug = `${base}-${i}`;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
964
977
|
function pickWritable(body) {
|
|
965
978
|
const data = {};
|
|
966
979
|
for (const key of WRITABLE) {
|
|
@@ -1026,13 +1039,7 @@ function createFormsAdminController(strapi) {
|
|
|
1026
1039
|
const body = ctx.request.body ?? {};
|
|
1027
1040
|
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
1028
1041
|
if (!name) ctx.throw(400, "A form needs a name");
|
|
1029
|
-
const
|
|
1030
|
-
let slug = base;
|
|
1031
|
-
for (let i = 2; ; i += 1) {
|
|
1032
|
-
const clash = await documents().findFirst({ filters: { slug } });
|
|
1033
|
-
if (!clash) break;
|
|
1034
|
-
slug = `${base}-${i}`;
|
|
1035
|
-
}
|
|
1042
|
+
const slug = await uniqueSlug(name);
|
|
1036
1043
|
let definition;
|
|
1037
1044
|
try {
|
|
1038
1045
|
definition = requireValidDefinition(ctx, body.definition);
|
|
@@ -1093,6 +1100,31 @@ function createFormsAdminController(strapi) {
|
|
|
1093
1100
|
});
|
|
1094
1101
|
ctx.body = { ok: true };
|
|
1095
1102
|
},
|
|
1103
|
+
/**
|
|
1104
|
+
* Feeds the `hubspot.form-picker` custom field: every form of the builder
|
|
1105
|
+
* as { name, slug, published }. Open to any authenticated admin — like the
|
|
1106
|
+
* properties route — because editors pick forms from the Content Manager,
|
|
1107
|
+
* not from the builder. `name`/`slug` are shared across locales.
|
|
1108
|
+
*/
|
|
1109
|
+
async options(ctx) {
|
|
1110
|
+
const drafts = await documents().findMany({
|
|
1111
|
+
fields: ["name", "slug"],
|
|
1112
|
+
sort: "name:asc",
|
|
1113
|
+
status: "draft"
|
|
1114
|
+
});
|
|
1115
|
+
const published = await documents().findMany({
|
|
1116
|
+
fields: ["slug"],
|
|
1117
|
+
status: "published"
|
|
1118
|
+
});
|
|
1119
|
+
const publishedSlugs = new Set(published.map((e) => e.slug));
|
|
1120
|
+
ctx.body = {
|
|
1121
|
+
forms: drafts.filter((e) => e.slug).map((e) => ({
|
|
1122
|
+
name: e.name ?? e.slug,
|
|
1123
|
+
slug: e.slug,
|
|
1124
|
+
published: publishedSlugs.has(e.slug)
|
|
1125
|
+
}))
|
|
1126
|
+
};
|
|
1127
|
+
},
|
|
1096
1128
|
/** Entries of the configured legacy content type, offered for import. */
|
|
1097
1129
|
async listSources(ctx) {
|
|
1098
1130
|
const config2 = strapi.plugin("hubspot").config("forms", {}).import;
|
|
@@ -1176,6 +1208,57 @@ function createFormsAdminController(strapi) {
|
|
|
1176
1208
|
if (!targetId) ctx.throw(404, "Source entry not found");
|
|
1177
1209
|
ctx.body = { documentId: targetId, locales: imported };
|
|
1178
1210
|
},
|
|
1211
|
+
/**
|
|
1212
|
+
* Copies a form (every locale of its draft) into a new draft document —
|
|
1213
|
+
* the quickest way to A/B a variant or to start from an existing form.
|
|
1214
|
+
* Internal step/field ids are kept: they only need uniqueness per form.
|
|
1215
|
+
*/
|
|
1216
|
+
async duplicate(ctx) {
|
|
1217
|
+
const sourceId = ctx.params.documentId;
|
|
1218
|
+
let locales = [void 0];
|
|
1219
|
+
try {
|
|
1220
|
+
const found = await strapi.plugin("i18n").service("locales").find();
|
|
1221
|
+
if (Array.isArray(found) && found.length) {
|
|
1222
|
+
locales = [...found].sort((a, b) => Number(b.isDefault ?? false) - Number(a.isDefault ?? false)).map((l) => l.code);
|
|
1223
|
+
}
|
|
1224
|
+
} catch {
|
|
1225
|
+
}
|
|
1226
|
+
let targetId = null;
|
|
1227
|
+
let slug = null;
|
|
1228
|
+
for (const locale of locales) {
|
|
1229
|
+
const source = await documents().findOne({
|
|
1230
|
+
documentId: sourceId,
|
|
1231
|
+
locale,
|
|
1232
|
+
status: "draft"
|
|
1233
|
+
});
|
|
1234
|
+
if (!source) continue;
|
|
1235
|
+
const name = `${source.name} (2)`;
|
|
1236
|
+
slug ??= await uniqueSlug(name);
|
|
1237
|
+
const data = {
|
|
1238
|
+
name,
|
|
1239
|
+
slug,
|
|
1240
|
+
title: source.title,
|
|
1241
|
+
subtitle: source.subtitle,
|
|
1242
|
+
nextLabel: source.nextLabel,
|
|
1243
|
+
submitLabel: source.submitLabel,
|
|
1244
|
+
successMessage: source.successMessage,
|
|
1245
|
+
class: source.class,
|
|
1246
|
+
definition: source.definition
|
|
1247
|
+
};
|
|
1248
|
+
if (targetId) {
|
|
1249
|
+
const { slug: _slug, name: _name, ...localized } = data;
|
|
1250
|
+
await documents().update({ documentId: targetId, locale, data: localized });
|
|
1251
|
+
} else {
|
|
1252
|
+
const created = await documents().create({
|
|
1253
|
+
locale,
|
|
1254
|
+
data
|
|
1255
|
+
});
|
|
1256
|
+
targetId = created.documentId;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
if (!targetId) ctx.throw(404, "Form not found");
|
|
1260
|
+
ctx.body = { documentId: targetId };
|
|
1261
|
+
},
|
|
1179
1262
|
async remove(ctx) {
|
|
1180
1263
|
await documents().delete({
|
|
1181
1264
|
documentId: ctx.params.documentId
|
|
@@ -1476,6 +1559,10 @@ const routes = {
|
|
|
1476
1559
|
adminRoute("POST", "/builder/forms/:documentId/publish", "formsAdmin.publish", [FORMS_ACTION]),
|
|
1477
1560
|
adminRoute("POST", "/builder/forms/:documentId/unpublish", "formsAdmin.unpublish", [FORMS_ACTION]),
|
|
1478
1561
|
adminRoute("DELETE", "/builder/forms/:documentId", "formsAdmin.remove", [FORMS_ACTION]),
|
|
1562
|
+
adminRoute("POST", "/builder/forms/:documentId/duplicate", "formsAdmin.duplicate", [FORMS_ACTION]),
|
|
1563
|
+
// No FORMS_ACTION: editors pick forms from the Content Manager, like
|
|
1564
|
+
// they pick properties — the builder itself stays gated.
|
|
1565
|
+
adminRoute("GET", "/forms-options", "formsAdmin.options"),
|
|
1479
1566
|
adminRoute("GET", "/builder/import/sources", "formsAdmin.listSources", [FORMS_ACTION]),
|
|
1480
1567
|
adminRoute("POST", "/builder/import", "formsAdmin.runImport", [FORMS_ACTION])
|
|
1481
1568
|
]
|
|
@@ -1511,6 +1598,11 @@ const index = {
|
|
|
1511
1598
|
plugin: "hubspot",
|
|
1512
1599
|
type: "string"
|
|
1513
1600
|
});
|
|
1601
|
+
strapi.customFields.register({
|
|
1602
|
+
name: "form-picker",
|
|
1603
|
+
plugin: "hubspot",
|
|
1604
|
+
type: "string"
|
|
1605
|
+
});
|
|
1514
1606
|
},
|
|
1515
1607
|
async bootstrap({ strapi }) {
|
|
1516
1608
|
await strapi.service("admin::permission").actionProvider.registerMany([
|
|
@@ -19,6 +19,7 @@ export interface FormEntry {
|
|
|
19
19
|
nextLabel?: string | null;
|
|
20
20
|
submitLabel?: string | null;
|
|
21
21
|
successMessage?: string | null;
|
|
22
|
+
class?: string | null;
|
|
22
23
|
locale?: string | null;
|
|
23
24
|
definition: FormDefinition;
|
|
24
25
|
[key: string]: unknown;
|
|
@@ -30,6 +31,7 @@ export interface PublicForm {
|
|
|
30
31
|
nextLabel?: string | null;
|
|
31
32
|
submitLabel?: string | null;
|
|
32
33
|
successMessage?: string | null;
|
|
34
|
+
class?: string | null;
|
|
33
35
|
locale?: string | null;
|
|
34
36
|
steps: FormDefinition["steps"];
|
|
35
37
|
}
|
|
@@ -27,6 +27,13 @@ export declare function createFormsAdminController(strapi: Core.Strapi): {
|
|
|
27
27
|
update(ctx: Ctx): Promise<void>;
|
|
28
28
|
publish(ctx: Ctx): Promise<void>;
|
|
29
29
|
unpublish(ctx: Ctx): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Feeds the `hubspot.form-picker` custom field: every form of the builder
|
|
32
|
+
* as { name, slug, published }. Open to any authenticated admin — like the
|
|
33
|
+
* properties route — because editors pick forms from the Content Manager,
|
|
34
|
+
* not from the builder. `name`/`slug` are shared across locales.
|
|
35
|
+
*/
|
|
36
|
+
options(ctx: Ctx): Promise<void>;
|
|
30
37
|
/** Entries of the configured legacy content type, offered for import. */
|
|
31
38
|
listSources(ctx: Ctx): Promise<void>;
|
|
32
39
|
/**
|
|
@@ -35,5 +42,11 @@ export declare function createFormsAdminController(strapi: Core.Strapi): {
|
|
|
35
42
|
* published version, and never modifies the source.
|
|
36
43
|
*/
|
|
37
44
|
runImport(ctx: Ctx): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Copies a form (every locale of its draft) into a new draft document —
|
|
47
|
+
* the quickest way to A/B a variant or to start from an existing form.
|
|
48
|
+
* Internal step/field ids are kept: they only need uniqueness per form.
|
|
49
|
+
*/
|
|
50
|
+
duplicate(ctx: Ctx): Promise<void>;
|
|
38
51
|
remove(ctx: Ctx): Promise<void>;
|
|
39
52
|
};
|
|
@@ -20,6 +20,7 @@ export interface ConvertedForm {
|
|
|
20
20
|
nextLabel?: string | null;
|
|
21
21
|
submitLabel?: string | null;
|
|
22
22
|
successMessage?: string | null;
|
|
23
|
+
class?: string | null;
|
|
23
24
|
definition: FormDefinition;
|
|
24
25
|
}
|
|
25
26
|
export declare function convertLegacyForm(entry: Record<string, unknown>, map?: ImportMap): ConvertedForm;
|