# 插件机制

插件作为 Slate 一等公民,具有能够完全修改编辑器行为的能力。

这里我们通过官方提供的 slate-history 来介绍 slate 的插件原理。

# 插件原理

# 实现插件

/**
 * withHistory 插件使用 撤消(redo) 和 重做(undo) 堆栈来跟踪对 Slate编辑器 执行操作时的操作历史记录。
 */
export const withHistory = <T extends Editor>(editor: T) => {
    const e = editor as T & HistoryEditor;
    const {apply} = e;
    // ...

    e.redo = () => {
        // ...
    };

    e.undo = () => {
        // ...
    };

    e.apply = (op: Operation) => {
        // ...
        apply(op);
    };

    return e;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 使用插件

import React, {useState, useCallback, useMemo} from 'react';
import {Slate, Editable, withReact} from 'slate-react';
import {Editor, Range, Point, Node, createEditor} from 'slate';
import {withHistory} from 'slate-history';

const TablesExample = () => {
    const [value, setValue] = useState<Node[]>(initialValue);
    const renderElement = useCallback(props => <Element {...props} />, []);
    const renderLeaf = useCallback(props => <Leaf {...props} />, []);
    const editor = useMemo(
		() => withTables(
			withHistory(
				withReact(createEditor())
			)
		),
		[]
	);
    return (
		<Slate
			editor={editor}
			value={value}
			onChange={value => setValue(value)}
		>
			<Editable
				renderElement={renderElement}
				renderLeaf={renderLeaf}
			/>
        </Slate>
    );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Slate 插件是一个返回 editor 实例的函数,在这个插件函数中我们可以调用编辑器自身的能力,这样就能完全控制编辑器行为,所以我们可以使用插件来定制我们想要的编辑器。

# 人间惨案

由于目前我们使用的版本还是slate@0.47.x, 它的插件原理是基于 middleware 的设计,绑定在editor上。

function registerPlugin(editor, plugin) {
    if (Array.isArray(plugin)) {
        plugin.forEach(p => registerPlugin(editor, p));
        return;
    }

    if (plugin == null) {
        return;
    }

    const {commands, queries, schema, ...rest} = plugin;

    if (commands) {
        const commandsPlugin = CommandsPlugin(commands);
        registerPlugin(editor, commandsPlugin);
    }

    if (queries) {
        const queriesPlugin = QueriesPlugin(queries);
        registerPlugin(editor, queriesPlugin);
    }

    if (schema) {
        const schemaPlugin = SchemaPlugin(schema);
        registerPlugin(editor, schemaPlugin);
    }

    for (const key in rest) {
        const fn = rest[key];
        const middleware = (editor.middleware[key] = editor.middleware[key] || []);
        middleware.push(fn);
    }
}
class Editor {
    constructor(attrs = {}, options = {}) {
        const {controller = this, construct = true} = options;
        const {plugins = []} = attrs;

        this.middleware = {};
        // ...

        this.registerPlugins(plugins);

        // ...
    }

    registerPlugins(plugins) {
        const core = CorePlugin({plugins});
        registerPlugin(this, core);
    }
    run(key, ...args) {
        const {controller, middleware} = this;
        const fns = middleware[key] || [];
        let i = 0;

        function next(...overrides) {
            const fn = fns[i++];
            if (!fn) {
                return;
            }

            if (overrides.length) {
                args = overrides;
            }

            const ret = fn(...args, controller, next);
            return ret;
        }
        // debug warning
        return next();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# 实现

function MyPlugin(options) {
    return {
        onKeyDown(t) {},
        onClick() {},
        renderBlock() {},
        renderInline() {},
        commands: {},
        queries: {},
        schema: {},
    };
}
1
2
3
4
5
6
7
8
9
10
11

# 使用

import {Value} from 'slate';
import {Editor} from 'slate-react';
import * as React from 'react';

const initialValue = {
    object: 'value',
    document: {
        object: 'document',
        nodes: [
            {
                object: 'block',
                type: 'paragraph',
                nodes: [
                    {
                        object: 'text',
                        text:
                            'Slate editors save all changes to an internal "history" automatically, so you don\'t need to implement undo/redo yourself. And the editor automatically binds to the browser\'s default undo/redo keyboard shortcuts.',
                    },
                ],
            },
            {
                object: 'block',
                type: 'paragraph',
                nodes: [
                    {
                        object: 'text',
                        text:
                            'Try it out for yourself! Make any changes you\'d like then press "cmd+z".',
                    },
                ],
            },
        ],
    },
};
class History extends React.Component {
    state = {
        value: Value.fromJSON(initialValue),
    };

    ref = editor => {
        this.editor = editor;
    };

    render() {
        const {value} = this.state;
        const {data} = value;
        const plugins = [MyPlugin];
        return (
            <div>
                <Editor
                    placeholder='Enter some text...'
                    ref={this.ref}
                    plugins={plugins}
                    value={this.state.value}
                    onChange={this.onChange}
                />
            </div>
        );
    }
    onChange = change => {
        this.setState({value: change.value});
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 如何注册自定义插件

# lastest

export const withHistory = <T extends Editor>(editor: T) => {
    const e = editor as T & HistoryEditor;
    const {apply} = e;
    e.apply = (op: Operation) => {
        // ...
        apply(op);
    };
    return e;
};
1
2
3
4
5
6
7
8
9

# 0.47.x

<Editor
    plugins={[
        {
            custom: (editor, next) => {
                console.log(1);
                next();
            }
        },
        {
            custom: (editor, next) => {
                console.log(2);
            }
        },
        {
            custom: (editor, next) => {
                console.log(3);
            }
        }
    ]}
/>;
// 使用
editor.run('custom'); // 输出 1 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23