@actuate-media/cms-admin 0.61.0 → 0.63.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 +16 -0
- package/dist/__tests__/lib/page-editor-service.test.js +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/__tests__/views/seo-settings.render.test.js +21 -0
- package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
- package/dist/components/forms/FormSchemaDrawer.d.ts.map +1 -1
- package/dist/components/forms/FormSchemaDrawer.js +6 -3
- package/dist/components/forms/FormSchemaDrawer.js.map +1 -1
- package/dist/components/forms/NotificationsPanel.d.ts.map +1 -1
- package/dist/components/forms/NotificationsPanel.js +2 -3
- package/dist/components/forms/NotificationsPanel.js.map +1 -1
- package/dist/components/forms/WebhooksPanel.d.ts +5 -0
- package/dist/components/forms/WebhooksPanel.d.ts.map +1 -0
- package/dist/components/forms/WebhooksPanel.js +129 -0
- package/dist/components/forms/WebhooksPanel.js.map +1 -0
- package/dist/lib/forms-service.d.ts +40 -2
- package/dist/lib/forms-service.d.ts.map +1 -1
- package/dist/lib/forms-service.js +24 -0
- package/dist/lib/forms-service.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +4 -0
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/WidgetEmbedSection.d.ts +9 -0
- package/dist/views/page-editor/sections/WidgetEmbedSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/WidgetEmbedSection.js +18 -0
- package/dist/views/page-editor/sections/WidgetEmbedSection.js.map +1 -0
- package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
- package/dist/views/settings/SeoSettingsTab.js +22 -1
- package/dist/views/settings/SeoSettingsTab.js.map +1 -1
- package/dist/views/settings/useSeoSettings.d.ts +8 -0
- package/dist/views/settings/useSeoSettings.d.ts.map +1 -1
- package/dist/views/settings/useSeoSettings.js +61 -1
- package/dist/views/settings/useSeoSettings.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/lib/page-editor-service.test.ts +1 -0
- package/src/__tests__/views/seo-settings.render.test.tsx +31 -0
- package/src/components/forms/FormSchemaDrawer.tsx +9 -2
- package/src/components/forms/NotificationsPanel.tsx +5 -4
- package/src/components/forms/WebhooksPanel.tsx +311 -0
- package/src/lib/forms-service.ts +81 -0
- package/src/views/page-editor/sections/SectionRenderer.tsx +4 -0
- package/src/views/page-editor/sections/WidgetEmbedSection.tsx +32 -0
- package/src/views/settings/SeoSettingsTab.tsx +145 -0
- package/src/views/settings/useSeoSettings.ts +74 -1
|
@@ -39,6 +39,14 @@ export interface SeoSettingsForm {
|
|
|
39
39
|
autoGenerateSitemap: boolean
|
|
40
40
|
/** Notify Google/Bing when the sitemap changes (`module`). */
|
|
41
41
|
pingOnPublish: boolean
|
|
42
|
+
/** Schema.org Organization name (falls back to site name when empty). */
|
|
43
|
+
organizationName: string
|
|
44
|
+
/** Canonical organization URL (falls back to site URL when empty). */
|
|
45
|
+
organizationUrl: string
|
|
46
|
+
/** Absolute logo URL for Organization JSON-LD. */
|
|
47
|
+
organizationLogo: string
|
|
48
|
+
/** Social profile URLs, one per line (`sameAs`). */
|
|
49
|
+
organizationSameAs: string
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
export interface SitemapSummary {
|
|
@@ -61,6 +69,10 @@ const EMPTY_FORM: SeoSettingsForm = {
|
|
|
61
69
|
facebookDomainVerification: '',
|
|
62
70
|
autoGenerateSitemap: true,
|
|
63
71
|
pingOnPublish: true,
|
|
72
|
+
organizationName: '',
|
|
73
|
+
organizationUrl: '',
|
|
74
|
+
organizationLogo: '',
|
|
75
|
+
organizationSameAs: '',
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
/** Recommended max length for the default meta description (SERP truncation). */
|
|
@@ -93,6 +105,31 @@ export function validateTitleTemplateClient(template: string): {
|
|
|
93
105
|
return { valid: true, unknownTokens: [] }
|
|
94
106
|
}
|
|
95
107
|
|
|
108
|
+
function parseSameAsLines(raw: string): string[] {
|
|
109
|
+
return raw
|
|
110
|
+
.split('\n')
|
|
111
|
+
.map((line) => line.trim())
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function validateUrlField(
|
|
116
|
+
value: string,
|
|
117
|
+
field: keyof SeoSettingsForm,
|
|
118
|
+
label: string,
|
|
119
|
+
): ValidationIssue[] {
|
|
120
|
+
const trimmed = value.trim()
|
|
121
|
+
if (!trimmed) return []
|
|
122
|
+
try {
|
|
123
|
+
const u = new URL(trimmed)
|
|
124
|
+
if (u.protocol !== 'https:' && u.protocol !== 'http:') {
|
|
125
|
+
return [{ field, message: `${label} must use http:// or https://`, severity: 'error' }]
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
return [{ field, message: `${label} is not a valid URL.`, severity: 'error' }]
|
|
129
|
+
}
|
|
130
|
+
return []
|
|
131
|
+
}
|
|
132
|
+
|
|
96
133
|
function validate(form: SeoSettingsForm): ValidationIssue[] {
|
|
97
134
|
const issues: ValidationIssue[] = []
|
|
98
135
|
const tpl = validateTitleTemplateClient(form.titleTemplate)
|
|
@@ -122,6 +159,11 @@ function validate(form: SeoSettingsForm): ValidationIssue[] {
|
|
|
122
159
|
severity: 'warning',
|
|
123
160
|
})
|
|
124
161
|
}
|
|
162
|
+
issues.push(...validateUrlField(form.organizationUrl, 'organizationUrl', 'Organization URL'))
|
|
163
|
+
issues.push(...validateUrlField(form.organizationLogo, 'organizationLogo', 'Organization logo'))
|
|
164
|
+
for (const url of parseSameAsLines(form.organizationSameAs)) {
|
|
165
|
+
issues.push(...validateUrlField(url, 'organizationSameAs', 'Social profile URL'))
|
|
166
|
+
}
|
|
125
167
|
return issues
|
|
126
168
|
}
|
|
127
169
|
|
|
@@ -192,6 +234,7 @@ export function useSeoSettings(canEdit: boolean): UseSeoSettings {
|
|
|
192
234
|
const staticSite = seo.static?.site ?? {}
|
|
193
235
|
const overSite = seo.overrides?.site ?? {}
|
|
194
236
|
const verification = effectiveSite.verification ?? {}
|
|
237
|
+
const organization = effectiveSite.organization ?? {}
|
|
195
238
|
const crawl = crawlRes.data ?? {}
|
|
196
239
|
|
|
197
240
|
const loaded: SeoSettingsForm = {
|
|
@@ -206,6 +249,14 @@ export function useSeoSettings(canEdit: boolean): UseSeoSettings {
|
|
|
206
249
|
facebookDomainVerification: String(verification.facebookDomain ?? ''),
|
|
207
250
|
autoGenerateSitemap: crawl.autoRegenerateSitemapOnPublish !== false,
|
|
208
251
|
pingOnPublish: crawl.pingSearchEnginesOnPublish !== false,
|
|
252
|
+
organizationName: String(organization.name ?? ''),
|
|
253
|
+
organizationUrl: String(organization.url ?? ''),
|
|
254
|
+
organizationLogo: String(organization.logo ?? ''),
|
|
255
|
+
organizationSameAs: Array.isArray(organization.sameAs)
|
|
256
|
+
? (organization.sameAs as unknown[])
|
|
257
|
+
.filter((u): u is string => typeof u === 'string')
|
|
258
|
+
.join('\n')
|
|
259
|
+
: '',
|
|
209
260
|
}
|
|
210
261
|
|
|
211
262
|
// `editable` reflects the field's MANAGEMENT source (config vs database),
|
|
@@ -216,11 +267,19 @@ export function useSeoSettings(canEdit: boolean): UseSeoSettings {
|
|
|
216
267
|
staticSite[key]
|
|
217
268
|
? { source: 'config', editable: false, reason: 'Set in actuate.config.ts' }
|
|
218
269
|
: { source: 'database', editable: true }
|
|
270
|
+
const orgConfigManaged = (): SettingsSourceMetadata =>
|
|
271
|
+
staticSite.organization
|
|
272
|
+
? { source: 'config', editable: false, reason: 'Set in actuate.config.ts' }
|
|
273
|
+
: { source: 'database', editable: true }
|
|
219
274
|
|
|
220
275
|
setSources({
|
|
221
276
|
titleTemplate: configManaged('titleTemplate'),
|
|
222
277
|
defaultMetaDescription: configManaged('defaultMetaDescription'),
|
|
223
278
|
defaultOgImage: configManaged('defaultOgImage'),
|
|
279
|
+
organizationName: orgConfigManaged(),
|
|
280
|
+
organizationUrl: orgConfigManaged(),
|
|
281
|
+
organizationLogo: orgConfigManaged(),
|
|
282
|
+
organizationSameAs: orgConfigManaged(),
|
|
224
283
|
verification: { source: 'database', editable: true },
|
|
225
284
|
sitemap: { source: 'database', editable: true },
|
|
226
285
|
})
|
|
@@ -279,7 +338,11 @@ export function useSeoSettings(canEdit: boolean): UseSeoSettings {
|
|
|
279
338
|
form.bingVerification !== baseline.bingVerification ||
|
|
280
339
|
form.yandexVerification !== baseline.yandexVerification ||
|
|
281
340
|
form.pinterestVerification !== baseline.pinterestVerification ||
|
|
282
|
-
form.facebookDomainVerification !== baseline.facebookDomainVerification
|
|
341
|
+
form.facebookDomainVerification !== baseline.facebookDomainVerification ||
|
|
342
|
+
form.organizationName !== baseline.organizationName ||
|
|
343
|
+
form.organizationUrl !== baseline.organizationUrl ||
|
|
344
|
+
form.organizationLogo !== baseline.organizationLogo ||
|
|
345
|
+
form.organizationSameAs !== baseline.organizationSameAs
|
|
283
346
|
const sitemapChanged =
|
|
284
347
|
form.autoGenerateSitemap !== baseline.autoGenerateSitemap ||
|
|
285
348
|
form.pingOnPublish !== baseline.pingOnPublish
|
|
@@ -302,6 +365,16 @@ export function useSeoSettings(canEdit: boolean): UseSeoSettings {
|
|
|
302
365
|
pinterest: form.pinterestVerification.trim() || undefined,
|
|
303
366
|
facebookDomain: form.facebookDomainVerification.trim() || undefined,
|
|
304
367
|
},
|
|
368
|
+
organization: {
|
|
369
|
+
...(overrideSite.organization ?? {}),
|
|
370
|
+
name: form.organizationName.trim() || undefined,
|
|
371
|
+
url: form.organizationUrl.trim() || undefined,
|
|
372
|
+
logo: form.organizationLogo.trim() || undefined,
|
|
373
|
+
sameAs: (() => {
|
|
374
|
+
const urls = parseSameAsLines(form.organizationSameAs)
|
|
375
|
+
return urls.length > 0 ? urls : undefined
|
|
376
|
+
})(),
|
|
377
|
+
},
|
|
305
378
|
}
|
|
306
379
|
const res = await cmsApi('/seo/config', {
|
|
307
380
|
method: 'PUT',
|