• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

worktile / slate-angular / 6659122c-e261-4b71-8a4c-31a76811ea45

pending completion
6659122c-e261-4b71-8a4c-31a76811ea45

Pull #226

circleci

pubuzhixing8
chore(core): update note
Pull Request #226: Android input handing

268 of 892 branches covered (30.04%)

Branch coverage included in aggregate %.

46 of 46 new or added lines in 2 files covered. (100.0%)

682 of 1526 relevant lines covered (44.69%)

29.63 hits per line

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

32.56
/packages/src/plugins/with-angular.ts
1
import { Editor, Node, Transforms, Range, Path, Operation, PathRef } from 'slate';
2
import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY, isDOMText, getPlainText, Key, getSlateFragmentAttribute } from '../utils';
3
import { AngularEditor } from './angular-editor';
4
import { SlateError } from '../types/error';
5
import { findCurrentLineRange } from '../utils/lines';
6

7
export const withAngular = <T extends Editor>(editor: T, clipboardFormatKey = 'x-slate-fragment') => {
1✔
8
    const e = editor as T & AngularEditor;
19✔
9
    const { apply, onChange, deleteBackward } = e;
19✔
10

11
    e.deleteBackward = unit => {
19✔
12
        if (unit !== 'line') {
×
13
            return deleteBackward(unit);
×
14
        }
15

16
        if (editor.selection && Range.isCollapsed(editor.selection)) {
×
17
            const parentBlockEntry = Editor.above(editor, {
×
18
                match: n => Editor.isBlock(editor, n),
×
19
                at: editor.selection
20
            });
21

22
            if (parentBlockEntry) {
×
23
                const [, parentBlockPath] = parentBlockEntry;
×
24
                const parentElementRange = Editor.range(editor, parentBlockPath, editor.selection.anchor);
×
25

26
                const currentLineRange = findCurrentLineRange(e, parentElementRange);
×
27

28
                if (!Range.isCollapsed(currentLineRange)) {
×
29
                    Transforms.delete(editor, { at: currentLineRange });
×
30
                }
31
            }
32
        }
33
    };
34

35
    e.apply = (op: Operation) => {
19✔
36
        const matches: [Path | PathRef, Key][] = [];
17✔
37

38
        switch (op.type) {
17✔
39
            case 'insert_text':
40
            case 'remove_text':
41
            case 'set_node': {
42
                for (const [node, path] of Editor.levels(e, { at: op.path })) {
1✔
43
                    const key = AngularEditor.findKey(e, node);
3✔
44
                    matches.push([path, key]);
3✔
45
                }
46

47
                break;
1✔
48
            }
49

50
            case 'insert_node':
51
            case 'remove_node':
52
            case 'merge_node':
53
            case 'split_node': {
54
                for (const [node, path] of Editor.levels(e, {
4✔
55
                    at: Path.parent(op.path)
56
                })) {
57
                    const key = AngularEditor.findKey(e, node);
5✔
58
                    matches.push([path, key]);
5✔
59
                }
60

61
                break;
4✔
62
            }
63

64
            case 'move_node': {
65
                const commonPath = Path.common(Path.parent(op.path), Path.parent(op.newPath));
7✔
66
                for (const [node, path] of Editor.levels(e, {
7✔
67
                    at: Path.parent(op.path)
68
                })) {
69
                    const key = AngularEditor.findKey(e, node);
13✔
70
                    matches.push([Editor.pathRef(editor, path), key]);
13✔
71
                }
72
                for (const [node, path] of Editor.levels(e, {
7✔
73
                    at: Path.parent(op.newPath)
74
                })) {
75
                    if (path.length > commonPath.length) {
15✔
76
                        const key = AngularEditor.findKey(e, node);
5✔
77
                        matches.push([Editor.pathRef(editor, path), key]);
5✔
78
                    }
79
                }
80
                break;
7✔
81
            }
82
        }
83

84
        apply(op);
17✔
85

86
        for (const [source, key] of matches) {
17✔
87
            const [node] = Editor.node(e, Path.isPath(source) ? source : source.current);
26✔
88
            NODE_TO_KEY.set(node, key);
26✔
89
        }
90
    };
91

92
    e.onChange = () => {
19✔
93
        const onContextChange = EDITOR_TO_ON_CHANGE.get(e);
11✔
94

95
        if (onContextChange) {
11✔
96
            onContextChange();
9✔
97
        }
98

99
        onChange();
11✔
100
    };
101

102
    e.setFragmentData = (data: Pick<DataTransfer, 'getData' | 'setData'>) => {
19✔
103
        const { selection } = e;
×
104

105
        if (!selection) {
×
106
            return;
×
107
        }
108

109
        const [start, end] = Range.edges(selection);
×
110
        const startVoid = Editor.void(e, { at: start.path });
×
111
        const endVoid = Editor.void(e, { at: end.path });
×
112

113
        if (Range.isCollapsed(selection) && !startVoid) {
×
114
            return;
×
115
        }
116

117
        // Create a fake selection so that we can add a Base64-encoded copy of the
118
        // fragment to the HTML, to decode on future pastes.
119
        const domRange = AngularEditor.toDOMRange(e, selection);
×
120
        let contents = domRange.cloneContents();
×
121
        let attach = contents.childNodes[0] as HTMLElement;
×
122

123
        // Make sure attach is non-empty, since empty nodes will not get copied.
124
        const contentsArray = Array.from(contents.children);
×
125
        contentsArray.forEach(node => {
×
126
            if (node.textContent && node.textContent.trim() !== '') {
×
127
                attach = node as HTMLElement;
×
128
            }
129
        });
130

131
        // COMPAT: If the end node is a void node, we need to move the end of the
132
        // range from the void node's spacer span, to the end of the void node's
133
        // content, since the spacer is before void's content in the DOM.
134
        if (endVoid) {
×
135
            const [voidNode] = endVoid;
×
136
            const r = domRange.cloneRange();
×
137
            const domNode = AngularEditor.toDOMNode(e, voidNode);
×
138
            r.setEndAfter(domNode);
×
139
            contents = r.cloneContents();
×
140
        }
141

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

150
        // Remove any zero-width space spans from the cloned DOM so that they don't
151
        // show up elsewhere when pasted.
152
        Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
×
153
            const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
×
154
            zw.textContent = isNewline ? '\n' : '';
×
155
        });
