feat: remove canonical URL field and add URL validation
All checks were successful
Deploy to Production / deploy (push) Successful in 2m1s
All checks were successful
Deploy to Production / deploy (push) Successful in 2m1s
- Removed canonical URL field from MetadataPanel component and related interfaces - Added URL validation function to ensure feature image and canonical URLs are valid HTTP/HTTPS - Updated Ghost API to skip invalid URLs when publishing posts - Simplified metadata state management by removing canonicalUrl from initial state and update handlers
This commit is contained in:
parent
3e3b314407
commit
b8f0da4644
@ -3,7 +3,6 @@ import { Box, Stack, TextField, Typography, IconButton } from '@mui/material';
|
||||
export type Metadata = {
|
||||
title: string;
|
||||
tagsText: string; // comma-separated
|
||||
canonicalUrl: string;
|
||||
featureImage?: string;
|
||||
};
|
||||
|
||||
@ -20,7 +19,6 @@ export default function MetadataPanel({ value, onChange }: {
|
||||
<Stack spacing={1}>
|
||||
<TextField label="Title" value={value.title} onChange={(e) => set({ title: e.target.value })} fullWidth />
|
||||
<TextField label="Tags (comma-separated)" value={value.tagsText} onChange={(e) => set({ tagsText: e.target.value })} fullWidth />
|
||||
<TextField label="Canonical URL" value={value.canonicalUrl} onChange={(e) => set({ canonicalUrl: e.target.value })} fullWidth />
|
||||
<TextField label="Feature Image URL" value={value.featureImage || ''} onChange={(e) => set({ featureImage: e.target.value })} fullWidth />
|
||||
{value.featureImage && (
|
||||
<Box sx={{ position: 'relative', border: '1px solid #eee', borderRadius: 1, overflow: 'hidden', height: 180, background: '#fafafa' }}>
|
||||
|
||||
@ -41,7 +41,6 @@ export default function StepMetadata({
|
||||
const newMetadata: Metadata = {
|
||||
title: metadata.title,
|
||||
tagsText: metadata.tags,
|
||||
canonicalUrl: metadata.canonicalUrl,
|
||||
featureImage: value.featureImage, // Keep existing feature image
|
||||
};
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import type { Metadata } from '../components/MetadataPanel';
|
||||
export function usePostEditor(initialPostId?: string | null) {
|
||||
const [postId, setPostId] = useState<string | null>(null);
|
||||
const [draft, setDraft] = useState<string>('');
|
||||
const [meta, setMeta] = useState<Metadata>({ title: '', tagsText: '', canonicalUrl: '', featureImage: '' });
|
||||
const [meta, setMeta] = useState<Metadata>({ title: '', tagsText: '', featureImage: '' });
|
||||
const [postClips, setPostClips] = useState<Array<{ id: string; bucket: string; key: string; mime: string; transcript?: string; createdAt: string }>>([]);
|
||||
const [postStatus, setPostStatus] = useState<'inbox' | 'editing' | 'ready_for_publish' | 'published' | 'archived'>('editing');
|
||||
const [promptText, setPromptText] = useState<string>('');
|
||||
@ -46,7 +46,6 @@ export function usePostEditor(initialPostId?: string | null) {
|
||||
...m,
|
||||
title: data.title || '',
|
||||
tagsText: data.tagsText || '',
|
||||
canonicalUrl: data.canonicalUrl || '',
|
||||
featureImage: data.featureImage || ''
|
||||
}));
|
||||
if (data.status) setPostStatus(data.status);
|
||||
@ -69,7 +68,6 @@ export function usePostEditor(initialPostId?: string | null) {
|
||||
contentHtml: draft,
|
||||
tags: meta.tagsText ? meta.tagsText.split(',').map(t => t.trim()).filter(Boolean) : undefined,
|
||||
featureImage: meta.featureImage || undefined,
|
||||
canonicalUrl: meta.canonicalUrl || undefined,
|
||||
status: postStatus,
|
||||
prompt: promptText || undefined,
|
||||
selectedImageKeys: genImageKeys.length > 0 ? genImageKeys : undefined,
|
||||
@ -102,7 +100,6 @@ export function usePostEditor(initialPostId?: string | null) {
|
||||
title: meta.title || 'Untitled',
|
||||
html: draft,
|
||||
feature_image: meta.featureImage || null,
|
||||
canonical_url: meta.canonicalUrl || null,
|
||||
tags,
|
||||
status,
|
||||
authors: selectedAuthor ? [selectedAuthor] : undefined,
|
||||
|
||||
@ -146,6 +146,19 @@ router.post('/post', async (req, res) => {
|
||||
}
|
||||
} catch {}
|
||||
|
||||
function isValidHttpUrl(u?: string | null): boolean {
|
||||
if (!u || typeof u !== 'string') return false;
|
||||
try {
|
||||
const parsed = new URL(u);
|
||||
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const safeCanonical = isValidHttpUrl(canonical_url) ? canonical_url : undefined;
|
||||
const safeFeatureImage = isValidHttpUrl(rewrittenFeatureImage) ? rewrittenFeatureImage : undefined;
|
||||
|
||||
const payload = {
|
||||
posts: [
|
||||
{
|
||||
@ -154,8 +167,8 @@ router.post('/post', async (req, res) => {
|
||||
html: rewrittenHtml,
|
||||
status: status || 'draft',
|
||||
tags: tags && Array.isArray(tags) ? tags : [],
|
||||
...(rewrittenFeatureImage ? { feature_image: rewrittenFeatureImage } : {}),
|
||||
...(canonical_url ? { canonical_url } : {}),
|
||||
...(safeFeatureImage ? { feature_image: safeFeatureImage } : {}),
|
||||
...(safeCanonical ? { canonical_url: safeCanonical } : {}),
|
||||
...(authors && Array.isArray(authors) && authors.length > 0 ? { authors } : {}),
|
||||
},
|
||||
],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user