image processing during saving
This commit is contained in:
@@ -23,6 +23,7 @@ export default function EditStoryPage() {
|
|||||||
const [story, setStory] = useState<Story | null>(null);
|
const [story, setStory] = useState<Story | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [processingImages, setProcessingImages] = useState(false);
|
||||||
const [resetingPosition, setResetingPosition] = useState(false);
|
const [resetingPosition, setResetingPosition] = useState(false);
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
@@ -41,6 +42,25 @@ export default function EditStoryPage() {
|
|||||||
|
|
||||||
const [coverImage, setCoverImage] = useState<File | null>(null);
|
const [coverImage, setCoverImage] = useState<File | null>(null);
|
||||||
|
|
||||||
|
// Helper function to detect external images in HTML content
|
||||||
|
const hasExternalImages = (htmlContent: string): boolean => {
|
||||||
|
if (!htmlContent) return false;
|
||||||
|
|
||||||
|
// Create a temporary DOM element to parse HTML
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = htmlContent;
|
||||||
|
|
||||||
|
const images = tempDiv.querySelectorAll('img');
|
||||||
|
for (let i = 0; i < images.length; i++) {
|
||||||
|
const img = images[i];
|
||||||
|
const src = img.getAttribute('src');
|
||||||
|
if (src && (src.startsWith('http://') || src.startsWith('https://'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadStory = async () => {
|
const loadStory = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -163,7 +183,7 @@ export default function EditStoryPage() {
|
|||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!validateForm() || !story) {
|
if (!validateForm() || !story) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -185,7 +205,34 @@ export default function EditStoryPage() {
|
|||||||
tagNames: formData.tags,
|
tagNames: formData.tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedStory = await storyApi.updateStory(storyId, updateData);
|
await storyApi.updateStory(storyId, updateData);
|
||||||
|
|
||||||
|
// Process external images if present (download and replace URLs)
|
||||||
|
if (hasExternalImages(formData.contentHtml)) {
|
||||||
|
try {
|
||||||
|
setProcessingImages(true);
|
||||||
|
const imageResult = await storyApi.processContentImages(storyId, formData.contentHtml);
|
||||||
|
|
||||||
|
// If images were processed and content was updated, save the updated content
|
||||||
|
if (imageResult.processedContent !== formData.contentHtml) {
|
||||||
|
await storyApi.updateStory(storyId, {
|
||||||
|
title: formData.title,
|
||||||
|
summary: formData.summary || undefined,
|
||||||
|
contentHtml: imageResult.processedContent,
|
||||||
|
sourceUrl: formData.sourceUrl || undefined,
|
||||||
|
volume: formData.seriesName && formData.volume ? parseInt(formData.volume) : undefined,
|
||||||
|
...(formData.seriesId ? { seriesId: formData.seriesId } : { seriesName: formData.seriesName }),
|
||||||
|
...(formData.authorId ? { authorId: formData.authorId } : { authorName: formData.authorName }),
|
||||||
|
tagNames: formData.tags,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (imageError) {
|
||||||
|
console.warn('Failed to process images, continuing with original content:', imageError);
|
||||||
|
// Don't fail the entire save if image processing fails
|
||||||
|
} finally {
|
||||||
|
setProcessingImages(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If there's a new cover image, upload it separately
|
// If there's a new cover image, upload it separately
|
||||||
if (coverImage) {
|
if (coverImage) {
|
||||||
@@ -199,6 +246,7 @@ export default function EditStoryPage() {
|
|||||||
setErrors({ submit: errorMessage });
|
setErrors({ submit: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
|
setProcessingImages(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,28 +496,28 @@ export default function EditStoryPage() {
|
|||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
disabled={saving}
|
disabled={saving || processingImages}
|
||||||
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
|
className="text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
|
||||||
>
|
>
|
||||||
Delete Story
|
Delete Story
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => router.push(`/stories/${storyId}`)}
|
onClick={() => router.push(`/stories/${storyId}`)}
|
||||||
disabled={saving}
|
disabled={saving || processingImages}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={saving}
|
loading={saving || processingImages}
|
||||||
disabled={!formData.title || !formData.authorName || !formData.contentHtml}
|
disabled={!formData.title || !formData.authorName || !formData.contentHtml}
|
||||||
>
|
>
|
||||||
Save Changes
|
{processingImages ? 'Processing images...' : 'Save Changes'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user