156

157
        // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
158
        // in the HTML, and can be used for intra-Slate pasting. If it's a text
159
        // node, wrap it in a `<span>` so we have something to set an attribute on.
160
        if (isDOMText(attach)) {
×
161
            const span = attach.ownerDocument.createElement('span');
×
162
            // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
163
            // then leading and trailing spaces will be ignored. (2017/09/21)
164
            span.style.whiteSpace = 'pre';
×
165
            span.appendChild(attach);
×
166
            contents.appendChild(span);
×
167
            attach = span;
×
168
        }
169

170
        const fragment = e.getFragment();
×
171
        const stringObj = JSON.stringify(fragment);
×
172
        const encoded = window.btoa(encodeURIComponent(stringObj));
×
173
        attach.setAttribute('data-slate-fragment', encoded);
×
174
        data.setData(`application/${clipboardFormatKey}`, encoded);
×
175

176
        // Add the content to a <div> so that we can get its inner HTML.
177
        const div = contents.ownerDocument.createElement('div');
×
178
        div.appendChild(contents);
×
179
        div.setAttribute('hidden', 'true');
×
180
        contents.ownerDocument.body.appendChild(div);
×
181
        data.setData('text/html', div.innerHTML);
×
182
        data.setData('text/plain', getPlainText(div));
×
183
        contents.ownerDocument.body.removeChild(div);
×
184
        return data;
×
185
    };
186

187
    e.deleteCutData = () => {
19✔
188
        const { selection } = editor;
×
189
        if (selection) {
×
190
            if (Range.isExpanded(selection)) {
×
191
                Editor.deleteFragment(editor);
×
192
            } else {
193
                const node = Node.parent(editor, selection.anchor.path);
×
194
                if (Editor.isVoid(editor, node)) {
×
195
                    Transforms.delete(editor);
×
196
                }
197
            }
198
        }
199
    };
200

201
    e.insertData = (data: DataTransfer) => {
19✔
202
        if (!e.insertFragmentData(data)) {
×
203
            e.insertTextData(data);
×
204
        }
205
    };
206

207
    e.insertFragmentData = (data: DataTransfer): boolean => {
19✔
208
        /**
209
         * Checking copied fragment from application/x-slate-fragment or data-slate-fragment
210
         */
211
        const fragment = data.getData(`application/${clipboardFormatKey}`) || getSlateFragmentAttribute(data);
×
212

213
        if (fragment) {
×
214
            const decoded = decodeURIComponent(window.atob(fragment));
×
215
            const parsed = JSON.parse(decoded) as Node[];
×
216
            e.insertFragment(parsed);
×
217
            return true;
×
218
        }
219
        return false;
×
220
    };
221

222
    e.insertTextData = (data: DataTransfer): boolean => {
19✔
223
        const text = data.getData('text/plain');
×
224

225
        if (text) {
×
226
            const lines = text.split(/\r\n|\r|\n/);
×
227
            let split = false;
×
228

229
            for (const line of lines) {
×
230
                if (split) {
×
231
                    Transforms.splitNodes(e, { always: true });
×
232
                }
233

234
                e.insertText(line);
×
235
                split = true;
×
236
            }
237
            return true;
×
238
        }
239
        return false;
×
240
    };
241

242
    e.onKeydown = () => {};
19✔
243

244
    e.onClick = () => {};
19✔
245

246
    e.isBlockCard = element => false;
260✔
247

248
    e.onError = (errorData: SlateError) => {
19✔
249
        if (errorData.nativeError) {
×
250
            console.error(errorData.nativeError);
×
251
        } else {
252
            console.error(errorData);
×
253
        }
254
    };
255

256
    return e;
19✔
257
};
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

© 2025 Coveralls, Inc