Skip to content

Commit c1639ba

Browse files
committed
feat: block kit editor
1 parent 17921dd commit c1639ba

18 files changed

Lines changed: 1390 additions & 8789 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BLUE_6, GREEN_7, ORANGE_7, TEXT_1 } from "sketching-utils";
2+
import { GRAY_4 } from "sketching-utils/src/palette";
23

3-
import { GRAY_4 } from "../../../utils/src/palette";
44
import { formatListSerial } from "../utils/list";
55
import { BACKGROUND_OFFSET, DIVIDING_LINE_OFFSET, TEXT_ATTRS } from "./constant";
66
import type { Attributes, RichTextLine, TextMatrix, TextMatrixItem } from "./types";

packages/plugin/src/text/rich-text.ts

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
drawingStrikeThrough,
1010
drawingUnderline,
1111
getLineOffset,
12-
} from "./text-matrix";
12+
} from "./drawing";
1313
import type { Attributes, RichTextLines, TextMatrices, TextMatrix, TextMatrixItem } from "./types";
1414

1515
export class RichText {
@@ -44,8 +44,9 @@ export class RichText {
4444
public parse = (lines: RichTextLines, width: number) => {
4545
const group: TextMatrices = [];
4646
for (const line of lines) {
47-
const lineHeight =
48-
Number(line.config[TEXT_ATTRS.LINE_HEIGHT]) || DEFAULT[TEXT_ATTRS.LINE_HEIGHT];
47+
const lineHeight = Number(
48+
line.config[TEXT_ATTRS.LINE_HEIGHT] || DEFAULT[TEXT_ATTRS.LINE_HEIGHT]
49+
);
4950
const lineOffset = getLineOffset(line);
5051
const getDefaultMatrix = (): TextMatrix => ({
5152
items: [],
@@ -60,33 +61,36 @@ export class RichText {
6061
descent: 0,
6162
});
6263
let matrix: TextMatrix = getDefaultMatrix();
63-
for (const item of line.chars) {
64-
const { metric, font } = this.measure(item.char, item.config);
65-
if (!metric) continue;
66-
const text: TextMatrixItem = {
67-
char: item.char,
68-
font,
69-
config: item.config,
70-
width: metric.width,
71-
height: 0,
72-
ascent: metric.actualBoundingBoxAscent,
73-
descent: metric.actualBoundingBoxDescent,
74-
};
75-
if (matrix.width + text.width + lineOffset > width) {
76-
group.push(matrix);
77-
// 重置行`matrix`
78-
matrix = getDefaultMatrix();
79-
// 换行标记
80-
matrix.config[TEXT_ATTRS.BREAK_LINE_START] = TRULY;
64+
for (const fragment of line.chars) {
65+
for (const character of fragment.char) {
66+
const item = { char: character, config: fragment.config };
67+
const { metric, font } = this.measure(item.char, item.config);
68+
if (!metric) continue;
69+
const text: TextMatrixItem = {
70+
char: item.char,
71+
font,
72+
config: item.config,
73+
width: metric.width,
74+
height: 0,
75+
ascent: metric.actualBoundingBoxAscent,
76+
descent: metric.actualBoundingBoxDescent,
77+
};
78+
if (matrix.width + text.width + lineOffset > width) {
79+
group.push(matrix);
80+
// 重置行`matrix`
81+
matrix = getDefaultMatrix();
82+
// 换行标记
83+
matrix.config[TEXT_ATTRS.BREAK_LINE_START] = TRULY;
84+
}
85+
const fontHeight = metric.actualBoundingBoxAscent + metric.actualBoundingBoxDescent;
86+
text.height = fontHeight;
87+
matrix.originHeight = Math.max(matrix.originHeight, fontHeight);
88+
matrix.height = Math.max(matrix.height, fontHeight * lineHeight);
89+
matrix.width = matrix.width + text.width;
90+
matrix.ascent = Math.max(matrix.ascent, metric.actualBoundingBoxAscent);
91+
matrix.descent = Math.max(matrix.descent, metric.actualBoundingBoxDescent);
92+
matrix.items.push(text);
8193
}
82-
const fontHeight = metric.actualBoundingBoxAscent + metric.actualBoundingBoxDescent;
83-
text.height = fontHeight;
84-
matrix.originHeight = Math.max(matrix.originHeight, fontHeight);
85-
matrix.height = Math.max(matrix.height, fontHeight * lineHeight);
86-
matrix.width = matrix.width + text.width;
87-
matrix.ascent = Math.max(matrix.ascent, metric.actualBoundingBoxAscent);
88-
matrix.descent = Math.max(matrix.descent, metric.actualBoundingBoxDescent);
89-
matrix.items.push(text);
9094
}
9195
matrix.break = true;
9296
group.push(matrix);

packages/plugin/src/text/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
export type Attributes = Record<string, string>;
22

33
export type RichTextLine = {
4-
chars: { char: string; config: Attributes }[];
4+
chars: {
5+
/** 文本组 */
6+
char: string;
7+
/** 文本样式 */
8+
config: Attributes;
9+
}[];
510
config: Attributes;
611
};
712
export type RichTextLines = RichTextLine[];
@@ -15,6 +20,7 @@ export type TextMatrixItem = {
1520
ascent: number;
1621
descent: number;
1722
};
23+
1824
export type TextMatrix = {
1925
config: Attributes;
2026
items: TextMatrixItem[];
@@ -27,4 +33,5 @@ export type TextMatrix = {
2733
ascent: number;
2834
descent: number;
2935
};
36+
3037
export type TextMatrices = TextMatrix[];

packages/react/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
"license": "MIT",
2323
"dependencies": {
2424
"@arco-design/web-react": "2.60.3",
25-
"ahooks": "3.7.8",
26-
"doc-editor-core": "1.0.6",
27-
"doc-editor-delta": "1.0.6",
28-
"doc-editor-plugin": "1.0.6",
29-
"doc-editor-utils": "1.0.6",
25+
"@block-kit/core": "1.0.4",
26+
"@block-kit/delta": "1.0.4",
27+
"@block-kit/plugin": "1.0.4",
28+
"@block-kit/react": "1.0.4",
29+
"@block-kit/utils": "1.0.4",
3030
"react": "17.0.2",
3131
"react-dom": "17.0.2",
3232
"sketching-core": "workspace: *",

packages/react/src/components/header/components/left.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemoizedFn } from "ahooks";
1+
import { useMemoFn } from "@block-kit/utils/dist/es/hooks";
22
import type { FC } from "react";
33
import React, { useEffect, useState } from "react";
44
import type { Editor } from "sketching-core";
@@ -21,7 +21,7 @@ export const Left: FC<{
2121
}> = ({ editor }) => {
2222
const [active, setActive] = useState<string>(NAV_ENUM.DEFAULT);
2323

24-
const switchIndex = useMemoizedFn((index: string) => {
24+
const switchIndex = useMemoFn((index: string) => {
2525
if (index === active) return void 0;
2626
editor.canvas.grab.close();
2727
editor.canvas.insert.close();

packages/react/src/components/header/components/right.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button, Dropdown, InputNumber, Menu, Modal } from "@arco-design/web-react";
22
import type { RefInputType } from "@arco-design/web-react/es/Input";
33
import { IconDown, IconGithub, IconRedo, IconUndo } from "@arco-design/web-react/icon";
4-
import { useMemoizedFn } from "ahooks";
4+
import { useMemoFn } from "@block-kit/utils/dist/es/hooks";
55
import type { FC } from "react";
66
import { useEffect, useRef, useState } from "react";
77
import type { Editor } from "sketching-core";
@@ -23,7 +23,7 @@ export const Right: FC<{
2323
const widthRef = useRef<RefInputType>(null);
2424
const heightRef = useRef<RefInputType>(null);
2525

26-
const query = useMemoizedFn(() => {
26+
const query = useMemoFn(() => {
2727
setUndoAble(editor.history.canUndo());
2828
setRedoAble(editor.history.canRedo());
2929
});
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
import type { EditorSchema } from "doc-editor-core";
2-
import { DIVIDING_LINE_KEY } from "doc-editor-plugin";
1+
import type { EditorSchema } from "@block-kit/core";
32

43
export const schema: EditorSchema = {
5-
[DIVIDING_LINE_KEY]: {
6-
void: true,
7-
},
4+
"bold": { mark: true },
5+
"italic": { mark: true },
6+
"underline": { mark: true },
7+
"strike": { mark: true },
8+
"inline-code": { mark: true, inline: true },
9+
"link": { mark: true, inline: true },
10+
"color": { mark: true },
11+
"font-size": { mark: true },
12+
"background": { mark: true },
13+
"divider": { block: true, void: true },
814
};

packages/react/src/components/right-panel/components/text/index.tsx

Lines changed: 17 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,32 @@
1-
import "doc-editor-plugin/dist/styles/index";
2-
31
import { Modal } from "@arco-design/web-react";
42
import { IconLaunch } from "@arco-design/web-react/icon";
5-
import { useMemoizedFn } from "ahooks";
6-
import { Editable, useMakeEditor } from "doc-editor-core";
7-
import type { BaseNode, BlockElement } from "doc-editor-delta";
8-
import {
9-
BoldPlugin,
10-
FontBasePlugin,
11-
HeadingPlugin,
12-
HyperLinkPlugin,
13-
InlineCodePlugin,
14-
ItalicPlugin,
15-
LineHeightPlugin,
16-
MenuToolBar,
17-
OrderedListPlugin,
18-
ParagraphPlugin,
19-
QuoteBlockPlugin,
20-
ShortCutPlugin,
21-
StrikeThroughPlugin,
22-
UnderLinePlugin,
23-
UnorderedListPlugin,
24-
} from "doc-editor-plugin";
3+
import type { Delta as BlockDelta } from "@block-kit/delta";
254
import type { FC } from "react";
265
import React, { useMemo, useRef, useState } from "react";
27-
import type { DeltaState, Editor } from "sketching-core";
28-
import type { DeltaAttributes } from "sketching-delta";
29-
import { Op, OP_TYPE } from "sketching-delta";
6+
import type { DeltaState } from "sketching-core";
7+
import type { Editor } from "sketching-core";
8+
import type { RichTextLines } from "sketching-plugin";
309
import { TEXT_ATTRS } from "sketching-plugin";
31-
import { debounce, TSON } from "sketching-utils";
10+
import { TSON } from "sketching-utils";
3211

33-
import { useIsMounted } from "../../../../hooks/is-mounted";
3412
import styles from "../index.m.scss";
35-
import { schema } from "./config/schema";
36-
import { DividingLinePlugin } from "./plugins/dividing-line";
37-
import { blocksToLines } from "./slate-kit";
13+
import { RichTextEditor } from "./modules/editor";
14+
import { getDefaultTextDelta } from "./utils/constant";
15+
import { sketchToTextDelta } from "./utils/transform";
3816

3917
export const Text: FC<{ editor: Editor; state: DeltaState }> = ({ editor, state }) => {
40-
const { isMounted } = useIsMounted();
4118
const [visible, setVisible] = useState(false);
42-
const dataRef = useRef<BlockElement[]>([]);
43-
44-
useMemo(() => {
45-
const data = state.getAttr(TEXT_ATTRS.ORIGIN_DATA);
46-
const blocks = data && TSON.parse<BlockElement[]>(data);
47-
if (blocks) dataRef.current = blocks;
48-
else dataRef.current = [{ children: [{ text: "" }] }] as BlockElement[];
49-
}, [state]);
19+
const dataRef = useRef<BlockDelta | null>(null);
5020

51-
const richText = useMakeEditor(schema, dataRef.current);
5221
useMemo(() => {
53-
richText.plugin.register(
54-
ParagraphPlugin(),
55-
HeadingPlugin(richText),
56-
BoldPlugin(),
57-
QuoteBlockPlugin(richText),
58-
HyperLinkPlugin(richText, false),
59-
UnderLinePlugin(),
60-
StrikeThroughPlugin(),
61-
ItalicPlugin(),
62-
InlineCodePlugin(),
63-
OrderedListPlugin(richText),
64-
UnorderedListPlugin(richText),
65-
DividingLinePlugin(),
66-
FontBasePlugin(),
67-
LineHeightPlugin(),
68-
ShortCutPlugin(richText)
69-
);
70-
}, [richText]);
71-
72-
const onChange = useMemoizedFn((attrs: DeltaAttributes) => {
73-
// COMPAT: 避免初始化时即触发`onChange`
74-
if (!isMounted()) return void 0;
75-
let allEqual = true;
76-
for (const [key, value] of Object.entries(attrs)) {
77-
if (state.getAttr(key) !== value) {
78-
allEqual = false;
79-
break;
80-
}
22+
const data = state.getAttr(TEXT_ATTRS.DATA);
23+
const blockDelta = data && TSON.parse<RichTextLines>(data);
24+
if (blockDelta) {
25+
dataRef.current = sketchToTextDelta(blockDelta);
26+
} else {
27+
dataRef.current = getDefaultTextDelta();
8128
}
82-
if (allEqual) return void 0;
83-
editor.state.apply(new Op(OP_TYPE.REVISE, { id: state.id, attrs }));
84-
});
85-
86-
const updateText = useMemo(
87-
() =>
88-
debounce((text: BaseNode[]) => {
89-
dataRef.current = text as BlockElement[];
90-
// fix: 切换面板会重建`Editable`需要更新初始值
91-
richText.init = dataRef.current;
92-
// 双写-空间换时间
93-
// @ts-expect-error BlockElement
94-
if (text.length === 1 && text[0].children.length === 1 && !text[0].children[0].text) {
95-
onChange({ [TEXT_ATTRS.DATA]: null, [TEXT_ATTRS.ORIGIN_DATA]: null });
96-
} else {
97-
onChange({
98-
[TEXT_ATTRS.DATA]: TSON.stringify(blocksToLines(richText, text as BlockElement[])),
99-
[TEXT_ATTRS.ORIGIN_DATA]: TSON.stringify(text),
100-
});
101-
}
102-
}, 300),
103-
[onChange, richText]
104-
);
29+
}, [state]);
10530

10631
const TextEditor = (
10732
<React.Fragment key={state.id}>
@@ -111,10 +36,7 @@ export const Text: FC<{ editor: Editor; state: DeltaState }> = ({ editor, state
11136
<IconLaunch className={styles.launch} onClick={() => setVisible(true)} />
11237
</div>
11338
)}
114-
<div onClick={e => e.stopPropagation()}>
115-
<MenuToolBar readonly={false} editor={richText}></MenuToolBar>
116-
</div>
117-
<Editable editor={richText} onChange={updateText} placeholder="Enter text ..."></Editable>
39+
<RichTextEditor editor={editor} state={state} dataRef={dataRef}></RichTextEditor>
11840
</React.Fragment>
11941
);
12042

0 commit comments

Comments
 (0)