update
This commit is contained in:
@ -374,7 +374,6 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
|
||||
this.blurTimeout = null;
|
||||
}
|
||||
|
||||
if (block.type !== 'divider') {
|
||||
const prevSelectedId = this.selectedBlockId;
|
||||
this.selectedBlockId = block.id;
|
||||
|
||||
@ -397,7 +396,6 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private blurTimeout: any = null;
|
||||
|
||||
@ -451,9 +449,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
|
||||
// Focus last block if clicking on empty editor area
|
||||
if (target.classList.contains('editor-content')) {
|
||||
const lastBlock = this.blocks[this.blocks.length - 1];
|
||||
if (lastBlock.type !== 'divider') {
|
||||
this.blockOperations.focusBlock(lastBlock.id, 'end');
|
||||
}
|
||||
this.blockOperations.focusBlock(lastBlock.id, lastBlock.type === 'divider' || lastBlock.type === 'image' ? undefined : 'end');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,15 +142,28 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
.block.divider {
|
||||
padding: 0;
|
||||
padding: 8px 0;
|
||||
margin: 16px 0;
|
||||
pointer-events: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.block.divider:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.block.divider.selected {
|
||||
background: ${cssManager.bdTheme('rgba(0, 102, 204, 0.05)', 'rgba(77, 148, 255, 0.08)')};
|
||||
box-shadow: inset 0 0 0 2px ${cssManager.bdTheme('rgba(0, 102, 204, 0.2)', 'rgba(77, 148, 255, 0.2)')};
|
||||
}
|
||||
|
||||
.block.divider hr {
|
||||
border: none;
|
||||
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
|
||||
margin: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Formatting styles */
|
||||
@ -271,6 +284,16 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.block.image:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.block.image.selected {
|
||||
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(0, 102, 204, 0.3)', 'rgba(77, 148, 255, 0.3)')};
|
||||
}
|
||||
|
||||
.image-upload-placeholder {
|
||||
@ -349,6 +372,20 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
];
|
||||
|
||||
protected shouldUpdate(changedProperties: Map<string, any>): boolean {
|
||||
// If selection state changed, we need to update for non-editable blocks
|
||||
if (changedProperties.has('isSelected') && (this.block?.type === 'divider' || this.block?.type === 'image')) {
|
||||
// For non-editable blocks, we need to update the selected class
|
||||
const element = this.shadowRoot?.querySelector('.block') as HTMLElement;
|
||||
if (element) {
|
||||
if (this.isSelected) {
|
||||
element.classList.add('selected');
|
||||
} else {
|
||||
element.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
return false; // Don't re-render, just update the class
|
||||
}
|
||||
|
||||
// Never update if only the block content changed
|
||||
if (changedProperties.has('block') && this.block) {
|
||||
const oldBlock = changedProperties.get('block');
|
||||
@ -372,10 +409,13 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
container.innerHTML = this.renderBlockContent();
|
||||
}
|
||||
|
||||
// Handle image block setup
|
||||
// Handle special block types
|
||||
if (this.block.type === 'image') {
|
||||
this.setupImageBlock();
|
||||
return; // Image blocks don't need the standard editable setup
|
||||
} else if (this.block.type === 'divider') {
|
||||
this.setupDividerBlock();
|
||||
return; // Divider blocks don't need the standard editable setup
|
||||
}
|
||||
|
||||
// Now find the actual editable block element
|
||||
@ -575,8 +615,9 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
if (!this.block) return '';
|
||||
|
||||
if (this.block.type === 'divider') {
|
||||
const selectedClass = this.isSelected ? ' selected' : '';
|
||||
return `
|
||||
<div class="block divider" data-block-id="${this.block.id}" data-block-type="${this.block.type}">
|
||||
<div class="block divider${selectedClass}" data-block-id="${this.block.id}" data-block-type="${this.block.type}" tabindex="0">
|
||||
<hr>
|
||||
</div>
|
||||
`;
|
||||
@ -603,7 +644,7 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
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}">
|
||||
<div class="block image${selectedClass}" data-block-id="${this.block.id}" data-block-type="${this.block.type}" tabindex="0">
|
||||
${isLoading ? `
|
||||
<div class="image-loading">Uploading image...</div>
|
||||
` : ''}
|
||||
@ -655,13 +696,19 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
|
||||
|
||||
public focus(): void {
|
||||
// Image blocks don't focus in the traditional way
|
||||
// Handle non-editable blocks
|
||||
if (this.block?.type === 'image') {
|
||||
const imageBlock = this.shadowRoot?.querySelector('.block.image') as HTMLDivElement;
|
||||
if (imageBlock) {
|
||||
imageBlock.focus();
|
||||
}
|
||||
return;
|
||||
} else if (this.block?.type === 'divider') {
|
||||
const dividerBlock = this.shadowRoot?.querySelector('.block.divider') as HTMLDivElement;
|
||||
if (dividerBlock) {
|
||||
dividerBlock.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
@ -687,8 +734,8 @@ 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') {
|
||||
// Non-editable blocks don't support cursor positioning
|
||||
if (this.block?.type === 'image' || this.block?.type === 'divider') {
|
||||
this.focus();
|
||||
return;
|
||||
}
|
||||
@ -866,6 +913,42 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup divider block functionality
|
||||
*/
|
||||
private setupDividerBlock(): void {
|
||||
const dividerBlock = this.shadowRoot?.querySelector('.block.divider') as HTMLDivElement;
|
||||
if (!dividerBlock) return;
|
||||
|
||||
// Handle click to select
|
||||
dividerBlock.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
// Focus will trigger the selection
|
||||
dividerBlock.focus();
|
||||
});
|
||||
|
||||
// Handle focus/blur
|
||||
dividerBlock.addEventListener('focus', () => {
|
||||
this.handlers?.onFocus?.();
|
||||
});
|
||||
|
||||
dividerBlock.addEventListener('blur', () => {
|
||||
this.handlers?.onBlur?.();
|
||||
});
|
||||
|
||||
// Handle keyboard events
|
||||
dividerBlock.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Backspace' || e.key === 'Delete') {
|
||||
e.preventDefault();
|
||||
// Let the keyboard handler in the parent component handle the deletion
|
||||
this.handlers?.onKeyDown?.(e);
|
||||
} else {
|
||||
// Handle navigation keys
|
||||
this.handlers?.onKeyDown?.(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup image block functionality
|
||||
*/
|
||||
@ -873,8 +956,17 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
const imageBlock = this.shadowRoot?.querySelector('.block.image') as HTMLDivElement;
|
||||
if (!imageBlock) return;
|
||||
|
||||
// Make the image block focusable
|
||||
imageBlock.setAttribute('tabindex', '0');
|
||||
// Note: tabindex is already set in the HTML
|
||||
|
||||
// Handle click to select the block
|
||||
imageBlock.addEventListener('click', (e) => {
|
||||
// Don't stop propagation for file input clicks
|
||||
if ((e.target as HTMLElement).tagName !== 'INPUT') {
|
||||
e.stopPropagation();
|
||||
// Focus will trigger the selection
|
||||
imageBlock.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle click on upload placeholder
|
||||
const uploadPlaceholder = imageBlock.querySelector('.image-upload-placeholder');
|
||||
@ -931,7 +1023,14 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
|
||||
// Handle keyboard events
|
||||
imageBlock.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Backspace' || e.key === 'Delete') {
|
||||
e.preventDefault();
|
||||
// Let the keyboard handler in the parent component handle the deletion
|
||||
this.handlers?.onKeyDown?.(e);
|
||||
} else {
|
||||
// Handle navigation keys
|
||||
this.handlers?.onKeyDown?.(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,9 @@ export class WysiwygKeyboardHandler {
|
||||
case 'Backspace':
|
||||
await this.handleBackspace(e, block);
|
||||
break;
|
||||
case 'Delete':
|
||||
await this.handleDelete(e, block);
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
await this.handleArrowUp(e, block);
|
||||
break;
|
||||
@ -116,6 +119,14 @@ export class WysiwygKeyboardHandler {
|
||||
private async handleEnter(e: KeyboardEvent, block: IBlock): Promise<void> {
|
||||
const blockOps = this.component.blockOperations;
|
||||
|
||||
// For non-editable blocks, create a new paragraph after
|
||||
if (block.type === 'divider' || block.type === 'image') {
|
||||
e.preventDefault();
|
||||
const newBlock = blockOps.createBlock();
|
||||
await blockOps.insertBlockAfter(block, newBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.type === 'code') {
|
||||
if (e.shiftKey) {
|
||||
// Shift+Enter in code blocks creates a new block
|
||||
@ -222,6 +233,41 @@ export class WysiwygKeyboardHandler {
|
||||
private async handleBackspace(e: KeyboardEvent, block: IBlock): Promise<void> {
|
||||
const blockOps = this.component.blockOperations;
|
||||
|
||||
// Handle non-editable blocks (divider, image)
|
||||
if (block.type === 'divider' || block.type === 'image') {
|
||||
e.preventDefault();
|
||||
|
||||
// Don't delete if it's the only block
|
||||
if (this.component.blocks.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save state for undo
|
||||
this.component.saveToHistory(false);
|
||||
|
||||
// Find the previous block to focus
|
||||
const prevBlock = blockOps.getPreviousBlock(block.id);
|
||||
const nextBlock = blockOps.getNextBlock(block.id);
|
||||
|
||||
// Remove the block
|
||||
blockOps.removeBlock(block.id);
|
||||
|
||||
// Focus the appropriate block
|
||||
if (prevBlock && prevBlock.type !== 'divider' && prevBlock.type !== 'image') {
|
||||
await blockOps.focusBlock(prevBlock.id, 'end');
|
||||
} else if (nextBlock && nextBlock.type !== 'divider' && nextBlock.type !== 'image') {
|
||||
await blockOps.focusBlock(nextBlock.id, 'start');
|
||||
} else if (prevBlock) {
|
||||
// If previous block is also non-editable, just select it
|
||||
await blockOps.focusBlock(prevBlock.id);
|
||||
} else if (nextBlock) {
|
||||
// If next block is also non-editable, just select it
|
||||
await blockOps.focusBlock(nextBlock.id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the block component to check cursor position
|
||||
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block') as any;
|
||||
@ -321,10 +367,66 @@ export class WysiwygKeyboardHandler {
|
||||
// Otherwise, let browser handle normal backspace
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Delete key
|
||||
*/
|
||||
private async handleDelete(e: KeyboardEvent, block: IBlock): Promise<void> {
|
||||
const blockOps = this.component.blockOperations;
|
||||
|
||||
// Handle non-editable blocks (divider, image) - same as backspace
|
||||
if (block.type === 'divider' || block.type === 'image') {
|
||||
e.preventDefault();
|
||||
|
||||
// Don't delete if it's the only block
|
||||
if (this.component.blocks.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save state for undo
|
||||
this.component.saveToHistory(false);
|
||||
|
||||
// Find the previous block to focus
|
||||
const prevBlock = blockOps.getPreviousBlock(block.id);
|
||||
const nextBlock = blockOps.getNextBlock(block.id);
|
||||
|
||||
// Remove the block
|
||||
blockOps.removeBlock(block.id);
|
||||
|
||||
// Focus the appropriate block
|
||||
if (nextBlock && nextBlock.type !== 'divider' && nextBlock.type !== 'image') {
|
||||
await blockOps.focusBlock(nextBlock.id, 'start');
|
||||
} else if (prevBlock && prevBlock.type !== 'divider' && prevBlock.type !== 'image') {
|
||||
await blockOps.focusBlock(prevBlock.id, 'end');
|
||||
} else if (nextBlock) {
|
||||
// If next block is also non-editable, just select it
|
||||
await blockOps.focusBlock(nextBlock.id);
|
||||
} else if (prevBlock) {
|
||||
// If previous block is also non-editable, just select it
|
||||
await blockOps.focusBlock(prevBlock.id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// For editable blocks, let browser handle normal delete
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles ArrowUp key - navigate to previous block if at beginning or first line
|
||||
*/
|
||||
private async handleArrowUp(e: KeyboardEvent, block: IBlock): Promise<void> {
|
||||
// For non-editable blocks, always navigate to previous block
|
||||
if (block.type === 'divider' || block.type === 'image') {
|
||||
e.preventDefault();
|
||||
const blockOps = this.component.blockOperations;
|
||||
const prevBlock = blockOps.getPreviousBlock(block.id);
|
||||
|
||||
if (prevBlock) {
|
||||
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the block component from the wysiwyg component's shadow DOM
|
||||
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
|
||||
@ -352,9 +454,9 @@ export class WysiwygKeyboardHandler {
|
||||
const blockOps = this.component.blockOperations;
|
||||
const prevBlock = blockOps.getPreviousBlock(block.id);
|
||||
|
||||
if (prevBlock && prevBlock.type !== 'divider') {
|
||||
if (prevBlock) {
|
||||
console.log('ArrowUp: Focusing previous block:', prevBlock.id);
|
||||
await blockOps.focusBlock(prevBlock.id, 'end');
|
||||
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
|
||||
}
|
||||
}
|
||||
// Otherwise, let browser handle normal navigation
|
||||
@ -364,6 +466,18 @@ export class WysiwygKeyboardHandler {
|
||||
* Handles ArrowDown key - navigate to next block if at end or last line
|
||||
*/
|
||||
private async handleArrowDown(e: KeyboardEvent, block: IBlock): Promise<void> {
|
||||
// For non-editable blocks, always navigate to next block
|
||||
if (block.type === 'divider' || block.type === 'image') {
|
||||
e.preventDefault();
|
||||
const blockOps = this.component.blockOperations;
|
||||
const nextBlock = blockOps.getNextBlock(block.id);
|
||||
|
||||
if (nextBlock) {
|
||||
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the block component from the wysiwyg component's shadow DOM
|
||||
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
|
||||
@ -391,9 +505,9 @@ export class WysiwygKeyboardHandler {
|
||||
const blockOps = this.component.blockOperations;
|
||||
const nextBlock = blockOps.getNextBlock(block.id);
|
||||
|
||||
if (nextBlock && nextBlock.type !== 'divider') {
|
||||
if (nextBlock) {
|
||||
console.log('ArrowDown: Focusing next block:', nextBlock.id);
|
||||
await blockOps.focusBlock(nextBlock.id, 'start');
|
||||
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
|
||||
}
|
||||
}
|
||||
// Otherwise, let browser handle normal navigation
|
||||
@ -419,6 +533,18 @@ export class WysiwygKeyboardHandler {
|
||||
* Handles ArrowLeft key - navigate to previous block if at beginning
|
||||
*/
|
||||
private async handleArrowLeft(e: KeyboardEvent, block: IBlock): Promise<void> {
|
||||
// For non-editable blocks, navigate to previous block
|
||||
if (block.type === 'divider' || block.type === 'image') {
|
||||
e.preventDefault();
|
||||
const blockOps = this.component.blockOperations;
|
||||
const prevBlock = blockOps.getPreviousBlock(block.id);
|
||||
|
||||
if (prevBlock) {
|
||||
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the block component from the wysiwyg component's shadow DOM
|
||||
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
|
||||
@ -448,9 +574,9 @@ export class WysiwygKeyboardHandler {
|
||||
const prevBlock = blockOps.getPreviousBlock(block.id);
|
||||
console.log('ArrowLeft: At start, previous block:', prevBlock?.id);
|
||||
|
||||
if (prevBlock && prevBlock.type !== 'divider') {
|
||||
if (prevBlock) {
|
||||
e.preventDefault();
|
||||
await blockOps.focusBlock(prevBlock.id, 'end');
|
||||
await blockOps.focusBlock(prevBlock.id, prevBlock.type === 'divider' || prevBlock.type === 'image' ? undefined : 'end');
|
||||
}
|
||||
}
|
||||
// Otherwise, let the browser handle normal left arrow navigation
|
||||
@ -460,6 +586,18 @@ export class WysiwygKeyboardHandler {
|
||||
* Handles ArrowRight key - navigate to next block if at end
|
||||
*/
|
||||
private async handleArrowRight(e: KeyboardEvent, block: IBlock): Promise<void> {
|
||||
// For non-editable blocks, navigate to next block
|
||||
if (block.type === 'divider' || block.type === 'image') {
|
||||
e.preventDefault();
|
||||
const blockOps = this.component.blockOperations;
|
||||
const nextBlock = blockOps.getNextBlock(block.id);
|
||||
|
||||
if (nextBlock) {
|
||||
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the block component from the wysiwyg component's shadow DOM
|
||||
const blockWrapper = this.component.shadowRoot?.querySelector(`[data-block-id="${block.id}"]`);
|
||||
const blockComponent = blockWrapper?.querySelector('dees-wysiwyg-block');
|
||||
@ -488,9 +626,9 @@ export class WysiwygKeyboardHandler {
|
||||
const blockOps = this.component.blockOperations;
|
||||
const nextBlock = blockOps.getNextBlock(block.id);
|
||||
|
||||
if (nextBlock && nextBlock.type !== 'divider') {
|
||||
if (nextBlock) {
|
||||
e.preventDefault();
|
||||
await blockOps.focusBlock(nextBlock.id, 'start');
|
||||
await blockOps.focusBlock(nextBlock.id, nextBlock.type === 'divider' || nextBlock.type === 'image' ? undefined : 'start');
|
||||
}
|
||||
}
|
||||
// Otherwise, let the browser handle normal right arrow navigation
|
||||
|
Reference in New Issue
Block a user