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

worktile / slate-angular / 9d03e406-b8cd-4dd9-8635-03ef3c65354d

27 May 2024 07:10AM UTC coverage: 46.754% (-1.7%) from 48.503%
9d03e406-b8cd-4dd9-8635-03ef3c65354d

Pull #267

circleci

pubuzhixing8
feat: need return text when text/html exit
Pull Request #267: feat(fragment): refactor clipboard by navigator api

406 of 1083 branches covered (37.49%)

Branch coverage included in aggregate %.

24 of 135 new or added lines in 6 files covered. (17.78%)

3 existing lines in 1 file now uncovered.

1020 of 1967 relevant lines covered (51.86%)

44.44 hits per line

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

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

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

13
    e.deleteBackward = unit => {
28✔
14
        if (unit !== 'line') {
×
15
            return deleteBackward(unit);
×
16
        }
17

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

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

28
                const currentLineRange = findCurrentLineRange(e, parentElementRange);
×
29

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

37
    e.apply = (op: Operation) => {
28✔
38
        const matches: [Path | PathRef, Key][] = [];
24✔
39

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

49
                break;
3✔
50
            }
51

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

63
                break;
5✔
64
            }
65

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

86
        apply(op);
24✔
87

88
        for (const [source, key] of matches) {
24✔
89
            const [node] = Editor.node(e, Path.isPath(source) ? source : source.current);
35✔
90
            NODE_TO_KEY.set(node, key);
35✔
91
        }
92
    };
93

94
    e.onChange = () => {
28✔
95
        const onContextChange = EDITOR_TO_ON_CHANGE.get(e);
16✔
96

97
        if (onContextChange) {
16✔
98
            onContextChange();
14✔
99
        }
100

101
        onChange();
16✔
102
    };
103

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

107
        if (!selection) {
×
108
            return;
×
109
        }
110

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

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

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

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

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

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

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

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

172
        const fragment = e.getFragment();
×
173

174
        // Add the content to a <div> so that we can get its inner HTML.
175
        const div = contents.ownerDocument.createElement('div');
×
176
        div.appendChild(contents);
×
177
        div.setAttribute('hidden', 'true');
×
178
        contents.ownerDocument.body.appendChild(div);
×
NEW
179
        setClipboardData({ text: getPlainText(div), elements: fragment as Element[] }, div, attach, dataTransfer);
×
180
        contents.ownerDocument.body.removeChild(div);
×
181
    };
182

183
    e.deleteCutData = () => {
28✔
184
        const { selection } = editor;
×
185
        if (selection) {
×
186
            if (Range.isExpanded(selection)) {
×
187
                Editor.deleteFragment(editor);
×
188
            } else {
189
                const node = Node.parent(editor, selection.anchor.path);
×
190
                if (Element.isElement(node) && Editor.isVoid(editor, node)) {
×
191
                    Transforms.delete(editor);
×
192
                }
193
            }
194
        }
195
    };
196

197
    e.insertData = async (data: DataTransfer) => {
28✔
NEW
198
        if (!(await e.insertFragmentData(data))) {
×
UNCOV
199
            e.insertTextData(data);
×
200
        }
201
    };
202

203
    e.insertFragmentData = async (data: DataTransfer): Promise<boolean> => {
28✔
204
        /**
205
         * Checking copied fragment from application/x-slate-fragment or data-slate-fragment
206
         */
NEW
207
        const clipboardData = await getClipboardData(data);
×
NEW
208
        if (clipboardData && clipboardData.elements) {
×
NEW
209
            e.insertFragment(clipboardData.elements);
×
UNCOV
210
            return true;
×
211
        }
212
        return false;
×
213
    };
214

215
    e.insertTextData = async (data: DataTransfer): Promise<boolean> => {
28✔
NEW
216
        const clipboardData = await getClipboardData(data);
×
217

NEW
218
        if (clipboardData && clipboardData.text) {
×
NEW
219
            const lines = clipboardData.text.split(/\r\n|\r|\n/);
×
UNCOV
220
            let split = false;
×
221

222
            for (const line of lines) {
×
223
                if (split) {
×
224
                    Transforms.splitNodes(e, { always: true });
×
225
                }
226

227
                e.insertText(line);
×
228
                split = true;
×
229
            }
230
            return true;
×
231
        }
232
        return false;
×
233
    };
234

235
    e.onKeydown = () => {};
28✔
236

237
    e.onClick = () => {};
28✔
238

239
    e.isBlockCard = element => false;
358✔
240

241
    e.isExpanded = element => true;
215✔
242

243
    e.onError = (errorData: SlateError) => {
28✔
244
        if (errorData.nativeError) {
×
245
            console.error(errorData.nativeError);
×
246
        } else {
247
            console.error(errorData);
×
248
        }
249
    };
250

251
    return e;
28✔
252
};
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