566 lines
20 KiB
TypeScript
566 lines
20 KiB
TypeScript
![]() |
import { BaseBlockHandler, type IBlockEventHandlers } from '../block.base.js';
|
||
|
import type { IBlock } from '../../wysiwyg.types.js';
|
||
|
import { cssManager } from '@design.estate/dees-element';
|
||
|
import { WysiwygBlocks } from '../../wysiwyg.blocks.js';
|
||
|
import { WysiwygSelection } from '../../wysiwyg.selection.js';
|
||
|
|
||
|
export class HeadingBlockHandler extends BaseBlockHandler {
|
||
|
type: string;
|
||
|
private level: 1 | 2 | 3;
|
||
|
|
||
|
// Track cursor position
|
||
|
private lastKnownCursorPosition: number = 0;
|
||
|
private lastSelectedText: string = '';
|
||
|
private selectionHandler: (() => void) | null = null;
|
||
|
|
||
|
constructor(type: 'heading-1' | 'heading-2' | 'heading-3') {
|
||
|
super();
|
||
|
this.type = type;
|
||
|
this.level = parseInt(type.split('-')[1]) as 1 | 2 | 3;
|
||
|
}
|
||
|
|
||
|
render(block: IBlock, isSelected: boolean): string {
|
||
|
const selectedClass = isSelected ? ' selected' : '';
|
||
|
const placeholder = this.getPlaceholder();
|
||
|
|
||
|
console.log('HeadingBlockHandler.render:', { blockId: block.id, isSelected, content: block.content, level: this.level });
|
||
|
|
||
|
return `
|
||
|
<div
|
||
|
class="block heading-${this.level}${selectedClass}"
|
||
|
contenteditable="true"
|
||
|
data-placeholder="${placeholder}"
|
||
|
data-block-id="${block.id}"
|
||
|
data-block-type="${block.type}"
|
||
|
>${block.content || ''}</div>
|
||
|
`;
|
||
|
}
|
||
|
|
||
|
setup(element: HTMLElement, block: IBlock, handlers: IBlockEventHandlers): void {
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (!headingBlock) {
|
||
|
console.error('HeadingBlockHandler.setup: No heading block element found');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
console.log('HeadingBlockHandler.setup: Setting up heading block', { blockId: block.id, level: this.level });
|
||
|
|
||
|
// Set initial content if needed
|
||
|
if (block.content && !headingBlock.innerHTML) {
|
||
|
headingBlock.innerHTML = block.content;
|
||
|
}
|
||
|
|
||
|
// Input handler with cursor tracking
|
||
|
headingBlock.addEventListener('input', (e) => {
|
||
|
console.log('HeadingBlockHandler: Input event', { blockId: block.id });
|
||
|
handlers.onInput(e as InputEvent);
|
||
|
|
||
|
// Track cursor position after input
|
||
|
const pos = this.getCursorPosition(element);
|
||
|
if (pos !== null) {
|
||
|
this.lastKnownCursorPosition = pos;
|
||
|
console.log('HeadingBlockHandler: Updated cursor position after input', { pos });
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Keydown handler with cursor tracking
|
||
|
headingBlock.addEventListener('keydown', (e) => {
|
||
|
// Track cursor position before keydown
|
||
|
const pos = this.getCursorPosition(element);
|
||
|
if (pos !== null) {
|
||
|
this.lastKnownCursorPosition = pos;
|
||
|
console.log('HeadingBlockHandler: Cursor position before keydown', { pos, key: e.key });
|
||
|
}
|
||
|
|
||
|
handlers.onKeyDown(e);
|
||
|
});
|
||
|
|
||
|
// Focus handler
|
||
|
headingBlock.addEventListener('focus', () => {
|
||
|
console.log('HeadingBlockHandler: Focus event', { blockId: block.id });
|
||
|
handlers.onFocus();
|
||
|
});
|
||
|
|
||
|
// Blur handler
|
||
|
headingBlock.addEventListener('blur', () => {
|
||
|
console.log('HeadingBlockHandler: Blur event', { blockId: block.id });
|
||
|
handlers.onBlur();
|
||
|
});
|
||
|
|
||
|
// Composition handlers for IME support
|
||
|
headingBlock.addEventListener('compositionstart', () => {
|
||
|
console.log('HeadingBlockHandler: Composition start', { blockId: block.id });
|
||
|
handlers.onCompositionStart();
|
||
|
});
|
||
|
|
||
|
headingBlock.addEventListener('compositionend', () => {
|
||
|
console.log('HeadingBlockHandler: Composition end', { blockId: block.id });
|
||
|
handlers.onCompositionEnd();
|
||
|
});
|
||
|
|
||
|
// Mouse up handler
|
||
|
headingBlock.addEventListener('mouseup', (e) => {
|
||
|
const pos = this.getCursorPosition(element);
|
||
|
if (pos !== null) {
|
||
|
this.lastKnownCursorPosition = pos;
|
||
|
console.log('HeadingBlockHandler: Cursor position after mouseup', { pos });
|
||
|
}
|
||
|
|
||
|
// Selection will be handled by selectionchange event
|
||
|
handlers.onMouseUp?.(e);
|
||
|
});
|
||
|
|
||
|
// Click handler with delayed cursor tracking
|
||
|
headingBlock.addEventListener('click', (e: MouseEvent) => {
|
||
|
// Small delay to let browser set cursor position
|
||
|
setTimeout(() => {
|
||
|
const pos = this.getCursorPosition(element);
|
||
|
if (pos !== null) {
|
||
|
this.lastKnownCursorPosition = pos;
|
||
|
console.log('HeadingBlockHandler: Cursor position after click', { pos });
|
||
|
}
|
||
|
}, 0);
|
||
|
});
|
||
|
|
||
|
// Keyup handler for additional cursor tracking
|
||
|
headingBlock.addEventListener('keyup', (e) => {
|
||
|
const pos = this.getCursorPosition(element);
|
||
|
if (pos !== null) {
|
||
|
this.lastKnownCursorPosition = pos;
|
||
|
console.log('HeadingBlockHandler: Cursor position after keyup', { pos, key: e.key });
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Set up selection change handler
|
||
|
this.setupSelectionHandler(element, headingBlock, block);
|
||
|
}
|
||
|
|
||
|
private setupSelectionHandler(element: HTMLElement, headingBlock: HTMLDivElement, block: IBlock): void {
|
||
|
// Add selection change handler
|
||
|
const checkSelection = () => {
|
||
|
const selection = window.getSelection();
|
||
|
if (!selection || selection.rangeCount === 0) return;
|
||
|
|
||
|
const selectedText = selection.toString();
|
||
|
if (selectedText.length === 0) {
|
||
|
// Clear selection if no text
|
||
|
if (this.lastSelectedText) {
|
||
|
this.lastSelectedText = '';
|
||
|
this.dispatchSelectionEvent(element, {
|
||
|
text: '',
|
||
|
blockId: block.id,
|
||
|
hasSelection: false
|
||
|
});
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Get parent wysiwyg component's shadow root - in setup, we need to traverse
|
||
|
const wysiwygBlock = (headingBlock.getRootNode() as ShadowRoot).host as any;
|
||
|
const parentComponent = wysiwygBlock?.closest('dees-input-wysiwyg');
|
||
|
const parentShadowRoot = parentComponent?.shadowRoot;
|
||
|
const blockShadowRoot = wysiwygBlock?.shadowRoot;
|
||
|
|
||
|
// Use getComposedRanges with shadow roots as per MDN docs
|
||
|
const shadowRoots: ShadowRoot[] = [];
|
||
|
if (parentShadowRoot) shadowRoots.push(parentShadowRoot);
|
||
|
if (blockShadowRoot) shadowRoots.push(blockShadowRoot);
|
||
|
|
||
|
// Get selection info using our Shadow DOM-aware utility
|
||
|
const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots);
|
||
|
if (!selectionInfo) return;
|
||
|
|
||
|
// Check if selection is within this block
|
||
|
const startInBlock = WysiwygSelection.containsAcrossShadowDOM(headingBlock, selectionInfo.startContainer);
|
||
|
const endInBlock = WysiwygSelection.containsAcrossShadowDOM(headingBlock, selectionInfo.endContainer);
|
||
|
|
||
|
if (startInBlock || endInBlock) {
|
||
|
if (selectedText !== this.lastSelectedText) {
|
||
|
this.lastSelectedText = selectedText;
|
||
|
|
||
|
console.log('HeadingBlockHandler: Text selected', {
|
||
|
text: selectedText,
|
||
|
blockId: block.id
|
||
|
});
|
||
|
|
||
|
// Create range and get rect
|
||
|
const range = WysiwygSelection.createRangeFromInfo(selectionInfo);
|
||
|
const rect = range.getBoundingClientRect();
|
||
|
|
||
|
// Dispatch event
|
||
|
this.dispatchSelectionEvent(element, {
|
||
|
text: selectedText.trim(),
|
||
|
blockId: block.id,
|
||
|
range: range,
|
||
|
rect: rect,
|
||
|
hasSelection: true
|
||
|
});
|
||
|
}
|
||
|
} else if (this.lastSelectedText) {
|
||
|
// Clear selection if no longer in this block
|
||
|
this.lastSelectedText = '';
|
||
|
this.dispatchSelectionEvent(element, {
|
||
|
text: '',
|
||
|
blockId: block.id,
|
||
|
hasSelection: false
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Listen for selection changes
|
||
|
document.addEventListener('selectionchange', checkSelection);
|
||
|
|
||
|
// Store the handler for cleanup
|
||
|
this.selectionHandler = checkSelection;
|
||
|
|
||
|
// Clean up on disconnect (will be called by dees-wysiwyg-block)
|
||
|
const wysiwygBlock = (headingBlock.getRootNode() as ShadowRoot).host as any;
|
||
|
if (wysiwygBlock) {
|
||
|
const originalDisconnectedCallback = (wysiwygBlock as any).disconnectedCallback;
|
||
|
(wysiwygBlock as any).disconnectedCallback = async function() {
|
||
|
if (this.selectionHandler) {
|
||
|
document.removeEventListener('selectionchange', this.selectionHandler);
|
||
|
this.selectionHandler = null;
|
||
|
}
|
||
|
if (originalDisconnectedCallback) {
|
||
|
await originalDisconnectedCallback.call(wysiwygBlock);
|
||
|
}
|
||
|
}.bind(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private dispatchSelectionEvent(element: HTMLElement, detail: any): void {
|
||
|
const event = new CustomEvent('block-text-selected', {
|
||
|
detail,
|
||
|
bubbles: true,
|
||
|
composed: true
|
||
|
});
|
||
|
element.dispatchEvent(event);
|
||
|
}
|
||
|
|
||
|
getStyles(): string {
|
||
|
// Return styles for all heading levels
|
||
|
return `
|
||
|
.block.heading-1 {
|
||
|
font-size: 32px;
|
||
|
font-weight: 700;
|
||
|
line-height: 1.2;
|
||
|
margin: 24px 0 8px 0;
|
||
|
color: ${cssManager.bdTheme('#000000', '#ffffff')};
|
||
|
}
|
||
|
|
||
|
.block.heading-2 {
|
||
|
font-size: 24px;
|
||
|
font-weight: 600;
|
||
|
line-height: 1.3;
|
||
|
margin: 20px 0 6px 0;
|
||
|
color: ${cssManager.bdTheme('#000000', '#ffffff')};
|
||
|
}
|
||
|
|
||
|
.block.heading-3 {
|
||
|
font-size: 20px;
|
||
|
font-weight: 600;
|
||
|
line-height: 1.4;
|
||
|
margin: 16px 0 4px 0;
|
||
|
color: ${cssManager.bdTheme('#000000', '#ffffff')};
|
||
|
}
|
||
|
`;
|
||
|
}
|
||
|
|
||
|
getPlaceholder(): string {
|
||
|
switch(this.level) {
|
||
|
case 1:
|
||
|
return 'Heading 1';
|
||
|
case 2:
|
||
|
return 'Heading 2';
|
||
|
case 3:
|
||
|
return 'Heading 3';
|
||
|
default:
|
||
|
return 'Heading';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Helper methods for heading functionality (mostly the same as paragraph)
|
||
|
|
||
|
getCursorPosition(element: HTMLElement, context?: any): number | null {
|
||
|
// Get the actual heading element
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (!headingBlock) {
|
||
|
console.log('HeadingBlockHandler.getCursorPosition: No heading element found');
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Get shadow roots from context
|
||
|
const wysiwygBlock = context?.component;
|
||
|
const parentComponent = wysiwygBlock?.closest('dees-input-wysiwyg');
|
||
|
const parentShadowRoot = parentComponent?.shadowRoot;
|
||
|
const blockShadowRoot = context?.shadowRoot;
|
||
|
|
||
|
// Get selection info with both shadow roots for proper traversal
|
||
|
const shadowRoots: ShadowRoot[] = [];
|
||
|
if (parentShadowRoot) shadowRoots.push(parentShadowRoot);
|
||
|
if (blockShadowRoot) shadowRoots.push(blockShadowRoot);
|
||
|
|
||
|
const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots);
|
||
|
console.log('HeadingBlockHandler.getCursorPosition: Selection info from shadow DOMs:', {
|
||
|
selectionInfo,
|
||
|
shadowRootsCount: shadowRoots.length
|
||
|
});
|
||
|
|
||
|
if (!selectionInfo) {
|
||
|
console.log('HeadingBlockHandler.getCursorPosition: No selection found');
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
console.log('HeadingBlockHandler.getCursorPosition: Range info:', {
|
||
|
startContainer: selectionInfo.startContainer,
|
||
|
startOffset: selectionInfo.startOffset,
|
||
|
collapsed: selectionInfo.collapsed,
|
||
|
startContainerText: selectionInfo.startContainer.textContent
|
||
|
});
|
||
|
|
||
|
if (!WysiwygSelection.containsAcrossShadowDOM(headingBlock, selectionInfo.startContainer)) {
|
||
|
console.log('HeadingBlockHandler.getCursorPosition: Range not in element');
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Create a range from start of element to cursor position
|
||
|
const preCaretRange = document.createRange();
|
||
|
preCaretRange.selectNodeContents(headingBlock);
|
||
|
preCaretRange.setEnd(selectionInfo.startContainer, selectionInfo.startOffset);
|
||
|
|
||
|
// Get the text content length up to cursor
|
||
|
const position = preCaretRange.toString().length;
|
||
|
console.log('HeadingBlockHandler.getCursorPosition: Calculated position:', {
|
||
|
position,
|
||
|
preCaretText: preCaretRange.toString(),
|
||
|
elementText: headingBlock.textContent,
|
||
|
elementTextLength: headingBlock.textContent?.length
|
||
|
});
|
||
|
|
||
|
return position;
|
||
|
}
|
||
|
|
||
|
getContent(element: HTMLElement, context?: any): string {
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (!headingBlock) return '';
|
||
|
|
||
|
// For headings, get the innerHTML which includes formatting tags
|
||
|
const content = headingBlock.innerHTML || '';
|
||
|
console.log('HeadingBlockHandler.getContent:', content);
|
||
|
return content;
|
||
|
}
|
||
|
|
||
|
setContent(element: HTMLElement, content: string, context?: any): void {
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (!headingBlock) return;
|
||
|
|
||
|
// Store if we have focus
|
||
|
const hadFocus = document.activeElement === headingBlock ||
|
||
|
element.shadowRoot?.activeElement === headingBlock;
|
||
|
|
||
|
headingBlock.innerHTML = content;
|
||
|
|
||
|
// Restore focus if we had it
|
||
|
if (hadFocus) {
|
||
|
headingBlock.focus();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setCursorToStart(element: HTMLElement, context?: any): void {
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (headingBlock) {
|
||
|
WysiwygBlocks.setCursorToStart(headingBlock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setCursorToEnd(element: HTMLElement, context?: any): void {
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (headingBlock) {
|
||
|
WysiwygBlocks.setCursorToEnd(headingBlock);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
focus(element: HTMLElement, context?: any): void {
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (!headingBlock) return;
|
||
|
|
||
|
// Ensure the element is focusable
|
||
|
if (!headingBlock.hasAttribute('contenteditable')) {
|
||
|
headingBlock.setAttribute('contenteditable', 'true');
|
||
|
}
|
||
|
|
||
|
headingBlock.focus();
|
||
|
|
||
|
// If focus failed, try again after a microtask
|
||
|
if (document.activeElement !== headingBlock && element.shadowRoot?.activeElement !== headingBlock) {
|
||
|
Promise.resolve().then(() => {
|
||
|
headingBlock.focus();
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
focusWithCursor(element: HTMLElement, position: 'start' | 'end' | number = 'end', context?: any): void {
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (!headingBlock) return;
|
||
|
|
||
|
// Ensure element is focusable first
|
||
|
if (!headingBlock.hasAttribute('contenteditable')) {
|
||
|
headingBlock.setAttribute('contenteditable', 'true');
|
||
|
}
|
||
|
|
||
|
// Focus the element
|
||
|
headingBlock.focus();
|
||
|
|
||
|
// Set cursor position after focus is established
|
||
|
const setCursor = () => {
|
||
|
if (position === 'start') {
|
||
|
this.setCursorToStart(element, context);
|
||
|
} else if (position === 'end') {
|
||
|
this.setCursorToEnd(element, context);
|
||
|
} else if (typeof position === 'number') {
|
||
|
// Use the selection utility to set cursor position
|
||
|
WysiwygSelection.setCursorPosition(headingBlock, position);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Ensure cursor is set after focus
|
||
|
if (document.activeElement === headingBlock || element.shadowRoot?.activeElement === headingBlock) {
|
||
|
setCursor();
|
||
|
} else {
|
||
|
// Wait for focus to be established
|
||
|
Promise.resolve().then(() => {
|
||
|
if (document.activeElement === headingBlock || element.shadowRoot?.activeElement === headingBlock) {
|
||
|
setCursor();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
getSplitContent(element: HTMLElement, context?: any): { before: string; after: string } | null {
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Starting...');
|
||
|
|
||
|
const headingBlock = element.querySelector(`.block.heading-${this.level}`) as HTMLDivElement;
|
||
|
if (!headingBlock) {
|
||
|
console.log('HeadingBlockHandler.getSplitContent: No heading element found');
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Element info:', {
|
||
|
innerHTML: headingBlock.innerHTML,
|
||
|
textContent: headingBlock.textContent,
|
||
|
textLength: headingBlock.textContent?.length
|
||
|
});
|
||
|
|
||
|
// Get shadow roots from context
|
||
|
const wysiwygBlock = context?.component;
|
||
|
const parentComponent = wysiwygBlock?.closest('dees-input-wysiwyg');
|
||
|
const parentShadowRoot = parentComponent?.shadowRoot;
|
||
|
const blockShadowRoot = context?.shadowRoot;
|
||
|
|
||
|
// Get selection info with both shadow roots for proper traversal
|
||
|
const shadowRoots: ShadowRoot[] = [];
|
||
|
if (parentShadowRoot) shadowRoots.push(parentShadowRoot);
|
||
|
if (blockShadowRoot) shadowRoots.push(blockShadowRoot);
|
||
|
|
||
|
const selectionInfo = WysiwygSelection.getSelectionInfo(...shadowRoots);
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Selection info from shadow DOMs:', {
|
||
|
selectionInfo,
|
||
|
shadowRootsCount: shadowRoots.length
|
||
|
});
|
||
|
|
||
|
if (!selectionInfo) {
|
||
|
console.log('HeadingBlockHandler.getSplitContent: No selection, using last known position:', this.lastKnownCursorPosition);
|
||
|
// Try using last known cursor position
|
||
|
if (this.lastKnownCursorPosition !== null) {
|
||
|
const fullText = headingBlock.textContent || '';
|
||
|
const pos = Math.min(this.lastKnownCursorPosition, fullText.length);
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Splitting with last known position:', {
|
||
|
pos,
|
||
|
fullTextLength: fullText.length,
|
||
|
before: fullText.substring(0, pos),
|
||
|
after: fullText.substring(pos)
|
||
|
});
|
||
|
return {
|
||
|
before: fullText.substring(0, pos),
|
||
|
after: fullText.substring(pos)
|
||
|
};
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Selection range:', {
|
||
|
startContainer: selectionInfo.startContainer,
|
||
|
startOffset: selectionInfo.startOffset,
|
||
|
startContainerInElement: headingBlock.contains(selectionInfo.startContainer)
|
||
|
});
|
||
|
|
||
|
// Make sure the selection is within this block
|
||
|
if (!WysiwygSelection.containsAcrossShadowDOM(headingBlock, selectionInfo.startContainer)) {
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Selection not in this block, using last known position:', this.lastKnownCursorPosition);
|
||
|
// Try using last known cursor position
|
||
|
if (this.lastKnownCursorPosition !== null) {
|
||
|
const fullText = headingBlock.textContent || '';
|
||
|
const pos = Math.min(this.lastKnownCursorPosition, fullText.length);
|
||
|
return {
|
||
|
before: fullText.substring(0, pos),
|
||
|
after: fullText.substring(pos)
|
||
|
};
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Get cursor position first
|
||
|
const cursorPos = this.getCursorPosition(element, context);
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Cursor position for HTML split:', cursorPos);
|
||
|
|
||
|
if (cursorPos === null || cursorPos === 0) {
|
||
|
// If cursor is at start or can't determine position, move all content
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Cursor at start or null, moving all content');
|
||
|
return {
|
||
|
before: '',
|
||
|
after: headingBlock.innerHTML
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// For HTML content, split using ranges to preserve formatting
|
||
|
const beforeRange = document.createRange();
|
||
|
const afterRange = document.createRange();
|
||
|
|
||
|
// Before range: from start of element to cursor
|
||
|
beforeRange.setStart(headingBlock, 0);
|
||
|
beforeRange.setEnd(selectionInfo.startContainer, selectionInfo.startOffset);
|
||
|
|
||
|
// After range: from cursor to end of element
|
||
|
afterRange.setStart(selectionInfo.startContainer, selectionInfo.startOffset);
|
||
|
afterRange.setEnd(headingBlock, headingBlock.childNodes.length);
|
||
|
|
||
|
// Extract HTML content
|
||
|
const beforeFragment = beforeRange.cloneContents();
|
||
|
const afterFragment = afterRange.cloneContents();
|
||
|
|
||
|
// Convert to HTML strings
|
||
|
const tempDiv = document.createElement('div');
|
||
|
tempDiv.appendChild(beforeFragment);
|
||
|
const beforeHtml = tempDiv.innerHTML;
|
||
|
|
||
|
tempDiv.innerHTML = '';
|
||
|
tempDiv.appendChild(afterFragment);
|
||
|
const afterHtml = tempDiv.innerHTML;
|
||
|
|
||
|
console.log('HeadingBlockHandler.getSplitContent: Final split result:', {
|
||
|
cursorPos,
|
||
|
beforeHtml,
|
||
|
beforeLength: beforeHtml.length,
|
||
|
beforeHtmlPreview: beforeHtml.substring(0, 100) + (beforeHtml.length > 100 ? '...' : ''),
|
||
|
afterHtml,
|
||
|
afterLength: afterHtml.length,
|
||
|
afterHtmlPreview: afterHtml.substring(0, 100) + (afterHtml.length > 100 ? '...' : '')
|
||
|
});
|
||
|
|
||
|
return {
|
||
|
before: beforeHtml,
|
||
|
after: afterHtml
|
||
|
};
|
||
|
}
|
||
|
}
|