/
docsSoap.js
183 lines (170 loc) · 5.57 KB
/
docsSoap.js
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// @flow
const constants = require('./constants');
const parseHTML = require('./parseHTML');
const { docsId, elements, headers, styles } = constants;
const wrapNodeAnchor = (
node: Node,
href: string
): HTMLAnchorElement => {
const anchor = document.createElement(elements.ANCHOR);
anchor.href = href;
anchor.appendChild(node.cloneNode(true));
return anchor;
};
const wrapNodeInline = (
node: Node,
style: string
): Node => {
const el = document.createElement(style);
el.appendChild(node.cloneNode(true));
return el;
};
const wrapNode = (
inner: Node,
result: Node
): Node => {
let newNode = result.cloneNode(true);
if (!inner) {
return newNode;
}
if (inner.style && inner.style.fontWeight === styles.BOLD) {
newNode = wrapNodeInline(newNode, elements.BOLD);
}
if (inner.style && inner.style.fontStyle === styles.ITALIC) {
newNode = wrapNodeInline(newNode, elements.ITALIC);
}
if (inner.style && inner.style.textDecoration === styles.UNDERLINE) {
newNode = wrapNodeInline(newNode, elements.UNDERLINE);
}
if (inner.style && inner.style.textDecoration === styles.STRIKETHROUGH) {
newNode = wrapNodeInline(newNode, elements.STRIKETHROUGH);
}
if (inner.style && inner.style.verticalAlign === styles.SUPERSCRIPT) {
newNode = wrapNodeInline(newNode, elements.SUPERSCRIPT);
}
if (inner.style && inner.style.verticalAlign === styles.SUBSCRIPT) {
newNode = wrapNodeInline(newNode, elements.SUBSCRIPT);
}
return newNode;
};
const applyBlockStyles = (
dirty: Node
): Node => {
const node = dirty.cloneNode(true);
let newNode = document.createTextNode(node.textContent);
let styledNode = document.createTextNode('');
if (node.childNodes[0] && node.childNodes[0].style) {
styledNode = node.childNodes[0];
}
if (node.childNodes[0] && node.childNodes[0].nodeName === 'A') {
// flow-ignore Flow doesn't recognize that a childNode can be an HTMLAnchorElement
newNode = wrapNodeAnchor(newNode.cloneNode(true), node.childNodes[0].href);
styledNode = node.childNodes[0].childNodes[0];
}
newNode = wrapNode(styledNode, newNode);
return newNode;
};
const applyInlineStyles = (
dirty: Node
): Node => {
const node = dirty.cloneNode(true);
let newNode = document.createTextNode(node.textContent);
let styledNode = node;
if (node.nodeName === 'A') {
// flow-ignore Flow doesn't recognize that cloneNode() can return an HTMLAnchorElement
newNode = wrapNodeAnchor(newNode, node.href);
if (node.childNodes[0] && node.childNodes[0].style) {
styledNode = node.childNodes[0];
}
}
newNode = wrapNode(styledNode, newNode);
return newNode;
};
const getCleanNode = (
node: Node
): Array<Node> => {
if (node.childNodes && (node.childNodes.length <= 1 || node.nodeName === 'OL' || node.nodeName === 'UL')) {
let newWrapper = null;
let newNode = document.createTextNode(node.textContent);
if (node.nodeName === 'UL' || node.nodeName === 'OL' || node.nodeName === 'LI') {
newWrapper = document.createElement(node.nodeName);
newNode = document.createDocumentFragment();
const items = [];
for (let i = 0; i < node.childNodes.length; i++) {
items.push(...getCleanNode(node.childNodes[i]));
}
items.map((i: Node): Node => newNode.appendChild(i));
} else if (headers.indexOf(node.nodeName) !== -1) {
newWrapper = document.createElement(node.nodeName);
newNode = applyInlineStyles(node.childNodes[0]);
} else if (node.nodeName === 'P') {
newWrapper = document.createElement('p');
newNode = applyBlockStyles(node);
} else if (node.nodeName === 'BR') {
newNode = node;
} else {
newWrapper = document.createElement('span');
newNode = applyInlineStyles(node);
}
if (newWrapper) {
newWrapper.appendChild(newNode);
return [newWrapper];
}
return [node.cloneNode(true)];
}
if (node.childNodes) {
const nodes = [];
for (let i = 0; i < node.childNodes.length; i++) {
nodes.push(...getCleanNode(node.childNodes[i]));
}
return nodes;
}
return [node];
};
/**
* filter unwanted node
* @param node
* @returns {boolean}
*/
const filterNode = (
node: Node
): boolean => node.nodeType !== 8; // Node.COMMENT_NODE = 8
/**
* parses the given "dirty" clipboard content and returns a (mostly) clean
* HTML document with only the HTML content you want
* @param dirty
* @returns {HTMLElement}
*/
const getCleanDocument = (
dirty: HTMLElement
): HTMLElement => {
// create a new document to preserve the integrity of the original data
const body = document.createElement('body');
const nodes = dirty.childNodes;
const filteredNodes = Array.from(nodes).filter(filterNode);
const cleanNodes = [];
// for each top level node, clean it up recursively
for (const node of filteredNodes) {
cleanNodes.push(...getCleanNode(node));
}
// append all of the clean nodes to the new document
for (let i = 0; i < cleanNodes.length; i++) {
body.appendChild(cleanNodes[i].cloneNode(true));
}
// all clean
return body;
};
module.exports = (
clipboardContent: string
): string => {
if (typeof clipboardContent !== 'string') {
throw new Error(`Expected 'clipboardContent' to be a string of HTML, received ${typeof clipboardContent}`);
}
if (clipboardContent.length <= 0) {
throw new Error('Expected clipboardContent to have content, received empty string');
}
if (!clipboardContent.match(docsId)) {
return parseHTML(clipboardContent.replace(/(\r\n|\n|\r)/, '')).outerHTML;
}
return getCleanDocument(parseHTML(clipboardContent.replace(/(\r\n|\n|\r)/, ''))).outerHTML;
};