Skip to content

Commit ec4615a

Browse files
authored
feat(rich-text-input): support admin center rich text (#1110)
* feat(rich-text-input): support admin center rich text * chore: use lodash * chore: fixup * chore: add test
1 parent f9c60a5 commit ec4615a

File tree

4 files changed

+181
-1
lines changed

4 files changed

+181
-1
lines changed

src/components/internals/rich-text-utils/html.js src/components/internals/rich-text-utils/html/html.js

+74-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
/* eslint-disable consistent-return */
22
import React from 'react';
33
import Html from 'slate-html-serializer';
4-
import { MARK_TAGS, BLOCK_TAGS } from './tags';
4+
import flatMap from 'lodash/flatMap';
5+
import { MARK_TAGS, BLOCK_TAGS } from '../tags';
6+
7+
const mapper = {
8+
'font-weight': {
9+
bold: 'strong',
10+
},
11+
'text-decoration-line': {
12+
underline: 'u',
13+
'line-through': 'del',
14+
},
15+
'text-decoration': {
16+
underline: 'u',
17+
},
18+
'font-style': {
19+
italic: 'em',
20+
},
21+
'vertical-align': {
22+
sub: 'sub',
23+
super: 'sup',
24+
},
25+
};
526

627
const rules = [
728
{
@@ -52,6 +73,56 @@ const rules = [
5273
}
5374
},
5475
},
76+
77+
{
78+
// Special case for code blocks, which need to grab the nested childNodes.
79+
deserialize(el, next) {
80+
if (el.tagName.toLowerCase() === 'span') {
81+
const styleAttribute = el.getAttribute('style');
82+
let tagName = 'span';
83+
const childNode = el.childNodes[0];
84+
85+
if (styleAttribute) {
86+
const marks = flatMap(styleAttribute.split(';'), val => {
87+
const split = val.trim().split(' ');
88+
89+
const [key, ...values] = split;
90+
91+
return values.map(value => ({
92+
// always remove the : from the key
93+
[key.slice(0, -1)]: value,
94+
}));
95+
})
96+
.map(val => {
97+
const [key, value] = Object.entries(val)[0];
98+
return mapper[key] && mapper[key][value];
99+
})
100+
.filter(val => Boolean(val));
101+
102+
let deepestNode = el;
103+
104+
if (marks && marks.length > 0) {
105+
tagName = marks[0];
106+
107+
marks.forEach(mark => {
108+
deepestNode.removeChild(childNode);
109+
const newNode = document.createElement(mark);
110+
newNode.appendChild(childNode);
111+
deepestNode.appendChild(newNode);
112+
deepestNode = newNode;
113+
});
114+
}
115+
}
116+
117+
return {
118+
object: 'mark',
119+
type: MARK_TAGS[tagName],
120+
nodes: next(el.childNodes),
121+
};
122+
}
123+
},
124+
},
125+
55126
// Add a new rule that handles marks...
56127
{
57128
deserialize(el, next) {
@@ -68,6 +139,8 @@ const rules = [
68139
if (obj.object === 'mark') {
69140
// eslint-disable-next-line default-case
70141
switch (obj.type) {
142+
case 'span':
143+
return <span>{children}</span>;
71144
case 'bold':
72145
return <strong>{children}</strong>;
73146
case 'italic':
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { JSDOM } from 'jsdom';
2+
import html from './html';
3+
4+
const dom = new JSDOM();
5+
global.document = dom.window.document;
6+
global.window = dom.window;
7+
8+
describe('html', () => {
9+
describe('deserialize', () => {
10+
describe('with empty value', () => {
11+
it('should return same value', () => {
12+
const htmlValue = '<p></p>';
13+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(htmlValue);
14+
});
15+
});
16+
describe('with inline span styles', () => {
17+
describe('with no styles', () => {
18+
it('should properly serialize', () => {
19+
const htmlValue = '<span>hello</span>';
20+
21+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
22+
'<p><span>hello</span></p>'
23+
);
24+
});
25+
});
26+
describe('with one known value', () => {
27+
it('should properly serialize', () => {
28+
const htmlValue = '<span style="font-weight: bold;">hello</span>';
29+
30+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
31+
'<p><strong>hello</strong></p>'
32+
);
33+
});
34+
});
35+
describe('with one unknown value', () => {
36+
it('should properly serialize', () => {
37+
const htmlValue = '<span style="display: block;">hello</span>';
38+
39+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
40+
'<p><span>hello</span></p>'
41+
);
42+
});
43+
});
44+
describe('with one unknown value and one known value', () => {
45+
it('should properly serialize', () => {
46+
const htmlValue =
47+
'<span style="display: block; text-decoration: underline;">hello</span>';
48+
49+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
50+
'<p><u>hello</u></p>'
51+
);
52+
});
53+
});
54+
describe('with two known values', () => {
55+
it('should properly serialize', () => {
56+
const htmlValue =
57+
'<span style="font-weight: bold; text-decoration: underline;">hello</span>';
58+
59+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
60+
'<p><strong><u>hello</u></strong></p>'
61+
);
62+
});
63+
});
64+
describe('with three known values', () => {
65+
it('should properly serialize', () => {
66+
const htmlValue =
67+
'<span style="font-weight: bold; font-style: italic; text-decoration: underline;">hello</span>';
68+
69+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
70+
'<p><strong><em><u>hello</u></em></strong></p>'
71+
);
72+
});
73+
});
74+
describe('with four known values', () => {
75+
it('should properly serialize', () => {
76+
const htmlValue =
77+
'<span style="font-weight: bold; font-style: italic; text-decoration-line: underline line-through;">hello</span>';
78+
79+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
80+
'<p><strong><em><u><del>hello</del></u></em></strong></p>'
81+
);
82+
});
83+
});
84+
describe('with line breaks', () => {
85+
it('should properly serialize', () => {
86+
const htmlValue =
87+
'<p><span style="text-decoration-line: underline;">Underline</span></p><p><span style="text-decoration-line: line-through;">Strikethrough</span></p>';
88+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
89+
'<p><u>Underline</u></p><p><del>Strikethrough</del></p>'
90+
);
91+
});
92+
});
93+
describe('mixed', () => {
94+
it('should properly serialize', () => {
95+
const htmlValue =
96+
'<span style="font-weight: bold"><span style="font-style: italic;">hello</span></span>';
97+
98+
expect(html.serialize(html.deserialize(htmlValue))).toEqual(
99+
'<p><strong><em>hello</em></strong></p>'
100+
);
101+
});
102+
});
103+
});
104+
});
105+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './html';

src/components/internals/rich-text-utils/tags.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ export const MARK_TAGS = {
2020
sup: 'superscript',
2121
sub: 'subscript',
2222
del: 'strikethrough',
23+
span: 'span',
2324
};

0 commit comments

Comments
 (0)