update
This commit is contained in:
@ -1,5 +1,13 @@
|
|||||||
# WYSIWYG Editor Refactoring Progress Summary
|
# WYSIWYG Editor Refactoring Progress Summary
|
||||||
|
|
||||||
|
## Latest Updates
|
||||||
|
|
||||||
|
### Selection Highlighting Fix ✅
|
||||||
|
- **Issue**: "Paragraphs are not highlighted consistently, headings are always highlighted"
|
||||||
|
- **Root Cause**: The `shouldUpdate` method in `dees-wysiwyg-block.ts` was using a generic `.block` selector that would match the first element with that class, not necessarily the correct block element
|
||||||
|
- **Solution**: Changed the selector to be more specific: `.block.${blockType}` which ensures the correct element is found for each block type
|
||||||
|
- **Result**: All block types now highlight consistently when selected
|
||||||
|
|
||||||
## Completed Phases
|
## Completed Phases
|
||||||
|
|
||||||
### Phase 1: Infrastructure ✅
|
### Phase 1: Infrastructure ✅
|
||||||
|
9
test/test.wysiwyg-basic.browser.ts
Normal file
9
test/test.wysiwyg-basic.browser.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
|
tap.test('should create wysiwyg editor', async () => {
|
||||||
|
const editor = new DeesInputWysiwyg();
|
||||||
|
expect(editor).toBeInstanceOf(DeesInputWysiwyg);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
156
test/test.wysiwyg-selection-highlight.browser.ts
Normal file
156
test/test.wysiwyg-selection-highlight.browser.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
|
tap.test('Selection highlighting should work consistently for all block types', async () => {
|
||||||
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Import various block types
|
||||||
|
editor.importBlocks([
|
||||||
|
{ id: 'para-1', type: 'paragraph', content: 'This is a paragraph' },
|
||||||
|
{ id: 'heading-1', type: 'heading-1', content: 'This is a heading' },
|
||||||
|
{ id: 'quote-1', type: 'quote', content: 'This is a quote' },
|
||||||
|
{ id: 'code-1', type: 'code', content: 'const x = 42;' },
|
||||||
|
{ id: 'list-1', type: 'list', content: 'Item 1\nItem 2' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
await editor.updateComplete;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Test paragraph highlighting
|
||||||
|
console.log('Testing paragraph highlighting...');
|
||||||
|
const paraWrapper = editor.shadowRoot?.querySelector('[data-block-id="para-1"]');
|
||||||
|
const paraComponent = paraWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const paraContainer = paraComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||||
|
const paraElement = paraContainer?.querySelector('.block.paragraph') as HTMLElement;
|
||||||
|
|
||||||
|
// Focus paragraph to select it
|
||||||
|
paraElement.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Check if paragraph has selected class
|
||||||
|
const paraHasSelected = paraElement.classList.contains('selected');
|
||||||
|
console.log('Paragraph has selected class:', paraHasSelected);
|
||||||
|
|
||||||
|
// Check computed styles
|
||||||
|
const paraStyle = window.getComputedStyle(paraElement);
|
||||||
|
console.log('Paragraph background:', paraStyle.background);
|
||||||
|
console.log('Paragraph box-shadow:', paraStyle.boxShadow);
|
||||||
|
|
||||||
|
// Test heading highlighting
|
||||||
|
console.log('\nTesting heading highlighting...');
|
||||||
|
const headingWrapper = editor.shadowRoot?.querySelector('[data-block-id="heading-1"]');
|
||||||
|
const headingComponent = headingWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const headingContainer = headingComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||||
|
const headingElement = headingContainer?.querySelector('.block.heading-1') as HTMLElement;
|
||||||
|
|
||||||
|
// Focus heading to select it
|
||||||
|
headingElement.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Check if heading has selected class
|
||||||
|
const headingHasSelected = headingElement.classList.contains('selected');
|
||||||
|
console.log('Heading has selected class:', headingHasSelected);
|
||||||
|
|
||||||
|
// Check computed styles
|
||||||
|
const headingStyle = window.getComputedStyle(headingElement);
|
||||||
|
console.log('Heading background:', headingStyle.background);
|
||||||
|
console.log('Heading box-shadow:', headingStyle.boxShadow);
|
||||||
|
|
||||||
|
// Test quote highlighting
|
||||||
|
console.log('\nTesting quote highlighting...');
|
||||||
|
const quoteWrapper = editor.shadowRoot?.querySelector('[data-block-id="quote-1"]');
|
||||||
|
const quoteComponent = quoteWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const quoteContainer = quoteComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||||
|
const quoteElement = quoteContainer?.querySelector('.block.quote') as HTMLElement;
|
||||||
|
|
||||||
|
// Focus quote to select it
|
||||||
|
quoteElement.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Check if quote has selected class
|
||||||
|
const quoteHasSelected = quoteElement.classList.contains('selected');
|
||||||
|
console.log('Quote has selected class:', quoteHasSelected);
|
||||||
|
|
||||||
|
// Test code highlighting
|
||||||
|
console.log('\nTesting code highlighting...');
|
||||||
|
const codeWrapper = editor.shadowRoot?.querySelector('[data-block-id="code-1"]');
|
||||||
|
const codeComponent = codeWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const codeContainer = codeComponent?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||||
|
const codeElement = codeContainer?.querySelector('.block.code') as HTMLElement;
|
||||||
|
|
||||||
|
// Focus code to select it
|
||||||
|
codeElement.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Check if code has selected class
|
||||||
|
const codeHasSelected = codeElement.classList.contains('selected');
|
||||||
|
console.log('Code has selected class:', codeHasSelected);
|
||||||
|
|
||||||
|
// Focus back on paragraph and check if others are deselected
|
||||||
|
console.log('\nFocusing back on paragraph...');
|
||||||
|
paraElement.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Check that only paragraph is selected
|
||||||
|
expect(paraElement.classList.contains('selected')).toBeTrue();
|
||||||
|
expect(headingElement.classList.contains('selected')).toBeFalse();
|
||||||
|
expect(quoteElement.classList.contains('selected')).toBeFalse();
|
||||||
|
expect(codeElement.classList.contains('selected')).toBeFalse();
|
||||||
|
|
||||||
|
console.log('Selection highlighting test complete');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('Selected class should toggle correctly when clicking between blocks', async () => {
|
||||||
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Import two blocks
|
||||||
|
editor.importBlocks([
|
||||||
|
{ id: 'block-1', type: 'paragraph', content: 'First block' },
|
||||||
|
{ id: 'block-2', type: 'paragraph', content: 'Second block' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
await editor.updateComplete;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Get both blocks
|
||||||
|
const block1Wrapper = editor.shadowRoot?.querySelector('[data-block-id="block-1"]');
|
||||||
|
const block1Component = block1Wrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const block1Container = block1Component?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||||
|
const block1Element = block1Container?.querySelector('.block.paragraph') as HTMLElement;
|
||||||
|
|
||||||
|
const block2Wrapper = editor.shadowRoot?.querySelector('[data-block-id="block-2"]');
|
||||||
|
const block2Component = block2Wrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const block2Container = block2Component?.shadowRoot?.querySelector('.wysiwyg-block-container') as HTMLElement;
|
||||||
|
const block2Element = block2Container?.querySelector('.block.paragraph') as HTMLElement;
|
||||||
|
|
||||||
|
// Initially neither should be selected
|
||||||
|
expect(block1Element.classList.contains('selected')).toBeFalse();
|
||||||
|
expect(block2Element.classList.contains('selected')).toBeFalse();
|
||||||
|
|
||||||
|
// Click on first block
|
||||||
|
block1Element.click();
|
||||||
|
block1Element.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// First block should be selected
|
||||||
|
expect(block1Element.classList.contains('selected')).toBeTrue();
|
||||||
|
expect(block2Element.classList.contains('selected')).toBeFalse();
|
||||||
|
|
||||||
|
// Click on second block
|
||||||
|
block2Element.click();
|
||||||
|
block2Element.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Second block should be selected, first should not
|
||||||
|
expect(block1Element.classList.contains('selected')).toBeFalse();
|
||||||
|
expect(block2Element.classList.contains('selected')).toBeTrue();
|
||||||
|
|
||||||
|
console.log('Toggle test complete');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
62
test/test.wysiwyg-selection-simple.browser.ts
Normal file
62
test/test.wysiwyg-selection-simple.browser.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
|
tap.test('Selection highlighting basic test', async () => {
|
||||||
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
webhelpers.html`<dees-input-wysiwyg></dees-input-wysiwyg>`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Import two blocks
|
||||||
|
editor.importBlocks([
|
||||||
|
{ id: 'para-1', type: 'paragraph', content: 'First paragraph' },
|
||||||
|
{ id: 'head-1', type: 'heading-1', content: 'First heading' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
await editor.updateComplete;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
// Get paragraph element
|
||||||
|
const paraWrapper = editor.shadowRoot?.querySelector('[data-block-id="para-1"]');
|
||||||
|
const paraComponent = paraWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const paraBlock = paraComponent?.shadowRoot?.querySelector('.block.paragraph') as HTMLElement;
|
||||||
|
|
||||||
|
// Get heading element
|
||||||
|
const headWrapper = editor.shadowRoot?.querySelector('[data-block-id="head-1"]');
|
||||||
|
const headComponent = headWrapper?.querySelector('dees-wysiwyg-block') as DeesWysiwygBlock;
|
||||||
|
const headBlock = headComponent?.shadowRoot?.querySelector('.block.heading-1') as HTMLElement;
|
||||||
|
|
||||||
|
console.log('Found elements:', {
|
||||||
|
paraBlock: !!paraBlock,
|
||||||
|
headBlock: !!headBlock
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus paragraph
|
||||||
|
paraBlock.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Check classes
|
||||||
|
console.log('Paragraph classes:', paraBlock.className);
|
||||||
|
console.log('Heading classes:', headBlock.className);
|
||||||
|
|
||||||
|
// Check isSelected property
|
||||||
|
console.log('Paragraph component isSelected:', paraComponent.isSelected);
|
||||||
|
console.log('Heading component isSelected:', headComponent.isSelected);
|
||||||
|
|
||||||
|
// Focus heading
|
||||||
|
headBlock.focus();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Check classes again
|
||||||
|
console.log('\nAfter focusing heading:');
|
||||||
|
console.log('Paragraph classes:', paraBlock.className);
|
||||||
|
console.log('Heading classes:', headBlock.className);
|
||||||
|
console.log('Paragraph component isSelected:', paraComponent.isSelected);
|
||||||
|
console.log('Heading component isSelected:', headComponent.isSelected);
|
||||||
|
|
||||||
|
// Check that heading is selected
|
||||||
|
expect(headBlock.classList.contains('selected')).toBeTrue();
|
||||||
|
expect(paraBlock.classList.contains('selected')).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@ -699,11 +699,17 @@ export class DeesWysiwygBlock extends DeesElement {
|
|||||||
];
|
];
|
||||||
|
|
||||||
protected shouldUpdate(changedProperties: Map<string, any>): boolean {
|
protected shouldUpdate(changedProperties: Map<string, any>): boolean {
|
||||||
// If selection state changed, we need to update for non-editable blocks
|
// If selection state changed, update the selected class without re-rendering
|
||||||
const nonEditableTypes = ['divider', 'image', 'youtube', 'markdown', 'html', 'attachment'];
|
if (changedProperties.has('isSelected') && this.block) {
|
||||||
if (changedProperties.has('isSelected') && this.block && nonEditableTypes.includes(this.block.type)) {
|
// Find the block element based on block type
|
||||||
// For non-editable blocks, we need to update the selected class
|
let element: HTMLElement | null = null;
|
||||||
const element = this.shadowRoot?.querySelector('.block') as HTMLElement;
|
|
||||||
|
// Build the specific selector based on block type
|
||||||
|
const blockType = this.block.type;
|
||||||
|
const selector = `.block.${blockType}`;
|
||||||
|
|
||||||
|
element = this.shadowRoot?.querySelector(selector) as HTMLElement;
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
if (this.isSelected) {
|
if (this.isSelected) {
|
||||||
element.classList.add('selected');
|
element.classList.add('selected');
|
||||||
|
Reference in New Issue
Block a user