medium-editor-rails 1.3.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/lib/medium-editor-rails/version.rb +2 -2
- data/vendor/assets/javascripts/medium-editor.js +1220 -968
- data/vendor/assets/stylesheets/medium-editor/medium-editor.css +2 -4
- data/vendor/assets/stylesheets/medium-editor/themes/bootstrap.css +2 -2
- data/vendor/assets/stylesheets/medium-editor/themes/default.css +2 -2
- data/vendor/assets/stylesheets/medium-editor/themes/flat.css +2 -2
- data/vendor/assets/stylesheets/medium-editor/themes/mani.css +2 -2
- data/vendor/assets/stylesheets/medium-editor/themes/roman.css +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4845247670516b2ca15fb5f3723f7f1aa360ab22
|
4
|
+
data.tar.gz: 0b0d17dadcd0c6dafab41c723311841fabe3927e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf8dd7040eaa863a61b86c2a136622f8770950b43c9149438639e50487a7263e7b24b983fd3d7d7bf524bb4a224a08eee7eb9efedf19a453d4dab7ff9c25a281
|
7
|
+
data.tar.gz: 4623476567bee1b1812d5e6dcbc1db59deb435548b6b30a2bc191e1686037cec318d86bd4e4f56ad4ecd88e0ea16aba5e9985064b143c3127d7c930fe09e2ba1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
|
2
2
|
#### [Current]
|
3
|
+
* [cad5c5c](../../commit/cad5c5c) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
|
+
* [2666922](../../commit/2666922) - __(Ahmet Sezgin Duran)__ Merge tag '1.3.0' into develop
|
5
|
+
|
6
|
+
1.3.0
|
7
|
+
|
8
|
+
#### 1.3.0
|
9
|
+
* [88e8565](../../commit/88e8565) - __(Ahmet Sezgin Duran)__ Bump versions 1.3.0 and 2.3.0
|
3
10
|
* [0ce611b](../../commit/0ce611b) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
11
|
* [423eaf7](../../commit/423eaf7) - __(Ahmet Sezgin Duran)__ Merge tag '1.2.0' into develop
|
5
12
|
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ This gem integrates [Medium Editor](https://github.com/daviferreira/medium-edito
|
|
8
8
|
|
9
9
|
## Version
|
10
10
|
|
11
|
-
The latest version of Medium Editor bundled by this gem is [2.
|
11
|
+
The latest version of Medium Editor bundled by this gem is [2.4.2](https://github.com/daviferreira/medium-editor/releases)
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -1,216 +1,599 @@
|
|
1
|
-
function
|
1
|
+
(function (root, factory) {
|
2
2
|
'use strict';
|
3
|
-
|
4
|
-
|
3
|
+
if (typeof module === 'object') {
|
4
|
+
module.exports = factory;
|
5
|
+
} else if (typeof define === 'function' && define.amd) {
|
6
|
+
define(factory);
|
7
|
+
} else {
|
8
|
+
root.MediumEditor = factory;
|
9
|
+
}
|
10
|
+
}(this, function () {
|
5
11
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
} else if (typeof define === 'function' && define.amd) {
|
10
|
-
define(function () {
|
11
|
-
'use strict';
|
12
|
-
return MediumEditor;
|
13
|
-
});
|
14
|
-
}
|
12
|
+
'use strict';
|
13
|
+
|
14
|
+
var mediumEditorUtil;
|
15
15
|
|
16
16
|
(function (window, document) {
|
17
17
|
'use strict';
|
18
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
|
-
|
19
|
+
mediumEditorUtil = {
|
20
|
+
|
21
|
+
// http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
|
22
|
+
// by rg89
|
23
|
+
isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))),
|
24
|
+
|
25
|
+
// https://github.com/jashkenas/underscore
|
26
|
+
keyCode: {
|
27
|
+
BACKSPACE: 8,
|
28
|
+
TAB: 9,
|
29
|
+
ENTER: 13,
|
30
|
+
ESCAPE: 27,
|
31
|
+
SPACE: 32,
|
32
|
+
DELETE: 46
|
33
|
+
},
|
34
|
+
|
35
|
+
parentElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'],
|
36
|
+
|
37
|
+
extend: function extend(b, a) {
|
38
|
+
var prop;
|
39
|
+
if (b === undefined) {
|
40
|
+
return a;
|
41
|
+
}
|
42
|
+
for (prop in a) {
|
43
|
+
if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop) === false) {
|
44
|
+
b[prop] = a[prop];
|
45
|
+
}
|
46
|
+
}
|
47
|
+
return b;
|
48
|
+
},
|
49
|
+
|
50
|
+
// Find the next node in the DOM tree that represents any text that is being
|
51
|
+
// displayed directly next to the targetNode (passed as an argument)
|
52
|
+
// Text that appears directly next to the current node can be:
|
53
|
+
// - A sibling text node
|
54
|
+
// - A descendant of a sibling element
|
55
|
+
// - A sibling text node of an ancestor
|
56
|
+
// - A descendant of a sibling element of an ancestor
|
57
|
+
findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent(rootNode, targetNode, ownerDocument) {
|
58
|
+
var pastTarget = false,
|
59
|
+
nextNode,
|
60
|
+
nodeIterator = ownerDocument.createNodeIterator(rootNode, NodeFilter.SHOW_TEXT, null, false);
|
61
|
+
|
62
|
+
// Use a native NodeIterator to iterate over all the text nodes that are descendants
|
63
|
+
// of the rootNode. Once past the targetNode, choose the first non-empty text node
|
64
|
+
nextNode = nodeIterator.nextNode();
|
65
|
+
while (nextNode) {
|
66
|
+
if (nextNode === targetNode) {
|
67
|
+
pastTarget = true;
|
68
|
+
} else if (pastTarget) {
|
69
|
+
if (nextNode.nodeType === 3 && nextNode.nodeValue && nextNode.nodeValue.trim().length > 0) {
|
70
|
+
break;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
nextNode = nodeIterator.nextNode();
|
74
|
+
}
|
75
|
+
|
76
|
+
return nextNode;
|
77
|
+
},
|
78
|
+
|
79
|
+
isDescendant: function isDescendant(parent, child) {
|
80
|
+
var node = child.parentNode;
|
81
|
+
while (node !== null) {
|
82
|
+
if (node === parent) {
|
83
|
+
return true;
|
84
|
+
}
|
85
|
+
node = node.parentNode;
|
86
|
+
}
|
87
|
+
return false;
|
88
|
+
},
|
89
|
+
|
90
|
+
// https://github.com/jashkenas/underscore
|
91
|
+
isElement: function isElement(obj) {
|
92
|
+
return !!(obj && obj.nodeType === 1);
|
93
|
+
},
|
94
|
+
|
95
|
+
now: function now() {
|
96
|
+
return Date.now || new Date().getTime();
|
97
|
+
},
|
98
|
+
|
99
|
+
// https://github.com/jashkenas/underscore
|
100
|
+
throttle: function throttle(func, wait) {
|
101
|
+
var THROTTLE_INTERVAL = 50,
|
102
|
+
context,
|
103
|
+
args,
|
104
|
+
result,
|
105
|
+
timeout = null,
|
106
|
+
previous = 0,
|
107
|
+
later;
|
108
|
+
|
109
|
+
if (!wait && wait !== 0) {
|
110
|
+
wait = THROTTLE_INTERVAL;
|
111
|
+
}
|
112
|
+
|
113
|
+
later = function () {
|
114
|
+
previous = mediumEditorUtil.now();
|
115
|
+
timeout = null;
|
116
|
+
result = func.apply(context, args);
|
117
|
+
if (!timeout) {
|
118
|
+
context = args = null;
|
119
|
+
}
|
120
|
+
};
|
121
|
+
|
122
|
+
return function () {
|
123
|
+
var currNow = mediumEditorUtil.now(),
|
124
|
+
remaining = wait - (currNow - previous);
|
125
|
+
context = this;
|
126
|
+
args = arguments;
|
127
|
+
if (remaining <= 0 || remaining > wait) {
|
128
|
+
clearTimeout(timeout);
|
129
|
+
timeout = null;
|
130
|
+
previous = currNow;
|
131
|
+
result = func.apply(context, args);
|
132
|
+
if (!timeout) {
|
133
|
+
context = args = null;
|
134
|
+
}
|
135
|
+
} else if (!timeout) {
|
136
|
+
timeout = setTimeout(later, remaining);
|
137
|
+
}
|
138
|
+
return result;
|
139
|
+
};
|
140
|
+
},
|
141
|
+
|
142
|
+
traverseUp: function (current, testElementFunction) {
|
143
|
+
|
144
|
+
do {
|
145
|
+
if (current.nodeType === 1) {
|
146
|
+
if (testElementFunction(current)) {
|
147
|
+
return current;
|
148
|
+
}
|
149
|
+
// do not traverse upwards past the nearest containing editor
|
150
|
+
if (current.getAttribute('data-medium-element')) {
|
151
|
+
return false;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
current = current.parentNode;
|
156
|
+
} while (current);
|
157
|
+
|
158
|
+
return false;
|
159
|
+
|
160
|
+
},
|
161
|
+
|
162
|
+
htmlEntities: function (str) {
|
163
|
+
// converts special characters (like <) into their escaped/encoded values (like <).
|
164
|
+
// This allows you to show to display the string without the browser reading it as HTML.
|
165
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
166
|
+
},
|
167
|
+
|
168
|
+
// http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
|
169
|
+
insertHTMLCommand: function (doc, html) {
|
170
|
+
var selection, range, el, fragment, node, lastNode;
|
171
|
+
|
172
|
+
if (doc.queryCommandSupported('insertHTML')) {
|
173
|
+
try {
|
174
|
+
return doc.execCommand('insertHTML', false, html);
|
175
|
+
} catch (ignore) {}
|
176
|
+
}
|
177
|
+
|
178
|
+
selection = window.getSelection();
|
179
|
+
if (selection.getRangeAt && selection.rangeCount) {
|
180
|
+
range = selection.getRangeAt(0);
|
181
|
+
range.deleteContents();
|
182
|
+
|
183
|
+
el = doc.createElement("div");
|
184
|
+
el.innerHTML = html;
|
185
|
+
fragment = doc.createDocumentFragment();
|
186
|
+
while (el.firstChild) {
|
187
|
+
node = el.firstChild;
|
188
|
+
lastNode = fragment.appendChild(node);
|
189
|
+
}
|
190
|
+
range.insertNode(fragment);
|
191
|
+
|
192
|
+
// Preserve the selection:
|
193
|
+
if (lastNode) {
|
194
|
+
range = range.cloneRange();
|
195
|
+
range.setStartAfter(lastNode);
|
196
|
+
range.collapse(true);
|
197
|
+
selection.removeAllRanges();
|
198
|
+
selection.addRange(range);
|
199
|
+
}
|
200
|
+
}
|
201
|
+
},
|
202
|
+
|
203
|
+
// TODO: not sure if this should be here
|
204
|
+
setTargetBlank: function (el) {
|
205
|
+
var i;
|
206
|
+
if (el.tagName.toLowerCase() === 'a') {
|
207
|
+
el.target = '_blank';
|
208
|
+
} else {
|
209
|
+
el = el.getElementsByTagName('a');
|
210
|
+
|
211
|
+
for (i = 0; i < el.length; i += 1) {
|
212
|
+
el[i].target = '_blank';
|
213
|
+
}
|
214
|
+
}
|
215
|
+
},
|
216
|
+
|
217
|
+
isListItemChild: function (node) {
|
218
|
+
var parentNode = node.parentNode,
|
219
|
+
tagName = parentNode.tagName.toLowerCase();
|
220
|
+
while (this.parentElements.indexOf(tagName) === -1 && tagName !== 'div') {
|
221
|
+
if (tagName === 'li') {
|
222
|
+
return true;
|
223
|
+
}
|
224
|
+
parentNode = parentNode.parentNode;
|
225
|
+
if (parentNode && parentNode.tagName) {
|
226
|
+
tagName = parentNode.tagName.toLowerCase();
|
227
|
+
} else {
|
228
|
+
return false;
|
229
|
+
}
|
230
|
+
}
|
231
|
+
return false;
|
232
|
+
}
|
233
|
+
};
|
234
|
+
}(window, document));
|
235
|
+
|
236
|
+
var meSelection;
|
237
|
+
|
238
|
+
(function (window, document) {
|
239
|
+
'use strict';
|
240
|
+
|
241
|
+
meSelection = {
|
242
|
+
// http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
|
243
|
+
// by You
|
244
|
+
getSelectionStart: function (ownerDocument) {
|
245
|
+
var node = ownerDocument.getSelection().anchorNode,
|
246
|
+
startNode = (node && node.nodeType === 3 ? node.parentNode : node);
|
247
|
+
return startNode;
|
248
|
+
},
|
249
|
+
|
250
|
+
findMatchingSelectionParent: function (testElementFunction, contentWindow) {
|
251
|
+
var selection = contentWindow.getSelection(), range, current;
|
252
|
+
|
253
|
+
if (selection.rangeCount === 0) {
|
254
|
+
return false;
|
255
|
+
}
|
256
|
+
|
257
|
+
range = selection.getRangeAt(0);
|
258
|
+
current = range.commonAncestorContainer;
|
259
|
+
|
260
|
+
return mediumEditorUtil.traverseUp(current, testElementFunction);
|
261
|
+
},
|
262
|
+
|
263
|
+
getSelectionElement: function (contentWindow) {
|
264
|
+
return this.findMatchingSelectionParent(function (el) {
|
265
|
+
return el.getAttribute('data-medium-element');
|
266
|
+
}, contentWindow);
|
267
|
+
},
|
268
|
+
|
269
|
+
selectionInContentEditableFalse: function (contentWindow) {
|
270
|
+
return this.findMatchingSelectionParent(function (el) {
|
271
|
+
return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
|
272
|
+
}, contentWindow);
|
273
|
+
},
|
274
|
+
|
275
|
+
// http://stackoverflow.com/questions/4176923/html-of-selected-text
|
276
|
+
// by Tim Down
|
277
|
+
getSelectionHtml: function getSelectionHtml() {
|
278
|
+
var i,
|
279
|
+
html = '',
|
280
|
+
sel,
|
281
|
+
len,
|
282
|
+
container;
|
283
|
+
if (this.options.contentWindow.getSelection !== undefined) {
|
284
|
+
sel = this.options.contentWindow.getSelection();
|
285
|
+
if (sel.rangeCount) {
|
286
|
+
container = this.options.ownerDocument.createElement('div');
|
287
|
+
for (i = 0, len = sel.rangeCount; i < len; i += 1) {
|
288
|
+
container.appendChild(sel.getRangeAt(i).cloneContents());
|
289
|
+
}
|
290
|
+
html = container.innerHTML;
|
291
|
+
}
|
292
|
+
} else if (this.options.ownerDocument.selection !== undefined) {
|
293
|
+
if (this.options.ownerDocument.selection.type === 'Text') {
|
294
|
+
html = this.options.ownerDocument.selection.createRange().htmlText;
|
295
|
+
}
|
296
|
+
}
|
297
|
+
return html;
|
298
|
+
},
|
299
|
+
|
300
|
+
/**
|
301
|
+
* Find the caret position within an element irrespective of any inline tags it may contain.
|
302
|
+
*
|
303
|
+
* @param {DOMElement} An element containing the cursor to find offsets relative to.
|
304
|
+
* @param {Range} A Range representing cursor position. Will window.getSelection if none is passed.
|
305
|
+
* @return {Object} 'left' and 'right' attributes contain offsets from begining and end of Element
|
306
|
+
*/
|
307
|
+
getCaretOffsets: function getCaretOffsets(element, range) {
|
308
|
+
var preCaretRange, postCaretRange;
|
309
|
+
|
310
|
+
if (!range) {
|
311
|
+
range = window.getSelection().getRangeAt(0);
|
312
|
+
}
|
313
|
+
|
314
|
+
preCaretRange = range.cloneRange();
|
315
|
+
postCaretRange = range.cloneRange();
|
316
|
+
|
317
|
+
preCaretRange.selectNodeContents(element);
|
318
|
+
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
319
|
+
|
320
|
+
postCaretRange.selectNodeContents(element);
|
321
|
+
postCaretRange.setStart(range.endContainer, range.endOffset);
|
322
|
+
|
323
|
+
return {
|
324
|
+
left: preCaretRange.toString().length,
|
325
|
+
right: postCaretRange.toString().length
|
326
|
+
};
|
327
|
+
},
|
328
|
+
|
329
|
+
// http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox
|
330
|
+
rangeSelectsSingleNode: function (range) {
|
331
|
+
var startNode = range.startContainer;
|
332
|
+
return startNode === range.endContainer &&
|
333
|
+
startNode.hasChildNodes() &&
|
334
|
+
range.endOffset === range.startOffset + 1;
|
335
|
+
},
|
336
|
+
|
337
|
+
getSelectedParentElement: function (range) {
|
338
|
+
var selectedParentElement = null;
|
339
|
+
if (this.rangeSelectsSingleNode(range) && range.startContainer.childNodes[range.startOffset].nodeType !== 3) {
|
340
|
+
selectedParentElement = range.startContainer.childNodes[range.startOffset];
|
341
|
+
} else if (range.startContainer.nodeType === 3) {
|
342
|
+
selectedParentElement = range.startContainer.parentNode;
|
343
|
+
} else {
|
344
|
+
selectedParentElement = range.startContainer;
|
345
|
+
}
|
346
|
+
return selectedParentElement;
|
347
|
+
},
|
348
|
+
|
349
|
+
getSelectionData: function (el) {
|
350
|
+
var tagName;
|
351
|
+
|
352
|
+
if (el && el.tagName) {
|
353
|
+
tagName = el.tagName.toLowerCase();
|
354
|
+
}
|
355
|
+
|
356
|
+
while (el && mediumEditorUtil.parentElements.indexOf(tagName) === -1) {
|
357
|
+
el = el.parentNode;
|
358
|
+
if (el && el.tagName) {
|
359
|
+
tagName = el.tagName.toLowerCase();
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
return {
|
364
|
+
el: el,
|
365
|
+
tagName: tagName
|
366
|
+
};
|
367
|
+
}
|
368
|
+
};
|
369
|
+
}(document, window));
|
370
|
+
|
371
|
+
var DefaultButton,
|
372
|
+
ButtonsData;
|
373
|
+
|
374
|
+
(function (window, document) {
|
375
|
+
'use strict';
|
376
|
+
|
377
|
+
ButtonsData = {
|
378
|
+
'bold': {
|
379
|
+
name: 'bold',
|
380
|
+
action: 'bold',
|
381
|
+
aria: 'bold',
|
382
|
+
tagNames: ['b', 'strong'],
|
383
|
+
style: {
|
384
|
+
prop: 'font-weight',
|
385
|
+
value: '700|bold'
|
74
386
|
},
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
387
|
+
useQueryState: true,
|
388
|
+
contentDefault: '<b>B</b>',
|
389
|
+
contentFA: '<i class="fa fa-bold"></i>'
|
390
|
+
},
|
391
|
+
'italic': {
|
392
|
+
name: 'italic',
|
393
|
+
action: 'italic',
|
394
|
+
aria: 'italic',
|
395
|
+
tagNames: ['i', 'em'],
|
396
|
+
style: {
|
397
|
+
prop: 'font-style',
|
398
|
+
value: 'italic'
|
82
399
|
},
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
400
|
+
useQueryState: true,
|
401
|
+
contentDefault: '<b><i>I</i></b>',
|
402
|
+
contentFA: '<i class="fa fa-italic"></i>'
|
403
|
+
},
|
404
|
+
'underline': {
|
405
|
+
name: 'underline',
|
406
|
+
action: 'underline',
|
407
|
+
aria: 'underline',
|
408
|
+
tagNames: ['u'],
|
409
|
+
style: {
|
410
|
+
prop: 'text-decoration',
|
411
|
+
value: 'underline'
|
90
412
|
},
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
413
|
+
useQueryState: true,
|
414
|
+
contentDefault: '<b><u>U</u></b>',
|
415
|
+
contentFA: '<i class="fa fa-underline"></i>'
|
416
|
+
},
|
417
|
+
'strikethrough': {
|
418
|
+
name: 'strikethrough',
|
419
|
+
action: 'strikethrough',
|
420
|
+
aria: 'strike through',
|
421
|
+
tagNames: ['strike'],
|
422
|
+
style: {
|
423
|
+
prop: 'text-decoration',
|
424
|
+
value: 'line-through'
|
98
425
|
},
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
426
|
+
useQueryState: true,
|
427
|
+
contentDefault: '<s>A</s>',
|
428
|
+
contentFA: '<i class="fa fa-strikethrough"></i>'
|
429
|
+
},
|
430
|
+
'superscript': {
|
431
|
+
name: 'superscript',
|
432
|
+
action: 'superscript',
|
433
|
+
aria: 'superscript',
|
434
|
+
tagNames: ['sup'],
|
435
|
+
/* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for superscript
|
436
|
+
https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */
|
437
|
+
// useQueryState: true
|
438
|
+
contentDefault: '<b>x<sup>1</sup></b>',
|
439
|
+
contentFA: '<i class="fa fa-superscript"></i>'
|
440
|
+
},
|
441
|
+
'subscript': {
|
442
|
+
name: 'subscript',
|
443
|
+
action: 'subscript',
|
444
|
+
aria: 'subscript',
|
445
|
+
tagNames: ['sub'],
|
446
|
+
/* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for subscript
|
447
|
+
https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */
|
448
|
+
// useQueryState: true
|
449
|
+
contentDefault: '<b>x<sub>1</sub></b>',
|
450
|
+
contentFA: '<i class="fa fa-subscript"></i>'
|
451
|
+
},
|
452
|
+
'anchor': {
|
453
|
+
name: 'anchor',
|
454
|
+
action: 'anchor',
|
455
|
+
aria: 'link',
|
456
|
+
tagNames: ['a'],
|
457
|
+
contentDefault: '<b>#</b>',
|
458
|
+
contentFA: '<i class="fa fa-link"></i>'
|
459
|
+
},
|
460
|
+
'image': {
|
461
|
+
name: 'image',
|
462
|
+
action: 'image',
|
463
|
+
aria: 'image',
|
464
|
+
tagNames: ['img'],
|
465
|
+
contentDefault: '<b>image</b>',
|
466
|
+
contentFA: '<i class="fa fa-picture-o"></i>'
|
467
|
+
},
|
468
|
+
'quote': {
|
469
|
+
name: 'quote',
|
470
|
+
action: 'append-blockquote',
|
471
|
+
aria: 'blockquote',
|
472
|
+
tagNames: ['blockquote'],
|
473
|
+
contentDefault: '<b>“</b>',
|
474
|
+
contentFA: '<i class="fa fa-quote-right"></i>'
|
475
|
+
},
|
476
|
+
'orderedlist': {
|
477
|
+
name: 'orderedlist',
|
478
|
+
action: 'insertorderedlist',
|
479
|
+
aria: 'ordered list',
|
480
|
+
tagNames: ['ol'],
|
481
|
+
useQueryState: true,
|
482
|
+
contentDefault: '<b>1.</b>',
|
483
|
+
contentFA: '<i class="fa fa-list-ol"></i>'
|
484
|
+
},
|
485
|
+
'unorderedlist': {
|
486
|
+
name: 'unorderedlist',
|
487
|
+
action: 'insertunorderedlist',
|
488
|
+
aria: 'unordered list',
|
489
|
+
tagNames: ['ul'],
|
490
|
+
useQueryState: true,
|
491
|
+
contentDefault: '<b>•</b>',
|
492
|
+
contentFA: '<i class="fa fa-list-ul"></i>'
|
493
|
+
},
|
494
|
+
'pre': {
|
495
|
+
name: 'pre',
|
496
|
+
action: 'append-pre',
|
497
|
+
aria: 'preformatted text',
|
498
|
+
tagNames: ['pre'],
|
499
|
+
contentDefault: '<b>0101</b>',
|
500
|
+
contentFA: '<i class="fa fa-code fa-lg"></i>'
|
501
|
+
},
|
502
|
+
'indent': {
|
503
|
+
name: 'indent',
|
504
|
+
action: 'indent',
|
505
|
+
aria: 'indent',
|
506
|
+
tagNames: [],
|
507
|
+
contentDefault: '<b>→</b>',
|
508
|
+
contentFA: '<i class="fa fa-indent"></i>'
|
509
|
+
},
|
510
|
+
'outdent': {
|
511
|
+
name: 'outdent',
|
512
|
+
action: 'outdent',
|
513
|
+
aria: 'outdent',
|
514
|
+
tagNames: [],
|
515
|
+
contentDefault: '<b>←</b>',
|
516
|
+
contentFA: '<i class="fa fa-outdent"></i>'
|
517
|
+
},
|
518
|
+
'justifyCenter': {
|
519
|
+
name: 'justifyCenter',
|
520
|
+
action: 'justifyCenter',
|
521
|
+
aria: 'center justify',
|
522
|
+
tagNames: [],
|
523
|
+
style: {
|
524
|
+
prop: 'text-align',
|
525
|
+
value: 'center'
|
106
526
|
},
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
527
|
+
useQueryState: true,
|
528
|
+
contentDefault: '<b>C</b>',
|
529
|
+
contentFA: '<i class="fa fa-align-center"></i>'
|
530
|
+
},
|
531
|
+
'justifyFull': {
|
532
|
+
name: 'justifyFull',
|
533
|
+
action: 'justifyFull',
|
534
|
+
aria: 'full justify',
|
535
|
+
tagNames: [],
|
536
|
+
style: {
|
537
|
+
prop: 'text-align',
|
538
|
+
value: 'justify'
|
114
539
|
},
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
540
|
+
useQueryState: true,
|
541
|
+
contentDefault: '<b>J</b>',
|
542
|
+
contentFA: '<i class="fa fa-align-justify"></i>'
|
543
|
+
},
|
544
|
+
'justifyLeft': {
|
545
|
+
name: 'justifyLeft',
|
546
|
+
action: 'justifyLeft',
|
547
|
+
aria: 'left justify',
|
548
|
+
tagNames: [],
|
549
|
+
style: {
|
550
|
+
prop: 'text-align',
|
551
|
+
value: 'left'
|
122
552
|
},
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
553
|
+
useQueryState: true,
|
554
|
+
contentDefault: '<b>L</b>',
|
555
|
+
contentFA: '<i class="fa fa-align-left"></i>'
|
556
|
+
},
|
557
|
+
'justifyRight': {
|
558
|
+
name: 'justifyRight',
|
559
|
+
action: 'justifyRight',
|
560
|
+
aria: 'right justify',
|
561
|
+
tagNames: [],
|
562
|
+
style: {
|
563
|
+
prop: 'text-align',
|
564
|
+
value: 'right'
|
130
565
|
},
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
566
|
+
useQueryState: true,
|
567
|
+
contentDefault: '<b>R</b>',
|
568
|
+
contentFA: '<i class="fa fa-align-right"></i>'
|
569
|
+
},
|
570
|
+
'header1': {
|
571
|
+
name: 'header1',
|
572
|
+
action: function (options) {
|
573
|
+
return 'append-' + options.firstHeader;
|
138
574
|
},
|
139
|
-
|
140
|
-
|
141
|
-
action: 'justifyCenter',
|
142
|
-
aria: 'center justify',
|
143
|
-
tagNames: [],
|
144
|
-
style: {
|
145
|
-
prop: 'text-align',
|
146
|
-
value: 'center'
|
147
|
-
},
|
148
|
-
contentDefault: '<b>C</b>',
|
149
|
-
contentFA: '<i class="fa fa-align-center"></i>'
|
575
|
+
aria: function (options) {
|
576
|
+
return options.firstHeader;
|
150
577
|
},
|
151
|
-
|
152
|
-
|
153
|
-
action: 'justifyFull',
|
154
|
-
aria: 'full justify',
|
155
|
-
tagNames: [],
|
156
|
-
style: {
|
157
|
-
prop: 'text-align',
|
158
|
-
value: 'justify'
|
159
|
-
},
|
160
|
-
contentDefault: '<b>J</b>',
|
161
|
-
contentFA: '<i class="fa fa-align-justify"></i>'
|
578
|
+
tagNames: function (options) {
|
579
|
+
return [options.firstHeader];
|
162
580
|
},
|
163
|
-
'
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
prop: 'text-align',
|
170
|
-
value: 'left'
|
171
|
-
},
|
172
|
-
contentDefault: '<b>L</b>',
|
173
|
-
contentFA: '<i class="fa fa-align-left"></i>'
|
581
|
+
contentDefault: '<b>H1</b>'
|
582
|
+
},
|
583
|
+
'header2': {
|
584
|
+
name: 'header2',
|
585
|
+
action: function (options) {
|
586
|
+
return 'append-' + options.secondHeader;
|
174
587
|
},
|
175
|
-
|
176
|
-
|
177
|
-
action: 'justifyRight',
|
178
|
-
aria: 'right justify',
|
179
|
-
tagNames: [],
|
180
|
-
style: {
|
181
|
-
prop: 'text-align',
|
182
|
-
value: 'right'
|
183
|
-
},
|
184
|
-
contentDefault: '<b>R</b>',
|
185
|
-
contentFA: '<i class="fa fa-align-right"></i>'
|
588
|
+
aria: function (options) {
|
589
|
+
return options.secondHeader;
|
186
590
|
},
|
187
|
-
|
188
|
-
|
189
|
-
action: function (options) {
|
190
|
-
return 'append-' + options.firstHeader;
|
191
|
-
},
|
192
|
-
aria: function (options) {
|
193
|
-
return options.firstHeader;
|
194
|
-
},
|
195
|
-
tagNames: function (options) {
|
196
|
-
return [options.firstHeader];
|
197
|
-
},
|
198
|
-
contentDefault: '<b>H1</b>'
|
591
|
+
tagNames: function (options) {
|
592
|
+
return [options.secondHeader];
|
199
593
|
},
|
200
|
-
'
|
201
|
-
|
202
|
-
|
203
|
-
return 'append-' + options.secondHeader;
|
204
|
-
},
|
205
|
-
aria: function (options) {
|
206
|
-
return options.secondHeader;
|
207
|
-
},
|
208
|
-
tagNames: function (options) {
|
209
|
-
return [options.secondHeader];
|
210
|
-
},
|
211
|
-
contentDefault: '<b>H2</b>'
|
212
|
-
}
|
213
|
-
};
|
594
|
+
contentDefault: '<b>H2</b>'
|
595
|
+
}
|
596
|
+
};
|
214
597
|
|
215
598
|
DefaultButton = function (options, instance) {
|
216
599
|
this.options = options;
|
@@ -286,235 +669,487 @@ if (typeof module === 'object') {
|
|
286
669
|
this.button.classList.add(this.base.options.activeButtonClass);
|
287
670
|
delete this.knownState;
|
288
671
|
},
|
672
|
+
queryCommandState: function () {
|
673
|
+
var queryState = null;
|
674
|
+
if (this.options.useQueryState) {
|
675
|
+
try {
|
676
|
+
queryState = this.base.options.ownerDocument.queryCommandState(this.getAction());
|
677
|
+
} catch (exc) {
|
678
|
+
queryState = null;
|
679
|
+
}
|
680
|
+
}
|
681
|
+
return queryState;
|
682
|
+
},
|
289
683
|
shouldActivate: function (node) {
|
290
684
|
var isMatch = false,
|
291
|
-
tagNames = this.getTagNames()
|
685
|
+
tagNames = this.getTagNames(),
|
686
|
+
styleVals,
|
687
|
+
computedStyle;
|
292
688
|
if (this.knownState === false || this.knownState === true) {
|
293
689
|
return this.knownState;
|
294
690
|
}
|
295
691
|
|
296
|
-
if (tagNames && tagNames.length > 0 && node.tagName) {
|
297
|
-
isMatch = tagNames.indexOf(node.tagName.toLowerCase()) !== -1;
|
692
|
+
if (tagNames && tagNames.length > 0 && node.tagName) {
|
693
|
+
isMatch = tagNames.indexOf(node.tagName.toLowerCase()) !== -1;
|
694
|
+
}
|
695
|
+
|
696
|
+
if (!isMatch && this.options.style) {
|
697
|
+
styleVals = this.options.style.value.split('|');
|
698
|
+
computedStyle = this.base.options.contentWindow.getComputedStyle(node, null).getPropertyValue(this.options.style.prop);
|
699
|
+
styleVals.forEach(function (val) {
|
700
|
+
if (!this.knownState) {
|
701
|
+
this.knownState = isMatch = (computedStyle.indexOf(val) !== -1);
|
702
|
+
}
|
703
|
+
}.bind(this));
|
704
|
+
}
|
705
|
+
|
706
|
+
return isMatch;
|
707
|
+
}
|
708
|
+
};
|
709
|
+
}(window, document));
|
710
|
+
var pasteHandler;
|
711
|
+
|
712
|
+
(function (window, document) {
|
713
|
+
'use strict';
|
714
|
+
/*jslint regexp: true*/
|
715
|
+
/*
|
716
|
+
jslint does not allow character negation, because the negation
|
717
|
+
will not match any unicode characters. In the regexes in this
|
718
|
+
block, negation is used specifically to match the end of an html
|
719
|
+
tag, and in fact unicode characters *should* be allowed.
|
720
|
+
*/
|
721
|
+
function createReplacements() {
|
722
|
+
return [
|
723
|
+
|
724
|
+
// replace two bogus tags that begin pastes from google docs
|
725
|
+
[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""],
|
726
|
+
[new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""],
|
727
|
+
|
728
|
+
// un-html spaces and newlines inserted by OS X
|
729
|
+
[new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '],
|
730
|
+
[new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],
|
731
|
+
|
732
|
+
// replace google docs italics+bold with a span to be replaced once the html is inserted
|
733
|
+
[new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
|
734
|
+
|
735
|
+
// replace google docs italics with a span to be replaced once the html is inserted
|
736
|
+
[new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],
|
737
|
+
|
738
|
+
//[replace google docs bolds with a span to be replaced once the html is inserted
|
739
|
+
[new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'],
|
740
|
+
|
741
|
+
// replace manually entered b/i/a tags with real ones
|
742
|
+
[new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'],
|
743
|
+
|
744
|
+
// replace manually a tags with real ones, converting smart-quotes from google docs
|
745
|
+
[new RegExp(/<a\s+href=("|”|“|“|”)([^&]+)("|”|“|“|”)>/gi), '<a href="$2">']
|
746
|
+
|
747
|
+
];
|
748
|
+
}
|
749
|
+
/*jslint regexp: false*/
|
750
|
+
|
751
|
+
pasteHandler = {
|
752
|
+
handlePaste: function (element, evt, options) {
|
753
|
+
var paragraphs,
|
754
|
+
html = '',
|
755
|
+
p,
|
756
|
+
dataFormatHTML = 'text/html',
|
757
|
+
dataFormatPlain = 'text/plain';
|
758
|
+
|
759
|
+
element.classList.remove('medium-editor-placeholder');
|
760
|
+
if (!options.forcePlainText && !options.cleanPastedHTML) {
|
761
|
+
return element;
|
762
|
+
}
|
763
|
+
|
764
|
+
if (options.contentWindow.clipboardData && evt.clipboardData === undefined) {
|
765
|
+
evt.clipboardData = options.contentWindow.clipboardData;
|
766
|
+
// If window.clipboardData exists, but e.clipboardData doesn't exist,
|
767
|
+
// we're probably in IE. IE only has two possibilities for clipboard
|
768
|
+
// data format: 'Text' and 'URL'.
|
769
|
+
//
|
770
|
+
// Of the two, we want 'Text':
|
771
|
+
dataFormatHTML = 'Text';
|
772
|
+
dataFormatPlain = 'Text';
|
773
|
+
}
|
774
|
+
|
775
|
+
if (evt.clipboardData && evt.clipboardData.getData && !evt.defaultPrevented) {
|
776
|
+
evt.preventDefault();
|
777
|
+
|
778
|
+
if (options.cleanPastedHTML && evt.clipboardData.getData(dataFormatHTML)) {
|
779
|
+
return this.cleanPaste(evt.clipboardData.getData(dataFormatHTML), options);
|
780
|
+
}
|
781
|
+
if (!(options.disableReturn || element.getAttribute('data-disable-return'))) {
|
782
|
+
paragraphs = evt.clipboardData.getData(dataFormatPlain).split(/[\r\n]/g);
|
783
|
+
for (p = 0; p < paragraphs.length; p += 1) {
|
784
|
+
if (paragraphs[p] !== '') {
|
785
|
+
html += '<p>' + mediumEditorUtil.htmlEntities(paragraphs[p]) + '</p>';
|
786
|
+
}
|
787
|
+
}
|
788
|
+
mediumEditorUtil.insertHTMLCommand(options.ownerDocument, html);
|
789
|
+
} else {
|
790
|
+
html = mediumEditorUtil.htmlEntities(evt.clipboardData.getData(dataFormatPlain));
|
791
|
+
mediumEditorUtil.insertHTMLCommand(options.ownerDocument, html);
|
792
|
+
}
|
793
|
+
}
|
794
|
+
},
|
795
|
+
|
796
|
+
cleanPaste: function (text, options) {
|
797
|
+
var i, elList, workEl,
|
798
|
+
el = meSelection.getSelectionElement(options.contentWindow),
|
799
|
+
multiline = /<p|<br|<div/.test(text),
|
800
|
+
replacements = createReplacements();
|
801
|
+
|
802
|
+
for (i = 0; i < replacements.length; i += 1) {
|
803
|
+
text = text.replace(replacements[i][0], replacements[i][1]);
|
804
|
+
}
|
805
|
+
|
806
|
+
if (multiline) {
|
807
|
+
// double br's aren't converted to p tags, but we want paragraphs.
|
808
|
+
elList = text.split('<br><br>');
|
809
|
+
|
810
|
+
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>', options.ownerDocument);
|
811
|
+
options.ownerDocument.execCommand('insertText', false, "\n");
|
812
|
+
|
813
|
+
// block element cleanup
|
814
|
+
elList = el.querySelectorAll('a,p,div,br');
|
815
|
+
for (i = 0; i < elList.length; i += 1) {
|
816
|
+
workEl = elList[i];
|
817
|
+
|
818
|
+
switch (workEl.tagName.toLowerCase()) {
|
819
|
+
case 'a':
|
820
|
+
if (options.targetBlank) {
|
821
|
+
mediumEditorUtil.setTargetBlank(workEl);
|
822
|
+
}
|
823
|
+
break;
|
824
|
+
case 'p':
|
825
|
+
case 'div':
|
826
|
+
this.filterCommonBlocks(workEl);
|
827
|
+
break;
|
828
|
+
case 'br':
|
829
|
+
this.filterLineBreak(workEl);
|
830
|
+
break;
|
831
|
+
}
|
832
|
+
}
|
833
|
+
} else {
|
834
|
+
this.pasteHTML(text, options.ownerDocument);
|
835
|
+
}
|
836
|
+
},
|
837
|
+
|
838
|
+
pasteHTML: function (html, ownerDocument) {
|
839
|
+
var elList, workEl, i, fragmentBody, pasteBlock = ownerDocument.createDocumentFragment();
|
840
|
+
|
841
|
+
pasteBlock.appendChild(ownerDocument.createElement('body'));
|
842
|
+
|
843
|
+
fragmentBody = pasteBlock.querySelector('body');
|
844
|
+
fragmentBody.innerHTML = html;
|
845
|
+
|
846
|
+
this.cleanupSpans(fragmentBody, ownerDocument);
|
847
|
+
|
848
|
+
elList = fragmentBody.querySelectorAll('*');
|
849
|
+
for (i = 0; i < elList.length; i += 1) {
|
850
|
+
workEl = elList[i];
|
851
|
+
|
852
|
+
// delete ugly attributes
|
853
|
+
workEl.removeAttribute('class');
|
854
|
+
workEl.removeAttribute('style');
|
855
|
+
workEl.removeAttribute('dir');
|
856
|
+
|
857
|
+
if (workEl.tagName.toLowerCase() === 'meta') {
|
858
|
+
workEl.parentNode.removeChild(workEl);
|
859
|
+
}
|
860
|
+
}
|
861
|
+
mediumEditorUtil.insertHTMLCommand(ownerDocument, fragmentBody.innerHTML.replace(/ /g, ' '));
|
862
|
+
},
|
863
|
+
isCommonBlock: function (el) {
|
864
|
+
return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div'));
|
865
|
+
},
|
866
|
+
filterCommonBlocks: function (el) {
|
867
|
+
if (/^\s*$/.test(el.textContent)) {
|
868
|
+
el.parentNode.removeChild(el);
|
869
|
+
}
|
870
|
+
},
|
871
|
+
filterLineBreak: function (el) {
|
872
|
+
if (this.isCommonBlock(el.previousElementSibling)) {
|
873
|
+
// remove stray br's following common block elements
|
874
|
+
el.parentNode.removeChild(el);
|
875
|
+
} else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
|
876
|
+
// remove br's just inside open or close tags of a div/p
|
877
|
+
el.parentNode.removeChild(el);
|
878
|
+
} else if (el.parentNode.childElementCount === 1) {
|
879
|
+
// and br's that are the only child of a div/p
|
880
|
+
this.removeWithParent(el);
|
881
|
+
}
|
882
|
+
|
883
|
+
},
|
884
|
+
|
885
|
+
// remove an element, including its parent, if it is the only element within its parent
|
886
|
+
removeWithParent: function (el) {
|
887
|
+
if (el && el.parentNode) {
|
888
|
+
if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
|
889
|
+
el.parentNode.parentNode.removeChild(el.parentNode);
|
890
|
+
} else {
|
891
|
+
el.parentNode.removeChild(el.parentNode);
|
892
|
+
}
|
893
|
+
}
|
894
|
+
},
|
895
|
+
|
896
|
+
cleanupSpans: function (container_el, ownerDocument) {
|
897
|
+
var i,
|
898
|
+
el,
|
899
|
+
new_el,
|
900
|
+
spans = container_el.querySelectorAll('.replace-with'),
|
901
|
+
isCEF = function (el) {
|
902
|
+
return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
|
903
|
+
};
|
904
|
+
|
905
|
+
for (i = 0; i < spans.length; i += 1) {
|
906
|
+
el = spans[i];
|
907
|
+
new_el = ownerDocument.createElement(el.classList.contains('bold') ? 'b' : 'i');
|
908
|
+
|
909
|
+
if (el.classList.contains('bold') && el.classList.contains('italic')) {
|
910
|
+
// add an i tag as well if this has both italics and bold
|
911
|
+
new_el.innerHTML = '<i>' + el.innerHTML + '</i>';
|
912
|
+
} else {
|
913
|
+
new_el.innerHTML = el.innerHTML;
|
914
|
+
}
|
915
|
+
el.parentNode.replaceChild(new_el, el);
|
916
|
+
}
|
917
|
+
|
918
|
+
spans = container_el.querySelectorAll('span');
|
919
|
+
for (i = 0; i < spans.length; i += 1) {
|
920
|
+
el = spans[i];
|
921
|
+
|
922
|
+
// bail if span is in contenteditable = false
|
923
|
+
if (mediumEditorUtil.traverseUp(el, isCEF)) {
|
924
|
+
return false;
|
925
|
+
}
|
926
|
+
|
927
|
+
// remove empty spans, replace others with their contents
|
928
|
+
if (/^\s*$/.test()) {
|
929
|
+
el.parentNode.removeChild(el);
|
930
|
+
} else {
|
931
|
+
el.parentNode.replaceChild(ownerDocument.createTextNode(el.textContent), el);
|
932
|
+
}
|
933
|
+
}
|
934
|
+
}
|
935
|
+
};
|
936
|
+
}(window, document));
|
937
|
+
|
938
|
+
var AnchorExtension;
|
939
|
+
|
940
|
+
(function (window, document) {
|
941
|
+
'use strict';
|
942
|
+
|
943
|
+
AnchorExtension = function (instance) {
|
944
|
+
this.base = instance;
|
945
|
+
};
|
946
|
+
|
947
|
+
AnchorExtension.prototype = {
|
948
|
+
|
949
|
+
getForm: function () {
|
950
|
+
if (!this.anchorForm) {
|
951
|
+
this.anchorForm = this.createForm();
|
952
|
+
}
|
953
|
+
return this.anchorForm;
|
954
|
+
},
|
955
|
+
|
956
|
+
getInput: function () {
|
957
|
+
return this.getForm().querySelector('input.medium-editor-toolbar-input');
|
958
|
+
},
|
959
|
+
|
960
|
+
deactivate: function () {
|
961
|
+
if (!this.anchorForm) {
|
962
|
+
return false;
|
298
963
|
}
|
299
964
|
|
300
|
-
if (
|
301
|
-
this.
|
965
|
+
if (this.anchorForm.parentNode) {
|
966
|
+
this.anchorForm.parentNode.removeChild(this.anchorForm);
|
302
967
|
}
|
303
968
|
|
304
|
-
|
305
|
-
}
|
306
|
-
};
|
969
|
+
delete this.anchorForm;
|
970
|
+
},
|
307
971
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
if (
|
315
|
-
|
972
|
+
doLinkCreation: function () {
|
973
|
+
var button = null,
|
974
|
+
target,
|
975
|
+
targetCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-target'),
|
976
|
+
buttonCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-button');
|
977
|
+
|
978
|
+
if (targetCheckbox && targetCheckbox.checked) {
|
979
|
+
target = "_blank";
|
980
|
+
} else {
|
981
|
+
target = "_self";
|
316
982
|
}
|
317
|
-
}
|
318
|
-
return b;
|
319
|
-
}
|
320
983
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
};
|
984
|
+
if (buttonCheckbox && buttonCheckbox.checked) {
|
985
|
+
button = this.base.options.anchorButtonClass;
|
986
|
+
}
|
325
987
|
|
326
|
-
|
327
|
-
|
328
|
-
TAB: 9,
|
329
|
-
ENTER: 13,
|
330
|
-
ESCAPE: 27,
|
331
|
-
SPACE: 32,
|
332
|
-
DELETE: 46
|
333
|
-
};
|
988
|
+
this.base.createLink(this.getInput(), target, button);
|
989
|
+
},
|
334
990
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
args,
|
340
|
-
result,
|
341
|
-
timeout = null,
|
342
|
-
previous = 0,
|
343
|
-
later;
|
344
|
-
|
345
|
-
if (!wait && wait !== 0) {
|
346
|
-
wait = THROTTLE_INTERVAL;
|
347
|
-
}
|
991
|
+
doFormCancel: function () {
|
992
|
+
this.base.showToolbarActions();
|
993
|
+
this.base.restoreSelection();
|
994
|
+
},
|
348
995
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
}
|
356
|
-
};
|
357
|
-
|
358
|
-
return function () {
|
359
|
-
var currNow = now(),
|
360
|
-
remaining = wait - (currNow - previous);
|
361
|
-
context = this;
|
362
|
-
args = arguments;
|
363
|
-
if (remaining <= 0 || remaining > wait) {
|
364
|
-
clearTimeout(timeout);
|
365
|
-
timeout = null;
|
366
|
-
previous = currNow;
|
367
|
-
result = func.apply(context, args);
|
368
|
-
if (!timeout) {
|
369
|
-
context = args = null;
|
370
|
-
}
|
371
|
-
} else if (!timeout) {
|
372
|
-
timeout = setTimeout(later, remaining);
|
996
|
+
handleOutsideInteraction: function (event) {
|
997
|
+
if (event.target !== this.getForm() &&
|
998
|
+
!mediumEditorUtil.isDescendant(this.getForm(), event.target) &&
|
999
|
+
!mediumEditorUtil.isDescendant(this.base.toolbarActions, event.target)) {
|
1000
|
+
this.base.keepToolbarAlive = false;
|
1001
|
+
this.base.checkSelection();
|
373
1002
|
}
|
374
|
-
|
375
|
-
};
|
376
|
-
}
|
1003
|
+
},
|
377
1004
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
1005
|
+
createForm: function () {
|
1006
|
+
var doc = this.base.options.ownerDocument,
|
1007
|
+
form = doc.createElement('div'),
|
1008
|
+
input = doc.createElement('input'),
|
1009
|
+
close = doc.createElement('a'),
|
1010
|
+
save = doc.createElement('a'),
|
1011
|
+
target,
|
1012
|
+
target_label,
|
1013
|
+
button,
|
1014
|
+
button_label;
|
388
1015
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
if (
|
409
|
-
|
1016
|
+
// Anchor Form (div)
|
1017
|
+
form.className = 'medium-editor-toolbar-form';
|
1018
|
+
form.id = 'medium-editor-toolbar-form-anchor-' + this.base.id;
|
1019
|
+
|
1020
|
+
// Handle clicks on the form itself
|
1021
|
+
this.base.on(form, 'click', function (event) {
|
1022
|
+
event.stopPropagation();
|
1023
|
+
this.base.keepToolbarAlive = true;
|
1024
|
+
}.bind(this));
|
1025
|
+
|
1026
|
+
// Add url textbox
|
1027
|
+
input.setAttribute('type', 'text');
|
1028
|
+
input.className = 'medium-editor-toolbar-input';
|
1029
|
+
input.setAttribute('placeholder', this.base.options.anchorInputPlaceholder);
|
1030
|
+
form.appendChild(input);
|
1031
|
+
|
1032
|
+
// Handle typing in the textbox
|
1033
|
+
this.base.on(input, 'keyup', function (event) {
|
1034
|
+
// For ENTER -> create the anchor
|
1035
|
+
if (event.keyCode === mediumEditorUtil.keyCode.ENTER) {
|
1036
|
+
event.preventDefault();
|
1037
|
+
this.doLinkCreation();
|
1038
|
+
return;
|
410
1039
|
}
|
411
|
-
}
|
412
|
-
nextNode = nodeIterator.nextNode();
|
413
|
-
}
|
414
1040
|
|
415
|
-
|
416
|
-
|
1041
|
+
// For ESCAPE -> close the form
|
1042
|
+
if (event.keyCode === mediumEditorUtil.keyCode.ESCAPE) {
|
1043
|
+
event.preventDefault();
|
1044
|
+
this.doFormCancel();
|
1045
|
+
}
|
1046
|
+
}.bind(this));
|
417
1047
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
sel = this.options.contentWindow.getSelection();
|
425
|
-
if (sel.getRangeAt && sel.rangeCount) {
|
426
|
-
ranges = [];
|
427
|
-
for (i = 0, len = sel.rangeCount; i < len; i += 1) {
|
428
|
-
ranges.push(sel.getRangeAt(i));
|
429
|
-
}
|
430
|
-
return ranges;
|
431
|
-
}
|
432
|
-
return null;
|
433
|
-
}
|
1048
|
+
// Handle clicks into the textbox
|
1049
|
+
this.base.on(input, 'click', function (event) {
|
1050
|
+
// make sure not to hide form when cliking into the input
|
1051
|
+
event.stopPropagation();
|
1052
|
+
this.base.keepToolbarAlive = true;
|
1053
|
+
}.bind(this));
|
434
1054
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
sel.removeAllRanges();
|
441
|
-
for (i = 0, len = savedSel.length; i < len; i += 1) {
|
442
|
-
sel.addRange(savedSel[i]);
|
443
|
-
}
|
444
|
-
}
|
445
|
-
}
|
1055
|
+
// Add save buton
|
1056
|
+
save.setAttribute('href', '#');
|
1057
|
+
save.className = 'medium-editor-toobar-save';
|
1058
|
+
save.innerHTML = '✓';
|
1059
|
+
form.appendChild(save);
|
446
1060
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
}
|
1061
|
+
// Handle save button clicks (capture)
|
1062
|
+
this.base.on(save, 'click', function (event) {
|
1063
|
+
// Clicking Save -> create the anchor
|
1064
|
+
event.preventDefault();
|
1065
|
+
this.doLinkCreation();
|
1066
|
+
}.bind(this), true);
|
454
1067
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
1068
|
+
// Add close button
|
1069
|
+
close.setAttribute('href', '#');
|
1070
|
+
close.className = 'medium-editor-toobar-close';
|
1071
|
+
close.innerHTML = '×';
|
1072
|
+
form.appendChild(close);
|
1073
|
+
|
1074
|
+
// Handle close button clicks
|
1075
|
+
this.base.on(close, 'click', function (event) {
|
1076
|
+
// Click Close -> close the form
|
1077
|
+
event.preventDefault();
|
1078
|
+
this.doFormCancel();
|
1079
|
+
}.bind(this));
|
1080
|
+
|
1081
|
+
// (Optional) Add 'open in new window' checkbox
|
1082
|
+
if (this.base.options.anchorTarget) {
|
1083
|
+
target = doc.createElement('input');
|
1084
|
+
target.setAttribute('type', 'checkbox');
|
1085
|
+
target.className = 'medium-editor-toolbar-anchor-target';
|
1086
|
+
|
1087
|
+
target_label = doc.createElement('label');
|
1088
|
+
target_label.innerHTML = this.base.options.anchorInputCheckboxLabel;
|
1089
|
+
target_label.insertBefore(target, target_label.firstChild);
|
1090
|
+
|
1091
|
+
form.appendChild(target_label);
|
471
1092
|
}
|
472
|
-
|
473
|
-
|
474
|
-
|
1093
|
+
|
1094
|
+
// (Optional) Add 'add button class to anchor' checkbox
|
1095
|
+
if (this.base.options.anchorButton) {
|
1096
|
+
button = doc.createElement('input');
|
1097
|
+
button.setAttribute('type', 'checkbox');
|
1098
|
+
button.className = 'medium-editor-toolbar-anchor-button';
|
1099
|
+
|
1100
|
+
button_label = doc.createElement('label');
|
1101
|
+
button_label.innerHTML = "Button";
|
1102
|
+
button_label.insertBefore(button, button_label.firstChild);
|
1103
|
+
|
1104
|
+
form.appendChild(button_label);
|
475
1105
|
}
|
476
|
-
}
|
477
|
-
return html;
|
478
|
-
}
|
479
1106
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
*/
|
487
|
-
function getCaretOffsets(element, range) {
|
488
|
-
var preCaretRange, postCaretRange;
|
489
|
-
|
490
|
-
if (!range) {
|
491
|
-
range = window.getSelection().getRangeAt(0);
|
492
|
-
}
|
1107
|
+
// Handle click (capture) & focus (capture) outside of the form
|
1108
|
+
this.base.on(doc.body, 'click', this.handleOutsideInteraction.bind(this), true);
|
1109
|
+
this.base.on(doc.body, 'focus', this.handleOutsideInteraction.bind(this), true);
|
1110
|
+
|
1111
|
+
return form;
|
1112
|
+
},
|
493
1113
|
|
494
|
-
|
495
|
-
|
1114
|
+
focus: function (value) {
|
1115
|
+
var input = this.getInput();
|
1116
|
+
input.focus();
|
1117
|
+
input.value = value || '';
|
1118
|
+
},
|
496
1119
|
|
497
|
-
|
498
|
-
|
1120
|
+
hideForm: function () {
|
1121
|
+
this.getForm().style.display = 'none';
|
1122
|
+
},
|
499
1123
|
|
500
|
-
|
501
|
-
|
1124
|
+
showForm: function () {
|
1125
|
+
this.getForm().style.display = 'block';
|
1126
|
+
},
|
502
1127
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
};
|
507
|
-
}
|
1128
|
+
isDisplayed: function () {
|
1129
|
+
return this.getForm().style.display === 'block';
|
1130
|
+
},
|
508
1131
|
|
1132
|
+
isClickIntoForm: function (event) {
|
1133
|
+
return (event &&
|
1134
|
+
event.type &&
|
1135
|
+
event.type.toLowerCase() === 'blur' &&
|
1136
|
+
event.relatedTarget &&
|
1137
|
+
event.relatedTarget === this.getInput());
|
1138
|
+
}
|
1139
|
+
};
|
1140
|
+
}(window, document));
|
1141
|
+
function MediumEditor(elements, options) {
|
1142
|
+
'use strict';
|
1143
|
+
return this.init(elements, options);
|
1144
|
+
}
|
509
1145
|
|
510
|
-
|
511
|
-
|
512
|
-
return !!(obj && obj.nodeType === 1);
|
513
|
-
}
|
1146
|
+
(function () {
|
1147
|
+
'use strict';
|
514
1148
|
|
515
1149
|
MediumEditor.statics = {
|
516
1150
|
ButtonsData: ButtonsData,
|
517
|
-
DefaultButton: DefaultButton
|
1151
|
+
DefaultButton: DefaultButton,
|
1152
|
+
AnchorExtension: AnchorExtension
|
518
1153
|
};
|
519
1154
|
|
520
1155
|
MediumEditor.prototype = {
|
@@ -555,19 +1190,15 @@ if (typeof module === 'object') {
|
|
555
1190
|
lastButtonClass: 'medium-editor-button-last'
|
556
1191
|
},
|
557
1192
|
|
558
|
-
// http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
|
559
|
-
// by rg89
|
560
|
-
isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))),
|
561
|
-
|
562
1193
|
init: function (elements, options) {
|
563
1194
|
var uniqueId = 1;
|
564
1195
|
|
565
|
-
this.options = extend(options, this.defaults);
|
1196
|
+
this.options = mediumEditorUtil.extend(options, this.defaults);
|
566
1197
|
this.setElementSelection(elements);
|
567
1198
|
if (this.elements.length === 0) {
|
568
1199
|
return;
|
569
1200
|
}
|
570
|
-
|
1201
|
+
|
571
1202
|
if (!this.options.elementsContainer) {
|
572
1203
|
this.options.elementsContainer = this.options.ownerDocument.body;
|
573
1204
|
}
|
@@ -643,7 +1274,7 @@ if (typeof module === 'object') {
|
|
643
1274
|
// handleResize is throttled because:
|
644
1275
|
// - It will be called when the browser is resizing, which can fire many times very quickly
|
645
1276
|
// - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits
|
646
|
-
this.handleResize = throttle(function () {
|
1277
|
+
this.handleResize = mediumEditorUtil.throttle(function () {
|
647
1278
|
if (self.isActive) {
|
648
1279
|
self.positionToolbarIfShown();
|
649
1280
|
}
|
@@ -653,7 +1284,7 @@ if (typeof module === 'object') {
|
|
653
1284
|
// - This method could be called many times due to the type of event handlers that are calling it
|
654
1285
|
// - We want a slight delay so that other events in the stack can run, some of which may
|
655
1286
|
// prevent the toolbar from being hidden (via this.keepToolbarAlive).
|
656
|
-
this.handleBlur = throttle(function () {
|
1287
|
+
this.handleBlur = mediumEditorUtil.throttle(function () {
|
657
1288
|
if (self.isActive && !self.keepToolbarAlive) {
|
658
1289
|
self.hideToolbarActions();
|
659
1290
|
}
|
@@ -684,7 +1315,6 @@ if (typeof module === 'object') {
|
|
684
1315
|
if (addToolbar) {
|
685
1316
|
this.initToolbar()
|
686
1317
|
.bindButtons()
|
687
|
-
.bindAnchorForm()
|
688
1318
|
.bindAnchorPreview();
|
689
1319
|
}
|
690
1320
|
return this;
|
@@ -699,7 +1329,7 @@ if (typeof module === 'object') {
|
|
699
1329
|
selector = this.options.ownerDocument.querySelectorAll(selector);
|
700
1330
|
}
|
701
1331
|
// If element, put into array
|
702
|
-
if (isElement(selector)) {
|
1332
|
+
if (mediumEditorUtil.isElement(selector)) {
|
703
1333
|
selector = [selector];
|
704
1334
|
}
|
705
1335
|
// Convert NodeList (or other array like object) into an array
|
@@ -712,7 +1342,7 @@ if (typeof module === 'object') {
|
|
712
1342
|
var isDescendantOfEditorElements = false,
|
713
1343
|
i;
|
714
1344
|
for (i = 0; i < self.elements.length; i += 1) {
|
715
|
-
if (isDescendant(self.elements[i], e.target)) {
|
1345
|
+
if (mediumEditorUtil.isDescendant(self.elements[i], e.target)) {
|
716
1346
|
isDescendantOfEditorElements = true;
|
717
1347
|
break;
|
718
1348
|
}
|
@@ -721,8 +1351,8 @@ if (typeof module === 'object') {
|
|
721
1351
|
if (e.target !== self.toolbar
|
722
1352
|
&& self.elements.indexOf(e.target) === -1
|
723
1353
|
&& !isDescendantOfEditorElements
|
724
|
-
&& !isDescendant(self.toolbar, e.target)
|
725
|
-
&& !isDescendant(self.anchorPreview, e.target)) {
|
1354
|
+
&& !mediumEditorUtil.isDescendant(self.toolbar, e.target)
|
1355
|
+
&& !mediumEditorUtil.isDescendant(self.anchorPreview, e.target)) {
|
726
1356
|
|
727
1357
|
// Activate the placeholder
|
728
1358
|
if (!self.options.disablePlaceholders) {
|
@@ -882,8 +1512,8 @@ if (typeof module === 'object') {
|
|
882
1512
|
this.on(this.elements[index], 'keypress', function (e) {
|
883
1513
|
var node,
|
884
1514
|
tagName;
|
885
|
-
if (e.which === keyCode.SPACE) {
|
886
|
-
node = getSelectionStart
|
1515
|
+
if (e.which === mediumEditorUtil.keyCode.SPACE) {
|
1516
|
+
node = meSelection.getSelectionStart(self.options.ownerDocument);
|
887
1517
|
tagName = node.tagName.toLowerCase();
|
888
1518
|
if (tagName === 'a') {
|
889
1519
|
self.options.ownerDocument.execCommand('unlink', false, null);
|
@@ -892,20 +1522,20 @@ if (typeof module === 'object') {
|
|
892
1522
|
});
|
893
1523
|
|
894
1524
|
this.on(this.elements[index], 'keyup', function (e) {
|
895
|
-
var node = getSelectionStart
|
1525
|
+
var node = meSelection.getSelectionStart(self.options.ownerDocument),
|
896
1526
|
tagName,
|
897
1527
|
editorElement;
|
898
1528
|
|
899
1529
|
if (node && node.getAttribute('data-medium-element') && node.children.length === 0 && !(self.options.disableReturn || node.getAttribute('data-disable-return'))) {
|
900
1530
|
self.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
901
1531
|
}
|
902
|
-
if (e.which === keyCode.ENTER) {
|
903
|
-
node = getSelectionStart
|
1532
|
+
if (e.which === mediumEditorUtil.keyCode.ENTER) {
|
1533
|
+
node = meSelection.getSelectionStart(self.options.ownerDocument);
|
904
1534
|
tagName = node.tagName.toLowerCase();
|
905
|
-
editorElement =
|
1535
|
+
editorElement = meSelection.getSelectionElement(self.options.contentWindow);
|
906
1536
|
|
907
1537
|
if (!(self.options.disableReturn || editorElement.getAttribute('data-disable-return')) &&
|
908
|
-
tagName !== 'li' && !
|
1538
|
+
tagName !== 'li' && !mediumEditorUtil.isListItemChild(node)) {
|
909
1539
|
if (!e.shiftKey) {
|
910
1540
|
|
911
1541
|
// paragraph creation should not be forced within a header tag
|
@@ -922,32 +1552,15 @@ if (typeof module === 'object') {
|
|
922
1552
|
return this;
|
923
1553
|
},
|
924
1554
|
|
925
|
-
isListItemChild: function (node) {
|
926
|
-
var parentNode = node.parentNode,
|
927
|
-
tagName = parentNode.tagName.toLowerCase();
|
928
|
-
while (this.parentElements.indexOf(tagName) === -1 && tagName !== 'div') {
|
929
|
-
if (tagName === 'li') {
|
930
|
-
return true;
|
931
|
-
}
|
932
|
-
parentNode = parentNode.parentNode;
|
933
|
-
if (parentNode && parentNode.tagName) {
|
934
|
-
tagName = parentNode.tagName.toLowerCase();
|
935
|
-
} else {
|
936
|
-
return false;
|
937
|
-
}
|
938
|
-
}
|
939
|
-
return false;
|
940
|
-
},
|
941
|
-
|
942
1555
|
bindReturn: function (index) {
|
943
1556
|
var self = this;
|
944
1557
|
this.on(this.elements[index], 'keypress', function (e) {
|
945
|
-
if (e.which === keyCode.ENTER) {
|
1558
|
+
if (e.which === mediumEditorUtil.keyCode.ENTER) {
|
946
1559
|
if (self.options.disableReturn || this.getAttribute('data-disable-return')) {
|
947
1560
|
e.preventDefault();
|
948
1561
|
} else if (self.options.disableDoubleReturn || this.getAttribute('data-disable-double-return')) {
|
949
|
-
var node = getSelectionStart
|
950
|
-
if (node && node.textContent === '
|
1562
|
+
var node = meSelection.getSelectionStart(self.options.contentWindow);
|
1563
|
+
if (node && node.textContent.trim() === '') {
|
951
1564
|
e.preventDefault();
|
952
1565
|
}
|
953
1566
|
}
|
@@ -960,9 +1573,9 @@ if (typeof module === 'object') {
|
|
960
1573
|
var self = this;
|
961
1574
|
this.on(this.elements[index], 'keydown', function (e) {
|
962
1575
|
|
963
|
-
if (e.which === keyCode.TAB) {
|
1576
|
+
if (e.which === mediumEditorUtil.keyCode.TAB) {
|
964
1577
|
// Override tab only for pre nodes
|
965
|
-
var node = getSelectionStart
|
1578
|
+
var node = meSelection.getSelectionStart(self.options.ownerDocument),
|
966
1579
|
tag = node && node.tagName.toLowerCase();
|
967
1580
|
|
968
1581
|
if (tag === 'pre') {
|
@@ -971,7 +1584,7 @@ if (typeof module === 'object') {
|
|
971
1584
|
}
|
972
1585
|
|
973
1586
|
// Tab to indent list structures!
|
974
|
-
if (tag === 'li' ||
|
1587
|
+
if (tag === 'li' || mediumEditorUtil.isListItemChild(node)) {
|
975
1588
|
e.preventDefault();
|
976
1589
|
|
977
1590
|
// If Shift is down, outdent, otherwise indent
|
@@ -981,7 +1594,7 @@ if (typeof module === 'object') {
|
|
981
1594
|
self.options.ownerDocument.execCommand('indent', e);
|
982
1595
|
}
|
983
1596
|
}
|
984
|
-
} else if (e.which === keyCode.BACKSPACE || e.which === keyCode.DELETE || e.which === keyCode.ENTER) {
|
1597
|
+
} else if (e.which === mediumEditorUtil.keyCode.BACKSPACE || e.which === mediumEditorUtil.keyCode.DELETE || e.which === mediumEditorUtil.keyCode.ENTER) {
|
985
1598
|
|
986
1599
|
// Bind keys which can create or destroy a block element: backspace, delete, return
|
987
1600
|
self.onBlockModifier(e);
|
@@ -992,24 +1605,24 @@ if (typeof module === 'object') {
|
|
992
1605
|
},
|
993
1606
|
|
994
1607
|
onBlockModifier: function (e) {
|
995
|
-
var range, sel, p, node = getSelectionStart
|
1608
|
+
var range, sel, p, node = meSelection.getSelectionStart(this.options.ownerDocument),
|
996
1609
|
tagName = node.tagName.toLowerCase(),
|
997
1610
|
isEmpty = /^(\s+|<br\/?>)?$/i,
|
998
1611
|
isHeader = /h\d/i;
|
999
1612
|
|
1000
|
-
if ((e.which === keyCode.BACKSPACE || e.which === keyCode.ENTER)
|
1613
|
+
if ((e.which === mediumEditorUtil.keyCode.BACKSPACE || e.which === mediumEditorUtil.keyCode.ENTER)
|
1001
1614
|
&& node.previousElementSibling
|
1002
1615
|
// in a header
|
1003
1616
|
&& isHeader.test(tagName)
|
1004
1617
|
// at the very end of the block
|
1005
|
-
&& getCaretOffsets(node).left === 0) {
|
1006
|
-
if (e.which === keyCode.BACKSPACE && isEmpty.test(node.previousElementSibling.innerHTML)) {
|
1618
|
+
&& meSelection.getCaretOffsets(node).left === 0) {
|
1619
|
+
if (e.which === mediumEditorUtil.keyCode.BACKSPACE && isEmpty.test(node.previousElementSibling.innerHTML)) {
|
1007
1620
|
// backspacing the begining of a header into an empty previous element will
|
1008
1621
|
// change the tagName of the current node to prevent one
|
1009
1622
|
// instead delete previous node and cancel the event.
|
1010
1623
|
node.previousElementSibling.parentNode.removeChild(node.previousElementSibling);
|
1011
1624
|
e.preventDefault();
|
1012
|
-
} else if (e.which === keyCode.ENTER) {
|
1625
|
+
} else if (e.which === mediumEditorUtil.keyCode.ENTER) {
|
1013
1626
|
// hitting return in the begining of a header will create empty header elements before the current one
|
1014
1627
|
// instead, make "<p><br></p>" element, which are what happens if you hit return in an empty paragraph
|
1015
1628
|
p = this.options.ownerDocument.createElement('p');
|
@@ -1017,7 +1630,7 @@ if (typeof module === 'object') {
|
|
1017
1630
|
node.previousElementSibling.parentNode.insertBefore(p, node);
|
1018
1631
|
e.preventDefault();
|
1019
1632
|
}
|
1020
|
-
} else if (e.which === keyCode.DELETE
|
1633
|
+
} else if (e.which === mediumEditorUtil.keyCode.DELETE
|
1021
1634
|
&& node.nextElementSibling
|
1022
1635
|
&& node.previousElementSibling
|
1023
1636
|
// not in a header
|
@@ -1056,13 +1669,6 @@ if (typeof module === 'object') {
|
|
1056
1669
|
this.toolbarActions = this.toolbar.querySelector('.medium-editor-toolbar-actions');
|
1057
1670
|
this.anchorPreview = this.createAnchorPreview();
|
1058
1671
|
|
1059
|
-
if (!this.options.disableAnchorForm) {
|
1060
|
-
this.anchorForm = this.toolbar.querySelector('.medium-editor-toolbar-form');
|
1061
|
-
this.anchorInput = this.anchorForm.querySelector('input.medium-editor-toolbar-input');
|
1062
|
-
this.anchorTarget = this.anchorForm.querySelector('input.medium-editor-toolbar-anchor-target');
|
1063
|
-
this.anchorButton = this.anchorForm.querySelector('input.medium-editor-toolbar-anchor-button');
|
1064
|
-
}
|
1065
|
-
|
1066
1672
|
this.addExtensionForms();
|
1067
1673
|
|
1068
1674
|
return this;
|
@@ -1081,7 +1687,8 @@ if (typeof module === 'object') {
|
|
1081
1687
|
|
1082
1688
|
toolbar.appendChild(this.toolbarButtons());
|
1083
1689
|
if (!this.options.disableAnchorForm) {
|
1084
|
-
|
1690
|
+
this.anchorExtension = new AnchorExtension(this);
|
1691
|
+
toolbar.appendChild(this.anchorExtension.getForm());
|
1085
1692
|
}
|
1086
1693
|
this.options.elementsContainer.appendChild(toolbar);
|
1087
1694
|
return toolbar;
|
@@ -1100,7 +1707,7 @@ if (typeof module === 'object') {
|
|
1100
1707
|
if (typeof extension.getButton === 'function') {
|
1101
1708
|
btn = extension.getButton(this);
|
1102
1709
|
li = this.options.ownerDocument.createElement('li');
|
1103
|
-
if (isElement(btn)) {
|
1710
|
+
if (mediumEditorUtil.isElement(btn)) {
|
1104
1711
|
li.appendChild(btn);
|
1105
1712
|
} else {
|
1106
1713
|
li.innerHTML = btn;
|
@@ -1129,84 +1736,32 @@ if (typeof module === 'object') {
|
|
1129
1736
|
}.bind(this));
|
1130
1737
|
},
|
1131
1738
|
|
1132
|
-
toolbarFormAnchor: function () {
|
1133
|
-
var anchor = this.options.ownerDocument.createElement('div'),
|
1134
|
-
input = this.options.ownerDocument.createElement('input'),
|
1135
|
-
target_label = this.options.ownerDocument.createElement('label'),
|
1136
|
-
target = this.options.ownerDocument.createElement('input'),
|
1137
|
-
button_label = this.options.ownerDocument.createElement('label'),
|
1138
|
-
button = this.options.ownerDocument.createElement('input'),
|
1139
|
-
close = this.options.ownerDocument.createElement('a'),
|
1140
|
-
save = this.options.ownerDocument.createElement('a');
|
1141
|
-
|
1142
|
-
close.setAttribute('href', '#');
|
1143
|
-
close.className = 'medium-editor-toobar-close';
|
1144
|
-
close.innerHTML = '×';
|
1145
|
-
|
1146
|
-
save.setAttribute('href', '#');
|
1147
|
-
save.className = 'medium-editor-toobar-save';
|
1148
|
-
save.innerHTML = '✓';
|
1149
|
-
|
1150
|
-
input.setAttribute('type', 'text');
|
1151
|
-
input.className = 'medium-editor-toolbar-input';
|
1152
|
-
input.setAttribute('placeholder', this.options.anchorInputPlaceholder);
|
1153
|
-
|
1154
|
-
|
1155
|
-
target.setAttribute('type', 'checkbox');
|
1156
|
-
target.className = 'medium-editor-toolbar-anchor-target';
|
1157
|
-
target_label.innerHTML = this.options.anchorInputCheckboxLabel;
|
1158
|
-
target_label.insertBefore(target, target_label.firstChild);
|
1159
|
-
|
1160
|
-
button.setAttribute('type', 'checkbox');
|
1161
|
-
button.className = 'medium-editor-toolbar-anchor-button';
|
1162
|
-
button_label.innerHTML = "Button";
|
1163
|
-
button_label.insertBefore(button, button_label.firstChild);
|
1164
|
-
|
1165
|
-
|
1166
|
-
anchor.className = 'medium-editor-toolbar-form';
|
1167
|
-
anchor.id = 'medium-editor-toolbar-form-anchor-' + this.id;
|
1168
|
-
anchor.appendChild(input);
|
1169
|
-
|
1170
|
-
anchor.appendChild(save);
|
1171
|
-
anchor.appendChild(close);
|
1172
|
-
|
1173
|
-
if (this.options.anchorTarget) {
|
1174
|
-
anchor.appendChild(target_label);
|
1175
|
-
}
|
1176
|
-
|
1177
|
-
if (this.options.anchorButton) {
|
1178
|
-
anchor.appendChild(button_label);
|
1179
|
-
}
|
1180
|
-
|
1181
|
-
return anchor;
|
1182
|
-
},
|
1183
|
-
|
1184
1739
|
bindSelect: function () {
|
1185
1740
|
var self = this,
|
1186
1741
|
i,
|
1187
|
-
|
1742
|
+
timeoutHelper;
|
1188
1743
|
|
1189
1744
|
this.checkSelectionWrapper = function (e) {
|
1190
|
-
e.stopPropagation();
|
1191
|
-
|
1192
|
-
clearTimeout(timer);
|
1193
|
-
|
1194
1745
|
// Do not close the toolbar when bluring the editable area and clicking into the anchor form
|
1195
|
-
if (
|
1746
|
+
if (e && this.anchorExtension && this.anchorExtension.isClickIntoForm(e)) {
|
1196
1747
|
return false;
|
1197
1748
|
}
|
1198
1749
|
|
1199
|
-
|
1200
|
-
self.checkSelection();
|
1201
|
-
}, 10);
|
1750
|
+
self.checkSelection();
|
1202
1751
|
};
|
1203
1752
|
|
1753
|
+
timeoutHelper = function (event) {
|
1754
|
+
setTimeout(function () {
|
1755
|
+
this.checkSelectionWrapper(event);
|
1756
|
+
}.bind(this), 0);
|
1757
|
+
}.bind(this);
|
1758
|
+
|
1204
1759
|
this.on(this.options.ownerDocument.documentElement, 'mouseup', this.checkSelectionWrapper);
|
1205
1760
|
|
1206
1761
|
for (i = 0; i < this.elements.length; i += 1) {
|
1207
1762
|
this.on(this.elements[i], 'keyup', this.checkSelectionWrapper);
|
1208
1763
|
this.on(this.elements[i], 'blur', this.checkSelectionWrapper);
|
1209
|
-
this.on(this.elements[i], '
|
1764
|
+
this.on(this.elements[i], 'click', timeoutHelper);
|
1210
1765
|
}
|
1211
1766
|
|
1212
1767
|
return this;
|
@@ -1279,7 +1834,7 @@ if (typeof module === 'object') {
|
|
1279
1834
|
fileReader.readAsDataURL(file);
|
1280
1835
|
|
1281
1836
|
id = 'medium-img-' + (+new Date());
|
1282
|
-
self.
|
1837
|
+
mediumEditorUtil.insertHTMLCommand(self.options.ownerDocument, '<img class="medium-image-loading" id="' + id + '" />');
|
1283
1838
|
|
1284
1839
|
fileReader.onload = function () {
|
1285
1840
|
var img = document.getElementById(id);
|
@@ -1314,7 +1869,6 @@ if (typeof module === 'object') {
|
|
1314
1869
|
},
|
1315
1870
|
|
1316
1871
|
checkSelection: function () {
|
1317
|
-
|
1318
1872
|
var newSelection,
|
1319
1873
|
selectionElement;
|
1320
1874
|
|
@@ -1326,17 +1880,17 @@ if (typeof module === 'object') {
|
|
1326
1880
|
|
1327
1881
|
if ((!this.options.updateOnEmptySelection && newSelection.toString().trim() === '') ||
|
1328
1882
|
(this.options.allowMultiParagraphSelection === false && this.hasMultiParagraphs()) ||
|
1329
|
-
|
1883
|
+
meSelection.selectionInContentEditableFalse(this.options.contentWindow)) {
|
1330
1884
|
|
1331
1885
|
if (!this.options.staticToolbar) {
|
1332
1886
|
this.hideToolbarActions();
|
1333
|
-
} else if (this.
|
1887
|
+
} else if (this.anchorExtension && this.anchorExtension.isDisplayed()) {
|
1334
1888
|
this.setToolbarButtonStates();
|
1335
1889
|
this.showToolbarActions();
|
1336
1890
|
}
|
1337
1891
|
|
1338
1892
|
} else {
|
1339
|
-
selectionElement =
|
1893
|
+
selectionElement = meSelection.getSelectionElement(this.options.contentWindow);
|
1340
1894
|
if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) {
|
1341
1895
|
if (!this.options.staticToolbar) {
|
1342
1896
|
this.hideToolbarActions();
|
@@ -1349,18 +1903,8 @@ if (typeof module === 'object') {
|
|
1349
1903
|
return this;
|
1350
1904
|
},
|
1351
1905
|
|
1352
|
-
clickingIntoArchorForm: function (e) {
|
1353
|
-
var self = this;
|
1354
|
-
|
1355
|
-
if (e.type && e.type.toLowerCase() === 'blur' && e.relatedTarget && e.relatedTarget === self.anchorInput) {
|
1356
|
-
return true;
|
1357
|
-
}
|
1358
|
-
|
1359
|
-
return false;
|
1360
|
-
},
|
1361
|
-
|
1362
1906
|
hasMultiParagraphs: function () {
|
1363
|
-
var selectionHtml = getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
|
1907
|
+
var selectionHtml = meSelection.getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
|
1364
1908
|
hasMultiParagraphs = selectionHtml.match(/<(p|h[0-6]|blockquote)>([\s\S]*?)<\/(p|h[0-6]|blockquote)>/g);
|
1365
1909
|
|
1366
1910
|
return (hasMultiParagraphs ? hasMultiParagraphs.length : 0);
|
@@ -1393,7 +1937,7 @@ if (typeof module === 'object') {
|
|
1393
1937
|
if (this.options.standardizeSelectionStart &&
|
1394
1938
|
this.selectionRange.startContainer.nodeValue &&
|
1395
1939
|
(this.selectionRange.startOffset === this.selectionRange.startContainer.nodeValue.length)) {
|
1396
|
-
adjacentNode = findAdjacentTextNodeWithContent(
|
1940
|
+
adjacentNode = mediumEditorUtil.findAdjacentTextNodeWithContent(meSelection.getSelectionElement(this.options.contentWindow), this.selectionRange.startContainer, this.options.ownerDocument);
|
1397
1941
|
if (adjacentNode) {
|
1398
1942
|
offset = 0;
|
1399
1943
|
while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) {
|
@@ -1422,52 +1966,6 @@ if (typeof module === 'object') {
|
|
1422
1966
|
}
|
1423
1967
|
},
|
1424
1968
|
|
1425
|
-
traverseUp: function (current, testElementFunction) {
|
1426
|
-
|
1427
|
-
do {
|
1428
|
-
if (current.nodeType === 1) {
|
1429
|
-
if (testElementFunction(current)) {
|
1430
|
-
return current;
|
1431
|
-
}
|
1432
|
-
// do not traverse upwards past the nearest containing editor
|
1433
|
-
if (current.getAttribute('data-medium-element')) {
|
1434
|
-
return false;
|
1435
|
-
}
|
1436
|
-
}
|
1437
|
-
|
1438
|
-
current = current.parentNode;
|
1439
|
-
} while (current);
|
1440
|
-
|
1441
|
-
return false;
|
1442
|
-
|
1443
|
-
},
|
1444
|
-
|
1445
|
-
findMatchingSelectionParent: function (testElementFunction) {
|
1446
|
-
var selection = this.options.contentWindow.getSelection(), range, current;
|
1447
|
-
|
1448
|
-
if (selection.rangeCount === 0) {
|
1449
|
-
return false;
|
1450
|
-
}
|
1451
|
-
|
1452
|
-
range = selection.getRangeAt(0);
|
1453
|
-
current = range.commonAncestorContainer;
|
1454
|
-
|
1455
|
-
return this.traverseUp(current, testElementFunction);
|
1456
|
-
|
1457
|
-
},
|
1458
|
-
|
1459
|
-
getSelectionElement: function () {
|
1460
|
-
return this.findMatchingSelectionParent(function (el) {
|
1461
|
-
return el.getAttribute('data-medium-element');
|
1462
|
-
});
|
1463
|
-
},
|
1464
|
-
|
1465
|
-
selectionInContentEditableFalse: function () {
|
1466
|
-
return this.findMatchingSelectionParent(function (el) {
|
1467
|
-
return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
|
1468
|
-
});
|
1469
|
-
},
|
1470
|
-
|
1471
1969
|
setToolbarPosition: function () {
|
1472
1970
|
// document.documentElement for IE 9
|
1473
1971
|
var scrollTop = (this.options.ownerDocument.documentElement && this.options.ownerDocument.documentElement.scrollTop) || this.options.ownerDocument.body.scrollTop,
|
@@ -1563,7 +2061,9 @@ if (typeof module === 'object') {
|
|
1563
2061
|
|
1564
2062
|
checkActiveButtons: function () {
|
1565
2063
|
var elements = Array.prototype.slice.call(this.elements),
|
1566
|
-
|
2064
|
+
manualStateChecks = [],
|
2065
|
+
queryState = null,
|
2066
|
+
parentNode = meSelection.getSelectedParentElement(this.selectionRange),
|
1567
2067
|
checkExtension = function (extension) {
|
1568
2068
|
if (typeof extension.checkState === 'function') {
|
1569
2069
|
extension.checkState(parentNode);
|
@@ -1573,9 +2073,29 @@ if (typeof module === 'object') {
|
|
1573
2073
|
}
|
1574
2074
|
}
|
1575
2075
|
};
|
1576
|
-
|
2076
|
+
|
2077
|
+
// Loop through all commands
|
2078
|
+
this.commands.forEach(function (command) {
|
2079
|
+
// For those commands where we can use document.queryCommandState(), do so
|
2080
|
+
if (typeof command.queryCommandState === 'function') {
|
2081
|
+
queryState = command.queryCommandState();
|
2082
|
+
// If queryCommandState returns a valid value, we can trust the browser
|
2083
|
+
// and don't need to do our manual checks
|
2084
|
+
if (queryState !== null) {
|
2085
|
+
if (queryState) {
|
2086
|
+
command.activate();
|
2087
|
+
}
|
2088
|
+
return;
|
2089
|
+
}
|
2090
|
+
}
|
2091
|
+
// We can't use queryCommandState for this command, so add to manualStateChecks
|
2092
|
+
manualStateChecks.push(command);
|
2093
|
+
});
|
2094
|
+
|
2095
|
+
// Climb up the DOM and do manual checks for whether a certain command is currently enabled for this node
|
2096
|
+
while (parentNode.tagName !== undefined && mediumEditorUtil.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) {
|
1577
2097
|
this.activateButton(parentNode.tagName.toLowerCase());
|
1578
|
-
|
2098
|
+
manualStateChecks.forEach(checkExtension.bind(this));
|
1579
2099
|
|
1580
2100
|
// we can abort the search upwards if we leave the contentEditable element
|
1581
2101
|
if (elements.indexOf(parentNode) !== -1) {
|
@@ -1644,9 +2164,10 @@ if (typeof module === 'object') {
|
|
1644
2164
|
el.style.display = 'none';
|
1645
2165
|
this.showToolbarActions();
|
1646
2166
|
this.setToolbarPosition();
|
1647
|
-
restoreSelection
|
2167
|
+
this.restoreSelection();
|
1648
2168
|
},
|
1649
2169
|
|
2170
|
+
// TODO: move these two methods to selection.js
|
1650
2171
|
// http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox
|
1651
2172
|
rangeSelectsSingleNode: function (range) {
|
1652
2173
|
var startNode = range.startContainer;
|
@@ -1669,12 +2190,12 @@ if (typeof module === 'object') {
|
|
1669
2190
|
},
|
1670
2191
|
|
1671
2192
|
triggerAnchorAction: function () {
|
1672
|
-
var selectedParentElement =
|
2193
|
+
var selectedParentElement = meSelection.getSelectedParentElement(this.selectionRange);
|
1673
2194
|
if (selectedParentElement.tagName &&
|
1674
2195
|
selectedParentElement.tagName.toLowerCase() === 'a') {
|
1675
2196
|
this.options.ownerDocument.execCommand('unlink', false, null);
|
1676
|
-
} else if (this.
|
1677
|
-
if (this.
|
2197
|
+
} else if (this.anchorExtension) {
|
2198
|
+
if (this.anchorExtension.isDisplayed()) {
|
1678
2199
|
this.showToolbarActions();
|
1679
2200
|
} else {
|
1680
2201
|
this.showAnchorForm();
|
@@ -1684,7 +2205,7 @@ if (typeof module === 'object') {
|
|
1684
2205
|
},
|
1685
2206
|
|
1686
2207
|
execFormatBlock: function (el) {
|
1687
|
-
var selectionData =
|
2208
|
+
var selectionData = meSelection.getSelectionData(this.selection.anchorNode);
|
1688
2209
|
// FF handles blockquote differently on formatBlock
|
1689
2210
|
// allowing nesting, we need to use outdent
|
1690
2211
|
// https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla
|
@@ -1699,7 +2220,7 @@ if (typeof module === 'object') {
|
|
1699
2220
|
// blockquote needs to be called as indent
|
1700
2221
|
// http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie
|
1701
2222
|
// http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777
|
1702
|
-
if (
|
2223
|
+
if (mediumEditorUtil.isIE) {
|
1703
2224
|
if (el === 'blockquote') {
|
1704
2225
|
return this.options.ownerDocument.execCommand('indent', false, el);
|
1705
2226
|
}
|
@@ -1708,34 +2229,6 @@ if (typeof module === 'object') {
|
|
1708
2229
|
return this.options.ownerDocument.execCommand('formatBlock', false, el);
|
1709
2230
|
},
|
1710
2231
|
|
1711
|
-
getSelectionData: function (el) {
|
1712
|
-
var tagName;
|
1713
|
-
|
1714
|
-
if (el && el.tagName) {
|
1715
|
-
tagName = el.tagName.toLowerCase();
|
1716
|
-
}
|
1717
|
-
|
1718
|
-
while (el && this.parentElements.indexOf(tagName) === -1) {
|
1719
|
-
el = el.parentNode;
|
1720
|
-
if (el && el.tagName) {
|
1721
|
-
tagName = el.tagName.toLowerCase();
|
1722
|
-
}
|
1723
|
-
}
|
1724
|
-
|
1725
|
-
return {
|
1726
|
-
el: el,
|
1727
|
-
tagName: tagName
|
1728
|
-
};
|
1729
|
-
},
|
1730
|
-
|
1731
|
-
getFirstChild: function (el) {
|
1732
|
-
var firstChild = el.firstChild;
|
1733
|
-
while (firstChild !== null && firstChild.nodeType !== 1) {
|
1734
|
-
firstChild = firstChild.nextSibling;
|
1735
|
-
}
|
1736
|
-
return firstChild;
|
1737
|
-
},
|
1738
|
-
|
1739
2232
|
isToolbarShown: function () {
|
1740
2233
|
return this.toolbar && this.toolbar.classList.contains('medium-editor-toolbar-active');
|
1741
2234
|
},
|
@@ -1769,123 +2262,121 @@ if (typeof module === 'object') {
|
|
1769
2262
|
this.hideToolbar();
|
1770
2263
|
},
|
1771
2264
|
|
1772
|
-
showToolbarActions: function () {
|
1773
|
-
var self = this;
|
1774
|
-
if (this.
|
1775
|
-
this.
|
1776
|
-
}
|
1777
|
-
this.toolbarActions.style.display = 'block';
|
1778
|
-
this.keepToolbarAlive = false;
|
1779
|
-
// Using setTimeout + options.delay because:
|
1780
|
-
// We will actually be displaying the toolbar, which should be controlled by options.delay
|
1781
|
-
this.delay(function () {
|
1782
|
-
self.showToolbar();
|
1783
|
-
});
|
1784
|
-
},
|
1785
|
-
|
1786
|
-
saveSelection: function () {
|
1787
|
-
this.savedSelection = saveSelection.call(this);
|
1788
|
-
},
|
1789
|
-
|
1790
|
-
restoreSelection: function () {
|
1791
|
-
restoreSelection.call(this, this.savedSelection);
|
1792
|
-
},
|
1793
|
-
|
1794
|
-
showAnchorForm: function (link_value) {
|
1795
|
-
if (!this.anchorForm) {
|
1796
|
-
return;
|
1797
|
-
}
|
1798
|
-
|
1799
|
-
this.toolbarActions.style.display = 'none';
|
1800
|
-
this.saveSelection();
|
1801
|
-
this.anchorForm.style.display = 'block';
|
1802
|
-
this.setToolbarPosition();
|
1803
|
-
this.keepToolbarAlive = true;
|
1804
|
-
this.anchorInput.focus();
|
1805
|
-
this.anchorInput.value = link_value || '';
|
1806
|
-
},
|
1807
|
-
|
1808
|
-
bindAnchorForm: function () {
|
1809
|
-
if (!this.anchorForm) {
|
1810
|
-
return this;
|
2265
|
+
showToolbarActions: function () {
|
2266
|
+
var self = this;
|
2267
|
+
if (this.anchorExtension) {
|
2268
|
+
this.anchorExtension.hideForm();
|
1811
2269
|
}
|
2270
|
+
this.toolbarActions.style.display = 'block';
|
2271
|
+
this.keepToolbarAlive = false;
|
2272
|
+
// Using setTimeout + options.delay because:
|
2273
|
+
// We will actually be displaying the toolbar, which should be controlled by options.delay
|
2274
|
+
this.delay(function () {
|
2275
|
+
self.showToolbar();
|
2276
|
+
});
|
2277
|
+
},
|
1812
2278
|
|
1813
|
-
|
1814
|
-
|
1815
|
-
|
2279
|
+
// http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html
|
2280
|
+
// Tim Down
|
2281
|
+
// TODO: move to selection.js and clean up old methods there
|
2282
|
+
saveSelection: function () {
|
2283
|
+
this.selectionState = null;
|
1816
2284
|
|
1817
|
-
this.
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
2285
|
+
var selection = this.options.contentWindow.getSelection(),
|
2286
|
+
range,
|
2287
|
+
preSelectionRange,
|
2288
|
+
start,
|
2289
|
+
editableElementIndex = -1;
|
1821
2290
|
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
2291
|
+
if (selection.rangeCount > 0) {
|
2292
|
+
range = selection.getRangeAt(0);
|
2293
|
+
preSelectionRange = range.cloneRange();
|
1825
2294
|
|
1826
|
-
|
1827
|
-
|
1828
|
-
if (
|
1829
|
-
|
1830
|
-
|
1831
|
-
target = "_self";
|
2295
|
+
// Find element current selection is inside
|
2296
|
+
this.elements.forEach(function (el, index) {
|
2297
|
+
if (el === range.startContainer || mediumEditorUtil.isDescendant(el, range.startContainer)) {
|
2298
|
+
editableElementIndex = index;
|
2299
|
+
return false;
|
1832
2300
|
}
|
2301
|
+
});
|
1833
2302
|
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
2303
|
+
if (editableElementIndex > -1) {
|
2304
|
+
preSelectionRange.selectNodeContents(this.elements[editableElementIndex]);
|
2305
|
+
preSelectionRange.setEnd(range.startContainer, range.startOffset);
|
2306
|
+
start = preSelectionRange.toString().length;
|
1837
2307
|
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
2308
|
+
this.selectionState = {
|
2309
|
+
start: start,
|
2310
|
+
end: start + range.toString().length,
|
2311
|
+
editableElementIndex: editableElementIndex
|
2312
|
+
};
|
1843
2313
|
}
|
1844
|
-
}
|
2314
|
+
}
|
2315
|
+
},
|
1845
2316
|
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
2317
|
+
// http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html
|
2318
|
+
// Tim Down
|
2319
|
+
// TODO: move to selection.js and clean up old methods there
|
2320
|
+
restoreSelection: function () {
|
2321
|
+
if (!this.selectionState) {
|
2322
|
+
return;
|
2323
|
+
}
|
2324
|
+
|
2325
|
+
var editableElement = this.elements[this.selectionState.editableElementIndex],
|
2326
|
+
charIndex = 0,
|
2327
|
+
range = this.options.ownerDocument.createRange(),
|
2328
|
+
nodeStack = [editableElement],
|
2329
|
+
node,
|
2330
|
+
foundStart = false,
|
2331
|
+
stop = false,
|
2332
|
+
i,
|
2333
|
+
sel,
|
2334
|
+
nextCharIndex;
|
2335
|
+
|
2336
|
+
range.setStart(editableElement, 0);
|
2337
|
+
range.collapse(true);
|
2338
|
+
|
2339
|
+
node = nodeStack.pop();
|
2340
|
+
while (!stop && node) {
|
2341
|
+
if (node.nodeType === 3) {
|
2342
|
+
nextCharIndex = charIndex + node.length;
|
2343
|
+
if (!foundStart && this.selectionState.start >= charIndex && this.selectionState.start <= nextCharIndex) {
|
2344
|
+
range.setStart(node, this.selectionState.start - charIndex);
|
2345
|
+
foundStart = true;
|
2346
|
+
}
|
2347
|
+
if (foundStart && this.selectionState.end >= charIndex && this.selectionState.end <= nextCharIndex) {
|
2348
|
+
range.setEnd(node, this.selectionState.end - charIndex);
|
2349
|
+
stop = true;
|
2350
|
+
}
|
2351
|
+
charIndex = nextCharIndex;
|
1852
2352
|
} else {
|
1853
|
-
|
2353
|
+
i = node.childNodes.length - 1;
|
2354
|
+
while (i >= 0) {
|
2355
|
+
nodeStack.push(node.childNodes[i]);
|
2356
|
+
i -= 1;
|
2357
|
+
}
|
1854
2358
|
}
|
1855
|
-
|
1856
|
-
|
1857
|
-
button = self.options.anchorButtonClass;
|
2359
|
+
if (!stop) {
|
2360
|
+
node = nodeStack.pop();
|
1858
2361
|
}
|
2362
|
+
}
|
1859
2363
|
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
// make sure not to hide form when cliking into the input
|
1865
|
-
e.stopPropagation();
|
1866
|
-
self.keepToolbarAlive = true;
|
1867
|
-
});
|
2364
|
+
sel = this.options.contentWindow.getSelection();
|
2365
|
+
sel.removeAllRanges();
|
2366
|
+
sel.addRange(range);
|
2367
|
+
},
|
1868
2368
|
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1873
|
-
self.checkSelection();
|
1874
|
-
}
|
1875
|
-
}, true);
|
1876
|
-
this.on(this.options.ownerDocument.body, 'focus', function (e) {
|
1877
|
-
if (e.target !== self.anchorForm && !isDescendant(self.anchorForm, e.target) && !isDescendant(self.toolbarActions, e.target)) {
|
1878
|
-
self.keepToolbarAlive = false;
|
1879
|
-
self.checkSelection();
|
1880
|
-
}
|
1881
|
-
}, true);
|
2369
|
+
showAnchorForm: function (link_value) {
|
2370
|
+
if (!this.anchorExtension) {
|
2371
|
+
return;
|
2372
|
+
}
|
1882
2373
|
|
1883
|
-
this.
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
2374
|
+
this.toolbarActions.style.display = 'none';
|
2375
|
+
this.saveSelection();
|
2376
|
+
this.anchorExtension.showForm();
|
2377
|
+
this.setToolbarPosition();
|
2378
|
+
this.keepToolbarAlive = true;
|
2379
|
+
this.anchorExtension.focus(link_value);
|
1889
2380
|
},
|
1890
2381
|
|
1891
2382
|
hideAnchorPreview: function () {
|
@@ -2002,7 +2493,7 @@ if (typeof module === 'object') {
|
|
2002
2493
|
sel.removeAllRanges();
|
2003
2494
|
sel.addRange(range);
|
2004
2495
|
// Using setTimeout + options.delay because:
|
2005
|
-
// We may actually be displaying the anchor
|
2496
|
+
// We may actually be displaying the anchor form, which should be controlled by options.delay
|
2006
2497
|
this.delay(function () {
|
2007
2498
|
if (self.activeAnchor) {
|
2008
2499
|
self.showAnchorForm(self.activeAnchor.attributes.href.value);
|
@@ -2067,22 +2558,8 @@ if (typeof module === 'object') {
|
|
2067
2558
|
return (re.test(value) ? '' : 'http://') + value;
|
2068
2559
|
},
|
2069
2560
|
|
2070
|
-
setTargetBlank: function (el) {
|
2071
|
-
var i;
|
2072
|
-
el = el || getSelectionStart.call(this);
|
2073
|
-
if (el.tagName.toLowerCase() === 'a') {
|
2074
|
-
el.target = '_blank';
|
2075
|
-
} else {
|
2076
|
-
el = el.getElementsByTagName('a');
|
2077
|
-
|
2078
|
-
for (i = 0; i < el.length; i += 1) {
|
2079
|
-
el[i].target = '_blank';
|
2080
|
-
}
|
2081
|
-
}
|
2082
|
-
},
|
2083
|
-
|
2084
2561
|
setButtonClass: function (buttonClass) {
|
2085
|
-
var el = getSelectionStart
|
2562
|
+
var el = meSelection.getSelectionStart(this.options.ownerDocument),
|
2086
2563
|
classes = buttonClass.split(' '),
|
2087
2564
|
i,
|
2088
2565
|
j;
|
@@ -2101,6 +2578,7 @@ if (typeof module === 'object') {
|
|
2101
2578
|
},
|
2102
2579
|
|
2103
2580
|
createLink: function (input, target, buttonClass) {
|
2581
|
+
|
2104
2582
|
var i, event;
|
2105
2583
|
|
2106
2584
|
this.createLinkInternal(input.value, target, buttonClass);
|
@@ -2124,7 +2602,7 @@ if (typeof module === 'object') {
|
|
2124
2602
|
return;
|
2125
2603
|
}
|
2126
2604
|
|
2127
|
-
restoreSelection
|
2605
|
+
this.restoreSelection();
|
2128
2606
|
|
2129
2607
|
if (this.options.checkLinkFormat) {
|
2130
2608
|
url = this.checkLinkFormat(url);
|
@@ -2133,7 +2611,7 @@ if (typeof module === 'object') {
|
|
2133
2611
|
this.options.ownerDocument.execCommand('createLink', false, url);
|
2134
2612
|
|
2135
2613
|
if (this.options.targetBlank || target === "_blank") {
|
2136
|
-
|
2614
|
+
mediumEditorUtil.setTargetBlank(meSelection.getSelectionStart(this.options.ownerDocument));
|
2137
2615
|
}
|
2138
2616
|
|
2139
2617
|
if (buttonClass) {
|
@@ -2195,59 +2673,23 @@ if (typeof module === 'object') {
|
|
2195
2673
|
this.elements[i].removeAttribute('data-medium-element');
|
2196
2674
|
}
|
2197
2675
|
|
2198
|
-
this.
|
2199
|
-
|
2676
|
+
this.commands.forEach(function (extension) {
|
2677
|
+
if (typeof extension.deactivate === 'function') {
|
2678
|
+
extension.deactivate();
|
2679
|
+
}
|
2680
|
+
}.bind(this));
|
2200
2681
|
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2682
|
+
if (this.anchorExtension) {
|
2683
|
+
this.anchorExtension.deactivate();
|
2684
|
+
}
|
2685
|
+
|
2686
|
+
this.removeAllEvents();
|
2205
2687
|
},
|
2206
2688
|
|
2207
2689
|
bindPaste: function () {
|
2208
2690
|
var i, self = this;
|
2209
2691
|
this.pasteWrapper = function (e) {
|
2210
|
-
|
2211
|
-
html = '',
|
2212
|
-
p,
|
2213
|
-
dataFormatHTML = 'text/html',
|
2214
|
-
dataFormatPlain = 'text/plain';
|
2215
|
-
|
2216
|
-
this.classList.remove('medium-editor-placeholder');
|
2217
|
-
if (!self.options.forcePlainText && !self.options.cleanPastedHTML) {
|
2218
|
-
return this;
|
2219
|
-
}
|
2220
|
-
|
2221
|
-
if (self.options.contentWindow.clipboardData && e.clipboardData === undefined) {
|
2222
|
-
e.clipboardData = self.options.contentWindow.clipboardData;
|
2223
|
-
// If window.clipboardData exists, but e.clipboardData doesn't exist,
|
2224
|
-
// we're probably in IE. IE only has two possibilities for clipboard
|
2225
|
-
// data format: 'Text' and 'URL'.
|
2226
|
-
//
|
2227
|
-
// Of the two, we want 'Text':
|
2228
|
-
dataFormatHTML = 'Text';
|
2229
|
-
dataFormatPlain = 'Text';
|
2230
|
-
}
|
2231
|
-
|
2232
|
-
if (e.clipboardData && e.clipboardData.getData && !e.defaultPrevented) {
|
2233
|
-
e.preventDefault();
|
2234
|
-
|
2235
|
-
if (self.options.cleanPastedHTML && e.clipboardData.getData(dataFormatHTML)) {
|
2236
|
-
return self.cleanPaste(e.clipboardData.getData(dataFormatHTML));
|
2237
|
-
}
|
2238
|
-
if (!(self.options.disableReturn || this.getAttribute('data-disable-return'))) {
|
2239
|
-
paragraphs = e.clipboardData.getData(dataFormatPlain).split(/[\r\n]/g);
|
2240
|
-
for (p = 0; p < paragraphs.length; p += 1) {
|
2241
|
-
if (paragraphs[p] !== '') {
|
2242
|
-
html += '<p>' + self.htmlEntities(paragraphs[p]) + '</p>';
|
2243
|
-
}
|
2244
|
-
}
|
2245
|
-
self.insertHTML(html);
|
2246
|
-
} else {
|
2247
|
-
html = self.htmlEntities(e.clipboardData.getData(dataFormatPlain));
|
2248
|
-
self.insertHTML(html);
|
2249
|
-
}
|
2250
|
-
}
|
2692
|
+
pasteHandler.handlePaste(this, e, self.options);
|
2251
2693
|
};
|
2252
2694
|
for (i = 0; i < this.elements.length; i += 1) {
|
2253
2695
|
this.on(this.elements[i], 'paste', this.pasteWrapper);
|
@@ -2268,205 +2710,15 @@ if (typeof module === 'object') {
|
|
2268
2710
|
},
|
2269
2711
|
|
2270
2712
|
cleanPaste: function (text) {
|
2271
|
-
|
2272
|
-
/*jslint regexp: true*/
|
2273
|
-
/*
|
2274
|
-
jslint does not allow character negation, because the negation
|
2275
|
-
will not match any unicode characters. In the regexes in this
|
2276
|
-
block, negation is used specifically to match the end of an html
|
2277
|
-
tag, and in fact unicode characters *should* be allowed.
|
2278
|
-
*/
|
2279
|
-
var i, elList, workEl,
|
2280
|
-
el = this.getSelectionElement(),
|
2281
|
-
multiline = /<p|<br|<div/.test(text),
|
2282
|
-
replacements = [
|
2283
|
-
|
2284
|
-
// replace two bogus tags that begin pastes from google docs
|
2285
|
-
[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""],
|
2286
|
-
[new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""],
|
2287
|
-
|
2288
|
-
// un-html spaces and newlines inserted by OS X
|
2289
|
-
[new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '],
|
2290
|
-
[new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],
|
2291
|
-
|
2292
|
-
// replace google docs italics+bold with a span to be replaced once the html is inserted
|
2293
|
-
[new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
|
2294
|
-
|
2295
|
-
// replace google docs italics with a span to be replaced once the html is inserted
|
2296
|
-
[new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],
|
2297
|
-
|
2298
|
-
//[replace google docs bolds with a span to be replaced once the html is inserted
|
2299
|
-
[new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'],
|
2300
|
-
|
2301
|
-
// replace manually entered b/i/a tags with real ones
|
2302
|
-
[new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'],
|
2303
|
-
|
2304
|
-
// replace manually a tags with real ones, converting smart-quotes from google docs
|
2305
|
-
[new RegExp(/<a\s+href=("|”|“|“|”)([^&]+)("|”|“|“|”)>/gi), '<a href="$2">']
|
2306
|
-
|
2307
|
-
];
|
2308
|
-
/*jslint regexp: false*/
|
2309
|
-
|
2310
|
-
for (i = 0; i < replacements.length; i += 1) {
|
2311
|
-
text = text.replace(replacements[i][0], replacements[i][1]);
|
2312
|
-
}
|
2313
|
-
|
2314
|
-
if (multiline) {
|
2315
|
-
|
2316
|
-
// double br's aren't converted to p tags, but we want paragraphs.
|
2317
|
-
elList = text.split('<br><br>');
|
2318
|
-
|
2319
|
-
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>');
|
2320
|
-
this.options.ownerDocument.execCommand('insertText', false, "\n");
|
2321
|
-
|
2322
|
-
// block element cleanup
|
2323
|
-
elList = el.querySelectorAll('a,p,div,br');
|
2324
|
-
for (i = 0; i < elList.length; i += 1) {
|
2325
|
-
|
2326
|
-
workEl = elList[i];
|
2327
|
-
|
2328
|
-
switch (workEl.tagName.toLowerCase()) {
|
2329
|
-
case 'a':
|
2330
|
-
if (this.options.targetBlank) {
|
2331
|
-
this.setTargetBlank(workEl);
|
2332
|
-
}
|
2333
|
-
break;
|
2334
|
-
case 'p':
|
2335
|
-
case 'div':
|
2336
|
-
this.filterCommonBlocks(workEl);
|
2337
|
-
break;
|
2338
|
-
case 'br':
|
2339
|
-
this.filterLineBreak(workEl);
|
2340
|
-
break;
|
2341
|
-
}
|
2342
|
-
|
2343
|
-
}
|
2344
|
-
|
2345
|
-
|
2346
|
-
} else {
|
2347
|
-
|
2348
|
-
this.pasteHTML(text);
|
2349
|
-
|
2350
|
-
}
|
2351
|
-
|
2713
|
+
pasteHandler.cleanPaste(text, this.options);
|
2352
2714
|
},
|
2353
2715
|
|
2354
2716
|
pasteHTML: function (html) {
|
2355
|
-
|
2356
|
-
|
2357
|
-
pasteBlock.appendChild(this.options.ownerDocument.createElement('body'));
|
2358
|
-
|
2359
|
-
fragmentBody = pasteBlock.querySelector('body');
|
2360
|
-
fragmentBody.innerHTML = html;
|
2361
|
-
|
2362
|
-
this.cleanupSpans(fragmentBody);
|
2363
|
-
|
2364
|
-
elList = fragmentBody.querySelectorAll('*');
|
2365
|
-
for (i = 0; i < elList.length; i += 1) {
|
2366
|
-
|
2367
|
-
workEl = elList[i];
|
2368
|
-
|
2369
|
-
// delete ugly attributes
|
2370
|
-
workEl.removeAttribute('class');
|
2371
|
-
workEl.removeAttribute('style');
|
2372
|
-
workEl.removeAttribute('dir');
|
2373
|
-
|
2374
|
-
if (workEl.tagName.toLowerCase() === 'meta') {
|
2375
|
-
workEl.parentNode.removeChild(workEl);
|
2376
|
-
}
|
2377
|
-
|
2378
|
-
}
|
2379
|
-
this.insertHTML(fragmentBody.innerHTML.replace(/ /g, ' '));
|
2380
|
-
},
|
2381
|
-
isCommonBlock: function (el) {
|
2382
|
-
return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div'));
|
2383
|
-
},
|
2384
|
-
filterCommonBlocks: function (el) {
|
2385
|
-
if (/^\s*$/.test(el.textContent)) {
|
2386
|
-
el.parentNode.removeChild(el);
|
2387
|
-
}
|
2388
|
-
},
|
2389
|
-
filterLineBreak: function (el) {
|
2390
|
-
if (this.isCommonBlock(el.previousElementSibling)) {
|
2391
|
-
|
2392
|
-
// remove stray br's following common block elements
|
2393
|
-
el.parentNode.removeChild(el);
|
2394
|
-
|
2395
|
-
} else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
|
2396
|
-
|
2397
|
-
// remove br's just inside open or close tags of a div/p
|
2398
|
-
el.parentNode.removeChild(el);
|
2399
|
-
|
2400
|
-
} else if (el.parentNode.childElementCount === 1) {
|
2401
|
-
|
2402
|
-
// and br's that are the only child of a div/p
|
2403
|
-
this.removeWithParent(el);
|
2404
|
-
|
2405
|
-
}
|
2406
|
-
|
2407
|
-
},
|
2408
|
-
|
2409
|
-
// remove an element, including its parent, if it is the only element within its parent
|
2410
|
-
removeWithParent: function (el) {
|
2411
|
-
if (el && el.parentNode) {
|
2412
|
-
if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
|
2413
|
-
el.parentNode.parentNode.removeChild(el.parentNode);
|
2414
|
-
} else {
|
2415
|
-
el.parentNode.removeChild(el.parentNode);
|
2416
|
-
}
|
2417
|
-
}
|
2418
|
-
},
|
2419
|
-
|
2420
|
-
cleanupSpans: function (container_el) {
|
2421
|
-
|
2422
|
-
var i,
|
2423
|
-
el,
|
2424
|
-
new_el,
|
2425
|
-
spans = container_el.querySelectorAll('.replace-with'),
|
2426
|
-
isCEF = function (el) {
|
2427
|
-
return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false');
|
2428
|
-
};
|
2429
|
-
|
2430
|
-
for (i = 0; i < spans.length; i += 1) {
|
2431
|
-
|
2432
|
-
el = spans[i];
|
2433
|
-
new_el = this.options.ownerDocument.createElement(el.classList.contains('bold') ? 'b' : 'i');
|
2434
|
-
|
2435
|
-
if (el.classList.contains('bold') && el.classList.contains('italic')) {
|
2436
|
-
|
2437
|
-
// add an i tag as well if this has both italics and bold
|
2438
|
-
new_el.innerHTML = '<i>' + el.innerHTML + '</i>';
|
2439
|
-
|
2440
|
-
} else {
|
2441
|
-
|
2442
|
-
new_el.innerHTML = el.innerHTML;
|
2443
|
-
|
2444
|
-
}
|
2445
|
-
el.parentNode.replaceChild(new_el, el);
|
2446
|
-
|
2447
|
-
}
|
2448
|
-
|
2449
|
-
spans = container_el.querySelectorAll('span');
|
2450
|
-
for (i = 0; i < spans.length; i += 1) {
|
2451
|
-
|
2452
|
-
el = spans[i];
|
2453
|
-
|
2454
|
-
// bail if span is in contenteditable = false
|
2455
|
-
if (this.traverseUp(el, isCEF)) {
|
2456
|
-
return false;
|
2457
|
-
}
|
2458
|
-
|
2459
|
-
// remove empty spans, replace others with their contents
|
2460
|
-
if (/^\s*$/.test()) {
|
2461
|
-
el.parentNode.removeChild(el);
|
2462
|
-
} else {
|
2463
|
-
el.parentNode.replaceChild(this.options.ownerDocument.createTextNode(el.textContent), el);
|
2464
|
-
}
|
2465
|
-
|
2466
|
-
}
|
2467
|
-
|
2717
|
+
pasteHandler.pasteHTML(html, this.options.ownerDocument);
|
2468
2718
|
}
|
2469
|
-
|
2470
2719
|
};
|
2471
2720
|
|
2472
|
-
}(
|
2721
|
+
}());
|
2722
|
+
|
2723
|
+
return MediumEditor;
|
2724
|
+
}()));
|