• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

worktile / slate-angular / 481f38a4-0122-4a38-ac16-5467b1fdb7c2

15 Dec 2025 03:04AM UTC coverage: 38.108% (-8.7%) from 46.809%
481f38a4-0122-4a38-ac16-5467b1fdb7c2

push

circleci

pubuzhixing8
build: release 20.2.0-next.12

386 of 1205 branches covered (32.03%)

Branch coverage included in aggregate %.

1072 of 2621 relevant lines covered (40.9%)

24.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

33.54
/packages/src/plugins/with-angular.ts
1
import { Editor, Element, Node, Operation, Path, PathRef, Range, Transforms } from 'slate';
2
import { ClipboardData, OriginEvent } from '../types/clipboard';
3
import { SlateError } from '../types/error';
4
import { completeTable, isInvalidTable } from '../utils';
5
import { getClipboardData, setClipboardData } from '../utils/clipboard/clipboard';
6
import { AngularEditor } from './angular-editor';
7
import { Key, NODE_TO_KEY, withDOM } from 'slate-dom';
8
import { getPlainText, isDOMText } from '../utils/dom';
9

10
export const withAngular = <T extends Editor>(editor: T, clipboardFormatKey = 'x-slate-fragment') => {
1✔
11
    let e = editor as T & AngularEditor;
28✔
12
    let { apply } = e;
28✔
13

14
    e = withDOM(e, clipboardFormatKey);
28✔
15

16
    e.setFragmentData = (dataTransfer?: Pick<DataTransfer, 'getData' | 'setData'>, originEvent?: OriginEvent) => {
28✔
17
        const { selection } = e;
×
18

19
        if (!selection) {
×
20
            return;
×
21
        }
22

23
        const [start, end] = Range.edges(selection);
×
24
        const startVoid = Editor.void(e, { at: start.path });
×
25
        const endVoid = Editor.void(e, { at: end.path });
×
26

27
        if (Range.isCollapsed(selection) && !startVoid) {
×
28
            return;
×
29
        }
30

31
        // Create a fake selection so that we can add a Base64-encoded copy of the
32
        // fragment to the HTML, to decode on future pastes.
33
        const domRange = AngularEditor.toDOMRange(e, selection);
×
34
        let contents = domRange.cloneContents();
×
35
        let attach = contents.childNodes[0] as HTMLElement;
×
36

37
        // Make sure attach is non-empty, since empty nodes will not get copied.
38
        const contentsArray = Array.from(contents.children);
×
39
        contentsArray.forEach(node => {
×
40
            if (node.textContent && node.textContent.trim() !== '') {
×
41
                attach = node as HTMLElement;
×
42
            }
43
        });
44

45
        // COMPAT: If the end node is a void node, we need to move the end of the
46
        // range from the void node's spacer span, to the end of the void node's
47
        // content, since the spacer is before void's content in the DOM.
48
        if (endVoid) {
×
49
            const [voidNode] = endVoid;
×
50
            const r = domRange.cloneRange();
×
51
            const domNode = AngularEditor.toDOMNode(e, voidNode);
×
52
            r.setEndAfter(domNode);
×
53
            contents = r.cloneContents();
×
54
        }
55

56
        // COMPAT: If the start node is a void node, we need to attach the encoded
57
        // fragment to the void node's content node instead of the spacer, because
58
        // attaching it to empty `<div>/<span>` nodes will end up having it erased by
59
        // most browsers. (2018/04/27)
60
        if (startVoid) {
×
61
            attach = contents.querySelector('[data-slate-spacer]')! as HTMLElement;
×
62
        }
63

64
        // Remove any zero-width space spans from the cloned DOM so that they don't
65
        // show up elsewhere when pasted.
66
        Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
×
67
            const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
×
68
            zw.textContent = isNewline ? '\n' : '';
×
69
        });
70

71
        // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
72
        // in the HTML, and can be used for intra-Slate pasting. If it's a text
73
        // node, wrap it in a `<span>` so we have something to set an attribute on.
74
        if (isDOMText(attach)) {
×
75
            const span = attach.ownerDocument.createElement('span');
×
76
            // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
77
            // then leading and trailing spaces will be ignored. (2017/09/21)
78
            span.style.whiteSpace = 'pre';
×
79
            span.appendChild(attach);
×
80
            contents.appendChild(span);
×
81
            attach = span;
×
82
        }
83

84
        const fragment = e.getFragment();
×
85

86
        // Add the content to a <div> so that we can get its inner HTML.
87
        const div = contents.ownerDocument.createElement('div');
×
88
        const attachWrapper = document.createElement('div');
×
89
        const elements = Array.from(contents.children);
×
90
        if (isInvalidTable(elements)) {
×
91
            contents = completeTable(contents.cloneNode(true) as DocumentFragment);
×
92
        }
93

94
        attachWrapper.appendChild(contents);
×
95
        div.appendChild(attachWrapper);
×
96
        div.setAttribute('hidden', 'true');
