@prenta/admin 1.17.1 → 1.17.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 +10 -0
- package/dist/__tests__/views/ai-settings.render.test.js +67 -1
- package/dist/__tests__/views/ai-settings.render.test.js.map +1 -1
- package/dist/components/CoauthorResultPopover.d.ts.map +1 -1
- package/dist/components/CoauthorResultPopover.js +4 -1
- package/dist/components/CoauthorResultPopover.js.map +1 -1
- package/dist/components/CommentSidePanel.d.ts +0 -7
- package/dist/components/CommentSidePanel.d.ts.map +1 -1
- package/dist/components/CommentSidePanel.js +0 -2
- package/dist/components/CommentSidePanel.js.map +1 -1
- package/dist/components/EditorRail.d.ts +2 -1
- package/dist/components/EditorRail.d.ts.map +1 -1
- package/dist/components/MediaPickerModal.d.ts +2 -1
- package/dist/components/MediaPickerModal.d.ts.map +1 -1
- package/dist/components/TipTapEditor.d.ts +3 -2
- package/dist/components/TipTapEditor.d.ts.map +1 -1
- package/dist/components/forms/EntriesTable.d.ts +0 -1
- package/dist/components/forms/EntriesTable.d.ts.map +1 -1
- package/dist/components/forms/EntriesTable.js +1 -1
- package/dist/components/forms/EntriesTable.js.map +1 -1
- package/dist/components/pages/tag-badges.d.ts +2 -2
- package/dist/components/pages/tag-badges.d.ts.map +1 -1
- package/dist/components/pages/tag-badges.js.map +1 -1
- package/dist/components/seo/FaqProposalPreview.d.ts +2 -3
- package/dist/components/seo/FaqProposalPreview.d.ts.map +1 -1
- package/dist/components/seo/FaqProposalPreview.js +1 -1
- package/dist/components/seo/FaqProposalPreview.js.map +1 -1
- package/dist/components/seo/SeoCopilotDrawer.js +1 -1
- package/dist/components/seo/SeoCopilotDrawer.js.map +1 -1
- package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
- package/dist/components/seo/SeoEditorPane.js +4 -1
- package/dist/components/seo/SeoEditorPane.js.map +1 -1
- package/dist/components/ui/Badge.d.ts +3 -9
- package/dist/components/ui/Badge.d.ts.map +1 -1
- package/dist/components/ui/Badge.js +2 -3
- package/dist/components/ui/Badge.js.map +1 -1
- package/dist/components/ui/Button.d.ts +2 -2
- package/dist/components/ui/Button.d.ts.map +1 -1
- package/dist/components/ui/Button.js +0 -1
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Card.d.ts +2 -3
- package/dist/components/ui/Card.d.ts.map +1 -1
- package/dist/components/ui/Card.js +0 -4
- package/dist/components/ui/Card.js.map +1 -1
- package/dist/components/ui/Input.d.ts +6 -8
- package/dist/components/ui/Input.d.ts.map +1 -1
- package/dist/components/ui/Input.js +6 -6
- package/dist/components/ui/Input.js.map +1 -1
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.d.ts.map +1 -1
- package/dist/components/ui/ResizableAside.d.ts +0 -13
- package/dist/components/ui/ResizableAside.d.ts.map +1 -1
- package/dist/components/ui/ResizableAside.js +5 -5
- package/dist/components/ui/ResizableAside.js.map +1 -1
- package/dist/components/ui/StatusBadge.d.ts +2 -2
- package/dist/components/ui/StatusBadge.d.ts.map +1 -1
- package/dist/components/ui/StatusBadge.js +0 -1
- package/dist/components/ui/StatusBadge.js.map +1 -1
- package/dist/components/ui/Toggle.d.ts +2 -8
- package/dist/components/ui/Toggle.d.ts.map +1 -1
- package/dist/components/ui/Toggle.js +0 -1
- package/dist/components/ui/Toggle.js.map +1 -1
- package/dist/fields/FieldRenderer.d.ts +2 -1
- package/dist/fields/FieldRenderer.d.ts.map +1 -1
- package/dist/fields/index.d.ts +0 -17
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +0 -15
- package/dist/fields/index.js.map +1 -1
- package/dist/layout/primitives/tokens.d.ts +0 -2
- package/dist/layout/primitives/tokens.d.ts.map +1 -1
- package/dist/layout/primitives/tokens.js +0 -6
- package/dist/layout/primitives/tokens.js.map +1 -1
- package/dist/lib/brand-voice-client.d.ts +14 -1
- package/dist/lib/brand-voice-client.d.ts.map +1 -1
- package/dist/lib/brand-voice-client.js +7 -0
- package/dist/lib/brand-voice-client.js.map +1 -1
- package/dist/lib/coauthor-client.d.ts +2 -1
- package/dist/lib/coauthor-client.d.ts.map +1 -1
- package/dist/lib/collection-schema.d.ts +0 -9
- package/dist/lib/collection-schema.d.ts.map +1 -1
- package/dist/lib/collection-schema.js +2 -2
- package/dist/lib/collection-schema.js.map +1 -1
- package/dist/lib/editor-seo-content.d.ts +0 -10
- package/dist/lib/editor-seo-content.d.ts.map +1 -1
- package/dist/lib/editor-seo-content.js +0 -13
- package/dist/lib/editor-seo-content.js.map +1 -1
- package/dist/lib/forms-service.d.ts +3 -21
- package/dist/lib/forms-service.d.ts.map +1 -1
- package/dist/lib/forms-service.js +0 -25
- package/dist/lib/forms-service.js.map +1 -1
- package/dist/lib/notifications-client.d.ts +4 -3
- package/dist/lib/notifications-client.d.ts.map +1 -1
- package/dist/lib/page-audit-input.d.ts +0 -10
- package/dist/lib/page-audit-input.d.ts.map +1 -1
- package/dist/lib/page-audit-input.js +1 -1
- package/dist/lib/page-audit-input.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts +2 -4
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +3 -3
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/lib/pages-service.d.ts +2 -13
- package/dist/lib/pages-service.d.ts.map +1 -1
- package/dist/lib/pages-service.js +3 -24
- package/dist/lib/pages-service.js.map +1 -1
- package/dist/lib/plan.d.ts +2 -1
- package/dist/lib/plan.d.ts.map +1 -1
- package/dist/lib/post-editor-service.d.ts +3 -13
- package/dist/lib/post-editor-service.d.ts.map +1 -1
- package/dist/lib/post-editor-service.js +5 -6
- package/dist/lib/post-editor-service.js.map +1 -1
- package/dist/lib/post-types-service.d.ts +2 -3
- package/dist/lib/post-types-service.d.ts.map +1 -1
- package/dist/lib/post-types-service.js +1 -1
- package/dist/lib/post-types-service.js.map +1 -1
- package/dist/lib/posts-service.d.ts +2 -1
- package/dist/lib/posts-service.d.ts.map +1 -1
- package/dist/lib/preview-link.d.ts +0 -9
- package/dist/lib/preview-link.d.ts.map +1 -1
- package/dist/lib/preview-link.js +0 -15
- package/dist/lib/preview-link.js.map +1 -1
- package/dist/lib/rail-width.d.ts +0 -1
- package/dist/lib/rail-width.d.ts.map +1 -1
- package/dist/lib/rail-width.js +1 -1
- package/dist/lib/rail-width.js.map +1 -1
- package/dist/lib/reflow-probe.d.ts +0 -67
- package/dist/lib/reflow-probe.d.ts.map +1 -1
- package/dist/lib/reflow-probe.js +4 -4
- package/dist/lib/reflow-probe.js.map +1 -1
- package/dist/lib/seo-service.d.ts +14 -32
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js +7 -26
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/lib/theme-chrome.d.ts +0 -1
- package/dist/lib/theme-chrome.d.ts.map +1 -1
- package/dist/lib/theme-chrome.js +1 -1
- package/dist/lib/theme-chrome.js.map +1 -1
- package/dist/lib/version-history.d.ts +2 -2
- package/dist/lib/version-history.d.ts.map +1 -1
- package/dist/lib/workflow-chrome.d.ts +0 -3
- package/dist/lib/workflow-chrome.d.ts.map +1 -1
- package/dist/lib/workflow-chrome.js +3 -3
- package/dist/lib/workflow-chrome.js.map +1 -1
- package/dist/views/Login.d.ts +2 -1
- package/dist/views/Login.d.ts.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +3 -2
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/PageEditor.d.ts +2 -1
- package/dist/views/PageEditor.d.ts.map +1 -1
- package/dist/views/Pages/PagesListView.d.ts +0 -1
- package/dist/views/Pages/PagesListView.d.ts.map +1 -1
- package/dist/views/Pages/PagesListView.js +1 -1
- package/dist/views/Pages/PagesListView.js.map +1 -1
- package/dist/views/Pages.d.ts +2 -1
- package/dist/views/Pages.d.ts.map +1 -1
- package/dist/views/Redirects.d.ts +2 -1
- package/dist/views/Redirects.d.ts.map +1 -1
- package/dist/views/page-editor/section-types.d.ts +2 -2
- package/dist/views/page-editor/section-types.d.ts.map +1 -1
- package/dist/views/page-editor/section-types.js +1 -1
- package/dist/views/page-editor/section-types.js.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.d.ts +2 -1
- package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
- package/dist/views/page-editor/theme-chrome/index.d.ts +1 -6
- package/dist/views/page-editor/theme-chrome/index.d.ts.map +1 -1
- package/dist/views/page-editor/theme-chrome/index.js +1 -3
- package/dist/views/page-editor/theme-chrome/index.js.map +1 -1
- package/dist/views/page-editor/viewports.d.ts +1 -1
- package/dist/views/page-editor/viewports.d.ts.map +1 -1
- package/dist/views/page-editor/viewports.js +1 -1
- package/dist/views/page-editor/viewports.js.map +1 -1
- package/dist/views/settings/AISettingsTab.d.ts.map +1 -1
- package/dist/views/settings/AISettingsTab.js +13 -3
- package/dist/views/settings/AISettingsTab.js.map +1 -1
- package/dist/views/settings/BrandVoiceCard.d.ts +3 -0
- package/dist/views/settings/BrandVoiceCard.d.ts.map +1 -1
- package/dist/views/settings/BrandVoiceCard.js +2 -2
- package/dist/views/settings/BrandVoiceCard.js.map +1 -1
- package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.d.ts.map +1 -1
- package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.js +4 -1
- package/dist/views/settings/brand-voice/BrandVoiceAlignmentPanel.js.map +1 -1
- package/dist/views/settings/featureModel.d.ts +0 -3
- package/dist/views/settings/featureModel.d.ts.map +1 -1
- package/dist/views/settings/featureModel.js +2 -2
- package/dist/views/settings/featureModel.js.map +1 -1
- package/dist/views/settings/useAiSettings.d.ts +2 -2
- package/dist/views/settings/useAiSettings.d.ts.map +1 -1
- package/dist/views/settings/useAiSettings.js +6 -2
- package/dist/views/settings/useAiSettings.js.map +1 -1
- package/dist/views/settings/useAppearanceSettings.d.ts +5 -6
- package/dist/views/settings/useAppearanceSettings.d.ts.map +1 -1
- package/dist/views/settings/useAppearanceSettings.js +1 -1
- package/dist/views/settings/useAppearanceSettings.js.map +1 -1
- package/dist/views/settings/useEmailSettings.d.ts +3 -2
- package/dist/views/settings/useEmailSettings.d.ts.map +1 -1
- package/dist/views/settings/useGeneralSettings.d.ts +2 -11
- package/dist/views/settings/useGeneralSettings.d.ts.map +1 -1
- package/dist/views/settings/useGeneralSettings.js +3 -3
- package/dist/views/settings/useGeneralSettings.js.map +1 -1
- package/dist/views/settings/useSeoSettings.d.ts +1 -1
- package/dist/views/settings/useSeoSettings.d.ts.map +1 -1
- package/dist/views/settings/useUpdates.d.ts +1 -3
- package/dist/views/settings/useUpdates.d.ts.map +1 -1
- package/dist/views/settings/useUpdates.js +1 -1
- package/dist/views/settings/useUpdates.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/views/ai-settings.render.test.tsx +86 -2
- package/src/components/CoauthorResultPopover.tsx +3 -1
- package/src/components/CommentSidePanel.tsx +0 -1
- package/src/components/EditorRail.tsx +1 -1
- package/src/components/MediaPickerModal.tsx +1 -1
- package/src/components/TipTapEditor.tsx +2 -2
- package/src/components/forms/EntriesTable.tsx +1 -1
- package/src/components/pages/tag-badges.tsx +1 -1
- package/src/components/seo/FaqProposalPreview.tsx +2 -2
- package/src/components/seo/SeoCopilotDrawer.tsx +1 -1
- package/src/components/seo/SeoEditorPane.tsx +3 -1
- package/src/components/ui/Badge.tsx +4 -6
- package/src/components/ui/Button.tsx +1 -3
- package/src/components/ui/Card.tsx +1 -14
- package/src/components/ui/Input.tsx +7 -15
- package/src/components/ui/Modal.tsx +1 -1
- package/src/components/ui/ResizableAside.tsx +5 -5
- package/src/components/ui/StatusBadge.tsx +1 -3
- package/src/components/ui/Toggle.tsx +1 -3
- package/src/fields/FieldRenderer.tsx +1 -1
- package/src/fields/index.ts +0 -17
- package/src/layout/primitives/tokens.ts +0 -7
- package/src/lib/brand-voice-client.ts +18 -1
- package/src/lib/coauthor-client.ts +1 -1
- package/src/lib/collection-schema.ts +2 -2
- package/src/lib/editor-seo-content.ts +0 -22
- package/src/lib/forms-service.ts +2 -42
- package/src/lib/notifications-client.ts +3 -3
- package/src/lib/page-audit-input.ts +1 -1
- package/src/lib/page-editor-service.ts +1 -3
- package/src/lib/pages-service.ts +4 -29
- package/src/lib/plan.ts +1 -1
- package/src/lib/post-editor-service.ts +4 -10
- package/src/lib/post-types-service.ts +2 -2
- package/src/lib/posts-service.ts +1 -1
- package/src/lib/preview-link.ts +0 -14
- package/src/lib/rail-width.ts +1 -1
- package/src/lib/reflow-probe.ts +4 -4
- package/src/lib/seo-service.ts +20 -60
- package/src/lib/theme-chrome.ts +1 -1
- package/src/lib/version-history.ts +2 -2
- package/src/lib/workflow-chrome.ts +3 -3
- package/src/views/Login.tsx +1 -1
- package/src/views/MediaBrowser.tsx +3 -2
- package/src/views/PageEditor.tsx +1 -1
- package/src/views/Pages/PagesListView.tsx +1 -1
- package/src/views/Pages.tsx +1 -1
- package/src/views/Redirects.tsx +1 -1
- package/src/views/page-editor/section-types.ts +0 -9
- package/src/views/page-editor/sections/admin-renderers.tsx +1 -1
- package/src/views/page-editor/theme-chrome/index.ts +2 -10
- package/src/views/page-editor/viewports.ts +0 -3
- package/src/views/settings/AISettingsTab.tsx +15 -3
- package/src/views/settings/BrandVoiceCard.tsx +11 -1
- package/src/views/settings/brand-voice/BrandVoiceAlignmentPanel.tsx +3 -1
- package/src/views/settings/featureModel.ts +2 -2
- package/src/views/settings/useAiSettings.ts +8 -3
- package/src/views/settings/useAppearanceSettings.ts +4 -6
- package/src/views/settings/useEmailSettings.ts +2 -2
- package/src/views/settings/useGeneralSettings.ts +5 -10
- package/src/views/settings/useSeoSettings.ts +1 -1
- package/src/views/settings/useUpdates.ts +2 -2
package/src/lib/forms-service.ts
CHANGED
|
@@ -34,21 +34,16 @@ export type {
|
|
|
34
34
|
FormSchemaVersion,
|
|
35
35
|
FormField,
|
|
36
36
|
FormFieldType,
|
|
37
|
-
FormFieldOption,
|
|
38
|
-
FieldValidationRules,
|
|
39
37
|
FormAnalyticsConfig,
|
|
40
38
|
FormNotificationSettings,
|
|
41
39
|
FormEmbedSettings,
|
|
42
40
|
FormWebhookConfig,
|
|
43
|
-
FormWebhookEvent,
|
|
44
41
|
FormStatus,
|
|
45
42
|
FormStats,
|
|
46
43
|
FormsSidebarCount,
|
|
47
44
|
FormSubmission,
|
|
48
|
-
FormSubmissionFile,
|
|
49
45
|
FetchFormEntriesParams,
|
|
50
46
|
EntryCounts,
|
|
51
|
-
SpamStatus,
|
|
52
47
|
} from '@prenta/core'
|
|
53
48
|
|
|
54
49
|
/**
|
|
@@ -170,34 +165,6 @@ export async function updateForm(
|
|
|
170
165
|
return { data: res.data, error: res.error }
|
|
171
166
|
}
|
|
172
167
|
|
|
173
|
-
export async function duplicateForm(
|
|
174
|
-
id: string,
|
|
175
|
-
): Promise<{ data?: FormDefinition; error?: string }> {
|
|
176
|
-
const res = await cmsApi<FormDefinition>(`/forms/${encodeURIComponent(id)}/duplicate`, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
})
|
|
179
|
-
return { data: res.data, error: res.error }
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Move a form to the archived state (hidden from embed pickers, no new submissions). */
|
|
183
|
-
export async function archiveForm(id: string): Promise<{ error?: string }> {
|
|
184
|
-
return updateForm(id, { status: 'archived' })
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** Restore an archived form back to draft. */
|
|
188
|
-
export async function restoreForm(id: string): Promise<{ error?: string }> {
|
|
189
|
-
return updateForm(id, { status: 'draft' })
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export async function deleteForm(
|
|
193
|
-
id: string,
|
|
194
|
-
opts: { force?: boolean } = {},
|
|
195
|
-
): Promise<{ error?: string }> {
|
|
196
|
-
const qs = opts.force ? '?force=true' : ''
|
|
197
|
-
const res = await cmsApi(`/forms/${encodeURIComponent(id)}${qs}`, { method: 'DELETE' })
|
|
198
|
-
return { error: res.error }
|
|
199
|
-
}
|
|
200
|
-
|
|
201
168
|
// ─── Schema ──────────────────────────────────────────────────────────────
|
|
202
169
|
|
|
203
170
|
export async function fetchFormSchema(id: string): Promise<FormSchemaResult> {
|
|
@@ -496,8 +463,8 @@ export async function downloadEntriesExport(
|
|
|
496
463
|
|
|
497
464
|
// ─── Delivery attempts (#675) ─────────────────────────────────────────────
|
|
498
465
|
|
|
499
|
-
|
|
500
|
-
|
|
466
|
+
type FormDeliveryChannel = 'email_notify' | 'email_autoresponder' | 'form_webhook'
|
|
467
|
+
type FormDeliveryStatus = 'pending' | 'retrying' | 'success' | 'failed' | 'skipped'
|
|
501
468
|
|
|
502
469
|
export interface FormDeliveryAttempt {
|
|
503
470
|
id: string
|
|
@@ -560,10 +527,3 @@ export async function retryFormDelivery(
|
|
|
560
527
|
)
|
|
561
528
|
return { data: res.data, error: res.error }
|
|
562
529
|
}
|
|
563
|
-
|
|
564
|
-
export async function fetchFailedDeliveryCount(formId?: string): Promise<number> {
|
|
565
|
-
const qs = formId ? `?formId=${encodeURIComponent(formId)}` : ''
|
|
566
|
-
const res = await cmsApi<{ count: number }>(`/forms/deliveries/failed-count${qs}`)
|
|
567
|
-
throwIfError(res)
|
|
568
|
-
return res.data?.count ?? 0
|
|
569
|
-
}
|
|
@@ -15,21 +15,21 @@ import { cmsApi, getApiBase } from './api.js'
|
|
|
15
15
|
|
|
16
16
|
export type NotificationKind = 'comment_reply' | 'comment_resolved' | 'comment_mention'
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
interface CommentReplyPayload {
|
|
19
19
|
kind: 'comment_reply'
|
|
20
20
|
actorId: string | null
|
|
21
21
|
actorName: string | null
|
|
22
22
|
excerpt: string
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
interface CommentResolvedPayload {
|
|
26
26
|
kind: 'comment_resolved'
|
|
27
27
|
actorId: string | null
|
|
28
28
|
actorName: string | null
|
|
29
29
|
rootExcerpt: string
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
interface CommentMentionPayload {
|
|
33
33
|
kind: 'comment_mention'
|
|
34
34
|
actorId: string | null
|
|
35
35
|
actorName: string | null
|
|
@@ -78,7 +78,7 @@ function stripHtml(html: string): string {
|
|
|
78
78
|
* when one is set — otherwise `imageSlots` would count an empty media field
|
|
79
79
|
* and fail alt-text for an image that is not on the page.
|
|
80
80
|
*/
|
|
81
|
-
|
|
81
|
+
function withPostAuditSlots(sections: PageSection[], post?: PostAuditExtras): PageSection[] {
|
|
82
82
|
if (!post) return sections
|
|
83
83
|
|
|
84
84
|
const extras: PageSection[] = []
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
moveSection,
|
|
28
28
|
reorderSections,
|
|
29
29
|
updateSectionContent,
|
|
30
|
-
updateSectionContentAtPath,
|
|
31
30
|
updateSectionSettings,
|
|
32
31
|
updateSectionMeta,
|
|
33
32
|
coerceSections as coerceSectionsCore,
|
|
@@ -55,7 +54,6 @@ export {
|
|
|
55
54
|
moveSection,
|
|
56
55
|
reorderSections,
|
|
57
56
|
updateSectionContent,
|
|
58
|
-
updateSectionContentAtPath,
|
|
59
57
|
updateSectionSettings,
|
|
60
58
|
updateSectionMeta,
|
|
61
59
|
}
|
|
@@ -278,7 +276,7 @@ function toWriteBody(page: EditorPage, extra?: Record<string, unknown>): string
|
|
|
278
276
|
}
|
|
279
277
|
|
|
280
278
|
/** Create a new page document, returning the new id. */
|
|
281
|
-
|
|
279
|
+
async function createPage(page: EditorPage): Promise<EditorPage> {
|
|
282
280
|
const res = await cmsApi<DocumentApiRow>(`/collections/${PAGES_COLLECTION}`, {
|
|
283
281
|
method: 'POST',
|
|
284
282
|
body: toWriteBody(page),
|
package/src/lib/pages-service.ts
CHANGED
|
@@ -30,8 +30,8 @@ import { cmsApi } from './api.js'
|
|
|
30
30
|
|
|
31
31
|
export type PageStatus = 'DRAFT' | 'PUBLISHED' | 'SCHEDULED' | 'ARCHIVED'
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const PAGES_COLLECTION = 'pages'
|
|
34
|
+
const PAGE_TAGS_COLLECTION = 'page-tags'
|
|
35
35
|
|
|
36
36
|
export interface PageTag {
|
|
37
37
|
id: string // == slug (stable join key; pages store slugs in data.tags)
|
|
@@ -47,7 +47,7 @@ export interface PageTag {
|
|
|
47
47
|
updatedAt: string | null
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
interface PageAuthor {
|
|
51
51
|
id: string
|
|
52
52
|
name: string
|
|
53
53
|
initials: string
|
|
@@ -222,22 +222,6 @@ async function fetchTagDocs(): Promise<DocumentApiRow[]> {
|
|
|
222
222
|
return (res.data?.docs ?? []) as DocumentApiRow[]
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
/**
|
|
226
|
-
* Fetch all tags. `pageCounts` (slug → count) is optional so the tag
|
|
227
|
-
* manager / pickers can render before page data is loaded.
|
|
228
|
-
*/
|
|
229
|
-
export async function fetchPageTags(pageCounts?: Map<string, number>): Promise<PageTag[]> {
|
|
230
|
-
const docs = await fetchTagDocs()
|
|
231
|
-
const tags = docs.map((d) => docToTag(d, pageCounts?.get(d.slug ?? d.id) ?? 0))
|
|
232
|
-
tags.sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name))
|
|
233
|
-
return tags
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export async function fetchAuthors(): Promise<PageAuthor[]> {
|
|
237
|
-
const map = await fetchAuthorsMap()
|
|
238
|
-
return [...map.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
239
|
-
}
|
|
240
|
-
|
|
241
225
|
// ─── Pages ───────────────────────────────────────────────────────────
|
|
242
226
|
|
|
243
227
|
function docToPage(d: DocumentApiRow, authors: Map<string, PageAuthor>): Page {
|
|
@@ -439,15 +423,6 @@ export async function fetchPages(query: PagesQuery = {}): Promise<PagesResult> {
|
|
|
439
423
|
}
|
|
440
424
|
}
|
|
441
425
|
|
|
442
|
-
export async function fetchPageById(pageId: string): Promise<Page | null> {
|
|
443
|
-
const res = await cmsApi<DocumentApiRow>(
|
|
444
|
-
`/collections/${PAGES_COLLECTION}/${encodeURIComponent(pageId)}`,
|
|
445
|
-
)
|
|
446
|
-
if (res.error || !res.data) return null
|
|
447
|
-
const authors = await fetchAuthorsMap()
|
|
448
|
-
return docToPage(res.data, authors)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
426
|
// ─── Bulk page mutations ─────────────────────────────────────────────
|
|
452
427
|
|
|
453
428
|
export interface BulkResult {
|
|
@@ -621,7 +596,7 @@ export async function updatePageTag(
|
|
|
621
596
|
return res.error ? { error: res.error } : {}
|
|
622
597
|
}
|
|
623
598
|
|
|
624
|
-
|
|
599
|
+
async function deletePageTag(tagDocId: string): Promise<{ error?: string }> {
|
|
625
600
|
const res = await cmsApi(`/collections/${PAGE_TAGS_COLLECTION}/${encodeURIComponent(tagDocId)}`, {
|
|
626
601
|
method: 'DELETE',
|
|
627
602
|
})
|
package/src/lib/plan.ts
CHANGED
|
@@ -30,10 +30,8 @@ import {
|
|
|
30
30
|
removeSection,
|
|
31
31
|
duplicateSection,
|
|
32
32
|
toggleSectionVisibility,
|
|
33
|
-
moveSection,
|
|
34
33
|
reorderSections,
|
|
35
34
|
updateSectionContent,
|
|
36
|
-
updateSectionContentAtPath,
|
|
37
35
|
updateSectionSettings,
|
|
38
36
|
updateSectionMeta,
|
|
39
37
|
coerceSections,
|
|
@@ -56,17 +54,13 @@ export {
|
|
|
56
54
|
removeSection,
|
|
57
55
|
duplicateSection,
|
|
58
56
|
toggleSectionVisibility,
|
|
59
|
-
moveSection,
|
|
60
57
|
reorderSections,
|
|
61
58
|
updateSectionContent,
|
|
62
|
-
updateSectionContentAtPath,
|
|
63
59
|
updateSectionSettings,
|
|
64
60
|
updateSectionMeta,
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
export const TEMPLATES_COLLECTION = 'post-templates'
|
|
63
|
+
type PostStatus = 'DRAFT' | 'PUBLISHED' | 'SCHEDULED' | 'ARCHIVED'
|
|
70
64
|
|
|
71
65
|
export interface EditorPost {
|
|
72
66
|
id: string | null
|
|
@@ -230,7 +224,7 @@ export interface FeaturedImageRef {
|
|
|
230
224
|
}
|
|
231
225
|
|
|
232
226
|
/** True for paths and http(s) URLs — never a bare media cuid. */
|
|
233
|
-
|
|
227
|
+
function isDisplayableImageSrc(value: string): boolean {
|
|
234
228
|
const v = value.trim()
|
|
235
229
|
return v.startsWith('/') || /^https?:\/\//i.test(v)
|
|
236
230
|
}
|
|
@@ -272,7 +266,7 @@ export function parseFeaturedImage(raw: unknown): string {
|
|
|
272
266
|
* `[{ tag: 'AI' }]` rows; older/hand-rolled configs may store `['AI']`
|
|
273
267
|
* directly — accept both so no site loses tags in the editor.
|
|
274
268
|
*/
|
|
275
|
-
|
|
269
|
+
function parseTags(raw: unknown): string[] {
|
|
276
270
|
if (!Array.isArray(raw)) return []
|
|
277
271
|
const out: string[] = []
|
|
278
272
|
for (const item of raw) {
|
|
@@ -435,7 +429,7 @@ function toWriteBody(post: EditorPost, extra?: Record<string, unknown>): string
|
|
|
435
429
|
return JSON.stringify(payload)
|
|
436
430
|
}
|
|
437
431
|
|
|
438
|
-
|
|
432
|
+
async function createPost(post: EditorPost): Promise<EditorPost> {
|
|
439
433
|
const res = await cmsApi<DocumentApiRow>(`/collections/${encodeURIComponent(post.postType)}`, {
|
|
440
434
|
method: 'POST',
|
|
441
435
|
body: toWriteBody(post),
|
|
@@ -6,7 +6,7 @@ import { cmsApi } from './api.js'
|
|
|
6
6
|
* schemas are file-defined in `prenta.config.ts`, so the Post Type editor
|
|
7
7
|
* inspects the live schema and generates config snippets for changes.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
interface PostTypeFieldSummary {
|
|
10
10
|
name: string
|
|
11
11
|
type: string
|
|
12
12
|
required: boolean
|
|
@@ -57,7 +57,7 @@ export async function fetchPostTypeDetail(slug: string): Promise<PostTypeDetail
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Friendly display names for cms-core field types (design's type badges). */
|
|
60
|
-
|
|
60
|
+
const FIELD_TYPE_LABELS: Record<string, string> = {
|
|
61
61
|
text: 'Short Text',
|
|
62
62
|
richText: 'Rich Text',
|
|
63
63
|
number: 'Number',
|
package/src/lib/posts-service.ts
CHANGED
package/src/lib/preview-link.ts
CHANGED
|
@@ -101,20 +101,6 @@ export function buildPublicUrl({
|
|
|
101
101
|
return resolveDocumentUrl({ siteUrl, urlPrefix, slug, path, data, trailingSlash })
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/**
|
|
105
|
-
* Resolve the public site origin for link building.
|
|
106
|
-
*
|
|
107
|
-
* Prefer an explicit URL (effective public apex from `/site/public-url` or
|
|
108
|
-
* `config.seo.siteUrl`). Falling back to `window.location.origin` is a last
|
|
109
|
-
* resort — on Astro-split installs the admin tab is the Next CMS host, so that
|
|
110
|
-
* fallback opens the wrong site. Callers should prefer {@link usePublicSiteUrl}.
|
|
111
|
-
*/
|
|
112
|
-
export function resolveSiteUrl(configSiteUrl?: string | null): string {
|
|
113
|
-
if (configSiteUrl && configSiteUrl.trim()) return configSiteUrl.trim().replace(/\/+$/, '')
|
|
114
|
-
if (typeof window !== 'undefined') return window.location.origin
|
|
115
|
-
return ''
|
|
116
|
-
}
|
|
117
|
-
|
|
118
104
|
/** True when `siteUrl` shares an origin with the admin tab (split-deploy smell). */
|
|
119
105
|
export function siteUrlMatchesAdminOrigin(
|
|
120
106
|
siteUrl: string | null | undefined,
|
package/src/lib/rail-width.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const RAIL_DEFAULT_WIDTH = 360
|
|
|
10
10
|
/** Width of the strip that replaces the rail when it is collapsed. */
|
|
11
11
|
export const RAIL_COLLAPSED_WIDTH = 38
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const RAIL_WIDTH_STORAGE_KEY = 'pb.panelW'
|
|
14
14
|
|
|
15
15
|
/** Constrain a candidate width to the rail's range, rejecting junk. */
|
|
16
16
|
export function clampRailWidth(width: number): number {
|
package/src/lib/reflow-probe.ts
CHANGED
|
@@ -66,7 +66,7 @@ export function linesFromMetrics(height: number, lineHeight: number, fontSize: n
|
|
|
66
66
|
* whose content is a single replaced node, or a layout engine that gives no
|
|
67
67
|
* rects for empty text.
|
|
68
68
|
*/
|
|
69
|
-
|
|
69
|
+
function countLines(el: HTMLElement): number {
|
|
70
70
|
const doc = el.ownerDocument
|
|
71
71
|
try {
|
|
72
72
|
const range = doc.createRange()
|
|
@@ -106,7 +106,7 @@ export function countLines(el: HTMLElement): number {
|
|
|
106
106
|
*
|
|
107
107
|
* Use `mountProbeHost` rather than appending by hand.
|
|
108
108
|
*/
|
|
109
|
-
|
|
109
|
+
function createProbeHost(doc: Document = document): HTMLElement {
|
|
110
110
|
const host = doc.createElement('div')
|
|
111
111
|
host.setAttribute('aria-hidden', 'true')
|
|
112
112
|
host.dataset.reflowProbe = 'true'
|
|
@@ -128,7 +128,7 @@ export function createProbeHost(doc: Document = document): HTMLElement {
|
|
|
128
128
|
* root exists (it always does in the app) — and callers should treat a
|
|
129
129
|
* fallback mount as untrustworthy rather than silently measuring.
|
|
130
130
|
*/
|
|
131
|
-
|
|
131
|
+
function mountProbeHost(
|
|
132
132
|
host: HTMLElement,
|
|
133
133
|
doc: Document = document,
|
|
134
134
|
): { detach: () => void; scoped: boolean } {
|
|
@@ -156,7 +156,7 @@ export function mountProbeHost(
|
|
|
156
156
|
* Reporting a line count from the wrong element would be worse than reporting
|
|
157
157
|
* none: an editor reads a number as a fact.
|
|
158
158
|
*/
|
|
159
|
-
|
|
159
|
+
function measureFieldAcrossBreakpoints(
|
|
160
160
|
container: HTMLElement,
|
|
161
161
|
fieldPath: string,
|
|
162
162
|
scope?: string,
|
package/src/lib/seo-service.ts
CHANGED
|
@@ -31,7 +31,7 @@ function throwIfError(res: { error?: string }): void {
|
|
|
31
31
|
export type SeoGrade = 'good' | 'fair' | 'poor' | 'critical'
|
|
32
32
|
export type SeoSeverity = 'critical' | 'warning' | 'info'
|
|
33
33
|
export type SeoIssueStatus = 'open' | 'ignored' | 'resolved' | 'recurring'
|
|
34
|
-
|
|
34
|
+
type SeoIssueCategory =
|
|
35
35
|
| 'content'
|
|
36
36
|
| 'technical'
|
|
37
37
|
| 'redirects'
|
|
@@ -44,7 +44,7 @@ export type RedirectStatusCode = 301 | 302 | 307 | 308 | 410
|
|
|
44
44
|
|
|
45
45
|
// ─── Overview ────────────────────────────────────────────────────────
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
interface SeoSiteScore {
|
|
48
48
|
score: number
|
|
49
49
|
grade: SeoGrade
|
|
50
50
|
/** Change vs the previous audit run, e.g. +6. */
|
|
@@ -93,7 +93,7 @@ export interface QuickWin {
|
|
|
93
93
|
fixActionType: string | null
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
interface SearchPerformanceRow {
|
|
97
97
|
url: string
|
|
98
98
|
title: string
|
|
99
99
|
impressions: number
|
|
@@ -135,20 +135,9 @@ export async function fetchSeoOverview(): Promise<SeoOverview> {
|
|
|
135
135
|
return res.data ?? EMPTY_OVERVIEW
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
export async function fetchSearchPerformance(
|
|
139
|
-
params: {
|
|
140
|
-
limit?: number
|
|
141
|
-
} = {},
|
|
142
|
-
): Promise<SearchPerformanceRow[]> {
|
|
143
|
-
const qs = params.limit ? `?limit=${params.limit}` : ''
|
|
144
|
-
const res = await cmsApi<{ rows: SearchPerformanceRow[] }>(`/seo/search-performance${qs}`)
|
|
145
|
-
throwIfError(res)
|
|
146
|
-
return res.data?.rows ?? []
|
|
147
|
-
}
|
|
148
|
-
|
|
149
138
|
// ─── Per-document search insight (synced GSC + CrUX) ─────────────────
|
|
150
139
|
|
|
151
|
-
|
|
140
|
+
interface SearchInsightTopQuery {
|
|
152
141
|
query: string
|
|
153
142
|
clicks: number
|
|
154
143
|
impressions: number
|
|
@@ -201,8 +190,6 @@ export const SEO_SCHEMA_TYPES = [
|
|
|
201
190
|
'Recipe',
|
|
202
191
|
] as const
|
|
203
192
|
|
|
204
|
-
export type SeoSchemaType = (typeof SEO_SCHEMA_TYPES)[number]
|
|
205
|
-
|
|
206
193
|
export interface SeoContentRecord {
|
|
207
194
|
id: string
|
|
208
195
|
entityType: 'page' | 'post'
|
|
@@ -423,12 +410,6 @@ export async function fetchSeoAuditRuns(): Promise<SeoAuditRun[]> {
|
|
|
423
410
|
return res.data?.runs ?? []
|
|
424
411
|
}
|
|
425
412
|
|
|
426
|
-
export async function fetchSeoAuditRun(auditRunId: string): Promise<SeoAuditRun | null> {
|
|
427
|
-
const res = await cmsApi<SeoAuditRun>(`/seo/audits/${encodeURIComponent(auditRunId)}`)
|
|
428
|
-
throwIfError(res)
|
|
429
|
-
return res.data ?? null
|
|
430
|
-
}
|
|
431
|
-
|
|
432
413
|
export interface SeoIssueQuery {
|
|
433
414
|
severity?: SeoSeverity | 'all'
|
|
434
415
|
category?: SeoIssueCategory | 'all'
|
|
@@ -566,7 +547,7 @@ export async function exportSeoFixBrief(
|
|
|
566
547
|
}
|
|
567
548
|
|
|
568
549
|
/** Issue types with inline Approve fix in the Audit tab (keep in sync with cms-core). */
|
|
569
|
-
|
|
550
|
+
const SEO_INLINE_FIX_ISSUE_TYPES = new Set([
|
|
570
551
|
'meta-title-length',
|
|
571
552
|
'meta-description-length',
|
|
572
553
|
'accidental-noindex',
|
|
@@ -585,7 +566,7 @@ export function isInlineSeoFixIssue(type: string): boolean {
|
|
|
585
566
|
return SEO_INLINE_FIX_ISSUE_TYPES.has(type)
|
|
586
567
|
}
|
|
587
568
|
|
|
588
|
-
|
|
569
|
+
const SEO_CONTENT_BRIEF_ISSUE_TYPES = [
|
|
589
570
|
'thin-content',
|
|
590
571
|
'missing-h1',
|
|
591
572
|
'multiple-h1',
|
|
@@ -596,7 +577,7 @@ export function supportsSeoContentBrief(type: string): boolean {
|
|
|
596
577
|
return (SEO_CONTENT_BRIEF_ISSUE_TYPES as readonly string[]).includes(type)
|
|
597
578
|
}
|
|
598
579
|
|
|
599
|
-
|
|
580
|
+
interface SeoContentBriefSection {
|
|
600
581
|
heading: string
|
|
601
582
|
bullets: string[]
|
|
602
583
|
}
|
|
@@ -643,13 +624,13 @@ export async function generateSeoContentBrief(
|
|
|
643
624
|
return { brief: res.data?.brief }
|
|
644
625
|
}
|
|
645
626
|
|
|
646
|
-
|
|
627
|
+
const SEO_RESEARCH_ISSUE_TYPES = ['thin-content'] as const
|
|
647
628
|
|
|
648
629
|
export function supportsSeoContentResearch(type: string): boolean {
|
|
649
630
|
return (SEO_RESEARCH_ISSUE_TYPES as readonly string[]).includes(type)
|
|
650
631
|
}
|
|
651
632
|
|
|
652
|
-
|
|
633
|
+
interface SeoContentResearchCompetitor {
|
|
653
634
|
url: string
|
|
654
635
|
title: string
|
|
655
636
|
note: string
|
|
@@ -688,21 +669,6 @@ export async function fetchSeoContentResearch(issueId: string): Promise<{
|
|
|
688
669
|
}
|
|
689
670
|
}
|
|
690
671
|
|
|
691
|
-
export async function generateSeoContentResearch(
|
|
692
|
-
issueId: string,
|
|
693
|
-
competitorUrls?: string[],
|
|
694
|
-
): Promise<{ research?: SeoContentResearch; error?: string }> {
|
|
695
|
-
const res = await cmsApi<{ research: SeoContentResearch }>(
|
|
696
|
-
`/seo/issues/${encodeURIComponent(issueId)}/content-research`,
|
|
697
|
-
{
|
|
698
|
-
method: 'POST',
|
|
699
|
-
body: JSON.stringify({ competitorUrls: competitorUrls?.filter(Boolean) }),
|
|
700
|
-
},
|
|
701
|
-
)
|
|
702
|
-
if (res.error) return { error: res.error }
|
|
703
|
-
return { research: res.data?.research }
|
|
704
|
-
}
|
|
705
|
-
|
|
706
672
|
export async function researchAndBriefSeoIssue(
|
|
707
673
|
issueId: string,
|
|
708
674
|
competitorUrls?: string[],
|
|
@@ -916,14 +882,14 @@ export interface SeoAutopilotRunSummary {
|
|
|
916
882
|
haltedReason: string | null
|
|
917
883
|
}
|
|
918
884
|
|
|
919
|
-
|
|
885
|
+
interface SeoAutopilotOutcomeWatch {
|
|
920
886
|
enabled: boolean
|
|
921
887
|
days: number
|
|
922
888
|
dropRatio: number
|
|
923
889
|
minPriorClicks: number
|
|
924
890
|
}
|
|
925
891
|
|
|
926
|
-
|
|
892
|
+
interface SeoAutopilotGovernance {
|
|
927
893
|
/** Settings → AI "Requires approval" on the issue-fix feature vetoes every metadata type. */
|
|
928
894
|
issueFixApprovalRequired: boolean
|
|
929
895
|
/** Same veto for 404-recovery redirects. */
|
|
@@ -1137,7 +1103,7 @@ export async function revertAutopilotChange(
|
|
|
1137
1103
|
|
|
1138
1104
|
// ─── AI crawler activity ─────────────────────────────────────────────
|
|
1139
1105
|
|
|
1140
|
-
|
|
1106
|
+
interface AiCrawlerRow {
|
|
1141
1107
|
bot: string
|
|
1142
1108
|
surface: string
|
|
1143
1109
|
category: string
|
|
@@ -1182,7 +1148,7 @@ export interface SeoGeoEntityRef {
|
|
|
1182
1148
|
* Why `enabled` is `false`: the kill switch in Settings → SEO (`settings`) or
|
|
1183
1149
|
* the GEO tables are not migrated on the CMS database (`unavailable`).
|
|
1184
1150
|
*/
|
|
1185
|
-
|
|
1151
|
+
type SeoGeoDisabledReason = 'settings' | 'unavailable'
|
|
1186
1152
|
|
|
1187
1153
|
export interface SeoGeoReport {
|
|
1188
1154
|
enabled: boolean
|
|
@@ -1354,13 +1320,6 @@ export interface RedirectOverview {
|
|
|
1354
1320
|
trafficRecovered: number
|
|
1355
1321
|
}
|
|
1356
1322
|
|
|
1357
|
-
export interface Redirect404 {
|
|
1358
|
-
id: string
|
|
1359
|
-
path: string
|
|
1360
|
-
hits: number
|
|
1361
|
-
lastSeenAt: string
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
1323
|
export interface RedirectSuggestion {
|
|
1365
1324
|
id: string
|
|
1366
1325
|
fromPath: string
|
|
@@ -1581,9 +1540,10 @@ export interface SeoAiResult {
|
|
|
1581
1540
|
}
|
|
1582
1541
|
|
|
1583
1542
|
/**
|
|
1584
|
-
* AI SEO endpoints report unavailability
|
|
1585
|
-
*
|
|
1586
|
-
*
|
|
1543
|
+
* AI SEO endpoints report unavailability as 503 (no provider connection or
|
|
1544
|
+
* model configured in Settings → AI; the body carries `code` + `hint`). 501 is
|
|
1545
|
+
* kept for CMS builds older than @prenta/core 7. Both map to the same graceful
|
|
1546
|
+
* "AI unavailable" UX.
|
|
1587
1547
|
*/
|
|
1588
1548
|
function isAiUnavailable(status?: number): boolean {
|
|
1589
1549
|
return status === 501 || status === 503
|
|
@@ -1755,7 +1715,7 @@ export async function fetchLinkHealthIssues(): Promise<{
|
|
|
1755
1715
|
// `seo/faq-signals.ts` (`FaqSignals`). The server is the contract.
|
|
1756
1716
|
|
|
1757
1717
|
/** Same union as core's `SeoProposalFixStrategy`; `null` for plain metadata writes. */
|
|
1758
|
-
|
|
1718
|
+
type SeoProposalFixStrategy = 'redirect' | 'fix-link' | 'insert-link' | 'add-section'
|
|
1759
1719
|
|
|
1760
1720
|
/** Why a page got a `missing-faq` proposal (28-day window). */
|
|
1761
1721
|
export interface SeoFaqSignals {
|
|
@@ -1769,7 +1729,7 @@ export interface SeoFaqSignals {
|
|
|
1769
1729
|
queries: Array<{ query: string; impressions: number; clicks: number }>
|
|
1770
1730
|
}
|
|
1771
1731
|
|
|
1772
|
-
|
|
1732
|
+
interface SeoFaqSectionDraft {
|
|
1773
1733
|
sectionType: 'faq'
|
|
1774
1734
|
content: { heading: string; items: Array<{ question: string; answer: string }> }
|
|
1775
1735
|
}
|
|
@@ -1845,7 +1805,7 @@ export interface SeoProposalBulkApplyItem {
|
|
|
1845
1805
|
}
|
|
1846
1806
|
|
|
1847
1807
|
/** Mirrors core's `BulkApplyItemStatus` (`seo/proposals-bulk-apply.ts`). */
|
|
1848
|
-
|
|
1808
|
+
type SeoProposalBulkApplyItemStatus =
|
|
1849
1809
|
| 'applied'
|
|
1850
1810
|
| 'stale'
|
|
1851
1811
|
| 'verify-failed'
|
package/src/lib/theme-chrome.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface AdminThemeChrome {
|
|
|
55
55
|
diagnostics?: { navLinkCount?: number; brandPresent?: boolean }
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
const THEME_CHROME_MODE_KEY = 'prenta-theme-chrome-mode'
|
|
59
59
|
export const THEME_CHROME_MAIN_ID = 'prenta-canvas-main'
|
|
60
60
|
|
|
61
61
|
export function isThemeChromeEnabled(
|
|
@@ -21,9 +21,9 @@ import { coerceSections, type PageSection } from '@prenta/core/page-sections'
|
|
|
21
21
|
* `UPDATE` as "Saved" rather than "Draft" — the neutral reading is the only
|
|
22
22
|
* one that is true for both old and new rows.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
type VersionChangeType = 'CREATE' | 'UPDATE' | 'PUBLISH' | 'RESTORE' | 'DELETE'
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
interface VersionAuthor {
|
|
27
27
|
id: string
|
|
28
28
|
/** Display name, falling back to email and then to a non-blank placeholder. */
|
|
29
29
|
name: string
|
|
@@ -5,11 +5,11 @@ export type WorkflowChromeAction = 'submit' | 'approve' | 'waiting'
|
|
|
5
5
|
export const SUBMIT_FOR_REVIEW_LABEL = 'Submit for review'
|
|
6
6
|
export const APPROVE_LABEL = 'Approve'
|
|
7
7
|
export const REQUEST_CHANGES_LABEL = 'Request changes'
|
|
8
|
-
|
|
8
|
+
const WAITING_FOR_REVIEW_LABEL = 'Waiting for review.'
|
|
9
9
|
export const REVIEWER_NOTE_PREFIX = 'Reviewer note:'
|
|
10
10
|
export const SUBMIT_BEFORE_PUBLISH_ERROR = 'Submit for review before publishing.'
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const SAVE_DRAFT_LABEL = 'Save Draft'
|
|
12
|
+
const UPDATE_LIVE_LABEL = 'Update live'
|
|
13
13
|
|
|
14
14
|
const REVIEWER_ROLES = new Set(['ADMIN', 'EDITOR'])
|
|
15
15
|
const SUBMIT_ROLES = new Set(['ADMIN', 'EDITOR', 'AUTHOR'])
|
package/src/views/Login.tsx
CHANGED
|
@@ -18,7 +18,7 @@ export interface CaptchaConfig {
|
|
|
18
18
|
* sidebar so integrators only need to set it once. `logo` should be a URL or
|
|
19
19
|
* data URL; pass `null` to opt out of any logo entirely.
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
interface LoginBrandingConfig {
|
|
22
22
|
logo?: string | null
|
|
23
23
|
name?: string
|
|
24
24
|
tagline?: string
|
|
@@ -342,8 +342,9 @@ export function MediaBrowser({ onNavigate, currentPath }: MediaBrowserProps) {
|
|
|
342
342
|
}>('/ai/media-metadata', { method: 'POST', body: JSON.stringify({ mediaId: activeItem.id }) })
|
|
343
343
|
|
|
344
344
|
if (res.error) {
|
|
345
|
-
// AI
|
|
346
|
-
// basic filename-derived suggestion so the action still
|
|
345
|
+
// No AI provider / model configured in Settings → AI (e.g. local dev):
|
|
346
|
+
// fall back to a basic filename-derived suggestion so the action still
|
|
347
|
+
// helps.
|
|
347
348
|
const base = (activeItem.name ?? '')
|
|
348
349
|
.replace(/\.\w+$/, '')
|
|
349
350
|
.replace(/[-_]/g, ' ')
|
package/src/views/PageEditor.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { SEOPanel, type SEOData } from '../components/SEOPanel.js'
|
|
|
7
7
|
import { useApiData } from '../lib/useApiData.js'
|
|
8
8
|
import { cmsApi } from '../lib/api.js'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
interface PageEditorProps {
|
|
11
11
|
id?: string
|
|
12
12
|
onNavigate?: (path: string) => void
|
|
13
13
|
}
|
|
@@ -1451,7 +1451,7 @@ function formatDateShort(input: string): string {
|
|
|
1451
1451
|
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })
|
|
1452
1452
|
}
|
|
1453
1453
|
|
|
1454
|
-
|
|
1454
|
+
function pageNumbers(current: number, total: number): Array<number | '…'> {
|
|
1455
1455
|
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1)
|
|
1456
1456
|
const out: Array<number | '…'> = [1]
|
|
1457
1457
|
const start = Math.max(2, current - 1)
|