feat: add toolbar styles and functionality for blog editor
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 18s
All checks were successful
CI - Build and Push / Build and Push Docker Image (push) Successful in 18s
feat: implement EditorTheme for consistent styling across editor components feat: define types for blog-related operations including image uploads and post creation feat: create DropdownColorPicker component for color selection in blog editor feat: implement ImageResizer component for resizing images in the blog editor feat: add export and import functionality for blog posts in JSON format feat: update main application routes to include CreatePost page feat: enhance Blog page with a button to navigate to CreatePost feat: implement CreatePost page with title, cover image upload, and content editor
This commit is contained in:
128
src/blog/api.ts
Normal file
128
src/blog/api.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Blog API functions
|
||||
*/
|
||||
|
||||
import { apiRequest } from '../utils/api';
|
||||
import type {
|
||||
UploadPresignedURLResponse,
|
||||
DownloadPresignedURLResponse,
|
||||
CreatePostResponse,
|
||||
} from './types';
|
||||
|
||||
const API_BASE = '/api/blog';
|
||||
|
||||
/**
|
||||
* Get presigned URL for file upload
|
||||
*/
|
||||
export async function getUploadPresignedURL(fileName: string): Promise<UploadPresignedURLResponse> {
|
||||
const response = await apiRequest(`${API_BASE}/file/upload`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ fileName }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get upload URL: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get presigned URL for file download
|
||||
*/
|
||||
export async function getDownloadPresignedURL(fileKey: string): Promise<DownloadPresignedURLResponse> {
|
||||
const response = await apiRequest(`${API_BASE}/file/download`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ fileKey }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get download URL: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file to S3 using presigned URL
|
||||
*/
|
||||
export async function uploadFileToS3(
|
||||
url: string,
|
||||
file: File,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
// Track upload progress
|
||||
xhr.upload.addEventListener('progress', (event) => {
|
||||
if (event.lengthComputable && onProgress) {
|
||||
const progress = (event.loaded / event.total) * 100;
|
||||
onProgress(progress);
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('load', () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Upload failed with status ${xhr.status}`));
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('error', () => {
|
||||
reject(new Error('Upload failed'));
|
||||
});
|
||||
|
||||
xhr.addEventListener('abort', () => {
|
||||
reject(new Error('Upload aborted'));
|
||||
});
|
||||
|
||||
xhr.open('PUT', url);
|
||||
xhr.setRequestHeader('Content-Type', file.type);
|
||||
xhr.send(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete image upload workflow
|
||||
*/
|
||||
export async function uploadImage(
|
||||
file: File,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<{ fileKey: string; url: string }> {
|
||||
// Step 1: Get presigned URL
|
||||
const { url: uploadUrl, fileKey } = await getUploadPresignedURL(file.name);
|
||||
|
||||
// Step 2: Upload file to S3
|
||||
await uploadFileToS3(uploadUrl, file, onProgress);
|
||||
|
||||
// Step 3: Get download URL
|
||||
const { url: downloadUrl } = await getDownloadPresignedURL(fileKey);
|
||||
|
||||
return { fileKey, url: downloadUrl };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new blog post
|
||||
*/
|
||||
export async function createBlogPost(
|
||||
title: string,
|
||||
content: string,
|
||||
coverImageKey: string
|
||||
): Promise<CreatePostResponse> {
|
||||
const response = await apiRequest(`${API_BASE}/post/create`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
content,
|
||||
coverImageKey,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to create post: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
Reference in New Issue
Block a user