implement image upload
This commit is contained in:
@ -259,6 +259,92 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
/* Image block styles */
|
||||
.block.image {
|
||||
min-height: 200px;
|
||||
padding: 0;
|
||||
margin: 16px 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-upload-placeholder {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background: ${cssManager.bdTheme('#f8f8f8', '#1a1a1a')};
|
||||
border: 2px dashed ${cssManager.bdTheme('#d0d0d0', '#404040')};
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.image-upload-placeholder:hover {
|
||||
background: ${cssManager.bdTheme('#f0f0f0', '#222222')};
|
||||
border-color: ${cssManager.bdTheme('#0066cc', '#4d94ff')};
|
||||
}
|
||||
|
||||
.image-upload-placeholder:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.image-upload-placeholder.drag-over {
|
||||
background: ${cssManager.bdTheme('#e3f2fd', '#1e3a5f')};
|
||||
border-color: ${cssManager.bdTheme('#2196F3', '#64b5f6')};
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 16px;
|
||||
color: ${cssManager.bdTheme('#666', '#999')};
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upload-hint {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('#999', '#666')};
|
||||
}
|
||||
|
||||
.image-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.image-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 16px 24px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@ -286,6 +372,12 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
container.innerHTML = this.renderBlockContent();
|
||||
}
|
||||
|
||||
// Handle image block setup
|
||||
if (this.block.type === 'image') {
|
||||
this.setupImageBlock();
|
||||
return; // Image blocks don't need the standard editable setup
|
||||
}
|
||||
|
||||
// Now find the actual editable block element
|
||||
const editableBlock = this.block.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
@ -505,6 +597,32 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.block.type === 'image') {
|
||||
const selectedClass = this.isSelected ? ' selected' : '';
|
||||
const imageUrl = this.block.metadata?.url || '';
|
||||
const isLoading = this.block.metadata?.loading || false;
|
||||
|
||||
return `
|
||||
<div class="block image${selectedClass}" data-block-id="${this.block.id}" data-block-type="${this.block.type}">
|
||||
${isLoading ? `
|
||||
<div class="image-loading">Uploading image...</div>
|
||||
` : ''}
|
||||
${imageUrl ? `
|
||||
<div class="image-container">
|
||||
<img src="${imageUrl}" alt="${this.block.content || 'Uploaded image'}" />
|
||||
</div>
|
||||
` : `
|
||||
<div class="image-upload-placeholder">
|
||||
<div class="upload-icon">🖼️</div>
|
||||
<div class="upload-text">Click to upload an image</div>
|
||||
<div class="upload-hint">or drag and drop</div>
|
||||
</div>
|
||||
<input type="file" accept="image/*" style="display: none;" />
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const placeholder = this.getPlaceholder();
|
||||
const selectedClass = this.isSelected ? ' selected' : '';
|
||||
return `
|
||||
@ -528,6 +646,8 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
return 'Heading 3';
|
||||
case 'quote':
|
||||
return 'Quote';
|
||||
case 'image':
|
||||
return 'Click to upload an image';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@ -535,6 +655,15 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
|
||||
|
||||
public focus(): void {
|
||||
// Image blocks don't focus in the traditional way
|
||||
if (this.block?.type === 'image') {
|
||||
const imageBlock = this.shadowRoot?.querySelector('.block.image') as HTMLDivElement;
|
||||
if (imageBlock) {
|
||||
imageBlock.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
@ -558,6 +687,12 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public focusWithCursor(position: 'start' | 'end' | number = 'end'): void {
|
||||
// Image blocks don't support cursor positioning
|
||||
if (this.block?.type === 'image') {
|
||||
this.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
@ -654,6 +789,11 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public getContent(): string {
|
||||
// Handle image blocks specially
|
||||
if (this.block?.type === 'image') {
|
||||
return this.block.content || ''; // Image blocks store alt text in content
|
||||
}
|
||||
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
@ -726,12 +866,153 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup image block functionality
|
||||
*/
|
||||
private setupImageBlock(): void {
|
||||
const imageBlock = this.shadowRoot?.querySelector('.block.image') as HTMLDivElement;
|
||||
if (!imageBlock) return;
|
||||
|
||||
// Make the image block focusable
|
||||
imageBlock.setAttribute('tabindex', '0');
|
||||
|
||||
// Handle click on upload placeholder
|
||||
const uploadPlaceholder = imageBlock.querySelector('.image-upload-placeholder');
|
||||
const fileInput = imageBlock.querySelector('input[type="file"]') as HTMLInputElement;
|
||||
|
||||
if (uploadPlaceholder && fileInput) {
|
||||
uploadPlaceholder.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (file) {
|
||||
this.handleImageUpload(file);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drag and drop
|
||||
imageBlock.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
uploadPlaceholder.classList.add('drag-over');
|
||||
});
|
||||
|
||||
imageBlock.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
uploadPlaceholder.classList.remove('drag-over');
|
||||
});
|
||||
|
||||
imageBlock.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
uploadPlaceholder.classList.remove('drag-over');
|
||||
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.type.startsWith('image/')) {
|
||||
this.handleImageUpload(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle focus/blur for the image block
|
||||
imageBlock.addEventListener('focus', () => {
|
||||
this.handlers?.onFocus?.();
|
||||
});
|
||||
|
||||
imageBlock.addEventListener('blur', () => {
|
||||
this.handlers?.onBlur?.();
|
||||
});
|
||||
|
||||
// Handle keyboard events
|
||||
imageBlock.addEventListener('keydown', (e) => {
|
||||
this.handlers?.onKeyDown?.(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle image file upload
|
||||
*/
|
||||
private async handleImageUpload(file: File): Promise<void> {
|
||||
// Check file size (max 10MB)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert('Image size must be less than 10MB');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update block to show loading state
|
||||
this.block.metadata = { ...this.block.metadata, loading: true };
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLDivElement;
|
||||
if (container) {
|
||||
container.innerHTML = this.renderBlockContent();
|
||||
this.setupImageBlock(); // Re-setup event handlers
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert to base64 for now (in production, you'd upload to a server)
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const base64 = e.target?.result as string;
|
||||
|
||||
// Update block with image URL
|
||||
this.block.metadata = {
|
||||
...this.block.metadata,
|
||||
url: base64,
|
||||
loading: false,
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
mimeType: file.type
|
||||
};
|
||||
|
||||
// Set alt text as content
|
||||
this.block.content = file.name.replace(/\.[^/.]+$/, ''); // Remove extension
|
||||
|
||||
// Re-render
|
||||
if (container) {
|
||||
container.innerHTML = this.renderBlockContent();
|
||||
}
|
||||
|
||||
// Notify parent component of the change
|
||||
this.handlers?.onInput?.(new InputEvent('input'));
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
alert('Failed to read image file');
|
||||
this.block.metadata = { ...this.block.metadata, loading: false };
|
||||
if (container) {
|
||||
container.innerHTML = this.renderBlockContent();
|
||||
this.setupImageBlock();
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
} catch (error) {
|
||||
console.error('Error uploading image:', error);
|
||||
alert('Failed to upload image');
|
||||
this.block.metadata = { ...this.block.metadata, loading: false };
|
||||
if (container) {
|
||||
container.innerHTML = this.renderBlockContent();
|
||||
this.setupImageBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content split at cursor position
|
||||
*/
|
||||
public getSplitContent(): { before: string; after: string } | null {
|
||||
console.log('getSplitContent: Starting...');
|
||||
|
||||
// Image blocks can't be split
|
||||
if (this.block?.type === 'image') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the actual editable element first
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
|
Reference in New Issue
Block a user