Skip to content

Commit 35276b0

Browse files
authored
Merge pull request #1284 from masechkacat/i/1281
I/1281
2 parents 798cce8 + 2a02f99 commit 35276b0

7 files changed

Lines changed: 222 additions & 10 deletions

File tree

src/core/selection/style/style.test.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,5 +2238,197 @@ describe('Apply style', () => {
22382238
});
22392239
});
22402240
});
2241+
2242+
describe('Issue #1281: HTML structure preservation with ALL style properties', function () {
2243+
describe('Case 1: Styled div with multiple properties should not break structure', function () {
2244+
it('Should change color but preserve HTML structure', function () {
2245+
const editor = getJodit();
2246+
2247+
// Set up div with multiple styles like in real usage
2248+
editor.value =
2249+
'<div style="font-family: Arial, sans-serif; background-color: yellow; color: blue; font-size: 14px;"><span>Test text here</span></div>';
2250+
2251+
const range = editor.s.createRange();
2252+
const spanElement = editor.editor.querySelector('span');
2253+
const textNode = spanElement.firstChild;
2254+
2255+
// Select "text" word
2256+
range.setStart(textNode, 5);
2257+
range.setEnd(textNode, 9);
2258+
editor.s.selectRange(range);
2259+
2260+
// Apply red color using CommitStyle directly (core style system test)
2261+
const Style = Jodit.ns.CommitStyle;
2262+
const style = new Style({
2263+
element: 'span',
2264+
attributes: {
2265+
style: { color: 'rgb(255, 0, 0)' }
2266+
}
2267+
});
2268+
style.apply(editor);
2269+
2270+
const result = editor.value;
2271+
2272+
// 1. BASIC FUNCTIONALITY: Text color should change
2273+
expect(result).to.include('color: rgb(255, 0, 0)'); // Must apply red color
2274+
expect(result).to.include('text'); // Must preserve the text
2275+
2276+
// 2. STRUCTURE PRESERVATION: Should NOT create multiple divs (this was the bug)
2277+
expect(result).to.not.include('</div><div'); // Should not break structure
2278+
2279+
// 3. EXPECTED BEHAVIOR: Should create nested span instead
2280+
expect(result).to.include(
2281+
'<span>Test <span style="color: rgb(255, 0, 0);">text</span> here</span>'
2282+
);
2283+
});
2284+
2285+
it('Should change background-color but preserve HTML structure', function () {
2286+
const editor = getJodit();
2287+
2288+
// Same div with multiple styles
2289+
editor.value =
2290+
'<div style="font-family: Arial, sans-serif; background-color: yellow; color: blue; font-size: 14px;"><span>Test text here</span></div>';
2291+
2292+
const range = editor.s.createRange();
2293+
const spanElement = editor.editor.querySelector('span');
2294+
const textNode = spanElement.firstChild;
2295+
2296+
// Select "text" word
2297+
range.setStart(textNode, 5);
2298+
range.setEnd(textNode, 9);
2299+
editor.s.selectRange(range);
2300+
2301+
// Apply red background
2302+
const Style = Jodit.ns.CommitStyle;
2303+
const style = new Style({
2304+
element: 'span',
2305+
attributes: {
2306+
style: { 'background-color': 'rgb(255, 0, 0)' }
2307+
}
2308+
});
2309+
style.apply(editor);
2310+
2311+
const result = editor.value;
2312+
2313+
// Should NOT break structure for background-color either
2314+
expect(result).to.include(
2315+
'background-color: rgb(255, 0, 0)'
2316+
); // Must apply red background
2317+
expect(result).to.not.include('</div><div'); // Should not break structure
2318+
expect(result).to.include(
2319+
'<span>Test <span style="background-color: rgb(255, 0, 0);">text</span> here</span>'
2320+
);
2321+
});
2322+
2323+
it('Should change font-family but preserve HTML structure', function () {
2324+
const editor = getJodit();
2325+
2326+
// Same div with multiple styles
2327+
editor.value =
2328+
'<div style="font-family: Arial, sans-serif; background-color: yellow; color: blue; font-size: 14px;"><span>Test text here</span></div>';
2329+
2330+
const range = editor.s.createRange();
2331+
const spanElement = editor.editor.querySelector('span');
2332+
const textNode = spanElement.firstChild;
2333+
2334+
// Select "text" word
2335+
range.setStart(textNode, 5);
2336+
range.setEnd(textNode, 9);
2337+
editor.s.selectRange(range);
2338+
2339+
// Apply different font-family
2340+
const Style = Jodit.ns.CommitStyle;
2341+
const style = new Style({
2342+
element: 'span',
2343+
attributes: {
2344+
style: { 'font-family': 'Times, serif' }
2345+
}
2346+
});
2347+
style.apply(editor);
2348+
2349+
const result = editor.value;
2350+
2351+
// Should NOT break structure for font-family
2352+
expect(result).to.include('Times, serif'); // Must apply new font
2353+
expect(result).to.not.include('</div><div'); // Should not break structure
2354+
expect(result).to.include(
2355+
'<span>Test <span style="font-family: Times, serif;">text</span> here</span>'
2356+
);
2357+
});
2358+
2359+
it('Should change font-size but preserve HTML structure', function () {
2360+
const editor = getJodit();
2361+
2362+
// Same div with multiple styles
2363+
editor.value =
2364+
'<div style="font-family: Arial, sans-serif; background-color: yellow; color: blue; font-size: 14px;"><span>Test text here</span></div>';
2365+
2366+
const range = editor.s.createRange();
2367+
const spanElement = editor.editor.querySelector('span');
2368+
const textNode = spanElement.firstChild;
2369+
2370+
// Select "text" word
2371+
range.setStart(textNode, 5);
2372+
range.setEnd(textNode, 9);
2373+
editor.s.selectRange(range);
2374+
2375+
// Apply different font-size
2376+
const Style = Jodit.ns.CommitStyle;
2377+
const style = new Style({
2378+
element: 'span',
2379+
attributes: {
2380+
style: { 'font-size': '18px' }
2381+
}
2382+
});
2383+
style.apply(editor);
2384+
2385+
const result = editor.value;
2386+
2387+
// Should NOT break structure for font-size
2388+
expect(result).to.include('18px'); // Must apply new font size
2389+
expect(result).to.not.include('</div><div'); // Should not break structure
2390+
expect(result).to.include(
2391+
'<span>Test <span style="font-size: 18px;">text</span> here</span>'
2392+
);
2393+
});
2394+
});
2395+
2396+
describe('Case 2: Simple div should work correctly for any property', function () {
2397+
it('Should work correctly with simple div structure', function () {
2398+
const editor = getJodit();
2399+
2400+
// Set up the working HTML structure (no conflicting styles)
2401+
editor.value = '<div><span>Same text here</span></div>';
2402+
2403+
const range = editor.s.createRange();
2404+
const spanElement = editor.editor.querySelector('span');
2405+
const textNode = spanElement.firstChild;
2406+
2407+
// Select "text" word
2408+
range.setStart(textNode, 5);
2409+
range.setEnd(textNode, 9);
2410+
editor.s.selectRange(range);
2411+
2412+
// Apply red color using CommitStyle directly (core style system test)
2413+
const Style = Jodit.ns.CommitStyle;
2414+
const style = new Style({
2415+
element: 'span',
2416+
attributes: {
2417+
style: { color: 'rgb(255, 0, 0)' }
2418+
}
2419+
});
2420+
style.apply(editor);
2421+
2422+
const result = editor.value;
2423+
2424+
// This should work correctly (as mentioned in the issue)
2425+
expect(result).to.include(
2426+
'<div><span>Same <span style="color:'
2427+
); // Should create nested span
2428+
expect(result).to.include('text'); // Should preserve the text
2429+
expect(result).to.include('here</span></div>'); // Should preserve structure
2430+
});
2431+
});
2432+
});
22412433
});
22422434
});

