implement image upload
This commit is contained in:
@ -533,6 +533,10 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
|
|||||||
currentBlock.metadata = { listType: 'bullet' };
|
currentBlock.metadata = { listType: 'bullet' };
|
||||||
// For lists, ensure we start with empty content
|
// For lists, ensure we start with empty content
|
||||||
currentBlock.content = '';
|
currentBlock.content = '';
|
||||||
|
} else if (type === 'image') {
|
||||||
|
// For image blocks, clear content and set empty metadata
|
||||||
|
currentBlock.content = '';
|
||||||
|
currentBlock.metadata = { url: '', loading: false };
|
||||||
} else {
|
} else {
|
||||||
// For all other block types, ensure content is clean
|
// For all other block types, ensure content is clean
|
||||||
currentBlock.content = currentBlock.content || '';
|
currentBlock.content = currentBlock.content || '';
|
||||||
@ -556,8 +560,11 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
|
|||||||
blockComponent.focusListItem();
|
blockComponent.focusListItem();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type !== 'divider') {
|
} else if (type !== 'divider' && type !== 'image') {
|
||||||
this.blockOperations.focusBlock(currentBlock.id, 'start');
|
this.blockOperations.focusBlock(currentBlock.id, 'start');
|
||||||
|
} else if (type === 'image') {
|
||||||
|
// Focus the image block (which will show the upload interface)
|
||||||
|
this.blockOperations.focusBlock(currentBlock.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +259,92 @@ export class DeesWysiwygBlock extends DeesElement {
|
|||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-right: 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();
|
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
|
// Now find the actual editable block element
|
||||||
const editableBlock = this.block.type === 'code'
|
const editableBlock = this.block.type === 'code'
|
||||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
? 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 placeholder = this.getPlaceholder();
|
||||||
const selectedClass = this.isSelected ? ' selected' : '';
|
const selectedClass = this.isSelected ? ' selected' : '';
|
||||||
return `
|
return `
|
||||||
@ -528,6 +646,8 @@ export class DeesWysiwygBlock extends DeesElement {
|
|||||||
return 'Heading 3';
|
return 'Heading 3';
|
||||||
case 'quote':
|
case 'quote':
|
||||||
return 'Quote';
|
return 'Quote';
|
||||||
|
case 'image':
|
||||||
|
return 'Click to upload an image';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -535,6 +655,15 @@ export class DeesWysiwygBlock extends DeesElement {
|
|||||||
|
|
||||||
|
|
||||||
public focus(): void {
|
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)
|
// Get the actual editable element (might be nested for code blocks)
|
||||||
const editableElement = this.block?.type === 'code'
|
const editableElement = this.block?.type === 'code'
|
||||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||||
@ -558,6 +687,12 @@ export class DeesWysiwygBlock extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public focusWithCursor(position: 'start' | 'end' | number = 'end'): void {
|
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)
|
// Get the actual editable element (might be nested for code blocks)
|
||||||
const editableElement = this.block?.type === 'code'
|
const editableElement = this.block?.type === 'code'
|
||||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||||
@ -654,6 +789,11 @@ export class DeesWysiwygBlock extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getContent(): string {
|
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)
|
// Get the actual editable element (might be nested for code blocks)
|
||||||
const editableElement = this.block?.type === 'code'
|
const editableElement = this.block?.type === 'code'
|
||||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
? 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
|
* Gets content split at cursor position
|
||||||
*/
|
*/
|
||||||
public getSplitContent(): { before: string; after: string } | null {
|
public getSplitContent(): { before: string; after: string } | null {
|
||||||
console.log('getSplitContent: Starting...');
|
console.log('getSplitContent: Starting...');
|
||||||
|
|
||||||
|
// Image blocks can't be split
|
||||||
|
if (this.block?.type === 'image') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the actual editable element first
|
// Get the actual editable element first
|
||||||
const editableElement = this.block?.type === 'code'
|
const editableElement = this.block?.type === 'code'
|
||||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||||
|
@ -37,6 +37,13 @@ export class WysiwygConverters {
|
|||||||
return '';
|
return '';
|
||||||
case 'divider':
|
case 'divider':
|
||||||
return '<hr>';
|
return '<hr>';
|
||||||
|
case 'image':
|
||||||
|
const imageUrl = block.metadata?.url;
|
||||||
|
if (imageUrl) {
|
||||||
|
const altText = this.escapeHtml(block.content || 'Image');
|
||||||
|
return `<img src="${imageUrl}" alt="${altText}" />`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
default:
|
default:
|
||||||
return `<p>${content}</p>`;
|
return `<p>${content}</p>`;
|
||||||
}
|
}
|
||||||
@ -67,6 +74,10 @@ export class WysiwygConverters {
|
|||||||
}
|
}
|
||||||
case 'divider':
|
case 'divider':
|
||||||
return '---';
|
return '---';
|
||||||
|
case 'image':
|
||||||
|
const imageUrl = block.metadata?.url;
|
||||||
|
const altText = block.content || 'Image';
|
||||||
|
return imageUrl ? `` : '';
|
||||||
default:
|
default:
|
||||||
return block.content;
|
return block.content;
|
||||||
}
|
}
|
||||||
@ -152,6 +163,15 @@ export class WysiwygConverters {
|
|||||||
content: ' ',
|
content: ' ',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'img':
|
||||||
|
const imgElement = element as HTMLImageElement;
|
||||||
|
blocks.push({
|
||||||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||||||
|
type: 'image',
|
||||||
|
content: imgElement.alt || '',
|
||||||
|
metadata: { url: imgElement.src }
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Process children for other elements
|
// Process children for other elements
|
||||||
element.childNodes.forEach(child => processNode(child));
|
element.childNodes.forEach(child => processNode(child));
|
||||||
@ -237,6 +257,17 @@ export class WysiwygConverters {
|
|||||||
type: 'divider',
|
type: 'divider',
|
||||||
content: ' ',
|
content: ' ',
|
||||||
});
|
});
|
||||||
|
} else if (line.match(/^!\[([^\]]*)\]\(([^\)]+)\)$/)) {
|
||||||
|
// Parse markdown image syntax 
|
||||||
|
const match = line.match(/^!\[([^\]]*)\]\(([^\)]+)\)$/);
|
||||||
|
if (match) {
|
||||||
|
blocks.push({
|
||||||
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||||||
|
type: 'image',
|
||||||
|
content: match[1] || '',
|
||||||
|
metadata: { url: match[2] }
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (line.trim()) {
|
} else if (line.trim()) {
|
||||||
blocks.push({
|
blocks.push({
|
||||||
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
||||||
|
@ -114,14 +114,11 @@ export class WysiwygFormatting {
|
|||||||
|
|
||||||
// Check if ANY part of the selection contains this formatting
|
// Check if ANY part of the selection contains this formatting
|
||||||
const hasFormatting = this.selectionContainsTag(range, tagName);
|
const hasFormatting = this.selectionContainsTag(range, tagName);
|
||||||
console.log(`Formatting check for <${tagName}>: ${hasFormatting ? 'HAS formatting' : 'NO formatting'}`);
|
|
||||||
|
|
||||||
if (hasFormatting) {
|
if (hasFormatting) {
|
||||||
console.log(`Removing <${tagName}> formatting from selection`);
|
|
||||||
// Remove all instances of this tag from the selection
|
// Remove all instances of this tag from the selection
|
||||||
this.removeTagFromSelection(range, tagName);
|
this.removeTagFromSelection(range, tagName);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Adding <${tagName}> formatting to selection`);
|
|
||||||
// Wrap selection with the tag
|
// Wrap selection with the tag
|
||||||
const wrapper = document.createElement(tagName);
|
const wrapper = document.createElement(tagName);
|
||||||
try {
|
try {
|
||||||
@ -144,18 +141,13 @@ export class WysiwygFormatting {
|
|||||||
* Check if the selection contains or is within any instances of a tag
|
* Check if the selection contains or is within any instances of a tag
|
||||||
*/
|
*/
|
||||||
private static selectionContainsTag(range: Range, tagName: string): boolean {
|
private static selectionContainsTag(range: Range, tagName: string): boolean {
|
||||||
console.log(`Checking if selection contains <${tagName}>...`);
|
|
||||||
|
|
||||||
// First check: Are we inside a tag? (even if selection doesn't include the tag)
|
// First check: Are we inside a tag? (even if selection doesn't include the tag)
|
||||||
let node: Node | null = range.startContainer;
|
let node: Node | null = range.startContainer;
|
||||||
console.log('Start container:', range.startContainer, 'type:', range.startContainer.nodeType);
|
|
||||||
|
|
||||||
while (node && node !== range.commonAncestorContainer.ownerDocument) {
|
while (node && node !== range.commonAncestorContainer.ownerDocument) {
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
const element = node as Element;
|
const element = node as Element;
|
||||||
console.log(` Checking parent element: <${element.tagName.toLowerCase()}>`);
|
|
||||||
if (element.tagName.toLowerCase() === tagName) {
|
if (element.tagName.toLowerCase() === tagName) {
|
||||||
console.log(` ✓ Found <${tagName}> as parent of start container`);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,14 +156,11 @@ export class WysiwygFormatting {
|
|||||||
|
|
||||||
// Also check the end container
|
// Also check the end container
|
||||||
node = range.endContainer;
|
node = range.endContainer;
|
||||||
console.log('End container:', range.endContainer, 'type:', range.endContainer.nodeType);
|
|
||||||
|
|
||||||
while (node && node !== range.commonAncestorContainer.ownerDocument) {
|
while (node && node !== range.commonAncestorContainer.ownerDocument) {
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
const element = node as Element;
|
const element = node as Element;
|
||||||
console.log(` Checking parent element: <${element.tagName.toLowerCase()}>`);
|
|
||||||
if (element.tagName.toLowerCase() === tagName) {
|
if (element.tagName.toLowerCase() === tagName) {
|
||||||
console.log(` ✓ Found <${tagName}> as parent of end container`);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,7 +172,6 @@ export class WysiwygFormatting {
|
|||||||
const contents = range.cloneContents();
|
const contents = range.cloneContents();
|
||||||
tempDiv.appendChild(contents);
|
tempDiv.appendChild(contents);
|
||||||
const tags = tempDiv.getElementsByTagName(tagName);
|
const tags = tempDiv.getElementsByTagName(tagName);
|
||||||
console.log(` Selection contains ${tags.length} complete <${tagName}> tags`);
|
|
||||||
|
|
||||||
return tags.length > 0;
|
return tags.length > 0;
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ export class WysiwygShortcuts {
|
|||||||
{ type: 'quote', label: 'Quote', icon: '"' },
|
{ type: 'quote', label: 'Quote', icon: '"' },
|
||||||
{ type: 'code', label: 'Code', icon: '<>' },
|
{ type: 'code', label: 'Code', icon: '<>' },
|
||||||
{ type: 'list', label: 'List', icon: '•' },
|
{ type: 'list', label: 'List', icon: '•' },
|
||||||
|
{ type: 'image', label: 'Image', icon: '🖼' },
|
||||||
{ type: 'divider', label: 'Divider', icon: '—' },
|
{ type: 'divider', label: 'Divider', icon: '—' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user