Skip to content

Commit a895497

Browse files
committed
fix: list-style-type parsing from and to html
1 parent 013a2c2 commit a895497

File tree

8 files changed

+91
-4
lines changed

8 files changed

+91
-4
lines changed

packages/pluggableWidgets/rich-text-web/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
99
### Fixed
1010

1111
- We fixed an issue where rich text table failed to properly functions if the widget is placed inside a table structure.
12+
- We fixed an issue where rich text list-style-type format incorrectly parsed.
1213

1314
## [4.8.0] - 2025-07-01
1415

packages/pluggableWidgets/rich-text-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@mendix/rich-text-web",
33
"widgetName": "RichText",
4-
"version": "4.8.0",
4+
"version": "4.8.1",
55
"description": "Rich inline or toolbar text editing",
66
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
77
"license": "Apache-2.0",

packages/pluggableWidgets/rich-text-web/src/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<package xmlns="http://www.mendix.com/package/1.0/">
3-
<clientModule name="RichText" version="4.8.0" xmlns="http://www.mendix.com/clientModule/1.0/">
3+
<clientModule name="RichText" version="4.8.1" xmlns="http://www.mendix.com/clientModule/1.0/">
44
<widgetFiles>
55
<widgetFile path="RichText.xml" />
66
</widgetFiles>

packages/pluggableWidgets/rich-text-web/src/ui/RichText.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ $rte-brand-primary: #264ae5;
124124
}
125125
}
126126

127+
.ql-editor li[data-list="ordered"][data-custom-list="lower-roman"] {
128+
counter-increment: custom-list-roman;
129+
& > .ql-ui:before {
130+
content: counter(custom-list-roman, lower-roman) ". ";
131+
}
132+
}
133+
127134
.widget-rich-text-prompt {
128135
position: absolute;
129136
top: 0;

packages/pluggableWidgets/rich-text-web/src/utils/MxQuill.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import Quill, { EmitterSource, QuillOptions } from "quill";
4141
import TextBlot, { escapeText } from "quill/blots/text";
4242
import { Delta, Op } from "quill/core";
4343
import Editor from "quill/core/editor";
44+
import { STANDARD_LIST_TYPES } from "./formats/customList";
4445

4546
interface ListItem {
4647
child: Blot;
@@ -109,6 +110,16 @@ const ListSequence = ["ordered", "lower-alpha", "lower-roman"];
109110

110111
// construct proper "list-style-type" style attribute
111112
function getExpectedType(type: string | undefined, indent: number): string {
113+
// bullet is standard list type, convert to disc for correct css display
114+
if (type === "bullet") {
115+
return "disc";
116+
}
117+
118+
// custom list type is not dependant on indent level, use as is
119+
const isCustomList = !STANDARD_LIST_TYPES.find(x => x === type);
120+
if (isCustomList && type) {
121+
return type;
122+
}
112123
const currentIndex = ListSequence.indexOf(type || "ordered");
113124
const expectedIndex = (currentIndex + indent) % 3;
114125
const expectedType = ListSequence[expectedIndex] ?? type;

packages/pluggableWidgets/rich-text-web/src/utils/customPluginRegisters.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import QuillResize from "quill-resize-module";
1616
import QuillTableBetter from "./formats/quill-table-better/quill-table-better";
1717
import MxUploader from "./modules/uploader";
1818
import MxBlock from "./formats/block";
19-
19+
import CustomClipboard from "./modules/clipboard";
2020
class Empty {
2121
doSomething(): string {
2222
return "";
@@ -43,3 +43,4 @@ Quill.register("modules/resize", QuillResize, true);
4343
// add empty handler for view code, this format is handled by toolbar's custom config via ViewCodeDialog
4444
Quill.register({ "ui/view-code": Empty });
4545
Quill.register({ "modules/table-better": QuillTableBetter }, true);
46+
Quill.register({ "modules/clipboard": CustomClipboard }, true);

packages/pluggableWidgets/rich-text-web/src/utils/formats/customList.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,26 @@ import "./customList.scss";
33
/**
44
* adding custom list item, alowing extra list style
55
*/
6+
7+
export const STANDARD_LIST_TYPES = ["ordered", "checked", "unchecked", "bullet"];
8+
69
export default class CustomListItem extends ListItem {
10+
format(name: string, value: string): void {
11+
if (name === this.statics.blotName && value) {
12+
if (!STANDARD_LIST_TYPES.find(x => x === value)) {
13+
this.domNode.setAttribute("data-custom-list", value);
14+
this.domNode.setAttribute("data-list", "ordered");
15+
} else {
16+
this.domNode.setAttribute("data-list", value);
17+
}
18+
} else {
19+
super.format(name, value);
20+
}
21+
}
22+
723
static create(value: any): any {
824
const node = super.create(value) as HTMLElement;
9-
if (!["ordered", "checked", "unchecked", "bullet"].find(x => x === value)) {
25+
if (!STANDARD_LIST_TYPES.find(x => x === value)) {
1026
node.setAttribute("data-custom-list", value);
1127
node.setAttribute("data-list", "ordered");
1228
} else {
@@ -15,4 +31,8 @@ export default class CustomListItem extends ListItem {
1531
node.setAttribute("title", this.blotName);
1632
return node;
1733
}
34+
35+
static formats(domNode: HTMLElement): string | undefined {
36+
return domNode.dataset.customList || domNode.dataset.list || undefined;
37+
}
1838
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import Quill, { Delta } from "quill";
2+
import Clipboard from "quill/modules/clipboard";
3+
4+
export default class CustomClipboard extends Clipboard {
5+
constructor(quill: Quill, options: any) {
6+
super(quill, options);
7+
8+
// remove default list matchers for ol and ul
9+
this.matchers = this.matchers.filter(matcher => matcher[0] !== "ol, ul");
10+
11+
// add custom list matchers for ol and ul to allow custom list types (lower-alpha, lower-roman, etc.)
12+
this.addMatcher("ul, ol", (node, delta) => {
13+
const format = "list";
14+
let list = "ordered";
15+
const element = node as HTMLUListElement;
16+
const checkedAttr = element.getAttribute("data-checked");
17+
if (checkedAttr) {
18+
list = checkedAttr === "true" ? "checked" : "unchecked";
19+
} else {
20+
const listStyleType = element.style.listStyleType;
21+
if (listStyleType) {
22+
if (listStyleType === "disc") {
23+
// disc is standard list type, convert to bullet
24+
list = "bullet";
25+
} else if (listStyleType === "decimal") {
26+
// list decimal type is dependant on indent level, convert to standard ordered list
27+
list = "ordered";
28+
} else {
29+
list = listStyleType;
30+
}
31+
} else {
32+
list = element.tagName === "OL" ? "ordered" : "bullet";
33+
}
34+
}
35+
36+
return delta.reduce((newDelta, op) => {
37+
if (!op.insert) return newDelta;
38+
if (op.attributes && op.attributes[format]) {
39+
return newDelta.push(op);
40+
}
41+
const formats = list ? { [format]: list } : {};
42+
43+
return newDelta.insert(op.insert, { ...formats, ...op.attributes });
44+
}, new Delta());
45+
});
46+
}
47+
}

0 commit comments

Comments
 (0)