src/core/selection/style/transactions.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,28 @@ export const transactions: IStyleTransactions = {
147147
const suit = suitableClosest(style, element, jodit.editor);
148148
assert(suit, 'This place should have an element');
149149

150-
if (!style.elementIsBlock) {
151-
extractSelectedPart(suit, element, jodit);
150+
// If we're applying inline styles to a block element, don't split the block
151+
const isApplyingInlineStyle =
152+
!style.elementIsBlock && style.options.attributes?.style;
153+
const shouldNotSplitBlock =
154+
isApplyingInlineStyle && Dom.isBlock(suit);
155+
156+
if (!shouldNotSplitBlock) {
157+
if (!style.elementIsBlock) {
158+
extractSelectedPart(suit, element, jodit);
159+
}
160+
161+
return {
162+
...value,
163+
element: suit,
164+
next: states.ELEMENT
165+
};
152166
}
153167

168+
// Create a new wrapper instead of splitting the block
154169
return {
155170
...value,
156-
element: suit,
157-
next: states.ELEMENT
171+
next: states.WRAP
158172
};
159173
}
160174
},

src/core/ui/popup/popup.less

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
@import (reference) '../../../styles/mixins';
99

1010
:root {
11-
--popup-box-shadow: 0 4px 1px -2px rgb(76 76 76 / 20%),
12-
0 3px 3px 0 rgb(76 76 76 / 15%), 0 1px 4px 0 rgb(76 76 76 / 13%);
11+
--popup-box-shadow:
12+
0 4px 1px -2px rgb(76 76 76 / 20%), 0 3px 3px 0 rgb(76 76 76 / 15%),
13+
0 1px 4px 0 rgb(76 76 76 / 13%);
1314
--popup-max-height: max(50vh, 350px);
1415
}
1516

src/modules/image-editor/image-editor.less

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@
8686
width: 100%;
8787
height: 100%;
8888
background-color: #eee;
89-
background-image: linear-gradient(
89+
background-image:
90+
linear-gradient(
9091
45deg,
9192
var(--color-border) 25%,
9293
transparent 25%,

src/modules/widget/tabs/tabs.less

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
@import (reference) '../../../styles/mixins';
99

1010
.jodit-tabs {
11-
--box-shadow-tabs: 0 0 #0000, 0 1px 3px 0 rgba(0, 0, 0, 0.1),
11+
--box-shadow-tabs:
12+
0 0 #0000, 0 1px 3px 0 rgba(0, 0, 0, 0.1),
1213
0 1px 2px -1px rgba(0, 0, 0, 0.1);
1314

1415
.font();

src/types/async.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export interface IAsync extends IDestructible {
8282
options: {
8383
delay?: number;
8484
priority?: 'background' | 'user-blocking' | 'user-visible';
85-
signal?: AbortSignal
85+
signal?: AbortSignal;
8686
}
8787
): Promise<T>;
8888
schedulerYield(): Promise<void>;

src/types/ui.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ export interface IUIGroup extends IUIElement {
102102
append(elm: IUIElement, index?: number): this;
103103
append(elm: IUIElement, distElement?: string): this;
104104
append(elms: IUIElement[], distElement?: string): this;
105-
append(elm: IUIElement | IUIElement[], distElementOrIndex?: string | number): this;
105+
append(
106+
elm: IUIElement | IUIElement[],
107+
distElementOrIndex?: string | number
108+
): this;
106109
remove(elm: IUIElement): this;
107110
clear(): this;
108111
}

0 commit comments

Comments
 (0)