refactor
This commit is contained in:
@ -11,6 +11,8 @@ import {
|
||||
import { type IBlock } from './wysiwyg.types.js';
|
||||
import { WysiwygBlocks } from './wysiwyg.blocks.js';
|
||||
import { WysiwygSelection } from './wysiwyg.selection.js';
|
||||
import { BlockRegistry, type IBlockEventHandlers } from './blocks/index.js';
|
||||
import './wysiwyg.blockregistration.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@ -34,15 +36,7 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
public isSelected: boolean = false;
|
||||
|
||||
@property({ type: Object })
|
||||
public handlers: {
|
||||
onInput: (e: InputEvent) => void;
|
||||
onKeyDown: (e: KeyboardEvent) => void;
|
||||
onFocus: () => void;
|
||||
onBlur: () => void;
|
||||
onCompositionStart: () => void;
|
||||
onCompositionEnd: () => void;
|
||||
onMouseUp?: (e: MouseEvent) => void;
|
||||
};
|
||||
public handlers: IBlockEventHandlers;
|
||||
|
||||
// Reference to the editable block element
|
||||
private blockElement: HTMLDivElement | null = null;
|
||||
@ -54,6 +48,31 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
private lastKnownCursorPosition: number = 0;
|
||||
private lastSelectedText: string = '';
|
||||
|
||||
private static handlerStylesInjected = false;
|
||||
|
||||
private injectHandlerStyles(): void {
|
||||
// Only inject once per component class
|
||||
if (DeesWysiwygBlock.handlerStylesInjected) return;
|
||||
DeesWysiwygBlock.handlerStylesInjected = true;
|
||||
|
||||
// Get styles from all registered block handlers
|
||||
let styles = '';
|
||||
const blockTypes = BlockRegistry.getAllTypes();
|
||||
for (const type of blockTypes) {
|
||||
const handler = BlockRegistry.getHandler(type);
|
||||
if (handler) {
|
||||
styles += handler.getStyles();
|
||||
}
|
||||
}
|
||||
|
||||
if (styles) {
|
||||
// Create and inject style element
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.textContent = styles;
|
||||
this.shadowRoot?.appendChild(styleElement);
|
||||
}
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
@ -141,30 +160,6 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.block.divider {
|
||||
padding: 8px 0;
|
||||
margin: 16px 0;
|
||||
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 */
|
||||
.block :is(b, strong) {
|
||||
@ -722,7 +717,7 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
// Never update if only the block content changed
|
||||
if (changedProperties.has('block') && this.block) {
|
||||
const oldBlock = changedProperties.get('block');
|
||||
if (oldBlock && oldBlock.id === this.block.id && oldBlock.type === this.block.type) {
|
||||
if (oldBlock && oldBlock.id && oldBlock.type && oldBlock.id === this.block.id && oldBlock.type === this.block.type) {
|
||||
// Only content or metadata changed, don't re-render
|
||||
return false;
|
||||
}
|
||||
@ -736,19 +731,31 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
// Mark that content has been initialized
|
||||
this.contentInitialized = true;
|
||||
|
||||
// Inject handler styles if not already done
|
||||
this.injectHandlerStyles();
|
||||
|
||||
// First, populate the container with the rendered content
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLDivElement;
|
||||
if (container && this.block) {
|
||||
container.innerHTML = this.renderBlockContent();
|
||||
}
|
||||
|
||||
// Check if we have a registered handler for this block type
|
||||
if (this.block) {
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler) {
|
||||
const blockElement = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
if (blockElement) {
|
||||
handler.setup(blockElement, this.block, this.handlers);
|
||||
}
|
||||
return; // Block handler takes care of all 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
|
||||
} else if (this.block.type === 'youtube') {
|
||||
this.setupYouTubeBlock();
|
||||
return;
|
||||
@ -875,8 +882,8 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
if (!selectionInfo) return;
|
||||
|
||||
// Check if selection is within this block
|
||||
const startInBlock = currentEditableBlock.contains(selectionInfo.startContainer);
|
||||
const endInBlock = currentEditableBlock.contains(selectionInfo.endContainer);
|
||||
const startInBlock = WysiwygSelection.containsAcrossShadowDOM(currentEditableBlock, selectionInfo.startContainer);
|
||||
const endInBlock = WysiwygSelection.containsAcrossShadowDOM(currentEditableBlock, selectionInfo.endContainer);
|
||||
|
||||
if (startInBlock || endInBlock) {
|
||||
if (selectedText !== this.lastSelectedText) {
|
||||
@ -956,13 +963,10 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
private renderBlockContent(): string {
|
||||
if (!this.block) return '';
|
||||
|
||||
if (this.block.type === 'divider') {
|
||||
const selectedClass = this.isSelected ? ' selected' : '';
|
||||
return `
|
||||
<div class="block divider${selectedClass}" data-block-id="${this.block.id}" data-block-type="${this.block.type}" tabindex="0">
|
||||
<hr>
|
||||
</div>
|
||||
`;
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler) {
|
||||
return handler.render(this.block, this.isSelected);
|
||||
}
|
||||
|
||||
if (this.block.type === 'code') {
|
||||
@ -1145,6 +1149,14 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
|
||||
|
||||
public focus(): void {
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler && handler.focus) {
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const context = { shadowRoot: this.shadowRoot!, component: this };
|
||||
return handler.focus(container, context);
|
||||
}
|
||||
|
||||
// Handle non-editable blocks
|
||||
const nonEditableTypes = ['image', 'divider', 'youtube', 'markdown', 'html', 'attachment'];
|
||||
if (this.block && nonEditableTypes.includes(this.block.type)) {
|
||||
@ -1178,6 +1190,14 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public focusWithCursor(position: 'start' | 'end' | number = 'end'): void {
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler && handler.focusWithCursor) {
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const context = { shadowRoot: this.shadowRoot!, component: this };
|
||||
return handler.focusWithCursor(container, position, context);
|
||||
}
|
||||
|
||||
// Non-editable blocks don't support cursor positioning
|
||||
const nonEditableTypes = ['image', 'divider', 'youtube', 'markdown', 'html', 'attachment'];
|
||||
if (this.block && nonEditableTypes.includes(this.block.type)) {
|
||||
@ -1231,6 +1251,13 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
* Get cursor position in the editable element
|
||||
*/
|
||||
public getCursorPosition(element: HTMLElement): number | null {
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler && handler.getCursorPosition) {
|
||||
const context = { shadowRoot: this.shadowRoot!, component: this };
|
||||
return handler.getCursorPosition(element, context);
|
||||
}
|
||||
|
||||
// Get parent wysiwyg component's shadow root
|
||||
const parentComponent = this.closest('dees-input-wysiwyg');
|
||||
const parentShadowRoot = parentComponent?.shadowRoot;
|
||||
@ -1281,6 +1308,14 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public getContent(): string {
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler && handler.getContent) {
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const context = { shadowRoot: this.shadowRoot!, component: this };
|
||||
return handler.getContent(container, context);
|
||||
}
|
||||
|
||||
// Handle image blocks specially
|
||||
if (this.block?.type === 'image') {
|
||||
return this.block.content || ''; // Image blocks store alt text in content
|
||||
@ -1307,6 +1342,14 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public setContent(content: string): void {
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler && handler.setContent) {
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const context = { shadowRoot: this.shadowRoot!, component: this };
|
||||
return handler.setContent(container, content, context);
|
||||
}
|
||||
|
||||
// Get the actual editable element (might be nested for code blocks)
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
@ -1332,6 +1375,14 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public setCursorToStart(): void {
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler && handler.setCursorToStart) {
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const context = { shadowRoot: this.shadowRoot!, component: this };
|
||||
return handler.setCursorToStart(container, context);
|
||||
}
|
||||
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
@ -1341,6 +1392,14 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
}
|
||||
|
||||
public setCursorToEnd(): void {
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
if (handler && handler.setCursorToEnd) {
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
const context = { shadowRoot: this.shadowRoot!, component: this };
|
||||
return handler.setCursorToEnd(container, context);
|
||||
}
|
||||
|
||||
const editableElement = this.block?.type === 'code'
|
||||
? this.shadowRoot?.querySelector('.block.code') as HTMLDivElement
|
||||
: this.blockElement;
|
||||
@ -1358,43 +1417,6 @@ 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();
|
||||
// Ensure focus handler is called immediately
|
||||
this.handlers?.onFocus?.();
|
||||
});
|
||||
|
||||
// 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 YouTube block functionality
|
||||
@ -1988,6 +2010,27 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
public getSplitContent(): { before: string; after: string } | null {
|
||||
console.log('getSplitContent: Starting...');
|
||||
|
||||
// Check if we have a registered handler for this block type
|
||||
const handler = BlockRegistry.getHandler(this.block.type);
|
||||
console.log('getSplitContent: Checking for handler', {
|
||||
blockType: this.block.type,
|
||||
hasHandler: !!handler,
|
||||
hasSplitMethod: !!(handler && handler.getSplitContent)
|
||||
});
|
||||
|
||||
if (handler && handler.getSplitContent) {
|
||||
const container = this.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||
console.log('getSplitContent: Found container', {
|
||||
container: !!container,
|
||||
containerHTML: container?.innerHTML?.substring(0, 100)
|
||||
});
|
||||
const context = {
|
||||
shadowRoot: this.shadowRoot!,
|
||||
component: this
|
||||
};
|
||||
return handler.getSplitContent(container, context);
|
||||
}
|
||||
|
||||
// Image blocks can't be split
|
||||
if (this.block?.type === 'image') {
|
||||
return null;
|
||||
@ -2052,7 +2095,7 @@ export class DeesWysiwygBlock extends DeesElement {
|
||||
});
|
||||
|
||||
// Make sure the selection is within this block
|
||||
if (!editableElement.contains(selectionInfo.startContainer)) {
|
||||
if (!WysiwygSelection.containsAcrossShadowDOM(editableElement, selectionInfo.startContainer)) {
|
||||
console.log('getSplitContent: Selection not in this block, using last known position:', this.lastKnownCursorPosition);
|
||||
// Try using last known cursor position
|
||||
if (this.lastKnownCursorPosition !== null) {
|
||||
|
Reference in New Issue
Block a user