medium-editor-rails 1.4.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +643 -462
- 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: ea0310100e4bb03780e766942afdb94119f88c38
|
4
|
+
data.tar.gz: eee6063511bbbe4b7a8c3493100379eb2fa60f60
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8699a3a049805261ddc04f90c73268056cae22d94633b5ca90539019bae92083f4a46de1435db42da4e7f0451c4a9257f01ae8ab86dd7998fc8c6b1774ed19c
|
7
|
+
data.tar.gz: deba6b68b29f27f4e44c8df1adfe544030d23831aa1db57c397c06ced4f08b553e5a4e036630d4cd68d83b0191bbe4beb3f8e905d981d88d179fba7776d7ba6c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
|
2
2
|
#### [Current]
|
3
|
+
* [c615afc](../../commit/c615afc) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
|
+
* [ba615ed](../../commit/ba615ed) - __(Ahmet Sezgin Duran)__ Merge tag '1.4.5' into develop
|
5
|
+
|
6
|
+
1.4.5
|
7
|
+
|
8
|
+
#### 1.4.5
|
9
|
+
* [a55de93](../../commit/a55de93) - __(Ahmet Sezgin Duran)__ Bump versions 1.4.5 and 2.4.5
|
3
10
|
* [0b17fe4](../../commit/0b17fe4) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
11
|
* [ebfc535](../../commit/ebfc535) - __(Ahmet Sezgin Duran)__ Merge tag '1.4.4' 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 [
|
11
|
+
The latest version of Medium Editor bundled by this gem is [3.0.0](https://github.com/daviferreira/medium-editor/releases)
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -1,3 +1,176 @@
|
|
1
|
+
/*global self, document, DOMException */
|
2
|
+
|
3
|
+
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
|
4
|
+
|
5
|
+
// Full polyfill for browsers with no classList support
|
6
|
+
if (!("classList" in document.createElement("_"))) {
|
7
|
+
(function (view) {
|
8
|
+
|
9
|
+
"use strict";
|
10
|
+
|
11
|
+
if (!('Element' in view)) return;
|
12
|
+
|
13
|
+
var
|
14
|
+
classListProp = "classList"
|
15
|
+
, protoProp = "prototype"
|
16
|
+
, elemCtrProto = view.Element[protoProp]
|
17
|
+
, objCtr = Object
|
18
|
+
, strTrim = String[protoProp].trim || function () {
|
19
|
+
return this.replace(/^\s+|\s+$/g, "");
|
20
|
+
}
|
21
|
+
, arrIndexOf = Array[protoProp].indexOf || function (item) {
|
22
|
+
var
|
23
|
+
i = 0
|
24
|
+
, len = this.length
|
25
|
+
;
|
26
|
+
for (; i < len; i++) {
|
27
|
+
if (i in this && this[i] === item) {
|
28
|
+
return i;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
return -1;
|
32
|
+
}
|
33
|
+
// Vendors: please allow content code to instantiate DOMExceptions
|
34
|
+
, DOMEx = function (type, message) {
|
35
|
+
this.name = type;
|
36
|
+
this.code = DOMException[type];
|
37
|
+
this.message = message;
|
38
|
+
}
|
39
|
+
, checkTokenAndGetIndex = function (classList, token) {
|
40
|
+
if (token === "") {
|
41
|
+
throw new DOMEx(
|
42
|
+
"SYNTAX_ERR"
|
43
|
+
, "An invalid or illegal string was specified"
|
44
|
+
);
|
45
|
+
}
|
46
|
+
if (/\s/.test(token)) {
|
47
|
+
throw new DOMEx(
|
48
|
+
"INVALID_CHARACTER_ERR"
|
49
|
+
, "String contains an invalid character"
|
50
|
+
);
|
51
|
+
}
|
52
|
+
return arrIndexOf.call(classList, token);
|
53
|
+
}
|
54
|
+
, ClassList = function (elem) {
|
55
|
+
var
|
56
|
+
trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
|
57
|
+
, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
|
58
|
+
, i = 0
|
59
|
+
, len = classes.length
|
60
|
+
;
|
61
|
+
for (; i < len; i++) {
|
62
|
+
this.push(classes[i]);
|
63
|
+
}
|
64
|
+
this._updateClassName = function () {
|
65
|
+
elem.setAttribute("class", this.toString());
|
66
|
+
};
|
67
|
+
}
|
68
|
+
, classListProto = ClassList[protoProp] = []
|
69
|
+
, classListGetter = function () {
|
70
|
+
return new ClassList(this);
|
71
|
+
}
|
72
|
+
;
|
73
|
+
// Most DOMException implementations don't allow calling DOMException's toString()
|
74
|
+
// on non-DOMExceptions. Error's toString() is sufficient here.
|
75
|
+
DOMEx[protoProp] = Error[protoProp];
|
76
|
+
classListProto.item = function (i) {
|
77
|
+
return this[i] || null;
|
78
|
+
};
|
79
|
+
classListProto.contains = function (token) {
|
80
|
+
token += "";
|
81
|
+
return checkTokenAndGetIndex(this, token) !== -1;
|
82
|
+
};
|
83
|
+
classListProto.add = function () {
|
84
|
+
var
|
85
|
+
tokens = arguments
|
86
|
+
, i = 0
|
87
|
+
, l = tokens.length
|
88
|
+
, token
|
89
|
+
, updated = false
|
90
|
+
;
|
91
|
+
do {
|
92
|
+
token = tokens[i] + "";
|
93
|
+
if (checkTokenAndGetIndex(this, token) === -1) {
|
94
|
+
this.push(token);
|
95
|
+
updated = true;
|
96
|
+
}
|
97
|
+
}
|
98
|
+
while (++i < l);
|
99
|
+
|
100
|
+
if (updated) {
|
101
|
+
this._updateClassName();
|
102
|
+
}
|
103
|
+
};
|
104
|
+
classListProto.remove = function () {
|
105
|
+
var
|
106
|
+
tokens = arguments
|
107
|
+
, i = 0
|
108
|
+
, l = tokens.length
|
109
|
+
, token
|
110
|
+
, updated = false
|
111
|
+
, index
|
112
|
+
;
|
113
|
+
do {
|
114
|
+
token = tokens[i] + "";
|
115
|
+
index = checkTokenAndGetIndex(this, token);
|
116
|
+
while (index !== -1) {
|
117
|
+
this.splice(index, 1);
|
118
|
+
updated = true;
|
119
|
+
index = checkTokenAndGetIndex(this, token);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
while (++i < l);
|
123
|
+
|
124
|
+
if (updated) {
|
125
|
+
this._updateClassName();
|
126
|
+
}
|
127
|
+
};
|
128
|
+
classListProto.toggle = function (token, force) {
|
129
|
+
token += "";
|
130
|
+
|
131
|
+
var
|
132
|
+
result = this.contains(token)
|
133
|
+
, method = result ?
|
134
|
+
force !== true && "remove"
|
135
|
+
:
|
136
|
+
force !== false && "add"
|
137
|
+
;
|
138
|
+
|
139
|
+
if (method) {
|
140
|
+
this[method](token);
|
141
|
+
}
|
142
|
+
|
143
|
+
if (force === true || force === false) {
|
144
|
+
return force;
|
145
|
+
} else {
|
146
|
+
return !result;
|
147
|
+
}
|
148
|
+
};
|
149
|
+
classListProto.toString = function () {
|
150
|
+
return this.join(" ");
|
151
|
+
};
|
152
|
+
|
153
|
+
if (objCtr.defineProperty) {
|
154
|
+
var classListPropDesc = {
|
155
|
+
get: classListGetter
|
156
|
+
, enumerable: true
|
157
|
+
, configurable: true
|
158
|
+
};
|
159
|
+
try {
|
160
|
+
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
161
|
+
} catch (ex) { // IE 8 doesn't support enumerable:true
|
162
|
+
if (ex.number === -0x7FF5EC54) {
|
163
|
+
classListPropDesc.enumerable = false;
|
164
|
+
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
} else if (objCtr[protoProp].__defineGetter__) {
|
168
|
+
elemCtrProto.__defineGetter__(classListProp, classListGetter);
|
169
|
+
}
|
170
|
+
|
171
|
+
}(self));
|
172
|
+
}
|
173
|
+
|
1
174
|
(function (root, factory) {
|
2
175
|
'use strict';
|
3
176
|
if (typeof module === 'object') {
|
@@ -11,12 +184,23 @@
|
|
11
184
|
|
12
185
|
'use strict';
|
13
186
|
|
14
|
-
var
|
187
|
+
var Util;
|
15
188
|
|
16
189
|
(function (window, document) {
|
17
190
|
'use strict';
|
18
191
|
|
19
|
-
|
192
|
+
function copyInto(dest, source, overwrite) {
|
193
|
+
var prop;
|
194
|
+
dest = dest || {};
|
195
|
+
for (prop in source) {
|
196
|
+
if (source.hasOwnProperty(prop) && (overwrite || dest.hasOwnProperty(prop) === false)) {
|
197
|
+
dest[prop] = source[prop];
|
198
|
+
}
|
199
|
+
}
|
200
|
+
return dest;
|
201
|
+
}
|
202
|
+
|
203
|
+
Util = {
|
20
204
|
|
21
205
|
// http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
|
22
206
|
// by rg89
|
@@ -34,17 +218,22 @@ var mediumEditorUtil;
|
|
34
218
|
|
35
219
|
parentElements: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre'],
|
36
220
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
221
|
+
defaults: function defaults(dest, source) {
|
222
|
+
return copyInto(dest, source);
|
223
|
+
},
|
224
|
+
|
225
|
+
extend: function extend(dest, source) {
|
226
|
+
return copyInto(dest, source, true);
|
227
|
+
},
|
228
|
+
|
229
|
+
derives: function derives(base, derived) {
|
230
|
+
var origPrototype = derived.prototype;
|
231
|
+
function Proto() { }
|
232
|
+
Proto.prototype = base.prototype;
|
233
|
+
derived.prototype = new Proto();
|
234
|
+
derived.prototype.constructor = base;
|
235
|
+
derived.prototype = copyInto(derived.prototype, origPrototype);
|
236
|
+
return derived;
|
48
237
|
},
|
49
238
|
|
50
239
|
// Find the next node in the DOM tree that represents any text that is being
|
@@ -114,7 +303,7 @@ var mediumEditorUtil;
|
|
114
303
|
}
|
115
304
|
|
116
305
|
later = function () {
|
117
|
-
previous =
|
306
|
+
previous = Util.now();
|
118
307
|
timeout = null;
|
119
308
|
result = func.apply(context, args);
|
120
309
|
if (!timeout) {
|
@@ -123,7 +312,7 @@ var mediumEditorUtil;
|
|
123
312
|
};
|
124
313
|
|
125
314
|
return function () {
|
126
|
-
var currNow =
|
315
|
+
var currNow = Util.now(),
|
127
316
|
remaining = wait - (currNow - previous);
|
128
317
|
context = this;
|
129
318
|
args = arguments;
|
@@ -236,12 +425,12 @@ var mediumEditorUtil;
|
|
236
425
|
};
|
237
426
|
}(window, document));
|
238
427
|
|
239
|
-
var
|
428
|
+
var Selection;
|
240
429
|
|
241
430
|
(function (window, document) {
|
242
431
|
'use strict';
|
243
432
|
|
244
|
-
|
433
|
+
Selection = {
|
245
434
|
// http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
|
246
435
|
// by You
|
247
436
|
getSelectionStart: function (ownerDocument) {
|
@@ -260,7 +449,7 @@ var meSelection;
|
|
260
449
|
range = selection.getRangeAt(0);
|
261
450
|
current = range.commonAncestorContainer;
|
262
451
|
|
263
|
-
return
|
452
|
+
return Util.traverseUp(current, testElementFunction);
|
264
453
|
},
|
265
454
|
|
266
455
|
getSelectionElement: function (contentWindow) {
|
@@ -356,7 +545,7 @@ var meSelection;
|
|
356
545
|
tagName = el.tagName.toLowerCase();
|
357
546
|
}
|
358
547
|
|
359
|
-
while (el &&
|
548
|
+
while (el && Util.parentElements.indexOf(tagName) === -1) {
|
360
549
|
el = el.parentNode;
|
361
550
|
if (el && el.tagName) {
|
362
551
|
tagName = el.tagName.toLowerCase();
|
@@ -455,14 +644,6 @@ var DefaultButton,
|
|
455
644
|
contentDefault: '<b>x<sub>1</sub></b>',
|
456
645
|
contentFA: '<i class="fa fa-subscript"></i>'
|
457
646
|
},
|
458
|
-
'anchor': {
|
459
|
-
name: 'anchor',
|
460
|
-
action: 'anchor',
|
461
|
-
aria: 'link',
|
462
|
-
tagNames: ['a'],
|
463
|
-
contentDefault: '<b>#</b>',
|
464
|
-
contentFA: '<i class="fa fa-link"></i>'
|
465
|
-
},
|
466
647
|
'image': {
|
467
648
|
name: 'image',
|
468
649
|
action: 'image',
|
@@ -646,32 +827,21 @@ var DefaultButton,
|
|
646
827
|
handleClick: function (evt) {
|
647
828
|
evt.preventDefault();
|
648
829
|
evt.stopPropagation();
|
649
|
-
var action = this.getAction();
|
650
|
-
if (!this.base.selection) {
|
651
|
-
this.base.checkSelection();
|
652
|
-
}
|
653
830
|
|
654
|
-
|
655
|
-
this.deactivate();
|
656
|
-
} else {
|
657
|
-
this.activate();
|
658
|
-
}
|
831
|
+
var action = this.getAction();
|
659
832
|
|
660
833
|
if (action) {
|
661
|
-
this.base.execAction(action
|
834
|
+
this.base.execAction(action);
|
662
835
|
}
|
663
|
-
//if (this.options.form) {
|
664
|
-
// this.base.showForm(this.form, evt);
|
665
|
-
//}
|
666
836
|
},
|
667
837
|
isActive: function () {
|
668
838
|
return this.button.classList.contains(this.base.options.activeButtonClass);
|
669
839
|
},
|
670
|
-
|
840
|
+
setInactive: function () {
|
671
841
|
this.button.classList.remove(this.base.options.activeButtonClass);
|
672
842
|
delete this.knownState;
|
673
843
|
},
|
674
|
-
|
844
|
+
setActive: function () {
|
675
845
|
this.button.classList.add(this.base.options.activeButtonClass);
|
676
846
|
delete this.knownState;
|
677
847
|
},
|
@@ -686,11 +856,12 @@ var DefaultButton,
|
|
686
856
|
}
|
687
857
|
return queryState;
|
688
858
|
},
|
689
|
-
|
859
|
+
isAlreadyApplied: function (node) {
|
690
860
|
var isMatch = false,
|
691
861
|
tagNames = this.getTagNames(),
|
692
862
|
styleVals,
|
693
863
|
computedStyle;
|
864
|
+
|
694
865
|
if (this.knownState === false || this.knownState === true) {
|
695
866
|
return this.knownState;
|
696
867
|
}
|
@@ -789,20 +960,20 @@ var pasteHandler;
|
|
789
960
|
paragraphs = evt.clipboardData.getData(dataFormatPlain).split(/[\r\n]/g);
|
790
961
|
for (p = 0; p < paragraphs.length; p += 1) {
|
791
962
|
if (paragraphs[p] !== '') {
|
792
|
-
html += '<p>' +
|
963
|
+
html += '<p>' + Util.htmlEntities(paragraphs[p]) + '</p>';
|
793
964
|
}
|
794
965
|
}
|
795
|
-
|
966
|
+
Util.insertHTMLCommand(options.ownerDocument, html);
|
796
967
|
} else {
|
797
|
-
html =
|
798
|
-
|
968
|
+
html = Util.htmlEntities(evt.clipboardData.getData(dataFormatPlain));
|
969
|
+
Util.insertHTMLCommand(options.ownerDocument, html);
|
799
970
|
}
|
800
971
|
}
|
801
972
|
},
|
802
973
|
|
803
974
|
cleanPaste: function (text, options) {
|
804
975
|
var i, elList, workEl,
|
805
|
-
el =
|
976
|
+
el = Selection.getSelectionElement(options.contentWindow),
|
806
977
|
multiline = /<p|<br|<div/.test(text),
|
807
978
|
replacements = createReplacements();
|
808
979
|
|
@@ -815,7 +986,10 @@ var pasteHandler;
|
|
815
986
|
elList = text.split('<br><br>');
|
816
987
|
|
817
988
|
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>', options.ownerDocument);
|
818
|
-
|
989
|
+
|
990
|
+
try {
|
991
|
+
options.ownerDocument.execCommand('insertText', false, "\n");
|
992
|
+
} catch (ignore) { }
|
819
993
|
|
820
994
|
// block element cleanup
|
821
995
|
elList = el.querySelectorAll('a,p,div,br');
|
@@ -825,7 +999,7 @@ var pasteHandler;
|
|
825
999
|
switch (workEl.tagName.toLowerCase()) {
|
826
1000
|
case 'a':
|
827
1001
|
if (options.targetBlank) {
|
828
|
-
|
1002
|
+
Util.setTargetBlank(workEl);
|
829
1003
|
}
|
830
1004
|
break;
|
831
1005
|
case 'p':
|
@@ -865,7 +1039,7 @@ var pasteHandler;
|
|
865
1039
|
workEl.parentNode.removeChild(workEl);
|
866
1040
|
}
|
867
1041
|
}
|
868
|
-
|
1042
|
+
Util.insertHTMLCommand(ownerDocument, fragmentBody.innerHTML.replace(/ /g, ' '));
|
869
1043
|
},
|
870
1044
|
isCommonBlock: function (el) {
|
871
1045
|
return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div'));
|
@@ -882,7 +1056,7 @@ var pasteHandler;
|
|
882
1056
|
} else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
|
883
1057
|
// remove br's just inside open or close tags of a div/p
|
884
1058
|
el.parentNode.removeChild(el);
|
885
|
-
} else if (el.parentNode.childElementCount === 1) {
|
1059
|
+
} else if (el.parentNode.childElementCount === 1 && el.parentNode.textContent === '') {
|
886
1060
|
// and br's that are the only child of a div/p
|
887
1061
|
this.removeWithParent(el);
|
888
1062
|
}
|
@@ -927,7 +1101,7 @@ var pasteHandler;
|
|
927
1101
|
el = spans[i];
|
928
1102
|
|
929
1103
|
// bail if span is in contenteditable = false
|
930
|
-
if (
|
1104
|
+
if (Util.traverseUp(el, isCEF)) {
|
931
1105
|
return false;
|
932
1106
|
}
|
933
1107
|
|
@@ -947,12 +1121,48 @@ var AnchorExtension;
|
|
947
1121
|
(function (window, document) {
|
948
1122
|
'use strict';
|
949
1123
|
|
950
|
-
|
951
|
-
this.
|
952
|
-
|
1124
|
+
function AnchorDerived() {
|
1125
|
+
this.parent = true;
|
1126
|
+
this.options = {
|
1127
|
+
name: 'anchor',
|
1128
|
+
action: 'createLink',
|
1129
|
+
aria: 'link',
|
1130
|
+
tagNames: ['a'],
|
1131
|
+
contentDefault: '<b>#</b>',
|
1132
|
+
contentFA: '<i class="fa fa-link"></i>'
|
1133
|
+
};
|
1134
|
+
this.name = 'anchor';
|
1135
|
+
this.hasForm = true;
|
1136
|
+
}
|
1137
|
+
|
1138
|
+
AnchorDerived.prototype = {
|
953
1139
|
|
954
|
-
|
1140
|
+
// Button and Extension handling
|
955
1141
|
|
1142
|
+
// Called when the button the toolbar is clicked
|
1143
|
+
// Overrides DefaultButton.handleClick
|
1144
|
+
handleClick: function (evt) {
|
1145
|
+
evt.preventDefault();
|
1146
|
+
evt.stopPropagation();
|
1147
|
+
|
1148
|
+
if (!this.base.selection) {
|
1149
|
+
this.base.checkSelection();
|
1150
|
+
}
|
1151
|
+
|
1152
|
+
var selectedParentElement = Selection.getSelectedParentElement(this.base.selectionRange);
|
1153
|
+
if (selectedParentElement.tagName &&
|
1154
|
+
selectedParentElement.tagName.toLowerCase() === 'a') {
|
1155
|
+
return this.base.execAction('unlink');
|
1156
|
+
}
|
1157
|
+
|
1158
|
+
if (!this.isDisplayed()) {
|
1159
|
+
this.showForm();
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
return false;
|
1163
|
+
},
|
1164
|
+
|
1165
|
+
// Called by medium-editor to append form to the toolbar
|
956
1166
|
getForm: function () {
|
957
1167
|
if (!this.anchorForm) {
|
958
1168
|
this.anchorForm = this.createForm();
|
@@ -960,10 +1170,30 @@ var AnchorExtension;
|
|
960
1170
|
return this.anchorForm;
|
961
1171
|
},
|
962
1172
|
|
963
|
-
|
964
|
-
|
1173
|
+
// Used by medium-editor when the default toolbar is to be displayed
|
1174
|
+
isDisplayed: function () {
|
1175
|
+
return this.getForm().style.display === 'block';
|
965
1176
|
},
|
966
1177
|
|
1178
|
+
hideForm: function () {
|
1179
|
+
this.getForm().style.display = 'none';
|
1180
|
+
this.getInput().value = '';
|
1181
|
+
},
|
1182
|
+
|
1183
|
+
showForm: function (link_value) {
|
1184
|
+
var input = this.getInput();
|
1185
|
+
|
1186
|
+
this.base.saveSelection();
|
1187
|
+
this.base.hideToolbarDefaultActions();
|
1188
|
+
this.getForm().style.display = 'block';
|
1189
|
+
this.base.setToolbarPosition();
|
1190
|
+
this.base.keepToolbarAlive = true;
|
1191
|
+
|
1192
|
+
input.value = link_value || '';
|
1193
|
+
input.focus();
|
1194
|
+
},
|
1195
|
+
|
1196
|
+
// Called by core when tearing down medium-editor (deactivate)
|
967
1197
|
deactivate: function () {
|
968
1198
|
if (!this.anchorForm) {
|
969
1199
|
return false;
|
@@ -976,38 +1206,48 @@ var AnchorExtension;
|
|
976
1206
|
delete this.anchorForm;
|
977
1207
|
},
|
978
1208
|
|
1209
|
+
// core methods
|
1210
|
+
|
979
1211
|
doLinkCreation: function () {
|
980
|
-
var
|
981
|
-
|
982
|
-
|
983
|
-
|
1212
|
+
var targetCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-target'),
|
1213
|
+
buttonCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-button'),
|
1214
|
+
opts = {
|
1215
|
+
url: this.getInput().value
|
1216
|
+
};
|
1217
|
+
|
1218
|
+
this.base.restoreSelection();
|
1219
|
+
|
1220
|
+
if (this.base.options.checkLinkFormat) {
|
1221
|
+
opts.url = this.checkLinkFormat(opts.url);
|
1222
|
+
}
|
984
1223
|
|
985
1224
|
if (targetCheckbox && targetCheckbox.checked) {
|
986
|
-
target = "_blank";
|
1225
|
+
opts.target = "_blank";
|
987
1226
|
} else {
|
988
|
-
target = "_self";
|
1227
|
+
opts.target = "_self";
|
989
1228
|
}
|
990
1229
|
|
991
1230
|
if (buttonCheckbox && buttonCheckbox.checked) {
|
992
|
-
|
1231
|
+
opts.buttonClass = this.base.options.anchorButtonClass;
|
993
1232
|
}
|
994
1233
|
|
995
|
-
this.base.createLink(
|
1234
|
+
this.base.createLink(opts);
|
1235
|
+
this.base.keepToolbarAlive = false;
|
1236
|
+
this.base.checkSelection();
|
1237
|
+
},
|
1238
|
+
|
1239
|
+
checkLinkFormat: function (value) {
|
1240
|
+
var re = /^(https?|ftps?|rtmpt?):\/\/|mailto:/;
|
1241
|
+
return (re.test(value) ? '' : 'http://') + value;
|
996
1242
|
},
|
997
1243
|
|
998
1244
|
doFormCancel: function () {
|
999
|
-
this.base.showToolbarActions();
|
1000
1245
|
this.base.restoreSelection();
|
1246
|
+
this.base.keepToolbarAlive = false;
|
1247
|
+
this.base.checkSelection();
|
1001
1248
|
},
|
1002
1249
|
|
1003
|
-
|
1004
|
-
if (event.target !== this.getForm() &&
|
1005
|
-
!mediumEditorUtil.isDescendant(this.getForm(), event.target) &&
|
1006
|
-
!mediumEditorUtil.isDescendant(this.base.toolbarActions, event.target)) {
|
1007
|
-
this.base.keepToolbarAlive = false;
|
1008
|
-
this.base.checkSelection();
|
1009
|
-
}
|
1010
|
-
},
|
1250
|
+
// form creation and event handling
|
1011
1251
|
|
1012
1252
|
createForm: function () {
|
1013
1253
|
var doc = this.base.options.ownerDocument,
|
@@ -1025,10 +1265,7 @@ var AnchorExtension;
|
|
1025
1265
|
form.id = 'medium-editor-toolbar-form-anchor-' + this.base.id;
|
1026
1266
|
|
1027
1267
|
// Handle clicks on the form itself
|
1028
|
-
this.base.on(form, 'click',
|
1029
|
-
event.stopPropagation();
|
1030
|
-
this.base.keepToolbarAlive = true;
|
1031
|
-
}.bind(this));
|
1268
|
+
this.base.on(form, 'click', this.handleFormClick.bind(this));
|
1032
1269
|
|
1033
1270
|
// Add url textbox
|
1034
1271
|
input.setAttribute('type', 'text');
|
@@ -1037,53 +1274,32 @@ var AnchorExtension;
|
|
1037
1274
|
form.appendChild(input);
|
1038
1275
|
|
1039
1276
|
// Handle typing in the textbox
|
1040
|
-
this.base.on(input, 'keyup',
|
1041
|
-
// For ENTER -> create the anchor
|
1042
|
-
if (event.keyCode === mediumEditorUtil.keyCode.ENTER) {
|
1043
|
-
event.preventDefault();
|
1044
|
-
this.doLinkCreation();
|
1045
|
-
return;
|
1046
|
-
}
|
1047
|
-
|
1048
|
-
// For ESCAPE -> close the form
|
1049
|
-
if (event.keyCode === mediumEditorUtil.keyCode.ESCAPE) {
|
1050
|
-
event.preventDefault();
|
1051
|
-
this.doFormCancel();
|
1052
|
-
}
|
1053
|
-
}.bind(this));
|
1277
|
+
this.base.on(input, 'keyup', this.handleTextboxKeyup.bind(this));
|
1054
1278
|
|
1055
1279
|
// Handle clicks into the textbox
|
1056
|
-
this.base.on(input, 'click',
|
1057
|
-
// make sure not to hide form when cliking into the input
|
1058
|
-
event.stopPropagation();
|
1059
|
-
this.base.keepToolbarAlive = true;
|
1060
|
-
}.bind(this));
|
1280
|
+
this.base.on(input, 'click', this.handleFormClick.bind(this));
|
1061
1281
|
|
1062
1282
|
// Add save buton
|
1063
1283
|
save.setAttribute('href', '#');
|
1064
1284
|
save.className = 'medium-editor-toobar-save';
|
1065
|
-
save.innerHTML = '
|
1285
|
+
save.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
|
1286
|
+
'<i class="fa fa-check"></i>' :
|
1287
|
+
'✓';
|
1066
1288
|
form.appendChild(save);
|
1067
1289
|
|
1068
1290
|
// Handle save button clicks (capture)
|
1069
|
-
this.base.on(save, 'click',
|
1070
|
-
// Clicking Save -> create the anchor
|
1071
|
-
event.preventDefault();
|
1072
|
-
this.doLinkCreation();
|
1073
|
-
}.bind(this), true);
|
1291
|
+
this.base.on(save, 'click', this.handleSaveClick.bind(this), true);
|
1074
1292
|
|
1075
1293
|
// Add close button
|
1076
1294
|
close.setAttribute('href', '#');
|
1077
1295
|
close.className = 'medium-editor-toobar-close';
|
1078
|
-
close.innerHTML = '
|
1296
|
+
close.innerHTML = this.base.options.buttonLabels === 'fontawesome' ?
|
1297
|
+
'<i class="fa fa-times"></i>' :
|
1298
|
+
'×';
|
1079
1299
|
form.appendChild(close);
|
1080
1300
|
|
1081
1301
|
// Handle close button clicks
|
1082
|
-
this.base.on(close, 'click',
|
1083
|
-
// Click Close -> close the form
|
1084
|
-
event.preventDefault();
|
1085
|
-
this.doFormCancel();
|
1086
|
-
}.bind(this));
|
1302
|
+
this.base.on(close, 'click', this.handleCloseClick.bind(this));
|
1087
1303
|
|
1088
1304
|
// (Optional) Add 'open in new window' checkbox
|
1089
1305
|
if (this.base.options.anchorTarget) {
|
@@ -1118,33 +1334,56 @@ var AnchorExtension;
|
|
1118
1334
|
return form;
|
1119
1335
|
},
|
1120
1336
|
|
1121
|
-
|
1122
|
-
|
1123
|
-
input.focus();
|
1124
|
-
input.value = value || '';
|
1337
|
+
getInput: function () {
|
1338
|
+
return this.getForm().querySelector('input.medium-editor-toolbar-input');
|
1125
1339
|
},
|
1126
1340
|
|
1127
|
-
|
1128
|
-
this.getForm()
|
1341
|
+
handleOutsideInteraction: function (event) {
|
1342
|
+
if (event.target !== this.getForm() &&
|
1343
|
+
!Util.isDescendant(this.getForm(), event.target) &&
|
1344
|
+
!Util.isDescendant(this.base.toolbarActions, event.target)) {
|
1345
|
+
this.base.keepToolbarAlive = false;
|
1346
|
+
this.base.checkSelection();
|
1347
|
+
}
|
1129
1348
|
},
|
1130
1349
|
|
1131
|
-
|
1132
|
-
|
1350
|
+
handleTextboxKeyup: function (event) {
|
1351
|
+
// For ENTER -> create the anchor
|
1352
|
+
if (event.keyCode === Util.keyCode.ENTER) {
|
1353
|
+
event.preventDefault();
|
1354
|
+
this.doLinkCreation();
|
1355
|
+
return;
|
1356
|
+
}
|
1357
|
+
|
1358
|
+
// For ESCAPE -> close the form
|
1359
|
+
if (event.keyCode === Util.keyCode.ESCAPE) {
|
1360
|
+
event.preventDefault();
|
1361
|
+
this.doFormCancel();
|
1362
|
+
}
|
1133
1363
|
},
|
1134
1364
|
|
1135
|
-
|
1136
|
-
|
1365
|
+
handleFormClick: function (event) {
|
1366
|
+
// make sure not to hide form when clicking inside the form
|
1367
|
+
event.stopPropagation();
|
1368
|
+
this.base.keepToolbarAlive = true;
|
1137
1369
|
},
|
1138
1370
|
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1371
|
+
handleSaveClick: function (event) {
|
1372
|
+
// Clicking Save -> create the anchor
|
1373
|
+
event.preventDefault();
|
1374
|
+
this.doLinkCreation();
|
1375
|
+
},
|
1376
|
+
|
1377
|
+
handleCloseClick: function (event) {
|
1378
|
+
// Click Close -> close the form
|
1379
|
+
event.preventDefault();
|
1380
|
+
this.doFormCancel();
|
1145
1381
|
}
|
1146
1382
|
};
|
1383
|
+
|
1384
|
+
AnchorExtension = Util.derives(DefaultButton, AnchorDerived);
|
1147
1385
|
}(window, document));
|
1386
|
+
|
1148
1387
|
function MediumEditor(elements, options) {
|
1149
1388
|
'use strict';
|
1150
1389
|
return this.init(elements, options);
|
@@ -1176,8 +1415,8 @@ function MediumEditor(elements, options) {
|
|
1176
1415
|
disableDoubleReturn: false,
|
1177
1416
|
disableToolbar: false,
|
1178
1417
|
disableEditing: false,
|
1179
|
-
disableAnchorForm: false,
|
1180
1418
|
disablePlaceholders: false,
|
1419
|
+
toolbarAlign: 'center',
|
1181
1420
|
elementsContainer: false,
|
1182
1421
|
imageDragging: true,
|
1183
1422
|
standardizeSelectionStart: false,
|
@@ -1200,7 +1439,7 @@ function MediumEditor(elements, options) {
|
|
1200
1439
|
init: function (elements, options) {
|
1201
1440
|
var uniqueId = 1;
|
1202
1441
|
|
1203
|
-
this.options =
|
1442
|
+
this.options = Util.defaults(options, this.defaults);
|
1204
1443
|
this.setElementSelection(elements);
|
1205
1444
|
if (this.elements.length === 0) {
|
1206
1445
|
return;
|
@@ -1281,7 +1520,7 @@ function MediumEditor(elements, options) {
|
|
1281
1520
|
// handleResize is throttled because:
|
1282
1521
|
// - It will be called when the browser is resizing, which can fire many times very quickly
|
1283
1522
|
// - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits
|
1284
|
-
this.handleResize =
|
1523
|
+
this.handleResize = Util.throttle(function () {
|
1285
1524
|
if (self.isActive) {
|
1286
1525
|
self.positionToolbarIfShown();
|
1287
1526
|
}
|
@@ -1291,7 +1530,7 @@ function MediumEditor(elements, options) {
|
|
1291
1530
|
// - This method could be called many times due to the type of event handlers that are calling it
|
1292
1531
|
// - We want a slight delay so that other events in the stack can run, some of which may
|
1293
1532
|
// prevent the toolbar from being hidden (via this.keepToolbarAlive).
|
1294
|
-
this.handleBlur =
|
1533
|
+
this.handleBlur = Util.throttle(function () {
|
1295
1534
|
if (self.isActive && !self.keepToolbarAlive) {
|
1296
1535
|
self.hideToolbarActions();
|
1297
1536
|
}
|
@@ -1321,7 +1560,7 @@ function MediumEditor(elements, options) {
|
|
1321
1560
|
// Init toolbar
|
1322
1561
|
if (addToolbar) {
|
1323
1562
|
this.initToolbar()
|
1324
|
-
.
|
1563
|
+
.setFirstAndLastButtons()
|
1325
1564
|
.bindAnchorPreview();
|
1326
1565
|
}
|
1327
1566
|
return this;
|
@@ -1336,7 +1575,7 @@ function MediumEditor(elements, options) {
|
|
1336
1575
|
selector = this.options.ownerDocument.querySelectorAll(selector);
|
1337
1576
|
}
|
1338
1577
|
// If element, put into array
|
1339
|
-
if (
|
1578
|
+
if (Util.isElement(selector)) {
|
1340
1579
|
selector = [selector];
|
1341
1580
|
}
|
1342
1581
|
// Convert NodeList (or other array like object) into an array
|
@@ -1350,15 +1589,15 @@ function MediumEditor(elements, options) {
|
|
1350
1589
|
selection = self.options.contentWindow.getSelection(),
|
1351
1590
|
selRange = selection.isCollapsed ?
|
1352
1591
|
null :
|
1353
|
-
|
1592
|
+
Selection.getSelectedParentElement(selection.getRangeAt(0)),
|
1354
1593
|
i;
|
1355
1594
|
|
1356
1595
|
// This control was introduced also to avoid the toolbar
|
1357
1596
|
// to disapper when selecting from right to left and
|
1358
1597
|
// the selection ends at the beginning of the text.
|
1359
1598
|
for (i = 0; i < self.elements.length; i += 1) {
|
1360
|
-
if (
|
1361
|
-
||
|
1599
|
+
if (Util.isDescendant(self.elements[i], e.target)
|
1600
|
+
|| Util.isDescendant(self.elements[i], selRange)) {
|
1362
1601
|
isDescendantOfEditorElements = true;
|
1363
1602
|
break;
|
1364
1603
|
}
|
@@ -1367,8 +1606,8 @@ function MediumEditor(elements, options) {
|
|
1367
1606
|
if (e.target !== self.toolbar
|
1368
1607
|
&& self.elements.indexOf(e.target) === -1
|
1369
1608
|
&& !isDescendantOfEditorElements
|
1370
|
-
&& !
|
1371
|
-
&& !
|
1609
|
+
&& !Util.isDescendant(self.toolbar, e.target)
|
1610
|
+
&& !Util.isDescendant(self.anchorPreview, e.target)) {
|
1372
1611
|
|
1373
1612
|
// Activate the placeholder
|
1374
1613
|
if (!self.options.disablePlaceholders) {
|
@@ -1481,6 +1720,9 @@ function MediumEditor(elements, options) {
|
|
1481
1720
|
if (extensions[buttonName]) {
|
1482
1721
|
ext = this.initExtension(extensions[buttonName], buttonName);
|
1483
1722
|
this.commands.push(ext);
|
1723
|
+
} else if (buttonName === 'anchor') {
|
1724
|
+
ext = this.initExtension(new AnchorExtension(), buttonName);
|
1725
|
+
this.commands.push(ext);
|
1484
1726
|
} else if (ButtonsData.hasOwnProperty(buttonName)) {
|
1485
1727
|
ext = new DefaultButton(ButtonsData[buttonName], this);
|
1486
1728
|
this.commands.push(ext);
|
@@ -1496,6 +1738,18 @@ function MediumEditor(elements, options) {
|
|
1496
1738
|
return this;
|
1497
1739
|
},
|
1498
1740
|
|
1741
|
+
getExtensionByName: function (name) {
|
1742
|
+
var extension;
|
1743
|
+
if (this.commands && this.commands.length) {
|
1744
|
+
this.commands.forEach(function (ext) {
|
1745
|
+
if (ext.name === name) {
|
1746
|
+
extension = ext;
|
1747
|
+
}
|
1748
|
+
});
|
1749
|
+
}
|
1750
|
+
return extension;
|
1751
|
+
},
|
1752
|
+
|
1499
1753
|
/**
|
1500
1754
|
* Helper function to call a method with a number of parameters on all registered extensions.
|
1501
1755
|
* The function assures that the function exists before calling.
|
@@ -1528,8 +1782,8 @@ function MediumEditor(elements, options) {
|
|
1528
1782
|
this.on(this.elements[index], 'keypress', function (e) {
|
1529
1783
|
var node,
|
1530
1784
|
tagName;
|
1531
|
-
if (e.which ===
|
1532
|
-
node =
|
1785
|
+
if (e.which === Util.keyCode.SPACE) {
|
1786
|
+
node = Selection.getSelectionStart(self.options.ownerDocument);
|
1533
1787
|
tagName = node.tagName.toLowerCase();
|
1534
1788
|
if (tagName === 'a') {
|
1535
1789
|
self.options.ownerDocument.execCommand('unlink', false, null);
|
@@ -1538,20 +1792,20 @@ function MediumEditor(elements, options) {
|
|
1538
1792
|
});
|
1539
1793
|
|
1540
1794
|
this.on(this.elements[index], 'keyup', function (e) {
|
1541
|
-
var node =
|
1795
|
+
var node = Selection.getSelectionStart(self.options.ownerDocument),
|
1542
1796
|
tagName,
|
1543
1797
|
editorElement;
|
1544
1798
|
|
1545
1799
|
if (node && node.getAttribute('data-medium-element') && node.children.length === 0 && !(self.options.disableReturn || node.getAttribute('data-disable-return'))) {
|
1546
1800
|
self.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
1547
1801
|
}
|
1548
|
-
if (e.which ===
|
1549
|
-
node =
|
1802
|
+
if (e.which === Util.keyCode.ENTER) {
|
1803
|
+
node = Selection.getSelectionStart(self.options.ownerDocument);
|
1550
1804
|
tagName = node.tagName.toLowerCase();
|
1551
|
-
editorElement =
|
1805
|
+
editorElement = Selection.getSelectionElement(self.options.contentWindow);
|
1552
1806
|
|
1553
1807
|
if (!(self.options.disableReturn || editorElement.getAttribute('data-disable-return')) &&
|
1554
|
-
tagName !== 'li' && !
|
1808
|
+
tagName !== 'li' && !Util.isListItemChild(node)) {
|
1555
1809
|
if (!e.shiftKey) {
|
1556
1810
|
|
1557
1811
|
// paragraph creation should not be forced within a header tag
|
@@ -1571,11 +1825,11 @@ function MediumEditor(elements, options) {
|
|
1571
1825
|
bindReturn: function (index) {
|
1572
1826
|
var self = this;
|
1573
1827
|
this.on(this.elements[index], 'keypress', function (e) {
|
1574
|
-
if (e.which ===
|
1828
|
+
if (e.which === Util.keyCode.ENTER) {
|
1575
1829
|
if (self.options.disableReturn || this.getAttribute('data-disable-return')) {
|
1576
1830
|
e.preventDefault();
|
1577
1831
|
} else if (self.options.disableDoubleReturn || this.getAttribute('data-disable-double-return')) {
|
1578
|
-
var node =
|
1832
|
+
var node = Selection.getSelectionStart(self.options.contentWindow);
|
1579
1833
|
if (node && node.textContent.trim() === '') {
|
1580
1834
|
e.preventDefault();
|
1581
1835
|
}
|
@@ -1590,9 +1844,9 @@ function MediumEditor(elements, options) {
|
|
1590
1844
|
this.on(this.elements[index], 'keydown', function (e) {
|
1591
1845
|
var node, tag, key;
|
1592
1846
|
|
1593
|
-
if (e.which ===
|
1847
|
+
if (e.which === Util.keyCode.TAB) {
|
1594
1848
|
// Override tab only for pre nodes
|
1595
|
-
node =
|
1849
|
+
node = Selection.getSelectionStart(self.options.ownerDocument);
|
1596
1850
|
tag = node && node.tagName.toLowerCase();
|
1597
1851
|
|
1598
1852
|
if (tag === 'pre') {
|
@@ -1601,7 +1855,7 @@ function MediumEditor(elements, options) {
|
|
1601
1855
|
}
|
1602
1856
|
|
1603
1857
|
// Tab to indent list structures!
|
1604
|
-
if (tag === 'li' ||
|
1858
|
+
if (tag === 'li' || Util.isListItemChild(node)) {
|
1605
1859
|
e.preventDefault();
|
1606
1860
|
|
1607
1861
|
// If Shift is down, outdent, otherwise indent
|
@@ -1611,12 +1865,12 @@ function MediumEditor(elements, options) {
|
|
1611
1865
|
self.options.ownerDocument.execCommand('indent', e);
|
1612
1866
|
}
|
1613
1867
|
}
|
1614
|
-
} else if (e.which ===
|
1868
|
+
} else if (e.which === Util.keyCode.BACKSPACE || e.which === Util.keyCode.DELETE || e.which === Util.keyCode.ENTER) {
|
1615
1869
|
|
1616
1870
|
// Bind keys which can create or destroy a block element: backspace, delete, return
|
1617
1871
|
self.onBlockModifier(e);
|
1618
1872
|
|
1619
|
-
} else if (e.ctrlKey) {
|
1873
|
+
} else if (e.ctrlKey || e.metaKey) {
|
1620
1874
|
key = String.fromCharCode(e.which || e.keyCode).toLowerCase();
|
1621
1875
|
self.commands.forEach(function (extension) {
|
1622
1876
|
if (extension.options.key && extension.options.key === key) {
|
@@ -1629,24 +1883,24 @@ function MediumEditor(elements, options) {
|
|
1629
1883
|
},
|
1630
1884
|
|
1631
1885
|
onBlockModifier: function (e) {
|
1632
|
-
var range, sel, p, node =
|
1886
|
+
var range, sel, p, node = Selection.getSelectionStart(this.options.ownerDocument),
|
1633
1887
|
tagName = node.tagName.toLowerCase(),
|
1634
1888
|
isEmpty = /^(\s+|<br\/?>)?$/i,
|
1635
1889
|
isHeader = /h\d/i;
|
1636
1890
|
|
1637
|
-
if ((e.which ===
|
1891
|
+
if ((e.which === Util.keyCode.BACKSPACE || e.which === Util.keyCode.ENTER)
|
1638
1892
|
&& node.previousElementSibling
|
1639
1893
|
// in a header
|
1640
1894
|
&& isHeader.test(tagName)
|
1641
1895
|
// at the very end of the block
|
1642
|
-
&&
|
1643
|
-
if (e.which ===
|
1896
|
+
&& Selection.getCaretOffsets(node).left === 0) {
|
1897
|
+
if (e.which === Util.keyCode.BACKSPACE && isEmpty.test(node.previousElementSibling.innerHTML)) {
|
1644
1898
|
// backspacing the begining of a header into an empty previous element will
|
1645
1899
|
// change the tagName of the current node to prevent one
|
1646
1900
|
// instead delete previous node and cancel the event.
|
1647
1901
|
node.previousElementSibling.parentNode.removeChild(node.previousElementSibling);
|
1648
1902
|
e.preventDefault();
|
1649
|
-
} else if (e.which ===
|
1903
|
+
} else if (e.which === Util.keyCode.ENTER) {
|
1650
1904
|
// hitting return in the begining of a header will create empty header elements before the current one
|
1651
1905
|
// instead, make "<p><br></p>" element, which are what happens if you hit return in an empty paragraph
|
1652
1906
|
p = this.options.ownerDocument.createElement('p');
|
@@ -1654,7 +1908,7 @@ function MediumEditor(elements, options) {
|
|
1654
1908
|
node.previousElementSibling.parentNode.insertBefore(p, node);
|
1655
1909
|
e.preventDefault();
|
1656
1910
|
}
|
1657
|
-
} else if (e.which ===
|
1911
|
+
} else if (e.which === Util.keyCode.DELETE
|
1658
1912
|
&& node.nextElementSibling
|
1659
1913
|
&& node.previousElementSibling
|
1660
1914
|
// not in a header
|
@@ -1693,8 +1947,6 @@ function MediumEditor(elements, options) {
|
|
1693
1947
|
this.toolbarActions = this.toolbar.querySelector('.medium-editor-toolbar-actions');
|
1694
1948
|
this.anchorPreview = this.createAnchorPreview();
|
1695
1949
|
|
1696
|
-
this.addExtensionForms();
|
1697
|
-
|
1698
1950
|
return this;
|
1699
1951
|
},
|
1700
1952
|
|
@@ -1710,10 +1962,14 @@ function MediumEditor(elements, options) {
|
|
1710
1962
|
}
|
1711
1963
|
|
1712
1964
|
toolbar.appendChild(this.toolbarButtons());
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1965
|
+
|
1966
|
+
// Add any forms that extensions may have
|
1967
|
+
this.commands.forEach(function (extension) {
|
1968
|
+
if (extension.hasForm) {
|
1969
|
+
toolbar.appendChild(extension.getForm());
|
1970
|
+
}
|
1971
|
+
});
|
1972
|
+
|
1717
1973
|
this.options.elementsContainer.appendChild(toolbar);
|
1718
1974
|
return toolbar;
|
1719
1975
|
},
|
@@ -1731,7 +1987,7 @@ function MediumEditor(elements, options) {
|
|
1731
1987
|
if (typeof extension.getButton === 'function') {
|
1732
1988
|
btn = extension.getButton(this);
|
1733
1989
|
li = this.options.ownerDocument.createElement('li');
|
1734
|
-
if (
|
1990
|
+
if (Util.isElement(btn)) {
|
1735
1991
|
li.appendChild(btn);
|
1736
1992
|
} else {
|
1737
1993
|
li.innerHTML = btn;
|
@@ -1743,89 +1999,36 @@ function MediumEditor(elements, options) {
|
|
1743
1999
|
return ul;
|
1744
2000
|
},
|
1745
2001
|
|
1746
|
-
addExtensionForms: function () {
|
1747
|
-
var form,
|
1748
|
-
id;
|
1749
|
-
|
1750
|
-
this.commands.forEach(function (extension) {
|
1751
|
-
if (extension.hasForm) {
|
1752
|
-
form = (typeof extension.getForm === 'function') ? extension.getForm() : null;
|
1753
|
-
}
|
1754
|
-
if (form) {
|
1755
|
-
id = 'medium-editor-toolbar-form-' + extension.name + '-' + this.id;
|
1756
|
-
form.className += ' medium-editor-toolbar-form';
|
1757
|
-
form.id = id;
|
1758
|
-
this.toolbar.appendChild(form);
|
1759
|
-
}
|
1760
|
-
}.bind(this));
|
1761
|
-
},
|
1762
|
-
|
1763
2002
|
bindSelect: function () {
|
1764
|
-
var
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
}.bind(this)
|
1781
|
-
}.bind(this);
|
2003
|
+
var i,
|
2004
|
+
blurHelper = function (event) {
|
2005
|
+
// Do not close the toolbar when bluring the editable area and clicking into the anchor form
|
2006
|
+
if (event &&
|
2007
|
+
event.type &&
|
2008
|
+
event.type.toLowerCase() === 'blur' &&
|
2009
|
+
event.relatedTarget &&
|
2010
|
+
Util.isDescendant(this.toolbar, event.relatedTarget)) {
|
2011
|
+
return false;
|
2012
|
+
}
|
2013
|
+
this.checkSelection();
|
2014
|
+
}.bind(this),
|
2015
|
+
timeoutHelper = function () {
|
2016
|
+
setTimeout(function () {
|
2017
|
+
this.checkSelection();
|
2018
|
+
}.bind(this), 0);
|
2019
|
+
}.bind(this);
|
1782
2020
|
|
1783
|
-
this.on(this.options.ownerDocument.documentElement, 'mouseup', this.
|
2021
|
+
this.on(this.options.ownerDocument.documentElement, 'mouseup', this.checkSelection.bind(this));
|
1784
2022
|
|
1785
2023
|
for (i = 0; i < this.elements.length; i += 1) {
|
1786
|
-
this.on(this.elements[i], 'keyup', this.
|
1787
|
-
this.on(this.elements[i], 'blur',
|
2024
|
+
this.on(this.elements[i], 'keyup', this.checkSelection.bind(this));
|
2025
|
+
this.on(this.elements[i], 'blur', blurHelper);
|
1788
2026
|
this.on(this.elements[i], 'click', timeoutHelper);
|
1789
2027
|
}
|
1790
2028
|
|
1791
2029
|
return this;
|
1792
2030
|
},
|
1793
2031
|
|
1794
|
-
// http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
|
1795
|
-
insertHTML: function insertHTML(html) {
|
1796
|
-
var selection, range, el, fragment, node, lastNode;
|
1797
|
-
|
1798
|
-
if (this.options.ownerDocument.queryCommandSupported('insertHTML')) {
|
1799
|
-
try {
|
1800
|
-
return this.options.ownerDocument.execCommand('insertHTML', false, html);
|
1801
|
-
} catch (ignore) {}
|
1802
|
-
}
|
1803
|
-
|
1804
|
-
selection = window.getSelection();
|
1805
|
-
if (selection.getRangeAt && selection.rangeCount) {
|
1806
|
-
range = selection.getRangeAt(0);
|
1807
|
-
range.deleteContents();
|
1808
|
-
|
1809
|
-
el = this.options.ownerDocument.createElement("div");
|
1810
|
-
el.innerHTML = html;
|
1811
|
-
fragment = this.options.ownerDocument.createDocumentFragment();
|
1812
|
-
while (el.firstChild) {
|
1813
|
-
node = el.firstChild;
|
1814
|
-
lastNode = fragment.appendChild(node);
|
1815
|
-
}
|
1816
|
-
range.insertNode(fragment);
|
1817
|
-
|
1818
|
-
// Preserve the selection:
|
1819
|
-
if (lastNode) {
|
1820
|
-
range = range.cloneRange();
|
1821
|
-
range.setStartAfter(lastNode);
|
1822
|
-
range.collapse(true);
|
1823
|
-
selection.removeAllRanges();
|
1824
|
-
selection.addRange(range);
|
1825
|
-
}
|
1826
|
-
}
|
1827
|
-
},
|
1828
|
-
|
1829
2032
|
bindDragDrop: function () {
|
1830
2033
|
var self = this, i, className, onDrag, onDrop, element;
|
1831
2034
|
|
@@ -1858,7 +2061,7 @@ function MediumEditor(elements, options) {
|
|
1858
2061
|
fileReader.readAsDataURL(file);
|
1859
2062
|
|
1860
2063
|
id = 'medium-img-' + (+new Date());
|
1861
|
-
|
2064
|
+
Util.insertHTMLCommand(self.options.ownerDocument, '<img class="medium-image-loading" id="' + id + '" />');
|
1862
2065
|
|
1863
2066
|
fileReader.onload = function () {
|
1864
2067
|
var img = document.getElementById(id);
|
@@ -1901,20 +2104,17 @@ function MediumEditor(elements, options) {
|
|
1901
2104
|
!this.options.disableToolbar) {
|
1902
2105
|
|
1903
2106
|
newSelection = this.options.contentWindow.getSelection();
|
1904
|
-
|
1905
2107
|
if ((!this.options.updateOnEmptySelection && newSelection.toString().trim() === '') ||
|
1906
|
-
(this.options.allowMultiParagraphSelection === false && this.
|
1907
|
-
|
1908
|
-
|
2108
|
+
(this.options.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected()) ||
|
2109
|
+
Selection.selectionInContentEditableFalse(this.options.contentWindow)) {
|
1909
2110
|
if (!this.options.staticToolbar) {
|
1910
2111
|
this.hideToolbarActions();
|
1911
|
-
} else
|
1912
|
-
this.
|
1913
|
-
this.showToolbarActions();
|
2112
|
+
} else {
|
2113
|
+
this.showAndUpdateToolbar();
|
1914
2114
|
}
|
1915
2115
|
|
1916
2116
|
} else {
|
1917
|
-
selectionElement =
|
2117
|
+
selectionElement = Selection.getSelectionElement(this.options.contentWindow);
|
1918
2118
|
if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) {
|
1919
2119
|
if (!this.options.staticToolbar) {
|
1920
2120
|
this.hideToolbarActions();
|
@@ -1927,11 +2127,14 @@ function MediumEditor(elements, options) {
|
|
1927
2127
|
return this;
|
1928
2128
|
},
|
1929
2129
|
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
2130
|
+
// Checks for existance of multiple block elements in the current selection
|
2131
|
+
multipleBlockElementsSelected: function () {
|
2132
|
+
/*jslint regexp: true*/
|
2133
|
+
var selectionHtml = Selection.getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
|
2134
|
+
hasMultiParagraphs = selectionHtml.match(/<(p|h[1-6]|blockquote)[^>]*>/g);
|
2135
|
+
/*jslint regexp: false*/
|
1933
2136
|
|
1934
|
-
return
|
2137
|
+
return !!hasMultiParagraphs && hasMultiParagraphs.length > 1;
|
1935
2138
|
},
|
1936
2139
|
|
1937
2140
|
checkSelectionElement: function (newSelection, selectionElement) {
|
@@ -1961,7 +2164,7 @@ function MediumEditor(elements, options) {
|
|
1961
2164
|
if (this.options.standardizeSelectionStart &&
|
1962
2165
|
this.selectionRange.startContainer.nodeValue &&
|
1963
2166
|
(this.selectionRange.startOffset === this.selectionRange.startContainer.nodeValue.length)) {
|
1964
|
-
adjacentNode =
|
2167
|
+
adjacentNode = Util.findAdjacentTextNodeWithContent(Selection.getSelectionElement(this.options.contentWindow), this.selectionRange.startContainer, this.options.ownerDocument);
|
1965
2168
|
if (adjacentNode) {
|
1966
2169
|
offset = 0;
|
1967
2170
|
while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) {
|
@@ -1978,9 +2181,7 @@ function MediumEditor(elements, options) {
|
|
1978
2181
|
|
1979
2182
|
for (i = 0; i < this.elements.length; i += 1) {
|
1980
2183
|
if (this.elements[i] === selectionElement) {
|
1981
|
-
this.
|
1982
|
-
.setToolbarPosition()
|
1983
|
-
.showToolbarActions();
|
2184
|
+
this.showAndUpdateToolbar();
|
1984
2185
|
return;
|
1985
2186
|
}
|
1986
2187
|
}
|
@@ -1990,62 +2191,95 @@ function MediumEditor(elements, options) {
|
|
1990
2191
|
}
|
1991
2192
|
},
|
1992
2193
|
|
2194
|
+
showAndUpdateToolbar: function () {
|
2195
|
+
this.setToolbarButtonStates()
|
2196
|
+
.setToolbarPosition()
|
2197
|
+
.showToolbarDefaultActions();
|
2198
|
+
},
|
2199
|
+
|
1993
2200
|
setToolbarPosition: function () {
|
1994
2201
|
// document.documentElement for IE 9
|
1995
2202
|
var scrollTop = (this.options.ownerDocument.documentElement && this.options.ownerDocument.documentElement.scrollTop) || this.options.ownerDocument.body.scrollTop,
|
1996
|
-
container = this.elements[0],
|
1997
|
-
containerRect = container.getBoundingClientRect(),
|
1998
|
-
containerTop = containerRect.top + scrollTop,
|
1999
|
-
buttonHeight = 50,
|
2000
2203
|
selection = this.options.contentWindow.getSelection(),
|
2204
|
+
windowWidth = this.options.contentWindow.innerWidth,
|
2205
|
+
container = Selection.getSelectionElement(this.options.contentWindow),
|
2206
|
+
buttonHeight = 50,
|
2207
|
+
toolbarWidth,
|
2208
|
+
toolbarHeight,
|
2209
|
+
halfOffsetWidth,
|
2210
|
+
defaultLeft,
|
2211
|
+
containerRect,
|
2212
|
+
containerTop,
|
2213
|
+
containerCenter,
|
2001
2214
|
range,
|
2002
2215
|
boundary,
|
2003
2216
|
middleBoundary,
|
2004
|
-
|
2005
|
-
halfOffsetWidth = this.toolbar.offsetWidth / 2,
|
2006
|
-
containerCenter = (containerRect.left + (containerRect.width / 2));
|
2217
|
+
targetLeft;
|
2007
2218
|
|
2008
|
-
|
2219
|
+
// If there isn't a valid selection, bail
|
2220
|
+
if (!container || !this.options.contentWindow.getSelection().focusNode) {
|
2009
2221
|
return this;
|
2010
2222
|
}
|
2011
2223
|
|
2012
|
-
this
|
2224
|
+
// If the container isn't part of this medium-editor instance, bail
|
2225
|
+
if (this.elements.indexOf(container) === -1) {
|
2226
|
+
return this;
|
2227
|
+
}
|
2228
|
+
|
2229
|
+
// Calculate container dimensions
|
2230
|
+
containerRect = container.getBoundingClientRect();
|
2231
|
+
containerTop = containerRect.top + scrollTop;
|
2232
|
+
containerCenter = (containerRect.left + (containerRect.width / 2));
|
2233
|
+
|
2234
|
+
// position the toolbar at left 0, so we can get the real width of the toolbar
|
2235
|
+
this.toolbar.style.left = '0';
|
2236
|
+
toolbarWidth = this.toolbar.offsetWidth;
|
2237
|
+
toolbarHeight = this.toolbar.offsetHeight;
|
2238
|
+
halfOffsetWidth = toolbarWidth / 2;
|
2239
|
+
defaultLeft = this.options.diffLeft - halfOffsetWidth;
|
2013
2240
|
|
2014
2241
|
if (this.options.staticToolbar) {
|
2242
|
+
this.showToolbar();
|
2015
2243
|
|
2016
2244
|
if (this.options.stickyToolbar) {
|
2017
|
-
|
2018
2245
|
// If it's beyond the height of the editor, position it at the bottom of the editor
|
2019
|
-
if (scrollTop > (containerTop +
|
2020
|
-
this.toolbar.style.top = (containerTop +
|
2246
|
+
if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight)) {
|
2247
|
+
this.toolbar.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px';
|
2248
|
+
this.toolbar.classList.remove('sticky-toolbar');
|
2021
2249
|
|
2022
2250
|
// Stick the toolbar to the top of the window
|
2023
|
-
} else if (scrollTop > (containerTop -
|
2251
|
+
} else if (scrollTop > (containerTop - toolbarHeight)) {
|
2024
2252
|
this.toolbar.classList.add('sticky-toolbar');
|
2025
2253
|
this.toolbar.style.top = "0px";
|
2254
|
+
|
2026
2255
|
// Normal static toolbar position
|
2027
2256
|
} else {
|
2028
2257
|
this.toolbar.classList.remove('sticky-toolbar');
|
2029
|
-
this.toolbar.style.top = containerTop -
|
2258
|
+
this.toolbar.style.top = containerTop - toolbarHeight + "px";
|
2030
2259
|
}
|
2031
|
-
|
2032
2260
|
} else {
|
2033
|
-
this.toolbar.style.top = containerTop -
|
2261
|
+
this.toolbar.style.top = containerTop - toolbarHeight + "px";
|
2034
2262
|
}
|
2035
2263
|
|
2036
|
-
if (this.options.toolbarAlign) {
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
this.toolbar.style.left = (containerRect.right - this.toolbar.offsetWidth) + "px";
|
2043
|
-
}
|
2044
|
-
} else {
|
2045
|
-
this.toolbar.style.left = (containerCenter - halfOffsetWidth) + "px";
|
2264
|
+
if (this.options.toolbarAlign === 'left') {
|
2265
|
+
targetLeft = containerRect.left;
|
2266
|
+
} else if (this.options.toolbarAlign === 'center') {
|
2267
|
+
targetLeft = containerCenter - halfOffsetWidth;
|
2268
|
+
} else if (this.options.toolbarAlign === 'right') {
|
2269
|
+
targetLeft = containerRect.right - toolbarWidth;
|
2046
2270
|
}
|
2047
2271
|
|
2272
|
+
if (targetLeft < 0) {
|
2273
|
+
targetLeft = 0;
|
2274
|
+
} else if ((targetLeft + toolbarWidth) > windowWidth) {
|
2275
|
+
targetLeft = windowWidth - toolbarWidth;
|
2276
|
+
}
|
2277
|
+
|
2278
|
+
this.toolbar.style.left = targetLeft + 'px';
|
2279
|
+
|
2048
2280
|
} else if (!selection.isCollapsed) {
|
2281
|
+
this.showToolbar();
|
2282
|
+
|
2049
2283
|
range = selection.getRangeAt(0);
|
2050
2284
|
boundary = range.getBoundingClientRect();
|
2051
2285
|
middleBoundary = (boundary.left + boundary.right) / 2;
|
@@ -2053,16 +2287,16 @@ function MediumEditor(elements, options) {
|
|
2053
2287
|
if (boundary.top < buttonHeight) {
|
2054
2288
|
this.toolbar.classList.add('medium-toolbar-arrow-over');
|
2055
2289
|
this.toolbar.classList.remove('medium-toolbar-arrow-under');
|
2056
|
-
this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + this.options.contentWindow.pageYOffset -
|
2290
|
+
this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
|
2057
2291
|
} else {
|
2058
2292
|
this.toolbar.classList.add('medium-toolbar-arrow-under');
|
2059
2293
|
this.toolbar.classList.remove('medium-toolbar-arrow-over');
|
2060
|
-
this.toolbar.style.top = boundary.top + this.options.diffTop + this.options.contentWindow.pageYOffset -
|
2294
|
+
this.toolbar.style.top = boundary.top + this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
|
2061
2295
|
}
|
2062
2296
|
if (middleBoundary < halfOffsetWidth) {
|
2063
2297
|
this.toolbar.style.left = defaultLeft + halfOffsetWidth + 'px';
|
2064
|
-
} else if ((
|
2065
|
-
this.toolbar.style.left =
|
2298
|
+
} else if ((windowWidth - middleBoundary) < halfOffsetWidth) {
|
2299
|
+
this.toolbar.style.left = windowWidth + defaultLeft - halfOffsetWidth + 'px';
|
2066
2300
|
} else {
|
2067
2301
|
this.toolbar.style.left = defaultLeft + middleBoundary + 'px';
|
2068
2302
|
}
|
@@ -2075,8 +2309,8 @@ function MediumEditor(elements, options) {
|
|
2075
2309
|
|
2076
2310
|
setToolbarButtonStates: function () {
|
2077
2311
|
this.commands.forEach(function (extension) {
|
2078
|
-
if (typeof extension.
|
2079
|
-
extension.
|
2312
|
+
if (typeof extension.isActive === 'function') {
|
2313
|
+
extension.setInactive();
|
2080
2314
|
}
|
2081
2315
|
}.bind(this));
|
2082
2316
|
this.checkActiveButtons();
|
@@ -2087,17 +2321,23 @@ function MediumEditor(elements, options) {
|
|
2087
2321
|
var elements = Array.prototype.slice.call(this.elements),
|
2088
2322
|
manualStateChecks = [],
|
2089
2323
|
queryState = null,
|
2090
|
-
parentNode
|
2324
|
+
parentNode,
|
2091
2325
|
checkExtension = function (extension) {
|
2092
2326
|
if (typeof extension.checkState === 'function') {
|
2093
2327
|
extension.checkState(parentNode);
|
2094
|
-
} else if (typeof extension.isActive === 'function'
|
2095
|
-
|
2096
|
-
|
2328
|
+
} else if (typeof extension.isActive === 'function' &&
|
2329
|
+
typeof extension.isAlreadyApplied === 'function') {
|
2330
|
+
if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) {
|
2331
|
+
extension.setActive();
|
2097
2332
|
}
|
2098
2333
|
}
|
2099
2334
|
};
|
2100
2335
|
|
2336
|
+
if (!this.selectionRange) {
|
2337
|
+
return;
|
2338
|
+
}
|
2339
|
+
parentNode = Selection.getSelectedParentElement(this.selectionRange);
|
2340
|
+
|
2101
2341
|
// Loop through all commands
|
2102
2342
|
this.commands.forEach(function (command) {
|
2103
2343
|
// For those commands where we can use document.queryCommandState(), do so
|
@@ -2107,7 +2347,7 @@ function MediumEditor(elements, options) {
|
|
2107
2347
|
// and don't need to do our manual checks
|
2108
2348
|
if (queryState !== null) {
|
2109
2349
|
if (queryState) {
|
2110
|
-
command.
|
2350
|
+
command.setActive();
|
2111
2351
|
}
|
2112
2352
|
return;
|
2113
2353
|
}
|
@@ -2117,8 +2357,7 @@ function MediumEditor(elements, options) {
|
|
2117
2357
|
});
|
2118
2358
|
|
2119
2359
|
// Climb up the DOM and do manual checks for whether a certain command is currently enabled for this node
|
2120
|
-
while (parentNode.tagName !== undefined &&
|
2121
|
-
this.activateButton(parentNode.tagName.toLowerCase());
|
2360
|
+
while (parentNode.tagName !== undefined && Util.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) {
|
2122
2361
|
manualStateChecks.forEach(checkExtension.bind(this));
|
2123
2362
|
|
2124
2363
|
// we can abort the search upwards if we leave the contentEditable element
|
@@ -2129,33 +2368,20 @@ function MediumEditor(elements, options) {
|
|
2129
2368
|
}
|
2130
2369
|
},
|
2131
2370
|
|
2132
|
-
|
2133
|
-
var
|
2134
|
-
if (el !== null && !el.classList.contains(this.options.activeButtonClass)) {
|
2135
|
-
el.classList.add(this.options.activeButtonClass);
|
2136
|
-
}
|
2137
|
-
},
|
2138
|
-
|
2139
|
-
bindButtons: function () {
|
2140
|
-
this.setFirstAndLastItems(this.toolbar.querySelectorAll('button'));
|
2141
|
-
return this;
|
2142
|
-
},
|
2143
|
-
|
2144
|
-
setFirstAndLastItems: function (buttons) {
|
2371
|
+
setFirstAndLastButtons: function () {
|
2372
|
+
var buttons = this.toolbar.querySelectorAll('button');
|
2145
2373
|
if (buttons.length > 0) {
|
2146
|
-
|
2147
2374
|
buttons[0].className += ' ' + this.options.firstButtonClass;
|
2148
2375
|
buttons[buttons.length - 1].className += ' ' + this.options.lastButtonClass;
|
2149
2376
|
}
|
2150
2377
|
return this;
|
2151
2378
|
},
|
2152
2379
|
|
2153
|
-
execAction: function (action,
|
2380
|
+
execAction: function (action, opts) {
|
2154
2381
|
/*jslint regexp: true*/
|
2155
2382
|
var fullAction = /^full-(.+)$/gi,
|
2156
|
-
|
2157
|
-
|
2158
|
-
match;
|
2383
|
+
match,
|
2384
|
+
result;
|
2159
2385
|
/*jslint regexp: false*/
|
2160
2386
|
|
2161
2387
|
// Actions starting with 'full-' should be applied to to the entire contents of the editable element
|
@@ -2166,57 +2392,39 @@ function MediumEditor(elements, options) {
|
|
2166
2392
|
this.saveSelection();
|
2167
2393
|
// Select all of the contents before calling the action
|
2168
2394
|
this.selectAllContents();
|
2169
|
-
this.
|
2395
|
+
result = this.execActionInternal(match[1], opts);
|
2170
2396
|
// Restore the previous selection
|
2171
2397
|
this.restoreSelection();
|
2172
|
-
|
2398
|
+
} else {
|
2399
|
+
result = this.execActionInternal(action, opts);
|
2173
2400
|
}
|
2174
2401
|
|
2402
|
+
this.checkSelection();
|
2403
|
+
return result;
|
2404
|
+
},
|
2405
|
+
|
2406
|
+
execActionInternal: function (action, opts) {
|
2407
|
+
/*jslint regexp: true*/
|
2408
|
+
var appendAction = /^append-(.+)$/gi,
|
2409
|
+
match;
|
2410
|
+
/*jslint regexp: false*/
|
2411
|
+
|
2175
2412
|
// Actions starting with 'append-' should attempt to format a block of text ('formatBlock') using a specific
|
2176
2413
|
// type of block element (ie append-blockquote, append-h1, append-pre, etc.)
|
2177
2414
|
match = appendAction.exec(action);
|
2178
2415
|
if (match) {
|
2179
|
-
this.execFormatBlock(match[1]);
|
2180
|
-
this.setToolbarPosition();
|
2181
|
-
this.setToolbarButtonStates();
|
2182
|
-
return;
|
2416
|
+
return this.execFormatBlock(match[1]);
|
2183
2417
|
}
|
2184
2418
|
|
2185
|
-
if (action === '
|
2186
|
-
|
2187
|
-
this.triggerAnchorAction(e);
|
2188
|
-
}
|
2189
|
-
} else if (action === 'image') {
|
2190
|
-
this.options.ownerDocument.execCommand('insertImage', false, this.options.contentWindow.getSelection());
|
2191
|
-
} else {
|
2192
|
-
this.options.ownerDocument.execCommand(action, false, null);
|
2193
|
-
this.setToolbarPosition();
|
2194
|
-
// Manually update the toolbar for text-alignment actions
|
2195
|
-
if (justifyAction.test(action)) {
|
2196
|
-
this.setToolbarButtonStates();
|
2197
|
-
}
|
2419
|
+
if (action === 'createLink') {
|
2420
|
+
return this.createLink(opts);
|
2198
2421
|
}
|
2199
|
-
},
|
2200
2422
|
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
this.toolbarActions.style.display = 'none';
|
2205
|
-
this.saveSelection();
|
2206
|
-
var form = document.getElementById(formId);
|
2207
|
-
form.style.display = 'block';
|
2208
|
-
this.setToolbarPosition();
|
2209
|
-
this.keepToolbarAlive = true;
|
2210
|
-
},
|
2423
|
+
if (action === 'image') {
|
2424
|
+
return this.options.ownerDocument.execCommand('insertImage', false, this.options.contentWindow.getSelection());
|
2425
|
+
}
|
2211
2426
|
|
2212
|
-
|
2213
|
-
// TO DO: Improve this
|
2214
|
-
hideForm: function (form, e) {
|
2215
|
-
var el = document.getElementById(form.id);
|
2216
|
-
el.style.display = 'none';
|
2217
|
-
this.showToolbarActions();
|
2218
|
-
this.setToolbarPosition();
|
2219
|
-
this.restoreSelection();
|
2427
|
+
return this.options.ownerDocument.execCommand(action, false, null);
|
2220
2428
|
},
|
2221
2429
|
|
2222
2430
|
// TODO: move these two methods to selection.js
|
@@ -2241,23 +2449,8 @@ function MediumEditor(elements, options) {
|
|
2241
2449
|
return selectedParentElement;
|
2242
2450
|
},
|
2243
2451
|
|
2244
|
-
triggerAnchorAction: function () {
|
2245
|
-
var selectedParentElement = meSelection.getSelectedParentElement(this.selectionRange);
|
2246
|
-
if (selectedParentElement.tagName &&
|
2247
|
-
selectedParentElement.tagName.toLowerCase() === 'a') {
|
2248
|
-
this.options.ownerDocument.execCommand('unlink', false, null);
|
2249
|
-
} else if (this.anchorExtension) {
|
2250
|
-
if (this.anchorExtension.isDisplayed()) {
|
2251
|
-
this.showToolbarActions();
|
2252
|
-
} else {
|
2253
|
-
this.showAnchorForm();
|
2254
|
-
}
|
2255
|
-
}
|
2256
|
-
return this;
|
2257
|
-
},
|
2258
|
-
|
2259
2452
|
execFormatBlock: function (el) {
|
2260
|
-
var selectionData =
|
2453
|
+
var selectionData = Selection.getSelectionData(this.selection.anchorNode);
|
2261
2454
|
// FF handles blockquote differently on formatBlock
|
2262
2455
|
// allowing nesting, we need to use outdent
|
2263
2456
|
// https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla
|
@@ -2272,7 +2465,7 @@ function MediumEditor(elements, options) {
|
|
2272
2465
|
// blockquote needs to be called as indent
|
2273
2466
|
// http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie
|
2274
2467
|
// http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777
|
2275
|
-
if (
|
2468
|
+
if (Util.isIE) {
|
2276
2469
|
if (el === 'blockquote') {
|
2277
2470
|
return this.options.ownerDocument.execCommand('indent', false, el);
|
2278
2471
|
}
|
@@ -2281,6 +2474,47 @@ function MediumEditor(elements, options) {
|
|
2281
2474
|
return this.options.ownerDocument.execCommand('formatBlock', false, el);
|
2282
2475
|
},
|
2283
2476
|
|
2477
|
+
isToolbarDefaultActionsShown: function () {
|
2478
|
+
return !!this.toolbarActions && this.toolbarActions.style.display === 'block';
|
2479
|
+
},
|
2480
|
+
|
2481
|
+
hideToolbarDefaultActions: function () {
|
2482
|
+
if (this.toolbarActions && this.isToolbarDefaultActionsShown()) {
|
2483
|
+
this.commands.forEach(function (extension) {
|
2484
|
+
if (extension.onHide && typeof extension.onHide === 'function') {
|
2485
|
+
extension.onHide();
|
2486
|
+
}
|
2487
|
+
});
|
2488
|
+
this.toolbarActions.style.display = 'none';
|
2489
|
+
}
|
2490
|
+
},
|
2491
|
+
|
2492
|
+
showToolbarDefaultActions: function () {
|
2493
|
+
this.hideExtensionForms();
|
2494
|
+
|
2495
|
+
if (this.toolbarActions && !this.isToolbarDefaultActionsShown()) {
|
2496
|
+
this.toolbarActions.style.display = 'block';
|
2497
|
+
}
|
2498
|
+
|
2499
|
+
this.keepToolbarAlive = false;
|
2500
|
+
// Using setTimeout + options.delay because:
|
2501
|
+
// We will actually be displaying the toolbar, which should be controlled by options.delay
|
2502
|
+
this.delay(function () {
|
2503
|
+
this.showToolbar();
|
2504
|
+
}.bind(this));
|
2505
|
+
|
2506
|
+
return this;
|
2507
|
+
},
|
2508
|
+
|
2509
|
+
hideExtensionForms: function () {
|
2510
|
+
// Hide all extension forms
|
2511
|
+
this.commands.forEach(function (extension) {
|
2512
|
+
if (extension.hasForm && extension.isDisplayed()) {
|
2513
|
+
extension.hideForm();
|
2514
|
+
}
|
2515
|
+
});
|
2516
|
+
},
|
2517
|
+
|
2284
2518
|
isToolbarShown: function () {
|
2285
2519
|
return this.toolbar && this.toolbar.classList.contains('medium-editor-toolbar-active');
|
2286
2520
|
},
|
@@ -2288,8 +2522,8 @@ function MediumEditor(elements, options) {
|
|
2288
2522
|
showToolbar: function () {
|
2289
2523
|
if (this.toolbar && !this.isToolbarShown()) {
|
2290
2524
|
this.toolbar.classList.add('medium-editor-toolbar-active');
|
2291
|
-
if (this.onShowToolbar) {
|
2292
|
-
this.onShowToolbar();
|
2525
|
+
if (typeof this.options.onShowToolbar === 'function') {
|
2526
|
+
this.options.onShowToolbar();
|
2293
2527
|
}
|
2294
2528
|
}
|
2295
2529
|
},
|
@@ -2297,9 +2531,8 @@ function MediumEditor(elements, options) {
|
|
2297
2531
|
hideToolbar: function () {
|
2298
2532
|
if (this.isToolbarShown()) {
|
2299
2533
|
this.toolbar.classList.remove('medium-editor-toolbar-active');
|
2300
|
-
|
2301
|
-
|
2302
|
-
this.onHideToolbar();
|
2534
|
+
if (typeof this.options.onHideToolbar === 'function') {
|
2535
|
+
this.options.onHideToolbar();
|
2303
2536
|
}
|
2304
2537
|
}
|
2305
2538
|
},
|
@@ -2314,24 +2547,10 @@ function MediumEditor(elements, options) {
|
|
2314
2547
|
this.hideToolbar();
|
2315
2548
|
},
|
2316
2549
|
|
2317
|
-
showToolbarActions: function () {
|
2318
|
-
var self = this;
|
2319
|
-
if (this.anchorExtension) {
|
2320
|
-
this.anchorExtension.hideForm();
|
2321
|
-
}
|
2322
|
-
this.toolbarActions.style.display = 'block';
|
2323
|
-
this.keepToolbarAlive = false;
|
2324
|
-
// Using setTimeout + options.delay because:
|
2325
|
-
// We will actually be displaying the toolbar, which should be controlled by options.delay
|
2326
|
-
this.delay(function () {
|
2327
|
-
self.showToolbar();
|
2328
|
-
});
|
2329
|
-
},
|
2330
|
-
|
2331
2550
|
selectAllContents: function () {
|
2332
2551
|
var range = this.options.ownerDocument.createRange(),
|
2333
2552
|
sel = this.options.contentWindow.getSelection(),
|
2334
|
-
currNode =
|
2553
|
+
currNode = Selection.getSelectionElement(this.options.contentWindow);
|
2335
2554
|
|
2336
2555
|
if (currNode) {
|
2337
2556
|
// Move to the lowest descendant node that still selects all of the contents
|
@@ -2363,7 +2582,7 @@ function MediumEditor(elements, options) {
|
|
2363
2582
|
|
2364
2583
|
// Find element current selection is inside
|
2365
2584
|
this.elements.forEach(function (el, index) {
|
2366
|
-
if (el === range.startContainer ||
|
2585
|
+
if (el === range.startContainer || Util.isDescendant(el, range.startContainer)) {
|
2367
2586
|
editableElementIndex = index;
|
2368
2587
|
return false;
|
2369
2588
|
}
|
@@ -2435,19 +2654,6 @@ function MediumEditor(elements, options) {
|
|
2435
2654
|
sel.addRange(range);
|
2436
2655
|
},
|
2437
2656
|
|
2438
|
-
showAnchorForm: function (link_value) {
|
2439
|
-
if (!this.anchorExtension) {
|
2440
|
-
return;
|
2441
|
-
}
|
2442
|
-
|
2443
|
-
this.toolbarActions.style.display = 'none';
|
2444
|
-
this.saveSelection();
|
2445
|
-
this.anchorExtension.showForm();
|
2446
|
-
this.setToolbarPosition();
|
2447
|
-
this.keepToolbarAlive = true;
|
2448
|
-
this.anchorExtension.focus(link_value);
|
2449
|
-
},
|
2450
|
-
|
2451
2657
|
hideAnchorPreview: function () {
|
2452
2658
|
this.anchorPreview.classList.remove('medium-editor-anchor-preview-active');
|
2453
2659
|
},
|
@@ -2551,25 +2757,26 @@ function MediumEditor(elements, options) {
|
|
2551
2757
|
'</div>';
|
2552
2758
|
},
|
2553
2759
|
|
2554
|
-
anchorPreviewClickHandler: function (
|
2555
|
-
|
2760
|
+
anchorPreviewClickHandler: function (event) {
|
2761
|
+
var range,
|
2762
|
+
sel,
|
2763
|
+
anchorExtension = this.getExtensionByName('anchor');
|
2556
2764
|
|
2557
|
-
|
2558
|
-
|
2559
|
-
|
2765
|
+
if (anchorExtension && this.activeAnchor) {
|
2766
|
+
range = this.options.ownerDocument.createRange();
|
2767
|
+
range.selectNodeContents(this.activeAnchor);
|
2560
2768
|
|
2561
|
-
|
2769
|
+
sel = this.options.contentWindow.getSelection();
|
2562
2770
|
sel.removeAllRanges();
|
2563
2771
|
sel.addRange(range);
|
2564
2772
|
// Using setTimeout + options.delay because:
|
2565
2773
|
// We may actually be displaying the anchor form, which should be controlled by options.delay
|
2566
2774
|
this.delay(function () {
|
2567
|
-
if (
|
2568
|
-
|
2775
|
+
if (this.activeAnchor) {
|
2776
|
+
anchorExtension.showForm(this.activeAnchor.attributes.href.value);
|
2569
2777
|
}
|
2570
|
-
|
2571
|
-
});
|
2572
|
-
|
2778
|
+
this.keepToolbarAlive = false;
|
2779
|
+
}.bind(this));
|
2573
2780
|
}
|
2574
2781
|
|
2575
2782
|
this.hideAnchorPreview();
|
@@ -2622,13 +2829,33 @@ function MediumEditor(elements, options) {
|
|
2622
2829
|
return this;
|
2623
2830
|
},
|
2624
2831
|
|
2625
|
-
|
2626
|
-
var
|
2627
|
-
|
2832
|
+
createLink: function (opts) {
|
2833
|
+
var customEvent,
|
2834
|
+
i;
|
2835
|
+
|
2836
|
+
if (opts.url && opts.url.trim().length > 0) {
|
2837
|
+
this.options.ownerDocument.execCommand('createLink', false, opts.url);
|
2838
|
+
|
2839
|
+
if (this.options.targetBlank || opts.target === '_blank') {
|
2840
|
+
Util.setTargetBlank(Selection.getSelectionStart(this.options.ownerDocument));
|
2841
|
+
}
|
2842
|
+
|
2843
|
+
if (opts.buttonClass) {
|
2844
|
+
this.setButtonClass(opts.buttonClass);
|
2845
|
+
}
|
2846
|
+
}
|
2847
|
+
|
2848
|
+
if (this.options.targetBlank || opts.target === "_blank" || opts.buttonClass) {
|
2849
|
+
customEvent = this.options.ownerDocument.createEvent("HTMLEvents");
|
2850
|
+
customEvent.initEvent("input", true, true, this.options.contentWindow);
|
2851
|
+
for (i = 0; i < this.elements.length; i += 1) {
|
2852
|
+
this.elements[i].dispatchEvent(customEvent);
|
2853
|
+
}
|
2854
|
+
}
|
2628
2855
|
},
|
2629
2856
|
|
2630
2857
|
setButtonClass: function (buttonClass) {
|
2631
|
-
var el =
|
2858
|
+
var el = Selection.getSelectionStart(this.options.ownerDocument),
|
2632
2859
|
classes = buttonClass.split(' '),
|
2633
2860
|
i,
|
2634
2861
|
j;
|
@@ -2646,48 +2873,6 @@ function MediumEditor(elements, options) {
|
|
2646
2873
|
}
|
2647
2874
|
},
|
2648
2875
|
|
2649
|
-
createLink: function (input, target, buttonClass) {
|
2650
|
-
|
2651
|
-
var i, event;
|
2652
|
-
|
2653
|
-
this.createLinkInternal(input.value, target, buttonClass);
|
2654
|
-
|
2655
|
-
if (this.options.targetBlank || target === "_blank" || buttonClass) {
|
2656
|
-
event = this.options.ownerDocument.createEvent("HTMLEvents");
|
2657
|
-
event.initEvent("input", true, true, this.options.contentWindow);
|
2658
|
-
for (i = 0; i < this.elements.length; i += 1) {
|
2659
|
-
this.elements[i].dispatchEvent(event);
|
2660
|
-
}
|
2661
|
-
}
|
2662
|
-
|
2663
|
-
this.checkSelection();
|
2664
|
-
this.showToolbarActions();
|
2665
|
-
input.value = '';
|
2666
|
-
},
|
2667
|
-
|
2668
|
-
createLinkInternal: function (url, target, buttonClass) {
|
2669
|
-
if (!url || url.trim().length === 0) {
|
2670
|
-
this.hideToolbarActions();
|
2671
|
-
return;
|
2672
|
-
}
|
2673
|
-
|
2674
|
-
this.restoreSelection();
|
2675
|
-
|
2676
|
-
if (this.options.checkLinkFormat) {
|
2677
|
-
url = this.checkLinkFormat(url);
|
2678
|
-
}
|
2679
|
-
|
2680
|
-
this.options.ownerDocument.execCommand('createLink', false, url);
|
2681
|
-
|
2682
|
-
if (this.options.targetBlank || target === "_blank") {
|
2683
|
-
mediumEditorUtil.setTargetBlank(meSelection.getSelectionStart(this.options.ownerDocument));
|
2684
|
-
}
|
2685
|
-
|
2686
|
-
if (buttonClass) {
|
2687
|
-
this.setButtonClass(buttonClass);
|
2688
|
-
}
|
2689
|
-
},
|
2690
|
-
|
2691
2876
|
positionToolbarIfShown: function () {
|
2692
2877
|
if (this.isToolbarShown()) {
|
2693
2878
|
this.setToolbarPosition();
|
@@ -2748,10 +2933,6 @@ function MediumEditor(elements, options) {
|
|
2748
2933
|
}
|
2749
2934
|
}.bind(this));
|
2750
2935
|
|
2751
|
-
if (this.anchorExtension) {
|
2752
|
-
this.anchorExtension.deactivate();
|
2753
|
-
}
|
2754
|
-
|
2755
2936
|
this.removeAllEvents();
|
2756
2937
|
},
|
2757
2938
|
|