×
97
        contents.ownerDocument.body.appendChild(div);
×
98
        setClipboardData({ text: getPlainText(div), elements: fragment as Element[] }, div, attachWrapper, dataTransfer);
×
99
        contents.ownerDocument.body.removeChild(div);
×
100
    };
101

102
    e.deleteCutData = () => {
28✔
103
        const { selection } = editor;
×
104
        if (selection) {
×
105
            if (Range.isExpanded(selection)) {
×
106
                Editor.deleteFragment(editor);
×
107
            } else {
108
                const node = Node.parent(editor, selection.anchor.path);
×
109
                if (Element.isElement(node) && Editor.isVoid(editor, node)) {
×
110
                    Transforms.delete(editor);
×
111
                }
112
            }
113
        }
114
    };
115

116
    e.insertData = async (data: DataTransfer) => {
28✔
117
        if (!(await e.customInsertFragmentData(data, null))) {
×
118
            e.insertTextData(data);
×
119
        }
120
    };
121

122
    e.customInsertFragmentData = async (data: DataTransfer, contextClipboardData: ClipboardData): Promise<boolean> => {
28✔
123
        /**
124
         * Checking copied fragment from application/x-slate-fragment or data-slate-fragment
125
         */
126
        const clipboardData = contextClipboardData || (await getClipboardData(data));
×
127
        if (clipboardData && clipboardData.elements) {
×
128
            e.insertFragment(clipboardData.elements);
×
129
            return true;
×
130
        }
131
        return false;
×
132
    };
133

134
    e.customInsertTextData = async (data: DataTransfer): Promise<boolean> => {
28✔
135
        const clipboardData = await getClipboardData(data);
×
136

137
        if (clipboardData && clipboardData.text) {
×
138
            const lines = clipboardData.text.split(/\r\n|\r|\n/);
×
139
            let split = false;
×
140

141
            for (const line of lines) {
×
142
                if (split) {
×
143
                    Transforms.splitNodes(e, { always: true });
×
144
                }
145

146
                e.insertText(line);
×
147
                split = true;
×
148
            }
149
            return true;
×
150
        }
151
        return false;
×
152
    };
153

154
    e.onKeydown = () => {};
28✔
155

156
    e.onClick = () => {};
28✔
157

158
    e.isBlockCard = element => false;
364✔
159

160
    e.isExpanded = element => true;
215✔
161

162
    e.onError = (errorData: SlateError) => {
28✔
163
        if (errorData.nativeError) {
×
164
            console.error(errorData.nativeError);
×
165
        } else {
166
            console.error(errorData);
×
167
        }
168
    };
169

170
    // exist issue for move operation in withDOM
171
    e.apply = (op: Operation) => {
28✔
172
        const matches: [Path | PathRef, Key][] = [];
24✔
173

174
        switch (op.type) {
24✔
175
            case 'insert_text':
176
            case 'remove_text':
177
            case 'set_node': {
178
                for (const [node, path] of Editor.levels(e, { at: op.path })) {
3✔
179
                    const key = AngularEditor.findKey(e, node);
9✔
180
                    matches.push([path, key]);
9✔
181
                }
182

183
                break;
3✔
184
            }
185

186
            case 'insert_node':
187
            case 'remove_node':
188
            case 'merge_node':
189
            case 'split_node': {
190
                for (const [node, path] of Editor.levels(e, {
5✔
191
                    at: Path.parent(op.path)
192
                })) {
193
                    const key = AngularEditor.findKey(e, node);
7✔
194
                    matches.push([path, key]);
7✔
195
                }
196

197
                break;
5✔
198
            }
199

200
            case 'move_node': {
201
                const commonPath = Path.common(Path.parent(op.path), Path.parent(op.newPath));
8✔
202
                for (const [node, path] of Editor.levels(e, {
8✔
203
                    at: Path.parent(op.path)
204
                })) {
205
                    const key = AngularEditor.findKey(e, node);
14✔
206
                    matches.push([Editor.pathRef(editor, path), key]);
14✔
207
                }
208
                for (const [node, path] of Editor.levels(e, {
8✔
209
                    at: Path.parent(op.newPath)
210
                })) {
211
                    if (path.length > commonPath.length) {
16✔
212
                        const key = AngularEditor.findKey(e, node);
5✔
213
                        matches.push([Editor.pathRef(editor, path), key]);
5✔
214
                    }
215
                }
216
                break;
8✔
217
            }
218
        }
219

220
        apply(op);
24✔
221

222
        for (const [source, key] of matches) {
24✔
223
            const [node] = Editor.node(e, Path.isPath(source) ? source : source.current);
35✔
224
            NODE_TO_KEY.set(node, key);
35✔
225
        }
226
    };
227

228
    e.selectAll = () => {
28✔
229
        Transforms.select(e, []);
×
230
    };
231

232
    e.isVisible = element => {
28✔
233
        return true;
×
234
    };
235

236
    return e;
28✔
237
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc