Skip to content

Commit a9c94b7

Browse files
authored
Merge pull request #1864 from merico-dev/richtext-lineheight
feat(rich-text-editor): Add line height control
2 parents 8401285 + 9be2fab commit a9c94b7

File tree

13 files changed

+195
-5
lines changed

13 files changed

+195
-5
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## 15.58.9 (2025-12-09)
2+
3+
### 🚀 Features
4+
5+
- **rich-text-editor:** Add line height control ([e1ab982d7](https://github.com/merico-dev/table/commit/e1ab982d7))
6+
7+
### ❤️ Thank You
8+
9+
- ZeekoZhu
10+
11+
## v15.58.9 (2025-12-09)
12+
13+
### 🚀 Features
14+
15+
- **rich-text-editor:** Add line height control ([e1ab982d7](https://github.com/merico-dev/table/commit/e1ab982d7))
16+
17+
### ❤️ Thank You
18+
19+
- ZeekoZhu

api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/api",
3-
"version": "14.57.2",
3+
"version": "14.58.9",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/dashboard",
3-
"version": "14.58.8",
3+
"version": "14.58.9",
44
"license": "Apache-2.0",
55
"repository": {
66
"url": "https://github.com/merico-dev/table"

dashboard/src/components/widgets/rich-text-editor/custom-rich-text-editor.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
ClearInteractionBlockControl,
3131
InteractionBlockControl,
3232
} from './interaction-block-node/interaction-block-control';
33+
import { LineHeightControl, LineHeightMark } from './line-height-mark';
3334

3435
const RTEContentStyle: EmotionSx = {
3536
'dynamic-color': {
@@ -108,6 +109,7 @@ export const CustomRichTextEditor = forwardRef(
108109
TextStyle,
109110
Color,
110111
FontSize,
112+
LineHeightMark,
111113
DynamicColorMark,
112114
InteractionBlock,
113115
];
@@ -231,6 +233,7 @@ export const CustomRichTextEditor = forwardRef(
231233
</RichTextEditor.ControlsGroup>
232234

233235
<ChooseFontSize editor={editor} />
236+
<LineHeightControl editor={editor} />
234237
</RichTextEditor.Toolbar>
235238
<RichTextEditor.Content />
236239
</RichTextEditor>

dashboard/src/components/widgets/rich-text-editor/font-size-extension.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const FontSize = Extension.create<FontSizeOptions>({
5151
}
5252

5353
return {
54-
style: `font-size: ${attributes.fontSize}; line-height: 1.2; overflow: auto`,
54+
style: `font-size: ${attributes.fontSize}; overflow: auto`,
5555
};
5656
},
5757
},
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './line-height-mark';
2+
export * from './line-height-control';
3+
export * from './utils';
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { NativeSelect } from '@mantine/core';
2+
import { RichTextEditor } from '@mantine/tiptap';
3+
import { IconLineHeight } from '@tabler/icons-react';
4+
import { Editor } from '@tiptap/react';
5+
import { useTranslation } from 'react-i18next';
6+
import { LineHeightAttrKey, LineHeightName } from './line-height-mark';
7+
8+
const LINE_HEIGHTS = ['1', '1.2', '1.5', '1.75', '2', '2.5', '3'];
9+
const LineHeightOptions = [{ label: 'auto', value: '' }].concat(
10+
LINE_HEIGHTS.map((s) => ({
11+
label: s,
12+
value: s,
13+
})),
14+
);
15+
16+
const NativeSelectStyles = {
17+
input: {
18+
height: '26px',
19+
minHeight: '26px',
20+
lineHeight: '26px',
21+
borderColor: '#ced4da !important',
22+
},
23+
};
24+
25+
export const LineHeightControl = ({ editor }: { editor: Editor }) => {
26+
const { t } = useTranslation();
27+
const currentLineHeight = editor.getAttributes(LineHeightName)[LineHeightAttrKey];
28+
29+
return (
30+
<RichTextEditor.ControlsGroup>
31+
<NativeSelect
32+
size="xs"
33+
leftSection={<IconLineHeight stroke={1.5} size={16} />}
34+
data={LineHeightOptions}
35+
styles={NativeSelectStyles}
36+
value={currentLineHeight ? currentLineHeight : ''}
37+
title={t('rich_text.line_height.label')}
38+
onChange={(e) => {
39+
const v = e.currentTarget.value;
40+
if (!v) {
41+
editor.chain().focus().unsetLineHeight().run();
42+
} else {
43+
editor.chain().focus().setLineHeight(v).run();
44+
}
45+
}}
46+
/>
47+
</RichTextEditor.ControlsGroup>
48+
);
49+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Mark } from '@tiptap/core';
2+
import '@tiptap/extension-text-style';
3+
import { ensurePrefixOnID, getLineHeightID } from './utils';
4+
5+
declare module '@tiptap/core' {
6+
interface Commands<ReturnType> {
7+
lineHeight: {
8+
setLineHeight: (lineHeight: string) => ReturnType;
9+
unsetLineHeight: () => ReturnType;
10+
};
11+
}
12+
}
13+
14+
const AttrKey = 'data-line-height';
15+
export const LineHeightAttrKey = AttrKey;
16+
export const LineHeightName = 'lineHeight';
17+
18+
export const LineHeightMark = Mark.create({
19+
name: LineHeightName,
20+
21+
addAttributes() {
22+
return {
23+
id: {
24+
default: getLineHeightID(6),
25+
parseHTML: (element) => {
26+
const id = element.getAttribute('id');
27+
return ensurePrefixOnID(id);
28+
},
29+
},
30+
[AttrKey]: {
31+
default: null,
32+
parseHTML: (element) => element.getAttribute(AttrKey),
33+
},
34+
};
35+
},
36+
37+
parseHTML() {
38+
return [
39+
{
40+
tag: 'line-height',
41+
getAttrs: (node: string | HTMLElement) => {
42+
if (typeof node === 'string') {
43+
console.debug(node);
44+
return false;
45+
}
46+
47+
return [node.getAttribute(AttrKey)];
48+
},
49+
},
50+
];
51+
},
52+
53+
renderHTML({ HTMLAttributes }) {
54+
const lineHeight = HTMLAttributes[AttrKey];
55+
if (lineHeight) {
56+
// Apply line-height as inline style
57+
return [
58+
'span',
59+
{
60+
...HTMLAttributes,
61+
style: `line-height: ${lineHeight}`,
62+
},
63+
0,
64+
];
65+
}
66+
return ['span', HTMLAttributes, 0];
67+
},
68+
69+
addCommands() {
70+
return {
71+
setLineHeight:
72+
(lineHeight) =>
73+
({ commands }) => {
74+
return commands.setMark(this.name, {
75+
[AttrKey]: lineHeight,
76+
});
77+
},
78+
unsetLineHeight:
79+
() =>
80+
({ commands }) => {
81+
return commands.unsetMark(this.name);
82+
},
83+
};
84+
},
85+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const LineHeightIDPrefix = 'line_height_';
2+
export const IDPrefixReg = new RegExp(`^(?!${LineHeightIDPrefix})(.+)$`);
3+
4+
export function ensurePrefixOnID(id: string | null) {
5+
if (!id) {
6+
return id;
7+
}
8+
return id.replace(IDPrefixReg, `${LineHeightIDPrefix}$1`);
9+
}
10+
11+
export const getLineHeightID = (size: number) => {
12+
const MASK = 0x3d;
13+
const LETTERS = 'abcdefghijklmnopqrstuvwxyz';
14+
const NUMBERS = '1234567890';
15+
const charset = `${NUMBERS}${LETTERS}${LETTERS.toUpperCase()}`.split('');
16+
17+
const bytes = new Uint8Array(size);
18+
crypto.getRandomValues(bytes);
19+
20+
const id = bytes.reduce((acc, byte) => `${acc}${charset[byte & MASK]}`, '');
21+
return `${LineHeightIDPrefix}${id}`;
22+
};

dashboard/src/i18n/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,9 @@ export const en = {
667667
toggle_tooltip: 'Toggle interaction block settings',
668668
clear_tooltip: 'Clear interaction block',
669669
},
670+
line_height: {
671+
label: 'Line Height',
672+
},
670673
},
671674
chart: {
672675
chart_config: 'Chart Config',

0 commit comments

Comments
 (0)