@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/__tests__/lib/page-editor-service.test.js +1 -0
  3. package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
  4. package/dist/__tests__/views/seo-settings.render.test.js +21 -0
  5. package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
  6. package/dist/components/forms/FormSchemaDrawer.d.ts.map +1 -1
  7. package/dist/components/forms/FormSchemaDrawer.js +6 -3
  8. package/dist/components/forms/FormSchemaDrawer.js.map +1 -1
  9. package/dist/components/forms/NotificationsPanel.d.ts.map +1 -1
  10. package/dist/components/forms/NotificationsPanel.js +2 -3
  11. package/dist/components/forms/NotificationsPanel.js.map +1 -1
  12. package/dist/components/forms/WebhooksPanel.d.ts +5 -0
  13. package/dist/components/forms/WebhooksPanel.d.ts.map +1 -0
  14. package/dist/components/forms/WebhooksPanel.js +129 -0
  15. package/dist/components/forms/WebhooksPanel.js.map +1 -0
  16. package/dist/lib/forms-service.d.ts +40 -2
  17. package/dist/lib/forms-service.d.ts.map +1 -1
  18. package/dist/lib/forms-service.js +24 -0
  19. package/dist/lib/forms-service.js.map +1 -1
  20. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
  21. package/dist/views/page-editor/sections/SectionRenderer.js +4 -0
  22. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
  23. package/dist/views/page-editor/sections/WidgetEmbedSection.d.ts +9 -0
  24. package/dist/views/page-editor/sections/WidgetEmbedSection.d.ts.map +1 -0
  25. package/dist/views/page-editor/sections/WidgetEmbedSection.js +18 -0
  26. package/dist/views/page-editor/sections/WidgetEmbedSection.js.map +1 -0
  27. package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
  28. package/dist/views/settings/SeoSettingsTab.js +22 -1
  29. package/dist/views/settings/SeoSettingsTab.js.map +1 -1
  30. package/dist/views/settings/useSeoSettings.d.ts +8 -0
  31. package/dist/views/settings/useSeoSettings.d.ts.map +1 -1
  32. package/dist/views/settings/useSeoSettings.js +61 -1
  33. package/dist/views/settings/useSeoSettings.js.map +1 -1
  34. package/package.json +2 -2
  35. package/src/__tests__/lib/page-editor-service.test.ts +1 -0
  36. package/src/__tests__/views/seo-settings.render.test.tsx +31 -0
  37. package/src/components/forms/FormSchemaDrawer.tsx +9 -2
  38. package/src/components/forms/NotificationsPanel.tsx +5 -4
  39. package/src/components/forms/WebhooksPanel.tsx +311 -0
  40. package/src/lib/forms-service.ts +81 -0
  41. package/src/views/page-editor/sections/SectionRenderer.tsx +4 -0
  42. package/src/views/page-editor/sections/WidgetEmbedSection.tsx +32 -0
  43. package/src/views/settings/SeoSettingsTab.tsx +145 -0
  44. 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',