medium-editor-rails 1.4.5 → 2.0.0
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 +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
|
|