medium-editor-rails 2.0.7 → 2.0.9
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 +1281 -906
- data/vendor/assets/stylesheets/medium-editor/themes/default.css +0 -0
- 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: f7001b46bce6793197c44e01b68a684b5e9cae6d
|
4
|
+
data.tar.gz: e3a755b3437e0d9fbdce90b8302446a68703b7f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 08250fccdf9ee1504c27583e385378ae7ad69ce9409b7a06a7ca7400040a24740401621126b23ddd5853444304627d7290b3b4a006dffbd24e148231622059bc
|
7
|
+
data.tar.gz: c26a53eb8a399c26dcaf772cedb9af2b3391155c450100e592a9905f3a0730faf99be895c57e329fc2dfb104da3a4bc219aeee0aeee967e262206ab32c8b8e6d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
|
2
2
|
#### [Current]
|
3
|
+
* [df257c8](../../commit/df257c8) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
|
+
* [3a3a5c1](../../commit/3a3a5c1) - __(Ahmet Sezgin Duran)__ Merge tag '2.0.7' into develop
|
5
|
+
|
6
|
+
2.0.7
|
7
|
+
|
8
|
+
#### 2.0.7
|
9
|
+
* [a7b71c7](../../commit/a7b71c7) - __(Ahmet Sezgin Duran)__ Bump versions 2.0.7 and 3.0.7
|
3
10
|
* [8efdb52](../../commit/8efdb52) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
11
|
* [b838798](../../commit/b838798) - __(Ahmet Sezgin Duran)__ Merge tag '2.0.0' into develop
|
5
12
|
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ This gem integrates [Medium Editor](https://github.com/daviferreira/medium-edito
|
|
8
8
|
|
9
9
|
## Version
|
10
10
|
|
11
|
-
The latest version of Medium Editor bundled by this gem is [3.0.
|
11
|
+
The latest version of Medium Editor bundled by this gem is [3.0.9](https://github.com/daviferreira/medium-editor/releases)
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -171,6 +171,218 @@ if (!("classList" in document.createElement("_"))) {
|
|
171
171
|
}(self));
|
172
172
|
}
|
173
173
|
|
174
|
+
/* Blob.js
|
175
|
+
* A Blob implementation.
|
176
|
+
* 2014-07-24
|
177
|
+
*
|
178
|
+
* By Eli Grey, http://eligrey.com
|
179
|
+
* By Devin Samarin, https://github.com/dsamarin
|
180
|
+
* License: X11/MIT
|
181
|
+
* See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
|
182
|
+
*/
|
183
|
+
|
184
|
+
/*global self, unescape */
|
185
|
+
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
186
|
+
plusplus: true */
|
187
|
+
|
188
|
+
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
189
|
+
|
190
|
+
(function (view) {
|
191
|
+
"use strict";
|
192
|
+
|
193
|
+
view.URL = view.URL || view.webkitURL;
|
194
|
+
|
195
|
+
if (view.Blob && view.URL) {
|
196
|
+
try {
|
197
|
+
new Blob;
|
198
|
+
return;
|
199
|
+
} catch (e) {}
|
200
|
+
}
|
201
|
+
|
202
|
+
// Internally we use a BlobBuilder implementation to base Blob off of
|
203
|
+
// in order to support older browsers that only have BlobBuilder
|
204
|
+
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
|
205
|
+
var
|
206
|
+
get_class = function(object) {
|
207
|
+
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
208
|
+
}
|
209
|
+
, FakeBlobBuilder = function BlobBuilder() {
|
210
|
+
this.data = [];
|
211
|
+
}
|
212
|
+
, FakeBlob = function Blob(data, type, encoding) {
|
213
|
+
this.data = data;
|
214
|
+
this.size = data.length;
|
215
|
+
this.type = type;
|
216
|
+
this.encoding = encoding;
|
217
|
+
}
|
218
|
+
, FBB_proto = FakeBlobBuilder.prototype
|
219
|
+
, FB_proto = FakeBlob.prototype
|
220
|
+
, FileReaderSync = view.FileReaderSync
|
221
|
+
, FileException = function(type) {
|
222
|
+
this.code = this[this.name = type];
|
223
|
+
}
|
224
|
+
, file_ex_codes = (
|
225
|
+
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
226
|
+
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
227
|
+
).split(" ")
|
228
|
+
, file_ex_code = file_ex_codes.length
|
229
|
+
, real_URL = view.URL || view.webkitURL || view
|
230
|
+
, real_create_object_URL = real_URL.createObjectURL
|
231
|
+
, real_revoke_object_URL = real_URL.revokeObjectURL
|
232
|
+
, URL = real_URL
|
233
|
+
, btoa = view.btoa
|
234
|
+
, atob = view.atob
|
235
|
+
|
236
|
+
, ArrayBuffer = view.ArrayBuffer
|
237
|
+
, Uint8Array = view.Uint8Array
|
238
|
+
|
239
|
+
, origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
|
240
|
+
;
|
241
|
+
FakeBlob.fake = FB_proto.fake = true;
|
242
|
+
while (file_ex_code--) {
|
243
|
+
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
244
|
+
}
|
245
|
+
// Polyfill URL
|
246
|
+
if (!real_URL.createObjectURL) {
|
247
|
+
URL = view.URL = function(uri) {
|
248
|
+
var
|
249
|
+
uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
250
|
+
, uri_origin
|
251
|
+
;
|
252
|
+
uri_info.href = uri;
|
253
|
+
if (!("origin" in uri_info)) {
|
254
|
+
if (uri_info.protocol.toLowerCase() === "data:") {
|
255
|
+
uri_info.origin = null;
|
256
|
+
} else {
|
257
|
+
uri_origin = uri.match(origin);
|
258
|
+
uri_info.origin = uri_origin && uri_origin[1];
|
259
|
+
}
|
260
|
+
}
|
261
|
+
return uri_info;
|
262
|
+
};
|
263
|
+
}
|
264
|
+
URL.createObjectURL = function(blob) {
|
265
|
+
var
|
266
|
+
type = blob.type
|
267
|
+
, data_URI_header
|
268
|
+
;
|
269
|
+
if (type === null) {
|
270
|
+
type = "application/octet-stream";
|
271
|
+
}
|
272
|
+
if (blob instanceof FakeBlob) {
|
273
|
+
data_URI_header = "data:" + type;
|
274
|
+
if (blob.encoding === "base64") {
|
275
|
+
return data_URI_header + ";base64," + blob.data;
|
276
|
+
} else if (blob.encoding === "URI") {
|
277
|
+
return data_URI_header + "," + decodeURIComponent(blob.data);
|
278
|
+
} if (btoa) {
|
279
|
+
return data_URI_header + ";base64," + btoa(blob.data);
|
280
|
+
} else {
|
281
|
+
return data_URI_header + "," + encodeURIComponent(blob.data);
|
282
|
+
}
|
283
|
+
} else if (real_create_object_URL) {
|
284
|
+
return real_create_object_URL.call(real_URL, blob);
|
285
|
+
}
|
286
|
+
};
|
287
|
+
URL.revokeObjectURL = function(object_URL) {
|
288
|
+
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
289
|
+
real_revoke_object_URL.call(real_URL, object_URL);
|
290
|
+
}
|
291
|
+
};
|
292
|
+
FBB_proto.append = function(data/*, endings*/) {
|
293
|
+
var bb = this.data;
|
294
|
+
// decode data to a binary string
|
295
|
+
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
296
|
+
var
|
297
|
+
str = ""
|
298
|
+
, buf = new Uint8Array(data)
|
299
|
+
, i = 0
|
300
|
+
, buf_len = buf.length
|
301
|
+
;
|
302
|
+
for (; i < buf_len; i++) {
|
303
|
+
str += String.fromCharCode(buf[i]);
|
304
|
+
}
|
305
|
+
bb.push(str);
|
306
|
+
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
307
|
+
if (FileReaderSync) {
|
308
|
+
var fr = new FileReaderSync;
|
309
|
+
bb.push(fr.readAsBinaryString(data));
|
310
|
+
} else {
|
311
|
+
// async FileReader won't work as BlobBuilder is sync
|
312
|
+
throw new FileException("NOT_READABLE_ERR");
|
313
|
+
}
|
314
|
+
} else if (data instanceof FakeBlob) {
|
315
|
+
if (data.encoding === "base64" && atob) {
|
316
|
+
bb.push(atob(data.data));
|
317
|
+
} else if (data.encoding === "URI") {
|
318
|
+
bb.push(decodeURIComponent(data.data));
|
319
|
+
} else if (data.encoding === "raw") {
|
320
|
+
bb.push(data.data);
|
321
|
+
}
|
322
|
+
} else {
|
323
|
+
if (typeof data !== "string") {
|
324
|
+
data += ""; // convert unsupported types to strings
|
325
|
+
}
|
326
|
+
// decode UTF-16 to binary string
|
327
|
+
bb.push(unescape(encodeURIComponent(data)));
|
328
|
+
}
|
329
|
+
};
|
330
|
+
FBB_proto.getBlob = function(type) {
|
331
|
+
if (!arguments.length) {
|
332
|
+
type = null;
|
333
|
+
}
|
334
|
+
return new FakeBlob(this.data.join(""), type, "raw");
|
335
|
+
};
|
336
|
+
FBB_proto.toString = function() {
|
337
|
+
return "[object BlobBuilder]";
|
338
|
+
};
|
339
|
+
FB_proto.slice = function(start, end, type) {
|
340
|
+
var args = arguments.length;
|
341
|
+
if (args < 3) {
|
342
|
+
type = null;
|
343
|
+
}
|
344
|
+
return new FakeBlob(
|
345
|
+
this.data.slice(start, args > 1 ? end : this.data.length)
|
346
|
+
, type
|
347
|
+
, this.encoding
|
348
|
+
);
|
349
|
+
};
|
350
|
+
FB_proto.toString = function() {
|
351
|
+
return "[object Blob]";
|
352
|
+
};
|
353
|
+
FB_proto.close = function() {
|
354
|
+
this.size = 0;
|
355
|
+
delete this.data;
|
356
|
+
};
|
357
|
+
return FakeBlobBuilder;
|
358
|
+
}(view));
|
359
|
+
|
360
|
+
view.Blob = function(blobParts, options) {
|
361
|
+
var type = options ? (options.type || "") : "";
|
362
|
+
var builder = new BlobBuilder();
|
363
|
+
if (blobParts) {
|
364
|
+
for (var i = 0, len = blobParts.length; i < len; i++) {
|
365
|
+
if (Uint8Array && blobParts[i] instanceof Uint8Array) {
|
366
|
+
builder.append(blobParts[i].buffer);
|
367
|
+
}
|
368
|
+
else {
|
369
|
+
builder.append(blobParts[i]);
|
370
|
+
}
|
371
|
+
}
|
372
|
+
}
|
373
|
+
var blob = builder.getBlob(type);
|
374
|
+
if (!blob.slice && blob.webkitSlice) {
|
375
|
+
blob.slice = blob.webkitSlice;
|
376
|
+
}
|
377
|
+
return blob;
|
378
|
+
};
|
379
|
+
|
380
|
+
var getPrototypeOf = Object.getPrototypeOf || function(object) {
|
381
|
+
return object.__proto__;
|
382
|
+
};
|
383
|
+
view.Blob.prototype = getPrototypeOf(new view.Blob());
|
384
|
+
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
|
385
|
+
|
174
386
|
(function (root, factory) {
|
175
387
|
'use strict';
|
176
388
|
if (typeof module === 'object') {
|
@@ -224,10 +436,6 @@ var Util;
|
|
224
436
|
return copyInto(dest, source);
|
225
437
|
},
|
226
438
|
|
227
|
-
extend: function extend(dest, source) {
|
228
|
-
return copyInto(dest, source, true);
|
229
|
-
},
|
230
|
-
|
231
439
|
derives: function derives(base, derived) {
|
232
440
|
var origPrototype = derived.prototype;
|
233
441
|
function Proto() { }
|
@@ -361,7 +569,7 @@ var Util;
|
|
361
569
|
|
362
570
|
// http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div
|
363
571
|
insertHTMLCommand: function (doc, html) {
|
364
|
-
var selection, range, el, fragment, node, lastNode;
|
572
|
+
var selection, range, el, fragment, node, lastNode, toReplace;
|
365
573
|
|
366
574
|
if (doc.queryCommandSupported('insertHTML')) {
|
367
575
|
try {
|
@@ -369,9 +577,21 @@ var Util;
|
|
369
577
|
} catch (ignore) {}
|
370
578
|
}
|
371
579
|
|
372
|
-
selection =
|
580
|
+
selection = doc.defaultView.getSelection();
|
373
581
|
if (selection.getRangeAt && selection.rangeCount) {
|
374
582
|
range = selection.getRangeAt(0);
|
583
|
+
toReplace = range.commonAncestorContainer;
|
584
|
+
// Ensure range covers maximum amount of nodes as possible
|
585
|
+
// By moving up the DOM and selecting ancestors whose only child is the range
|
586
|
+
if ((toReplace.nodeType === 3 && toReplace.nodeValue === range.toString()) ||
|
587
|
+
(toReplace.nodeType !== 3 && toReplace.innerHTML === range.toString())) {
|
588
|
+
while (toReplace.parentNode &&
|
589
|
+
toReplace.parentNode.childNodes.length === 1 &&
|
590
|
+
!toReplace.parentNode.getAttribute('data-medium-element')) {
|
591
|
+
toReplace = toReplace.parentNode;
|
592
|
+
}
|
593
|
+
range.selectNode(toReplace);
|
594
|
+
}
|
375
595
|
range.deleteContents();
|
376
596
|
|
377
597
|
el = doc.createElement("div");
|
@@ -471,22 +691,15 @@ var Selection;
|
|
471
691
|
getSelectionHtml: function getSelectionHtml() {
|
472
692
|
var i,
|
473
693
|
html = '',
|
474
|
-
sel,
|
694
|
+
sel = this.options.contentWindow.getSelection(),
|
475
695
|
len,
|
476
696
|
container;
|
477
|
-
if (
|
478
|
-
|
479
|
-
|
480
|
-
container
|
481
|
-
for (i = 0, len = sel.rangeCount; i < len; i += 1) {
|
482
|
-
container.appendChild(sel.getRangeAt(i).cloneContents());
|
483
|
-
}
|
484
|
-
html = container.innerHTML;
|
485
|
-
}
|
486
|
-
} else if (this.options.ownerDocument.selection !== undefined) {
|
487
|
-
if (this.options.ownerDocument.selection.type === 'Text') {
|
488
|
-
html = this.options.ownerDocument.selection.createRange().htmlText;
|
697
|
+
if (sel.rangeCount) {
|
698
|
+
container = this.options.ownerDocument.createElement('div');
|
699
|
+
for (i = 0, len = sel.rangeCount; i < len; i += 1) {
|
700
|
+
container.appendChild(sel.getRangeAt(i).cloneContents());
|
489
701
|
}
|
702
|
+
html = container.innerHTML;
|
490
703
|
}
|
491
704
|
return html;
|
492
705
|
},
|
@@ -873,7 +1086,13 @@ var DefaultButton,
|
|
873
1086
|
computedStyle = this.base.options.contentWindow.getComputedStyle(node, null).getPropertyValue(this.options.style.prop);
|
874
1087
|
styleVals.forEach(function (val) {
|
875
1088
|
if (!this.knownState) {
|
876
|
-
|
1089
|
+
isMatch = (computedStyle.indexOf(val) !== -1);
|
1090
|
+
// text-decoration is not inherited by default
|
1091
|
+
// so if the computed style for text-decoration doesn't match
|
1092
|
+
// don't write to knownState so we can fallback to other checks
|
1093
|
+
if (isMatch || this.options.style.prop !== 'text-decoration') {
|
1094
|
+
this.knownState = isMatch;
|
1095
|
+
}
|
877
1096
|
}
|
878
1097
|
}.bind(this));
|
879
1098
|
}
|
@@ -1050,15 +1269,14 @@ var pasteHandler;
|
|
1050
1269
|
filterLineBreak: function (el) {
|
1051
1270
|
if (this.isCommonBlock(el.previousElementSibling)) {
|
1052
1271
|
// remove stray br's following common block elements
|
1053
|
-
|
1272
|
+
this.removeWithParent(el);
|
1054
1273
|
} else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
|
1055
1274
|
// remove br's just inside open or close tags of a div/p
|
1056
|
-
|
1275
|
+
this.removeWithParent(el);
|
1057
1276
|
} else if (el.parentNode.childElementCount === 1 && el.parentNode.textContent === '') {
|
1058
|
-
// and br's that are the only child of
|
1277
|
+
// and br's that are the only child of elements other than div/p
|
1059
1278
|
this.removeWithParent(el);
|
1060
1279
|
}
|
1061
|
-
|
1062
1280
|
},
|
1063
1281
|
|
1064
1282
|
// remove an element, including its parent, if it is the only element within its parent
|
@@ -1067,7 +1285,7 @@ var pasteHandler;
|
|
1067
1285
|
if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
|
1068
1286
|
el.parentNode.parentNode.removeChild(el.parentNode);
|
1069
1287
|
} else {
|
1070
|
-
el.parentNode.removeChild(el
|
1288
|
+
el.parentNode.removeChild(el);
|
1071
1289
|
}
|
1072
1290
|
}
|
1073
1291
|
},
|
@@ -1162,10 +1380,10 @@ var AnchorExtension;
|
|
1162
1380
|
|
1163
1381
|
// Called by medium-editor to append form to the toolbar
|
1164
1382
|
getForm: function () {
|
1165
|
-
if (!this.
|
1166
|
-
this.
|
1383
|
+
if (!this.form) {
|
1384
|
+
this.form = this.createForm();
|
1167
1385
|
}
|
1168
|
-
return this.
|
1386
|
+
return this.form;
|
1169
1387
|
},
|
1170
1388
|
|
1171
1389
|
// Used by medium-editor when the default toolbar is to be displayed
|
@@ -1185,7 +1403,6 @@ var AnchorExtension;
|
|
1185
1403
|
this.base.hideToolbarDefaultActions();
|
1186
1404
|
this.getForm().style.display = 'block';
|
1187
1405
|
this.base.setToolbarPosition();
|
1188
|
-
this.base.keepToolbarAlive = true;
|
1189
1406
|
|
1190
1407
|
input.value = link_value || '';
|
1191
1408
|
input.focus();
|
@@ -1193,20 +1410,20 @@ var AnchorExtension;
|
|
1193
1410
|
|
1194
1411
|
// Called by core when tearing down medium-editor (deactivate)
|
1195
1412
|
deactivate: function () {
|
1196
|
-
if (!this.
|
1413
|
+
if (!this.form) {
|
1197
1414
|
return false;
|
1198
1415
|
}
|
1199
1416
|
|
1200
|
-
if (this.
|
1201
|
-
this.
|
1417
|
+
if (this.form.parentNode) {
|
1418
|
+
this.form.parentNode.removeChild(this.form);
|
1202
1419
|
}
|
1203
1420
|
|
1204
|
-
delete this.
|
1421
|
+
delete this.form;
|
1205
1422
|
},
|
1206
1423
|
|
1207
1424
|
// core methods
|
1208
1425
|
|
1209
|
-
|
1426
|
+
doFormSave: function () {
|
1210
1427
|
var targetCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-target'),
|
1211
1428
|
buttonCheckbox = this.getForm().querySelector('.medium-editor-toolbar-anchor-button'),
|
1212
1429
|
opts = {
|
@@ -1230,7 +1447,6 @@ var AnchorExtension;
|
|
1230
1447
|
}
|
1231
1448
|
|
1232
1449
|
this.base.createLink(opts);
|
1233
|
-
this.base.keepToolbarAlive = false;
|
1234
1450
|
this.base.checkSelection();
|
1235
1451
|
},
|
1236
1452
|
|
@@ -1241,7 +1457,6 @@ var AnchorExtension;
|
|
1241
1457
|
|
1242
1458
|
doFormCancel: function () {
|
1243
1459
|
this.base.restoreSelection();
|
1244
|
-
this.base.keepToolbarAlive = false;
|
1245
1460
|
this.base.checkSelection();
|
1246
1461
|
},
|
1247
1462
|
|
@@ -1274,9 +1489,6 @@ var AnchorExtension;
|
|
1274
1489
|
// Handle typing in the textbox
|
1275
1490
|
this.base.on(input, 'keyup', this.handleTextboxKeyup.bind(this));
|
1276
1491
|
|
1277
|
-
// Handle clicks into the textbox
|
1278
|
-
this.base.on(input, 'click', this.handleFormClick.bind(this));
|
1279
|
-
|
1280
1492
|
// Add save buton
|
1281
1493
|
save.setAttribute('href', '#');
|
1282
1494
|
save.className = 'medium-editor-toobar-save';
|
@@ -1325,10 +1537,6 @@ var AnchorExtension;
|
|
1325
1537
|
form.appendChild(button_label);
|
1326
1538
|
}
|
1327
1539
|
|
1328
|
-
// Handle click (capture) & focus (capture) outside of the form
|
1329
|
-
this.base.on(doc.body, 'click', this.handleOutsideInteraction.bind(this), true);
|
1330
|
-
this.base.on(doc.body, 'focus', this.handleOutsideInteraction.bind(this), true);
|
1331
|
-
|
1332
1540
|
return form;
|
1333
1541
|
},
|
1334
1542
|
|
@@ -1336,20 +1544,11 @@ var AnchorExtension;
|
|
1336
1544
|
return this.getForm().querySelector('input.medium-editor-toolbar-input');
|
1337
1545
|
},
|
1338
1546
|
|
1339
|
-
handleOutsideInteraction: function (event) {
|
1340
|
-
if (event.target !== this.getForm() &&
|
1341
|
-
!Util.isDescendant(this.getForm(), event.target) &&
|
1342
|
-
!Util.isDescendant(this.base.toolbarActions, event.target)) {
|
1343
|
-
this.base.keepToolbarAlive = false;
|
1344
|
-
this.base.checkSelection();
|
1345
|
-
}
|
1346
|
-
},
|
1347
|
-
|
1348
1547
|
handleTextboxKeyup: function (event) {
|
1349
1548
|
// For ENTER -> create the anchor
|
1350
1549
|
if (event.keyCode === Util.keyCode.ENTER) {
|
1351
1550
|
event.preventDefault();
|
1352
|
-
this.
|
1551
|
+
this.doFormSave();
|
1353
1552
|
return;
|
1354
1553
|
}
|
1355
1554
|
|
@@ -1363,13 +1562,12 @@ var AnchorExtension;
|
|
1363
1562
|
handleFormClick: function (event) {
|
1364
1563
|
// make sure not to hide form when clicking inside the form
|
1365
1564
|
event.stopPropagation();
|
1366
|
-
this.base.keepToolbarAlive = true;
|
1367
1565
|
},
|
1368
1566
|
|
1369
1567
|
handleSaveClick: function (event) {
|
1370
1568
|
// Clicking Save -> create the anchor
|
1371
1569
|
event.preventDefault();
|
1372
|
-
this.
|
1570
|
+
this.doFormSave();
|
1373
1571
|
},
|
1374
1572
|
|
1375
1573
|
handleCloseClick: function (event) {
|
@@ -1382,164 +1580,925 @@ var AnchorExtension;
|
|
1382
1580
|
AnchorExtension = Util.derives(DefaultButton, AnchorDerived);
|
1383
1581
|
}(window, document));
|
1384
1582
|
|
1385
|
-
|
1386
|
-
'use strict';
|
1387
|
-
return this.init(elements, options);
|
1388
|
-
}
|
1583
|
+
var AnchorPreview;
|
1389
1584
|
|
1390
|
-
(function () {
|
1585
|
+
(function (window, document) {
|
1391
1586
|
'use strict';
|
1392
1587
|
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
AnchorExtension: AnchorExtension
|
1588
|
+
AnchorPreview = function () {
|
1589
|
+
this.parent = true;
|
1590
|
+
this.name = 'anchor-preview';
|
1397
1591
|
};
|
1398
1592
|
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
checkLinkFormat: false,
|
1408
|
-
cleanPastedHTML: false,
|
1409
|
-
delay: 0,
|
1410
|
-
diffLeft: 0,
|
1411
|
-
diffTop: -10,
|
1412
|
-
disableReturn: false,
|
1413
|
-
disableDoubleReturn: false,
|
1414
|
-
disableToolbar: false,
|
1415
|
-
disableEditing: false,
|
1416
|
-
disablePlaceholders: false,
|
1417
|
-
toolbarAlign: 'center',
|
1418
|
-
elementsContainer: false,
|
1419
|
-
imageDragging: true,
|
1420
|
-
standardizeSelectionStart: false,
|
1421
|
-
contentWindow: window,
|
1422
|
-
ownerDocument: document,
|
1423
|
-
firstHeader: 'h3',
|
1424
|
-
forcePlainText: true,
|
1425
|
-
placeholder: 'Type your text',
|
1426
|
-
secondHeader: 'h4',
|
1427
|
-
targetBlank: false,
|
1428
|
-
anchorTarget: false,
|
1429
|
-
anchorButton: false,
|
1430
|
-
anchorButtonClass: 'btn',
|
1431
|
-
extensions: {},
|
1432
|
-
activeButtonClass: 'medium-editor-button-active',
|
1433
|
-
firstButtonClass: 'medium-editor-button-first',
|
1434
|
-
lastButtonClass: 'medium-editor-button-last'
|
1593
|
+
AnchorPreview.prototype = {
|
1594
|
+
|
1595
|
+
init: function (instance) {
|
1596
|
+
this.base = instance;
|
1597
|
+
this.anchorPreview = this.createPreview();
|
1598
|
+
this.base.options.elementsContainer.appendChild(this.anchorPreview);
|
1599
|
+
|
1600
|
+
this.attachToEditables();
|
1435
1601
|
},
|
1436
1602
|
|
1437
|
-
|
1438
|
-
|
1603
|
+
getPreviewElement: function () {
|
1604
|
+
return this.anchorPreview;
|
1605
|
+
},
|
1439
1606
|
|
1440
|
-
|
1441
|
-
this.
|
1442
|
-
|
1443
|
-
|
1607
|
+
createPreview: function () {
|
1608
|
+
var el = this.base.options.ownerDocument.createElement('div');
|
1609
|
+
|
1610
|
+
el.id = 'medium-editor-anchor-preview-' + this.base.id;
|
1611
|
+
el.className = 'medium-editor-anchor-preview';
|
1612
|
+
el.innerHTML = this.getTemplate();
|
1613
|
+
|
1614
|
+
this.base.on(el, 'click', this.handleClick.bind(this));
|
1615
|
+
|
1616
|
+
return el;
|
1617
|
+
},
|
1618
|
+
|
1619
|
+
getTemplate: function () {
|
1620
|
+
return '<div class="medium-editor-toolbar-anchor-preview" id="medium-editor-toolbar-anchor-preview">' +
|
1621
|
+
' <i class="medium-editor-toolbar-anchor-preview-inner"></i>' +
|
1622
|
+
'</div>';
|
1623
|
+
},
|
1624
|
+
|
1625
|
+
deactivate: function () {
|
1626
|
+
if (this.anchorPreview) {
|
1627
|
+
if (this.anchorPreview.parentNode) {
|
1628
|
+
this.anchorPreview.parentNode.removeChild(this.anchorPreview);
|
1629
|
+
}
|
1630
|
+
delete this.anchorPreview;
|
1444
1631
|
}
|
1632
|
+
},
|
1445
1633
|
|
1446
|
-
|
1447
|
-
|
1634
|
+
hidePreview: function () {
|
1635
|
+
this.anchorPreview.classList.remove('medium-editor-anchor-preview-active');
|
1636
|
+
this.activeAnchor = null;
|
1637
|
+
},
|
1638
|
+
|
1639
|
+
showPreview: function (anchorEl) {
|
1640
|
+
if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')
|
1641
|
+
|| anchorEl.getAttribute('data-disable-preview')) {
|
1642
|
+
return true;
|
1448
1643
|
}
|
1449
1644
|
|
1450
|
-
|
1451
|
-
|
1645
|
+
this.anchorPreview.querySelector('i').textContent = anchorEl.attributes.href.value;
|
1646
|
+
|
1647
|
+
this.anchorPreview.classList.add('medium-toolbar-arrow-over');
|
1648
|
+
this.anchorPreview.classList.remove('medium-toolbar-arrow-under');
|
1649
|
+
|
1650
|
+
if (!this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) {
|
1651
|
+
this.anchorPreview.classList.add('medium-editor-anchor-preview-active');
|
1452
1652
|
}
|
1453
1653
|
|
1454
|
-
this.
|
1654
|
+
this.activeAnchor = anchorEl;
|
1455
1655
|
|
1456
|
-
|
1656
|
+
this.positionPreview();
|
1657
|
+
this.attachPreviewHandlers();
|
1658
|
+
|
1659
|
+
return this;
|
1457
1660
|
},
|
1458
1661
|
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1662
|
+
positionPreview: function () {
|
1663
|
+
var buttonHeight = 40,
|
1664
|
+
boundary = this.activeAnchor.getBoundingClientRect(),
|
1665
|
+
middleBoundary = (boundary.left + boundary.right) / 2,
|
1666
|
+
halfOffsetWidth,
|
1667
|
+
defaultLeft;
|
1668
|
+
|
1669
|
+
halfOffsetWidth = this.anchorPreview.offsetWidth / 2;
|
1670
|
+
defaultLeft = this.base.options.diffLeft - halfOffsetWidth;
|
1671
|
+
|
1672
|
+
this.anchorPreview.style.top = Math.round(buttonHeight + boundary.bottom - this.base.options.diffTop + this.base.options.contentWindow.pageYOffset - this.anchorPreview.offsetHeight) + 'px';
|
1673
|
+
if (middleBoundary < halfOffsetWidth) {
|
1674
|
+
this.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px';
|
1675
|
+
} else if ((this.base.options.contentWindow.innerWidth - middleBoundary) < halfOffsetWidth) {
|
1676
|
+
this.anchorPreview.style.left = this.base.options.contentWindow.innerWidth + defaultLeft - halfOffsetWidth + 'px';
|
1677
|
+
} else {
|
1678
|
+
this.anchorPreview.style.left = defaultLeft + middleBoundary + 'px';
|
1679
|
+
}
|
1471
1680
|
},
|
1472
1681
|
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1682
|
+
attachToEditables: function () {
|
1683
|
+
this.base.elements.forEach(function (element) {
|
1684
|
+
this.base.on(element, 'mouseover', this.handleEditableMouseover.bind(this));
|
1685
|
+
}.bind(this));
|
1476
1686
|
},
|
1477
1687
|
|
1478
|
-
|
1479
|
-
var
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1688
|
+
handleClick: function (event) {
|
1689
|
+
var range,
|
1690
|
+
sel,
|
1691
|
+
anchorExtension = this.base.getExtensionByName('anchor'),
|
1692
|
+
activeAnchor = this.activeAnchor;
|
1693
|
+
|
1694
|
+
if (anchorExtension && activeAnchor) {
|
1695
|
+
range = this.base.options.ownerDocument.createRange();
|
1696
|
+
range.selectNodeContents(this.activeAnchor);
|
1697
|
+
|
1698
|
+
sel = this.base.options.contentWindow.getSelection();
|
1699
|
+
sel.removeAllRanges();
|
1700
|
+
sel.addRange(range);
|
1701
|
+
// Using setTimeout + options.delay because:
|
1702
|
+
// We may actually be displaying the anchor form, which should be controlled by options.delay
|
1703
|
+
this.base.delay(function () {
|
1704
|
+
if (activeAnchor) {
|
1705
|
+
anchorExtension.showForm(activeAnchor.attributes.href.value);
|
1706
|
+
activeAnchor = null;
|
1707
|
+
}
|
1708
|
+
}.bind(this));
|
1484
1709
|
}
|
1710
|
+
|
1711
|
+
this.hidePreview();
|
1485
1712
|
},
|
1486
1713
|
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1714
|
+
handleAnchorMouseout: function (event) {
|
1715
|
+
this.anchorToPreview = null;
|
1716
|
+
this.base.off(this.activeAnchor, 'mouseout', this.instance_handleAnchorMouseout);
|
1717
|
+
this.instance_handleAnchorMouseout = null;
|
1718
|
+
},
|
1719
|
+
|
1720
|
+
handleEditableMouseover: function (event) {
|
1721
|
+
/*var overAnchor = true,
|
1722
|
+
leaveAnchor = function () {
|
1723
|
+
// mark the anchor as no longer hovered, and stop listening
|
1724
|
+
overAnchor = false;
|
1725
|
+
this.base.off(this.activeAnchor, 'mouseout', leaveAnchor);
|
1726
|
+
}.bind(this);*/
|
1727
|
+
|
1728
|
+
if (event.target && event.target.tagName.toLowerCase() === 'a') {
|
1729
|
+
|
1730
|
+
// Detect empty href attributes
|
1731
|
+
// The browser will make href="" or href="#top"
|
1732
|
+
// into absolute urls when accessed as event.targed.href, so check the html
|
1733
|
+
if (!/href=["']\S+["']/.test(event.target.outerHTML) || /href=["']#\S+["']/.test(event.target.outerHTML)) {
|
1734
|
+
return true;
|
1735
|
+
}
|
1736
|
+
|
1737
|
+
// only show when hovering on anchors
|
1738
|
+
if (this.base.toolbar && this.base.toolbar.isDisplayed()) {
|
1739
|
+
// only show when toolbar is not present
|
1740
|
+
return true;
|
1741
|
+
}
|
1742
|
+
|
1743
|
+
// detach handler for other anchor in case we hovered multiple anchors quickly
|
1744
|
+
if (this.activeAnchor && this.activeAnchor !== event.target) {
|
1745
|
+
this.detachPreviewHandlers();
|
1493
1746
|
}
|
1747
|
+
|
1748
|
+
this.anchorToPreview = event.target;
|
1749
|
+
|
1750
|
+
this.instance_handleAnchorMouseout = this.handleAnchorMouseout.bind(this);
|
1751
|
+
this.base.on(this.anchorToPreview, 'mouseout', this.instance_handleAnchorMouseout);
|
1752
|
+
// Using setTimeout + options.delay because:
|
1753
|
+
// - We're going to show the anchor preview according to the configured delay
|
1754
|
+
// if the mouse has not left the anchor tag in that time
|
1755
|
+
this.base.delay(function () {
|
1756
|
+
if (this.anchorToPreview) {
|
1757
|
+
//this.activeAnchor = this.anchorToPreview;
|
1758
|
+
this.showPreview(this.anchorToPreview);
|
1759
|
+
}
|
1760
|
+
}.bind(this));
|
1494
1761
|
}
|
1495
|
-
return -1;
|
1496
1762
|
},
|
1497
1763
|
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
if (self.isActive) {
|
1502
|
-
fn();
|
1503
|
-
}
|
1504
|
-
}, this.options.delay);
|
1764
|
+
handlePreviewMouseover: function (event) {
|
1765
|
+
this.lastOver = (new Date()).getTime();
|
1766
|
+
this.hovering = true;
|
1505
1767
|
},
|
1506
1768
|
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
e[0].removeEventListener(e[1], e[2], e[3]);
|
1511
|
-
e = this.events.pop();
|
1769
|
+
handlePreviewMouseout: function (event) {
|
1770
|
+
if (!event.relatedTarget || !/anchor-preview/.test(event.relatedTarget.className)) {
|
1771
|
+
this.hovering = false;
|
1512
1772
|
}
|
1513
1773
|
},
|
1514
1774
|
|
1515
|
-
|
1516
|
-
|
1775
|
+
updatePreview: function () {
|
1776
|
+
if (this.hovering) {
|
1777
|
+
return true;
|
1778
|
+
}
|
1779
|
+
var durr = (new Date()).getTime() - this.lastOver;
|
1780
|
+
if (durr > this.base.options.anchorPreviewHideDelay) {
|
1781
|
+
// hide the preview 1/2 second after mouse leaves the link
|
1782
|
+
this.detachPreviewHandlers();
|
1783
|
+
}
|
1784
|
+
},
|
1517
1785
|
|
1518
|
-
|
1519
|
-
//
|
1520
|
-
|
1521
|
-
this.
|
1522
|
-
|
1523
|
-
|
1786
|
+
detachPreviewHandlers: function () {
|
1787
|
+
// cleanup
|
1788
|
+
clearInterval(this.interval_timer);
|
1789
|
+
if (this.instance_handlePreviewMouseover) {
|
1790
|
+
this.base.off(this.anchorPreview, 'mouseover', this.instance_handlePreviewMouseover);
|
1791
|
+
this.base.off(this.anchorPreview, 'mouseout', this.instance_handlePreviewMouseout);
|
1792
|
+
if (this.activeAnchor) {
|
1793
|
+
this.base.off(this.activeAnchor, 'mouseover', this.instance_handlePreviewMouseover);
|
1794
|
+
this.base.off(this.activeAnchor, 'mouseout', this.instance_handlePreviewMouseout);
|
1524
1795
|
}
|
1525
|
-
}
|
1796
|
+
}
|
1526
1797
|
|
1527
|
-
|
1528
|
-
// - This method could be called many times due to the type of event handlers that are calling it
|
1529
|
-
// - We want a slight delay so that other events in the stack can run, some of which may
|
1530
|
-
// prevent the toolbar from being hidden (via this.keepToolbarAlive).
|
1531
|
-
this.handleBlur = Util.throttle(function () {
|
1532
|
-
if (self.isActive && !self.keepToolbarAlive) {
|
1533
|
-
self.hideToolbarActions();
|
1534
|
-
}
|
1535
|
-
});
|
1798
|
+
this.hidePreview();
|
1536
1799
|
|
1537
|
-
|
1800
|
+
this.hovering = this.instance_handlePreviewMouseover = this.instance_handlePreviewMouseout = null;
|
1538
1801
|
},
|
1539
1802
|
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1803
|
+
// TODO: break up method and extract out handlers
|
1804
|
+
attachPreviewHandlers: function () {
|
1805
|
+
this.lastOver = (new Date()).getTime();
|
1806
|
+
this.hovering = true;
|
1807
|
+
|
1808
|
+
this.instance_handlePreviewMouseover = this.handlePreviewMouseover.bind(this);
|
1809
|
+
this.instance_handlePreviewMouseout = this.handlePreviewMouseout.bind(this);
|
1810
|
+
|
1811
|
+
this.interval_timer = setInterval(this.updatePreview.bind(this), 200);
|
1812
|
+
|
1813
|
+
this.base.on(this.anchorPreview, 'mouseover', this.instance_handlePreviewMouseover);
|
1814
|
+
this.base.on(this.anchorPreview, 'mouseout', this.instance_handlePreviewMouseout);
|
1815
|
+
this.base.on(this.activeAnchor, 'mouseover', this.instance_handlePreviewMouseover);
|
1816
|
+
this.base.on(this.activeAnchor, 'mouseout', this.instance_handlePreviewMouseout);
|
1817
|
+
}
|
1818
|
+
};
|
1819
|
+
}(window, document));
|
1820
|
+
|
1821
|
+
var Toolbar;
|
1822
|
+
|
1823
|
+
(function (window, document) {
|
1824
|
+
'use strict';
|
1825
|
+
|
1826
|
+
Toolbar = function Toolbar(instance) {
|
1827
|
+
this.base = instance;
|
1828
|
+
this.options = instance.options;
|
1829
|
+
this.initThrottledMethods();
|
1830
|
+
};
|
1831
|
+
|
1832
|
+
Toolbar.prototype = {
|
1833
|
+
|
1834
|
+
// Toolbar creation/deletion
|
1835
|
+
|
1836
|
+
createToolbar: function () {
|
1837
|
+
var toolbar = this.base.options.ownerDocument.createElement('div');
|
1838
|
+
|
1839
|
+
toolbar.id = 'medium-editor-toolbar-' + this.base.id;
|
1840
|
+
toolbar.className = 'medium-editor-toolbar';
|
1841
|
+
|
1842
|
+
if (this.options.staticToolbar) {
|
1843
|
+
toolbar.className += " static-toolbar";
|
1844
|
+
} else {
|
1845
|
+
toolbar.className += " stalker-toolbar";
|
1846
|
+
}
|
1847
|
+
|
1848
|
+
toolbar.appendChild(this.createToolbarButtons());
|
1849
|
+
|
1850
|
+
// Add any forms that extensions may have
|
1851
|
+
this.base.commands.forEach(function (extension) {
|
1852
|
+
if (extension.hasForm) {
|
1853
|
+
toolbar.appendChild(extension.getForm());
|
1854
|
+
}
|
1855
|
+
});
|
1856
|
+
|
1857
|
+
this.attachEventHandlers();
|
1858
|
+
|
1859
|
+
return toolbar;
|
1860
|
+
},
|
1861
|
+
|
1862
|
+
createToolbarButtons: function () {
|
1863
|
+
var ul = this.base.options.ownerDocument.createElement('ul'),
|
1864
|
+
li,
|
1865
|
+
btn,
|
1866
|
+
buttons;
|
1867
|
+
|
1868
|
+
ul.id = 'medium-editor-toolbar-actions' + this.base.id;
|
1869
|
+
ul.className = 'medium-editor-toolbar-actions clearfix';
|
1870
|
+
ul.style.display = 'block';
|
1871
|
+
|
1872
|
+
this.base.commands.forEach(function (extension) {
|
1873
|
+
if (typeof extension.getButton === 'function') {
|
1874
|
+
btn = extension.getButton(this.base);
|
1875
|
+
li = this.base.options.ownerDocument.createElement('li');
|
1876
|
+
if (Util.isElement(btn)) {
|
1877
|
+
li.appendChild(btn);
|
1878
|
+
} else {
|
1879
|
+
li.innerHTML = btn;
|
1880
|
+
}
|
1881
|
+
ul.appendChild(li);
|
1882
|
+
}
|
1883
|
+
}.bind(this));
|
1884
|
+
|
1885
|
+
buttons = ul.querySelectorAll('button');
|
1886
|
+
if (buttons.length > 0) {
|
1887
|
+
buttons[0].classList.add(this.options.firstButtonClass);
|
1888
|
+
buttons[buttons.length - 1].classList.add(this.options.lastButtonClass);
|
1889
|
+
}
|
1890
|
+
|
1891
|
+
return ul;
|
1892
|
+
},
|
1893
|
+
|
1894
|
+
deactivate: function () {
|
1895
|
+
if (this.toolbar) {
|
1896
|
+
if (this.toolbar.parentNode) {
|
1897
|
+
this.toolbar.parentNode.removeChild(this.toolbar);
|
1898
|
+
}
|
1899
|
+
delete this.toolbar;
|
1900
|
+
}
|
1901
|
+
},
|
1902
|
+
|
1903
|
+
// Toolbar accessors
|
1904
|
+
|
1905
|
+
getToolbarElement: function () {
|
1906
|
+
if (!this.toolbar) {
|
1907
|
+
this.toolbar = this.createToolbar();
|
1908
|
+
}
|
1909
|
+
|
1910
|
+
return this.toolbar;
|
1911
|
+
},
|
1912
|
+
|
1913
|
+
getToolbarActionsElement: function () {
|
1914
|
+
return this.getToolbarElement().querySelector('.medium-editor-toolbar-actions');
|
1915
|
+
},
|
1916
|
+
|
1917
|
+
// Toolbar event handlers
|
1918
|
+
|
1919
|
+
initThrottledMethods: function () {
|
1920
|
+
// throttledPositionToolbar is throttled because:
|
1921
|
+
// - It will be called when the browser is resizing, which can fire many times very quickly
|
1922
|
+
// - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits
|
1923
|
+
this.throttledPositionToolbar = Util.throttle(function (event) {
|
1924
|
+
if (this.base.isActive) {
|
1925
|
+
this.positionToolbarIfShown();
|
1926
|
+
}
|
1927
|
+
}.bind(this));
|
1928
|
+
|
1929
|
+
// throttledHideToolbarActions is throttled because:
|
1930
|
+
// - This method could be called many times due to the type of event handlers that are calling it
|
1931
|
+
// - We want a slight delay so that other events in the stack can run, some of which may
|
1932
|
+
// prevent the toolbar from being hidden
|
1933
|
+
this.throttledHideToolbarActions = Util.throttle(function (event) {
|
1934
|
+
if (this.base.isActive) {
|
1935
|
+
this.hideToolbarActions();
|
1936
|
+
}
|
1937
|
+
}.bind(this));
|
1938
|
+
},
|
1939
|
+
|
1940
|
+
attachEventHandlers: function () {
|
1941
|
+
// Handle mouseup on document for updating the selection in the toolbar
|
1942
|
+
this.base.on(this.options.ownerDocument.documentElement, 'mouseup', this.handleDocumentMouseup.bind(this));
|
1943
|
+
|
1944
|
+
// Add a scroll event for sticky toolbar
|
1945
|
+
if (this.options.staticToolbar && this.options.stickyToolbar) {
|
1946
|
+
// On scroll (capture), re-position the toolbar
|
1947
|
+
this.base.on(this.options.contentWindow, 'scroll', this.handleWindowScroll.bind(this), true);
|
1948
|
+
}
|
1949
|
+
|
1950
|
+
// On resize, re-position the toolbar
|
1951
|
+
this.base.on(this.options.contentWindow, 'resize', this.handleWindowResize.bind(this));
|
1952
|
+
|
1953
|
+
// Handlers for each contentedtiable element
|
1954
|
+
this.base.elements.forEach(function (element) {
|
1955
|
+
// Attach click handler to each contenteditable element
|
1956
|
+
this.base.on(element, 'click', this.handleEditableClick.bind(this));
|
1957
|
+
|
1958
|
+
// Attach keyup handler to each contenteditable element
|
1959
|
+
this.base.on(element, 'keyup', this.handleEditableKeyup.bind(this));
|
1960
|
+
|
1961
|
+
// Attach blur handler to each contenteditable element
|
1962
|
+
this.base.on(element, 'blur', this.handleEditableBlur.bind(this));
|
1963
|
+
}.bind(this));
|
1964
|
+
},
|
1965
|
+
|
1966
|
+
handleWindowScroll: function (event) {
|
1967
|
+
this.positionToolbarIfShown();
|
1968
|
+
},
|
1969
|
+
|
1970
|
+
handleWindowResize: function (event) {
|
1971
|
+
this.throttledPositionToolbar();
|
1972
|
+
},
|
1973
|
+
|
1974
|
+
handleDocumentMouseup: function (event) {
|
1975
|
+
// Do not trigger checkState when mouseup fires over the toolbar
|
1976
|
+
if (event &&
|
1977
|
+
event.target &&
|
1978
|
+
Util.isDescendant(this.getToolbarElement(), event.target)) {
|
1979
|
+
return false;
|
1980
|
+
}
|
1981
|
+
this.checkState();
|
1982
|
+
},
|
1983
|
+
|
1984
|
+
handleEditableClick: function (event) {
|
1985
|
+
// Delay the call to checkState to handle bug where selection is empty
|
1986
|
+
// immediately after clicking inside a pre-existing selection
|
1987
|
+
setTimeout(function () {
|
1988
|
+
this.checkState();
|
1989
|
+
}.bind(this), 0);
|
1990
|
+
},
|
1991
|
+
|
1992
|
+
handleEditableKeyup: function (event) {
|
1993
|
+
this.checkState();
|
1994
|
+
},
|
1995
|
+
|
1996
|
+
handleEditableBlur: function (event) {
|
1997
|
+
// Do not trigger checkState when bluring the editable area and clicking into the toolbar
|
1998
|
+
if (event &&
|
1999
|
+
event.relatedTarget &&
|
2000
|
+
Util.isDescendant(this.getToolbarElement(), event.relatedTarget)) {
|
2001
|
+
return false;
|
2002
|
+
}
|
2003
|
+
this.checkState();
|
2004
|
+
},
|
2005
|
+
|
2006
|
+
handleBlur: function (event) {
|
2007
|
+
// Hide the toolbar after a small delay so we can prevent this on toolbar click
|
2008
|
+
this.throttledHideToolbarActions();
|
2009
|
+
},
|
2010
|
+
|
2011
|
+
// Hiding/showing toolbar
|
2012
|
+
|
2013
|
+
isDisplayed: function () {
|
2014
|
+
return this.getToolbarElement().classList.contains('medium-editor-toolbar-active');
|
2015
|
+
},
|
2016
|
+
|
2017
|
+
showToolbar: function () {
|
2018
|
+
if (!this.isDisplayed()) {
|
2019
|
+
this.getToolbarElement().classList.add('medium-editor-toolbar-active');
|
2020
|
+
if (typeof this.options.onShowToolbar === 'function') {
|
2021
|
+
this.options.onShowToolbar();
|
2022
|
+
}
|
2023
|
+
}
|
2024
|
+
},
|
2025
|
+
|
2026
|
+
hideToolbar: function () {
|
2027
|
+
if (this.isDisplayed()) {
|
2028
|
+
this.getToolbarElement().classList.remove('medium-editor-toolbar-active');
|
2029
|
+
if (typeof this.options.onHideToolbar === 'function') {
|
2030
|
+
this.options.onHideToolbar();
|
2031
|
+
}
|
2032
|
+
}
|
2033
|
+
},
|
2034
|
+
|
2035
|
+
hideToolbarActions: function () {
|
2036
|
+
this.base.commands.forEach(function (extension) {
|
2037
|
+
if (extension.onHide && typeof extension.onHide === 'function') {
|
2038
|
+
extension.onHide();
|
2039
|
+
}
|
2040
|
+
});
|
2041
|
+
this.hideToolbar();
|
2042
|
+
},
|
2043
|
+
|
2044
|
+
isToolbarDefaultActionsDisplayed: function () {
|
2045
|
+
return this.getToolbarActionsElement().style.display === 'block';
|
2046
|
+
},
|
2047
|
+
|
2048
|
+
hideToolbarDefaultActions: function () {
|
2049
|
+
if (this.isToolbarDefaultActionsDisplayed()) {
|
2050
|
+
this.getToolbarActionsElement().style.display = 'none';
|
2051
|
+
}
|
2052
|
+
},
|
2053
|
+
|
2054
|
+
showToolbarDefaultActions: function () {
|
2055
|
+
this.hideExtensionForms();
|
2056
|
+
|
2057
|
+
if (!this.isToolbarDefaultActionsDisplayed()) {
|
2058
|
+
this.getToolbarActionsElement().style.display = 'block';
|
2059
|
+
}
|
2060
|
+
|
2061
|
+
// Using setTimeout + options.delay because:
|
2062
|
+
// We will actually be displaying the toolbar, which should be controlled by options.delay
|
2063
|
+
this.base.delay(function () {
|
2064
|
+
this.showToolbar();
|
2065
|
+
}.bind(this));
|
2066
|
+
},
|
2067
|
+
|
2068
|
+
hideExtensionForms: function () {
|
2069
|
+
// Hide all extension forms
|
2070
|
+
this.base.commands.forEach(function (extension) {
|
2071
|
+
if (extension.hasForm && extension.isDisplayed()) {
|
2072
|
+
extension.hideForm();
|
2073
|
+
}
|
2074
|
+
});
|
2075
|
+
},
|
2076
|
+
|
2077
|
+
// Responding to changes in user selection
|
2078
|
+
|
2079
|
+
// Checks for existance of multiple block elements in the current selection
|
2080
|
+
multipleBlockElementsSelected: function () {
|
2081
|
+
/*jslint regexp: true*/
|
2082
|
+
var selectionHtml = Selection.getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
|
2083
|
+
hasMultiParagraphs = selectionHtml.match(/<(p|h[1-6]|blockquote)[^>]*>/g);
|
2084
|
+
/*jslint regexp: false*/
|
2085
|
+
|
2086
|
+
return !!hasMultiParagraphs && hasMultiParagraphs.length > 1;
|
2087
|
+
},
|
2088
|
+
|
2089
|
+
// TODO: selection and selectionRange should be properties of the
|
2090
|
+
// Selection object
|
2091
|
+
checkSelectionElement: function (newSelection, selectionElement) {
|
2092
|
+
var i,
|
2093
|
+
adjacentNode,
|
2094
|
+
offset = 0,
|
2095
|
+
newRange;
|
2096
|
+
this.base.selection = newSelection;
|
2097
|
+
this.base.selectionRange = this.base.selection.getRangeAt(0);
|
2098
|
+
|
2099
|
+
/*
|
2100
|
+
* In firefox, there are cases (ie doubleclick of a word) where the selectionRange start
|
2101
|
+
* will be at the very end of an element. In other browsers, the selectionRange start
|
2102
|
+
* would instead be at the very beginning of an element that actually has content.
|
2103
|
+
* example:
|
2104
|
+
* <span>foo</span><span>bar</span>
|
2105
|
+
*
|
2106
|
+
* If the text 'bar' is selected, most browsers will have the selectionRange start at the beginning
|
2107
|
+
* of the 'bar' span. However, there are cases where firefox will have the selectionRange start
|
2108
|
+
* at the end of the 'foo' span. The contenteditable behavior will be ok, but if there are any
|
2109
|
+
* properties on the 'bar' span, they won't be reflected accurately in the toolbar
|
2110
|
+
* (ie 'Bold' button wouldn't be active)
|
2111
|
+
*
|
2112
|
+
* So, for cases where the selectionRange start is at the end of an element/node, find the next
|
2113
|
+
* adjacent text node that actually has content in it, and move the selectionRange start there.
|
2114
|
+
*/
|
2115
|
+
if (this.options.standardizeSelectionStart &&
|
2116
|
+
this.base.selectionRange.startContainer.nodeValue &&
|
2117
|
+
(this.base.selectionRange.startOffset === this.base.selectionRange.startContainer.nodeValue.length)) {
|
2118
|
+
adjacentNode = Util.findAdjacentTextNodeWithContent(Selection.getSelectionElement(this.options.contentWindow), this.base.selectionRange.startContainer, this.options.ownerDocument);
|
2119
|
+
if (adjacentNode) {
|
2120
|
+
offset = 0;
|
2121
|
+
while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) {
|
2122
|
+
offset = offset + 1;
|
2123
|
+
}
|
2124
|
+
newRange = this.options.ownerDocument.createRange();
|
2125
|
+
newRange.setStart(adjacentNode, offset);
|
2126
|
+
newRange.setEnd(this.base.selectionRange.endContainer, this.base.selectionRange.endOffset);
|
2127
|
+
this.base.selection.removeAllRanges();
|
2128
|
+
this.base.selection.addRange(newRange);
|
2129
|
+
this.base.selectionRange = newRange;
|
2130
|
+
}
|
2131
|
+
}
|
2132
|
+
|
2133
|
+
for (i = 0; i < this.base.elements.length; i += 1) {
|
2134
|
+
if (this.base.elements[i] === selectionElement) {
|
2135
|
+
this.showAndUpdateToolbar();
|
2136
|
+
return;
|
2137
|
+
}
|
2138
|
+
}
|
2139
|
+
|
2140
|
+
if (!this.options.staticToolbar) {
|
2141
|
+
this.hideToolbarActions();
|
2142
|
+
}
|
2143
|
+
},
|
2144
|
+
|
2145
|
+
checkState: function () {
|
2146
|
+
var newSelection,
|
2147
|
+
selectionElement;
|
2148
|
+
|
2149
|
+
if (!this.base.preventSelectionUpdates) {
|
2150
|
+
newSelection = this.options.contentWindow.getSelection();
|
2151
|
+
if ((!this.options.updateOnEmptySelection && newSelection.toString().trim() === '') ||
|
2152
|
+
(this.options.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected()) ||
|
2153
|
+
Selection.selectionInContentEditableFalse(this.options.contentWindow)) {
|
2154
|
+
if (!this.options.staticToolbar) {
|
2155
|
+
this.hideToolbarActions();
|
2156
|
+
} else {
|
2157
|
+
this.showAndUpdateToolbar();
|
2158
|
+
}
|
2159
|
+
|
2160
|
+
} else {
|
2161
|
+
selectionElement = Selection.getSelectionElement(this.options.contentWindow);
|
2162
|
+
if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) {
|
2163
|
+
if (!this.options.staticToolbar) {
|
2164
|
+
this.hideToolbarActions();
|
2165
|
+
}
|
2166
|
+
} else {
|
2167
|
+
this.checkSelectionElement(newSelection, selectionElement);
|
2168
|
+
}
|
2169
|
+
}
|
2170
|
+
}
|
2171
|
+
},
|
2172
|
+
|
2173
|
+
// Updating the toolbar
|
2174
|
+
|
2175
|
+
showAndUpdateToolbar: function () {
|
2176
|
+
this.setToolbarButtonStates();
|
2177
|
+
this.showToolbarDefaultActions();
|
2178
|
+
this.setToolbarPosition();
|
2179
|
+
},
|
2180
|
+
|
2181
|
+
setToolbarButtonStates: function () {
|
2182
|
+
this.base.commands.forEach(function (extension) {
|
2183
|
+
if (typeof extension.isActive === 'function') {
|
2184
|
+
extension.setInactive();
|
2185
|
+
}
|
2186
|
+
}.bind(this));
|
2187
|
+
this.checkActiveButtons();
|
2188
|
+
},
|
2189
|
+
|
2190
|
+
checkActiveButtons: function () {
|
2191
|
+
var manualStateChecks = [],
|
2192
|
+
queryState = null,
|
2193
|
+
parentNode,
|
2194
|
+
updateExtensionState = function (extension) {
|
2195
|
+
if (typeof extension.checkState === 'function') {
|
2196
|
+
extension.checkState(parentNode);
|
2197
|
+
} else if (typeof extension.isActive === 'function' &&
|
2198
|
+
typeof extension.isAlreadyApplied === 'function') {
|
2199
|
+
if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) {
|
2200
|
+
extension.setActive();
|
2201
|
+
}
|
2202
|
+
}
|
2203
|
+
};
|
2204
|
+
|
2205
|
+
if (!this.base.selectionRange) {
|
2206
|
+
return;
|
2207
|
+
}
|
2208
|
+
|
2209
|
+
parentNode = Selection.getSelectedParentElement(this.base.selectionRange);
|
2210
|
+
|
2211
|
+
// Loop through all commands
|
2212
|
+
this.base.commands.forEach(function (command) {
|
2213
|
+
// For those commands where we can use document.queryCommandState(), do so
|
2214
|
+
if (typeof command.queryCommandState === 'function') {
|
2215
|
+
queryState = command.queryCommandState();
|
2216
|
+
// If queryCommandState returns a valid value, we can trust the browser
|
2217
|
+
// and don't need to do our manual checks
|
2218
|
+
if (queryState !== null) {
|
2219
|
+
if (queryState) {
|
2220
|
+
command.setActive();
|
2221
|
+
}
|
2222
|
+
return;
|
2223
|
+
}
|
2224
|
+
}
|
2225
|
+
// We can't use queryCommandState for this command, so add to manualStateChecks
|
2226
|
+
manualStateChecks.push(command);
|
2227
|
+
});
|
2228
|
+
|
2229
|
+
// Climb up the DOM and do manual checks for whether a certain command is currently enabled for this node
|
2230
|
+
while (parentNode.tagName !== undefined && Util.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) {
|
2231
|
+
manualStateChecks.forEach(updateExtensionState);
|
2232
|
+
|
2233
|
+
// we can abort the search upwards if we leave the contentEditable element
|
2234
|
+
if (this.base.elements.indexOf(parentNode) !== -1) {
|
2235
|
+
break;
|
2236
|
+
}
|
2237
|
+
parentNode = parentNode.parentNode;
|
2238
|
+
}
|
2239
|
+
},
|
2240
|
+
|
2241
|
+
// Positioning toolbar
|
2242
|
+
|
2243
|
+
positionToolbarIfShown: function () {
|
2244
|
+
if (this.isDisplayed()) {
|
2245
|
+
this.setToolbarPosition();
|
2246
|
+
}
|
2247
|
+
},
|
2248
|
+
|
2249
|
+
setToolbarPosition: function () {
|
2250
|
+
var container = Selection.getSelectionElement(this.options.contentWindow),
|
2251
|
+
selection = this.options.contentWindow.getSelection(),
|
2252
|
+
anchorPreview;
|
2253
|
+
|
2254
|
+
// If there isn't a valid selection, bail
|
2255
|
+
if (!container || !this.options.contentWindow.getSelection().focusNode) {
|
2256
|
+
return this;
|
2257
|
+
}
|
2258
|
+
|
2259
|
+
// If the container isn't part of this medium-editor instance, bail
|
2260
|
+
if (this.base.elements.indexOf(container) === -1) {
|
2261
|
+
return this;
|
2262
|
+
}
|
2263
|
+
|
2264
|
+
if (this.options.staticToolbar) {
|
2265
|
+
this.showToolbar();
|
2266
|
+
this.positionStaticToolbar(container);
|
2267
|
+
|
2268
|
+
} else if (!selection.isCollapsed) {
|
2269
|
+
this.showToolbar();
|
2270
|
+
this.positionToolbar(selection);
|
2271
|
+
}
|
2272
|
+
|
2273
|
+
anchorPreview = this.base.getExtensionByName('anchor-preview');
|
2274
|
+
|
2275
|
+
if (anchorPreview && typeof anchorPreview.hidePreview === 'function') {
|
2276
|
+
anchorPreview.hidePreview();
|
2277
|
+
}
|
2278
|
+
},
|
2279
|
+
|
2280
|
+
positionStaticToolbar: function (container) {
|
2281
|
+
// position the toolbar at left 0, so we can get the real width of the toolbar
|
2282
|
+
this.getToolbarElement().style.left = '0';
|
2283
|
+
|
2284
|
+
// document.documentElement for IE 9
|
2285
|
+
var scrollTop = (this.options.ownerDocument.documentElement && this.options.ownerDocument.documentElement.scrollTop) || this.options.ownerDocument.body.scrollTop,
|
2286
|
+
windowWidth = this.options.contentWindow.innerWidth,
|
2287
|
+
toolbarElement = this.getToolbarElement(),
|
2288
|
+
containerRect = container.getBoundingClientRect(),
|
2289
|
+
containerTop = containerRect.top + scrollTop,
|
2290
|
+
containerCenter = (containerRect.left + (containerRect.width / 2)),
|
2291
|
+
toolbarHeight = toolbarElement.offsetHeight,
|
2292
|
+
toolbarWidth = toolbarElement.offsetWidth,
|
2293
|
+
halfOffsetWidth = toolbarWidth / 2,
|
2294
|
+
targetLeft;
|
2295
|
+
|
2296
|
+
if (this.options.stickyToolbar) {
|
2297
|
+
// If it's beyond the height of the editor, position it at the bottom of the editor
|
2298
|
+
if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight)) {
|
2299
|
+
toolbarElement.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px';
|
2300
|
+
toolbarElement.classList.remove('sticky-toolbar');
|
2301
|
+
|
2302
|
+
// Stick the toolbar to the top of the window
|
2303
|
+
} else if (scrollTop > (containerTop - toolbarHeight)) {
|
2304
|
+
toolbarElement.classList.add('sticky-toolbar');
|
2305
|
+
toolbarElement.style.top = "0px";
|
2306
|
+
|
2307
|
+
// Normal static toolbar position
|
2308
|
+
} else {
|
2309
|
+
toolbarElement.classList.remove('sticky-toolbar');
|
2310
|
+
toolbarElement.style.top = containerTop - toolbarHeight + "px";
|
2311
|
+
}
|
2312
|
+
} else {
|
2313
|
+
toolbarElement.style.top = containerTop - toolbarHeight + "px";
|
2314
|
+
}
|
2315
|
+
|
2316
|
+
if (this.options.toolbarAlign === 'left') {
|
2317
|
+
targetLeft = containerRect.left;
|
2318
|
+
} else if (this.options.toolbarAlign === 'center') {
|
2319
|
+
targetLeft = containerCenter - halfOffsetWidth;
|
2320
|
+
} else if (this.options.toolbarAlign === 'right') {
|
2321
|
+
targetLeft = containerRect.right - toolbarWidth;
|
2322
|
+
}
|
2323
|
+
|
2324
|
+
if (targetLeft < 0) {
|
2325
|
+
targetLeft = 0;
|
2326
|
+
} else if ((targetLeft + toolbarWidth) > windowWidth) {
|
2327
|
+
targetLeft = windowWidth - toolbarWidth;
|
2328
|
+
}
|
2329
|
+
|
2330
|
+
toolbarElement.style.left = targetLeft + 'px';
|
2331
|
+
},
|
2332
|
+
|
2333
|
+
positionToolbar: function (selection) {
|
2334
|
+
// position the toolbar at left 0, so we can get the real width of the toolbar
|
2335
|
+
this.getToolbarElement().style.left = '0';
|
2336
|
+
|
2337
|
+
var windowWidth = this.options.contentWindow.innerWidth,
|
2338
|
+
range = selection.getRangeAt(0),
|
2339
|
+
boundary = range.getBoundingClientRect(),
|
2340
|
+
middleBoundary = (boundary.left + boundary.right) / 2,
|
2341
|
+
toolbarElement = this.getToolbarElement(),
|
2342
|
+
toolbarHeight = toolbarElement.offsetHeight,
|
2343
|
+
toolbarWidth = toolbarElement.offsetWidth,
|
2344
|
+
halfOffsetWidth = toolbarWidth / 2,
|
2345
|
+
buttonHeight = 50,
|
2346
|
+
defaultLeft = this.options.diffLeft - halfOffsetWidth;
|
2347
|
+
|
2348
|
+
if (boundary.top < buttonHeight) {
|
2349
|
+
toolbarElement.classList.add('medium-toolbar-arrow-over');
|
2350
|
+
toolbarElement.classList.remove('medium-toolbar-arrow-under');
|
2351
|
+
toolbarElement.style.top = buttonHeight + boundary.bottom - this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
|
2352
|
+
} else {
|
2353
|
+
toolbarElement.classList.add('medium-toolbar-arrow-under');
|
2354
|
+
toolbarElement.classList.remove('medium-toolbar-arrow-over');
|
2355
|
+
toolbarElement.style.top = boundary.top + this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
|
2356
|
+
}
|
2357
|
+
if (middleBoundary < halfOffsetWidth) {
|
2358
|
+
toolbarElement.style.left = defaultLeft + halfOffsetWidth + 'px';
|
2359
|
+
} else if ((windowWidth - middleBoundary) < halfOffsetWidth) {
|
2360
|
+
toolbarElement.style.left = windowWidth + defaultLeft - halfOffsetWidth + 'px';
|
2361
|
+
} else {
|
2362
|
+
toolbarElement.style.left = defaultLeft + middleBoundary + 'px';
|
2363
|
+
}
|
2364
|
+
}
|
2365
|
+
};
|
2366
|
+
}(window, document));
|
2367
|
+
|
2368
|
+
function MediumEditor(elements, options) {
|
2369
|
+
'use strict';
|
2370
|
+
return this.init(elements, options);
|
2371
|
+
}
|
2372
|
+
|
2373
|
+
(function () {
|
2374
|
+
'use strict';
|
2375
|
+
|
2376
|
+
MediumEditor.statics = {
|
2377
|
+
ButtonsData: ButtonsData,
|
2378
|
+
DefaultButton: DefaultButton,
|
2379
|
+
AnchorExtension: AnchorExtension,
|
2380
|
+
Toolbar: Toolbar,
|
2381
|
+
AnchorPreview: AnchorPreview
|
2382
|
+
};
|
2383
|
+
|
2384
|
+
MediumEditor.prototype = {
|
2385
|
+
defaults: {
|
2386
|
+
allowMultiParagraphSelection: true,
|
2387
|
+
anchorInputPlaceholder: 'Paste or type a link',
|
2388
|
+
anchorInputCheckboxLabel: 'Open in new window',
|
2389
|
+
anchorPreviewHideDelay: 500,
|
2390
|
+
buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote'],
|
2391
|
+
buttonLabels: false,
|
2392
|
+
checkLinkFormat: false,
|
2393
|
+
cleanPastedHTML: false,
|
2394
|
+
delay: 0,
|
2395
|
+
diffLeft: 0,
|
2396
|
+
diffTop: -10,
|
2397
|
+
disableReturn: false,
|
2398
|
+
disableDoubleReturn: false,
|
2399
|
+
disableToolbar: false,
|
2400
|
+
disableAnchorPreview: false,
|
2401
|
+
disableEditing: false,
|
2402
|
+
disablePlaceholders: false,
|
2403
|
+
toolbarAlign: 'center',
|
2404
|
+
elementsContainer: false,
|
2405
|
+
imageDragging: true,
|
2406
|
+
standardizeSelectionStart: false,
|
2407
|
+
contentWindow: window,
|
2408
|
+
ownerDocument: document,
|
2409
|
+
firstHeader: 'h3',
|
2410
|
+
forcePlainText: true,
|
2411
|
+
placeholder: 'Type your text',
|
2412
|
+
secondHeader: 'h4',
|
2413
|
+
targetBlank: false,
|
2414
|
+
anchorTarget: false,
|
2415
|
+
anchorButton: false,
|
2416
|
+
anchorButtonClass: 'btn',
|
2417
|
+
extensions: {},
|
2418
|
+
activeButtonClass: 'medium-editor-button-active',
|
2419
|
+
firstButtonClass: 'medium-editor-button-first',
|
2420
|
+
lastButtonClass: 'medium-editor-button-last'
|
2421
|
+
},
|
2422
|
+
|
2423
|
+
init: function (elements, options) {
|
2424
|
+
var uniqueId = 1;
|
2425
|
+
|
2426
|
+
this.options = Util.defaults(options, this.defaults);
|
2427
|
+
this.setElementSelection(elements);
|
2428
|
+
if (this.elements.length === 0) {
|
2429
|
+
return;
|
2430
|
+
}
|
2431
|
+
|
2432
|
+
if (!this.options.elementsContainer) {
|
2433
|
+
this.options.elementsContainer = this.options.ownerDocument.body;
|
2434
|
+
}
|
2435
|
+
|
2436
|
+
while (this.options.elementsContainer.querySelector('#medium-editor-toolbar-' + uniqueId)) {
|
2437
|
+
uniqueId = uniqueId + 1;
|
2438
|
+
}
|
2439
|
+
|
2440
|
+
this.id = uniqueId;
|
2441
|
+
|
2442
|
+
return this.setup();
|
2443
|
+
},
|
2444
|
+
|
2445
|
+
setup: function () {
|
2446
|
+
this.events = [];
|
2447
|
+
this.isActive = true;
|
2448
|
+
this.initCommands()
|
2449
|
+
.initElements()
|
2450
|
+
.bindDragDrop()
|
2451
|
+
.bindPaste()
|
2452
|
+
.setPlaceholders()
|
2453
|
+
.bindElementActions()
|
2454
|
+
.bindBlur();
|
2455
|
+
},
|
2456
|
+
|
2457
|
+
on: function (target, event, listener, useCapture) {
|
2458
|
+
target.addEventListener(event, listener, useCapture);
|
2459
|
+
this.events.push([target, event, listener, useCapture]);
|
2460
|
+
},
|
2461
|
+
|
2462
|
+
off: function (target, event, listener, useCapture) {
|
2463
|
+
var index = this.indexOfListener(target, event, listener, useCapture),
|
2464
|
+
e;
|
2465
|
+
if (index !== -1) {
|
2466
|
+
e = this.events.splice(index, 1)[0];
|
2467
|
+
e[0].removeEventListener(e[1], e[2], e[3]);
|
2468
|
+
}
|
2469
|
+
},
|
2470
|
+
|
2471
|
+
indexOfListener: function (target, event, listener, useCapture) {
|
2472
|
+
var i, n, item;
|
2473
|
+
for (i = 0, n = this.events.length; i < n; i = i + 1) {
|
2474
|
+
item = this.events[i];
|
2475
|
+
if (item[0] === target && item[1] === event && item[2] === listener && item[3] === useCapture) {
|
2476
|
+
return i;
|
2477
|
+
}
|
2478
|
+
}
|
2479
|
+
return -1;
|
2480
|
+
},
|
2481
|
+
|
2482
|
+
delay: function (fn) {
|
2483
|
+
var self = this;
|
2484
|
+
setTimeout(function () {
|
2485
|
+
if (self.isActive) {
|
2486
|
+
fn();
|
2487
|
+
}
|
2488
|
+
}, this.options.delay);
|
2489
|
+
},
|
2490
|
+
|
2491
|
+
removeAllEvents: function () {
|
2492
|
+
var e = this.events.pop();
|
2493
|
+
while (e) {
|
2494
|
+
e[0].removeEventListener(e[1], e[2], e[3]);
|
2495
|
+
e = this.events.pop();
|
2496
|
+
}
|
2497
|
+
},
|
2498
|
+
|
2499
|
+
initElements: function () {
|
2500
|
+
var i,
|
2501
|
+
addToolbar = false;
|
1543
2502
|
for (i = 0; i < this.elements.length; i += 1) {
|
1544
2503
|
if (!this.options.disableEditing && !this.elements[i].getAttribute('data-disable-editing')) {
|
1545
2504
|
this.elements[i].setAttribute('contentEditable', true);
|
@@ -1557,9 +2516,7 @@ function MediumEditor(elements, options) {
|
|
1557
2516
|
}
|
1558
2517
|
// Init toolbar
|
1559
2518
|
if (addToolbar) {
|
1560
|
-
this.initToolbar()
|
1561
|
-
.setFirstAndLastButtons()
|
1562
|
-
.bindAnchorPreview();
|
2519
|
+
this.initToolbar();
|
1563
2520
|
}
|
1564
2521
|
return this;
|
1565
2522
|
},
|
@@ -1585,6 +2542,9 @@ function MediumEditor(elements, options) {
|
|
1585
2542
|
blurFunction = function (e) {
|
1586
2543
|
var isDescendantOfEditorElements = false,
|
1587
2544
|
selection = self.options.contentWindow.getSelection(),
|
2545
|
+
toolbarEl = (self.toolbar) ? self.toolbar.getToolbarElement() : null,
|
2546
|
+
anchorPreview = self.getExtensionByName('anchor-preview'),
|
2547
|
+
previewEl = (anchorPreview && anchorPreview.getPreviewElement) ? anchorPreview.getPreviewElement() : null,
|
1588
2548
|
selRange = selection.isCollapsed ?
|
1589
2549
|
null :
|
1590
2550
|
Selection.getSelectedParentElement(selection.getRangeAt(0)),
|
@@ -1594,26 +2554,27 @@ function MediumEditor(elements, options) {
|
|
1594
2554
|
// to disapper when selecting from right to left and
|
1595
2555
|
// the selection ends at the beginning of the text.
|
1596
2556
|
for (i = 0; i < self.elements.length; i += 1) {
|
1597
|
-
if (
|
2557
|
+
if (self.elements[i] === e.target
|
2558
|
+
|| Util.isDescendant(self.elements[i], e.target)
|
1598
2559
|
|| Util.isDescendant(self.elements[i], selRange)) {
|
1599
2560
|
isDescendantOfEditorElements = true;
|
1600
2561
|
break;
|
1601
2562
|
}
|
1602
2563
|
}
|
1603
|
-
// If it's not part of the editor, or
|
1604
|
-
if (
|
1605
|
-
&&
|
1606
|
-
&& !
|
1607
|
-
&& !Util.isDescendant(self.toolbar, e.target)
|
1608
|
-
&& !Util.isDescendant(self.anchorPreview, e.target)) {
|
2564
|
+
// If it's not part of the editor, toolbar, or anchor preview
|
2565
|
+
if (!isDescendantOfEditorElements
|
2566
|
+
&& (!toolbarEl || (toolbarEl !== e.target && !Util.isDescendant(toolbarEl, e.target)))
|
2567
|
+
&& (!previewEl || (previewEl !== e.target && !Util.isDescendant(previewEl, e.target)))) {
|
1609
2568
|
|
1610
2569
|
// Activate the placeholder
|
1611
2570
|
if (!self.options.disablePlaceholders) {
|
1612
2571
|
self.placeholderWrapper(e, self.elements[0]);
|
1613
2572
|
}
|
1614
2573
|
|
1615
|
-
//
|
1616
|
-
self.
|
2574
|
+
// Let the toolbar know that we've detected a blur
|
2575
|
+
if (self.toolbar) {
|
2576
|
+
self.toolbar.handleBlur(e);
|
2577
|
+
}
|
1617
2578
|
}
|
1618
2579
|
};
|
1619
2580
|
|
@@ -1625,18 +2586,12 @@ function MediumEditor(elements, options) {
|
|
1625
2586
|
},
|
1626
2587
|
|
1627
2588
|
bindClick: function (i) {
|
1628
|
-
|
1629
|
-
|
1630
|
-
this.on(this.elements[i], 'click', function () {
|
1631
|
-
if (!self.options.disablePlaceholders) {
|
2589
|
+
if (!this.options.disablePlaceholders) {
|
2590
|
+
this.on(this.elements[i], 'click', function () {
|
1632
2591
|
// Remove placeholder
|
1633
2592
|
this.classList.remove('medium-editor-placeholder');
|
1634
|
-
}
|
1635
|
-
|
1636
|
-
if (self.options.staticToolbar) {
|
1637
|
-
self.setToolbarPosition();
|
1638
|
-
}
|
1639
|
-
});
|
2593
|
+
});
|
2594
|
+
}
|
1640
2595
|
|
1641
2596
|
return this;
|
1642
2597
|
},
|
@@ -1707,6 +2662,32 @@ function MediumEditor(elements, options) {
|
|
1707
2662
|
return extension;
|
1708
2663
|
},
|
1709
2664
|
|
2665
|
+
shouldAddDefaultAnchorPreview: function () {
|
2666
|
+
var i,
|
2667
|
+
shouldAdd = false;
|
2668
|
+
|
2669
|
+
// If anchor-preview is disabled, don't add
|
2670
|
+
if (this.options.disableAnchorPreview) {
|
2671
|
+
return false;
|
2672
|
+
}
|
2673
|
+
// If anchor-preview extension has been overriden, don't add
|
2674
|
+
if (this.options.extensions['anchor-preview']) {
|
2675
|
+
return false;
|
2676
|
+
}
|
2677
|
+
// If toolbar is disabled, don't add
|
2678
|
+
if (this.options.disableToolbar) {
|
2679
|
+
return false;
|
2680
|
+
}
|
2681
|
+
// If all elements have 'data-disable-toolbar' attribute, don't add
|
2682
|
+
for (i = 0; i < this.elements.length; i += 1) {
|
2683
|
+
if (!this.elements[i].getAttribute('data-disable-toolbar')) {
|
2684
|
+
shouldAdd = true;
|
2685
|
+
}
|
2686
|
+
}
|
2687
|
+
|
2688
|
+
return shouldAdd;
|
2689
|
+
},
|
2690
|
+
|
1710
2691
|
initCommands: function () {
|
1711
2692
|
var buttons = this.options.buttons,
|
1712
2693
|
extensions = this.options.extensions,
|
@@ -1733,6 +2714,11 @@ function MediumEditor(elements, options) {
|
|
1733
2714
|
}
|
1734
2715
|
}
|
1735
2716
|
|
2717
|
+
// Add AnchorPreview as extension if needed
|
2718
|
+
if (this.shouldAddDefaultAnchorPreview()) {
|
2719
|
+
this.commands.push(this.initExtension(new AnchorPreview(), 'anchor-preview'));
|
2720
|
+
}
|
2721
|
+
|
1736
2722
|
return this;
|
1737
2723
|
},
|
1738
2724
|
|
@@ -1794,7 +2780,10 @@ function MediumEditor(elements, options) {
|
|
1794
2780
|
tagName,
|
1795
2781
|
editorElement;
|
1796
2782
|
|
1797
|
-
if (node
|
2783
|
+
if (node
|
2784
|
+
&& node.getAttribute('data-medium-element')
|
2785
|
+
&& node.children.length === 0
|
2786
|
+
&& !(self.options.disableReturn || node.getAttribute('data-disable-return'))) {
|
1798
2787
|
self.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
1799
2788
|
}
|
1800
2789
|
if (e.which === Util.keyCode.ENTER) {
|
@@ -1804,16 +2793,15 @@ function MediumEditor(elements, options) {
|
|
1804
2793
|
|
1805
2794
|
if (!(self.options.disableReturn || editorElement.getAttribute('data-disable-return')) &&
|
1806
2795
|
tagName !== 'li' && !Util.isListItemChild(node)) {
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
2796
|
+
// For anchor tags, unlink
|
2797
|
+
if (tagName === 'a') {
|
2798
|
+
self.options.ownerDocument.execCommand('unlink', false, null);
|
2799
|
+
} else if (!e.shiftKey) {
|
2800
|
+
// only format block if this is not a header tag
|
1810
2801
|
if (!/h\d/.test(tagName)) {
|
1811
2802
|
self.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
1812
2803
|
}
|
1813
2804
|
}
|
1814
|
-
if (tagName === 'a') {
|
1815
|
-
self.options.ownerDocument.execCommand('unlink', false, null);
|
1816
|
-
}
|
1817
2805
|
}
|
1818
2806
|
}
|
1819
2807
|
});
|
@@ -1849,7 +2837,7 @@ function MediumEditor(elements, options) {
|
|
1849
2837
|
|
1850
2838
|
if (tag === 'pre') {
|
1851
2839
|
e.preventDefault();
|
1852
|
-
self.options.ownerDocument
|
2840
|
+
Util.insertHTMLCommand(self.options.ownerDocument, ' ');
|
1853
2841
|
}
|
1854
2842
|
|
1855
2843
|
// Tab to indent list structures!
|
@@ -1858,9 +2846,9 @@ function MediumEditor(elements, options) {
|
|
1858
2846
|
|
1859
2847
|
// If Shift is down, outdent, otherwise indent
|
1860
2848
|
if (e.shiftKey) {
|
1861
|
-
self.options.ownerDocument.execCommand('outdent',
|
2849
|
+
self.options.ownerDocument.execCommand('outdent', false, null);
|
1862
2850
|
} else {
|
1863
|
-
self.options.ownerDocument.execCommand('indent',
|
2851
|
+
self.options.ownerDocument.execCommand('indent', false, null);
|
1864
2852
|
}
|
1865
2853
|
}
|
1866
2854
|
} else if (e.which === Util.keyCode.BACKSPACE || e.which === Util.keyCode.DELETE || e.which === Util.keyCode.ENTER) {
|
@@ -1871,7 +2859,7 @@ function MediumEditor(elements, options) {
|
|
1871
2859
|
} else if (e.ctrlKey || e.metaKey) {
|
1872
2860
|
key = String.fromCharCode(e.which || e.keyCode).toLowerCase();
|
1873
2861
|
self.commands.forEach(function (extension) {
|
1874
|
-
if (extension.options.key && extension.options.key === key) {
|
2862
|
+
if (extension.options && extension.options.key && extension.options.key === key) {
|
1875
2863
|
extension.handleClick(e);
|
1876
2864
|
}
|
1877
2865
|
});
|
@@ -1922,455 +2910,102 @@ function MediumEditor(elements, options) {
|
|
1922
2910
|
|
1923
2911
|
// remove node and move cursor to start of header
|
1924
2912
|
range = document.createRange();
|
1925
|
-
sel =
|
2913
|
+
sel = this.options.contentWindow.getSelection();
|
1926
2914
|
|
1927
2915
|
range.setStart(node.nextElementSibling, 0);
|
1928
2916
|
range.collapse(true);
|
1929
2917
|
|
1930
|
-
sel.removeAllRanges();
|
1931
|
-
sel.addRange(range);
|
1932
|
-
|
1933
|
-
node.previousElementSibling.parentNode.removeChild(node);
|
1934
|
-
|
1935
|
-
e.preventDefault();
|
1936
|
-
}
|
1937
|
-
},
|
1938
|
-
|
1939
|
-
initToolbar: function () {
|
1940
|
-
if (this.toolbar) {
|
1941
|
-
return this;
|
1942
|
-
}
|
1943
|
-
this.toolbar = this.createToolbar();
|
1944
|
-
this.keepToolbarAlive = false;
|
1945
|
-
this.toolbarActions = this.toolbar.querySelector('.medium-editor-toolbar-actions');
|
1946
|
-
this.anchorPreview = this.createAnchorPreview();
|
1947
|
-
|
1948
|
-
return this;
|
1949
|
-
},
|
1950
|
-
|
1951
|
-
createToolbar: function () {
|
1952
|
-
var toolbar = this.options.ownerDocument.createElement('div');
|
1953
|
-
toolbar.id = 'medium-editor-toolbar-' + this.id;
|
1954
|
-
toolbar.className = 'medium-editor-toolbar';
|
1955
|
-
|
1956
|
-
if (this.options.staticToolbar) {
|
1957
|
-
toolbar.className += " static-toolbar";
|
1958
|
-
} else {
|
1959
|
-
toolbar.className += " stalker-toolbar";
|
1960
|
-
}
|
1961
|
-
|
1962
|
-
toolbar.appendChild(this.toolbarButtons());
|
1963
|
-
|
1964
|
-
// Add any forms that extensions may have
|
1965
|
-
this.commands.forEach(function (extension) {
|
1966
|
-
if (extension.hasForm) {
|
1967
|
-
toolbar.appendChild(extension.getForm());
|
1968
|
-
}
|
1969
|
-
});
|
1970
|
-
|
1971
|
-
this.options.elementsContainer.appendChild(toolbar);
|
1972
|
-
return toolbar;
|
1973
|
-
},
|
1974
|
-
|
1975
|
-
//TODO: actionTemplate
|
1976
|
-
toolbarButtons: function () {
|
1977
|
-
var ul = this.options.ownerDocument.createElement('ul'),
|
1978
|
-
li,
|
1979
|
-
btn;
|
1980
|
-
|
1981
|
-
ul.id = 'medium-editor-toolbar-actions' + this.id;
|
1982
|
-
ul.className = 'medium-editor-toolbar-actions clearfix';
|
1983
|
-
|
1984
|
-
this.commands.forEach(function (extension) {
|
1985
|
-
if (typeof extension.getButton === 'function') {
|
1986
|
-
btn = extension.getButton(this);
|
1987
|
-
li = this.options.ownerDocument.createElement('li');
|
1988
|
-
if (Util.isElement(btn)) {
|
1989
|
-
li.appendChild(btn);
|
1990
|
-
} else {
|
1991
|
-
li.innerHTML = btn;
|
1992
|
-
}
|
1993
|
-
ul.appendChild(li);
|
1994
|
-
}
|
1995
|
-
}.bind(this));
|
1996
|
-
|
1997
|
-
return ul;
|
1998
|
-
},
|
1999
|
-
|
2000
|
-
bindSelect: function () {
|
2001
|
-
var i,
|
2002
|
-
blurHelper = function (event) {
|
2003
|
-
// Do not close the toolbar when bluring the editable area and clicking into the anchor form
|
2004
|
-
if (event &&
|
2005
|
-
event.type &&
|
2006
|
-
event.type.toLowerCase() === 'blur' &&
|
2007
|
-
event.relatedTarget &&
|
2008
|
-
Util.isDescendant(this.toolbar, event.relatedTarget)) {
|
2009
|
-
return false;
|
2010
|
-
}
|
2011
|
-
this.checkSelection();
|
2012
|
-
}.bind(this),
|
2013
|
-
timeoutHelper = function () {
|
2014
|
-
setTimeout(function () {
|
2015
|
-
this.checkSelection();
|
2016
|
-
}.bind(this), 0);
|
2017
|
-
}.bind(this);
|
2018
|
-
|
2019
|
-
this.on(this.options.ownerDocument.documentElement, 'mouseup', this.checkSelection.bind(this));
|
2020
|
-
|
2021
|
-
for (i = 0; i < this.elements.length; i += 1) {
|
2022
|
-
this.on(this.elements[i], 'keyup', this.checkSelection.bind(this));
|
2023
|
-
this.on(this.elements[i], 'blur', blurHelper);
|
2024
|
-
this.on(this.elements[i], 'click', timeoutHelper);
|
2025
|
-
}
|
2026
|
-
|
2027
|
-
return this;
|
2028
|
-
},
|
2029
|
-
|
2030
|
-
bindDragDrop: function () {
|
2031
|
-
var self = this, i, className, onDrag, onDrop, element;
|
2032
|
-
|
2033
|
-
if (!self.options.imageDragging) {
|
2034
|
-
return this;
|
2035
|
-
}
|
2036
|
-
|
2037
|
-
className = 'medium-editor-dragover';
|
2038
|
-
|
2039
|
-
onDrag = function (e) {
|
2040
|
-
e.preventDefault();
|
2041
|
-
e.dataTransfer.dropEffect = "copy";
|
2042
|
-
|
2043
|
-
if (e.type === "dragover") {
|
2044
|
-
this.classList.add(className);
|
2045
|
-
} else {
|
2046
|
-
this.classList.remove(className);
|
2047
|
-
}
|
2048
|
-
};
|
2049
|
-
|
2050
|
-
onDrop = function (e) {
|
2051
|
-
var files;
|
2052
|
-
e.preventDefault();
|
2053
|
-
e.stopPropagation();
|
2054
|
-
files = Array.prototype.slice.call(e.dataTransfer.files, 0);
|
2055
|
-
files.some(function (file) {
|
2056
|
-
if (file.type.match("image")) {
|
2057
|
-
var fileReader, id;
|
2058
|
-
fileReader = new FileReader();
|
2059
|
-
fileReader.readAsDataURL(file);
|
2060
|
-
|
2061
|
-
id = 'medium-img-' + (+new Date());
|
2062
|
-
Util.insertHTMLCommand(self.options.ownerDocument, '<img class="medium-image-loading" id="' + id + '" />');
|
2063
|
-
|
2064
|
-
fileReader.onload = function () {
|
2065
|
-
var img = document.getElementById(id);
|
2066
|
-
if (img) {
|
2067
|
-
img.removeAttribute('id');
|
2068
|
-
img.removeAttribute('class');
|
2069
|
-
img.src = fileReader.result;
|
2070
|
-
}
|
2071
|
-
};
|
2072
|
-
}
|
2073
|
-
});
|
2074
|
-
this.classList.remove(className);
|
2075
|
-
};
|
2076
|
-
|
2077
|
-
for (i = 0; i < this.elements.length; i += 1) {
|
2078
|
-
element = this.elements[i];
|
2079
|
-
|
2080
|
-
|
2081
|
-
this.on(element, 'dragover', onDrag);
|
2082
|
-
this.on(element, 'dragleave', onDrag);
|
2083
|
-
this.on(element, 'drop', onDrop);
|
2084
|
-
}
|
2085
|
-
return this;
|
2086
|
-
},
|
2087
|
-
|
2088
|
-
stopSelectionUpdates: function () {
|
2089
|
-
this.preventSelectionUpdates = true;
|
2090
|
-
},
|
2091
|
-
|
2092
|
-
startSelectionUpdates: function () {
|
2093
|
-
this.preventSelectionUpdates = false;
|
2094
|
-
},
|
2095
|
-
|
2096
|
-
checkSelection: function () {
|
2097
|
-
var newSelection,
|
2098
|
-
selectionElement;
|
2099
|
-
|
2100
|
-
if (!this.preventSelectionUpdates &&
|
2101
|
-
this.keepToolbarAlive !== true &&
|
2102
|
-
!this.options.disableToolbar) {
|
2103
|
-
|
2104
|
-
newSelection = this.options.contentWindow.getSelection();
|
2105
|
-
if ((!this.options.updateOnEmptySelection && newSelection.toString().trim() === '') ||
|
2106
|
-
(this.options.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected()) ||
|
2107
|
-
Selection.selectionInContentEditableFalse(this.options.contentWindow)) {
|
2108
|
-
if (!this.options.staticToolbar) {
|
2109
|
-
this.hideToolbarActions();
|
2110
|
-
} else {
|
2111
|
-
this.showAndUpdateToolbar();
|
2112
|
-
}
|
2113
|
-
|
2114
|
-
} else {
|
2115
|
-
selectionElement = Selection.getSelectionElement(this.options.contentWindow);
|
2116
|
-
if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) {
|
2117
|
-
if (!this.options.staticToolbar) {
|
2118
|
-
this.hideToolbarActions();
|
2119
|
-
}
|
2120
|
-
} else {
|
2121
|
-
this.checkSelectionElement(newSelection, selectionElement);
|
2122
|
-
}
|
2123
|
-
}
|
2124
|
-
}
|
2125
|
-
return this;
|
2126
|
-
},
|
2127
|
-
|
2128
|
-
// Checks for existance of multiple block elements in the current selection
|
2129
|
-
multipleBlockElementsSelected: function () {
|
2130
|
-
/*jslint regexp: true*/
|
2131
|
-
var selectionHtml = Selection.getSelectionHtml.call(this).replace(/<[\S]+><\/[\S]+>/gim, ''),
|
2132
|
-
hasMultiParagraphs = selectionHtml.match(/<(p|h[1-6]|blockquote)[^>]*>/g);
|
2133
|
-
/*jslint regexp: false*/
|
2134
|
-
|
2135
|
-
return !!hasMultiParagraphs && hasMultiParagraphs.length > 1;
|
2136
|
-
},
|
2137
|
-
|
2138
|
-
checkSelectionElement: function (newSelection, selectionElement) {
|
2139
|
-
var i,
|
2140
|
-
adjacentNode,
|
2141
|
-
offset = 0,
|
2142
|
-
newRange;
|
2143
|
-
this.selection = newSelection;
|
2144
|
-
this.selectionRange = this.selection.getRangeAt(0);
|
2145
|
-
|
2146
|
-
/*
|
2147
|
-
* In firefox, there are cases (ie doubleclick of a word) where the selectionRange start
|
2148
|
-
* will be at the very end of an element. In other browsers, the selectionRange start
|
2149
|
-
* would instead be at the very beginning of an element that actually has content.
|
2150
|
-
* example:
|
2151
|
-
* <span>foo</span><span>bar</span>
|
2152
|
-
*
|
2153
|
-
* If the text 'bar' is selected, most browsers will have the selectionRange start at the beginning
|
2154
|
-
* of the 'bar' span. However, there are cases where firefox will have the selectionRange start
|
2155
|
-
* at the end of the 'foo' span. The contenteditable behavior will be ok, but if there are any
|
2156
|
-
* properties on the 'bar' span, they won't be reflected accurately in the toolbar
|
2157
|
-
* (ie 'Bold' button wouldn't be active)
|
2158
|
-
*
|
2159
|
-
* So, for cases where the selectionRange start is at the end of an element/node, find the next
|
2160
|
-
* adjacent text node that actually has content in it, and move the selectionRange start there.
|
2161
|
-
*/
|
2162
|
-
if (this.options.standardizeSelectionStart &&
|
2163
|
-
this.selectionRange.startContainer.nodeValue &&
|
2164
|
-
(this.selectionRange.startOffset === this.selectionRange.startContainer.nodeValue.length)) {
|
2165
|
-
adjacentNode = Util.findAdjacentTextNodeWithContent(Selection.getSelectionElement(this.options.contentWindow), this.selectionRange.startContainer, this.options.ownerDocument);
|
2166
|
-
if (adjacentNode) {
|
2167
|
-
offset = 0;
|
2168
|
-
while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) {
|
2169
|
-
offset = offset + 1;
|
2170
|
-
}
|
2171
|
-
newRange = this.options.ownerDocument.createRange();
|
2172
|
-
newRange.setStart(adjacentNode, offset);
|
2173
|
-
newRange.setEnd(this.selectionRange.endContainer, this.selectionRange.endOffset);
|
2174
|
-
this.selection.removeAllRanges();
|
2175
|
-
this.selection.addRange(newRange);
|
2176
|
-
this.selectionRange = newRange;
|
2177
|
-
}
|
2178
|
-
}
|
2179
|
-
|
2180
|
-
for (i = 0; i < this.elements.length; i += 1) {
|
2181
|
-
if (this.elements[i] === selectionElement) {
|
2182
|
-
this.showAndUpdateToolbar();
|
2183
|
-
return;
|
2184
|
-
}
|
2185
|
-
}
|
2186
|
-
|
2187
|
-
if (!this.options.staticToolbar) {
|
2188
|
-
this.hideToolbarActions();
|
2189
|
-
}
|
2190
|
-
},
|
2191
|
-
|
2192
|
-
showAndUpdateToolbar: function () {
|
2193
|
-
this.setToolbarButtonStates()
|
2194
|
-
.setToolbarPosition()
|
2195
|
-
.showToolbarDefaultActions();
|
2196
|
-
},
|
2197
|
-
|
2198
|
-
setToolbarPosition: function () {
|
2199
|
-
// document.documentElement for IE 9
|
2200
|
-
var scrollTop = (this.options.ownerDocument.documentElement && this.options.ownerDocument.documentElement.scrollTop) || this.options.ownerDocument.body.scrollTop,
|
2201
|
-
selection = this.options.contentWindow.getSelection(),
|
2202
|
-
windowWidth = this.options.contentWindow.innerWidth,
|
2203
|
-
container = Selection.getSelectionElement(this.options.contentWindow),
|
2204
|
-
buttonHeight = 50,
|
2205
|
-
toolbarWidth,
|
2206
|
-
toolbarHeight,
|
2207
|
-
halfOffsetWidth,
|
2208
|
-
defaultLeft,
|
2209
|
-
containerRect,
|
2210
|
-
containerTop,
|
2211
|
-
containerCenter,
|
2212
|
-
range,
|
2213
|
-
boundary,
|
2214
|
-
middleBoundary,
|
2215
|
-
targetLeft;
|
2216
|
-
|
2217
|
-
// If there isn't a valid selection, bail
|
2218
|
-
if (!container || !this.options.contentWindow.getSelection().focusNode) {
|
2219
|
-
return this;
|
2220
|
-
}
|
2221
|
-
|
2222
|
-
// If the container isn't part of this medium-editor instance, bail
|
2223
|
-
if (this.elements.indexOf(container) === -1) {
|
2224
|
-
return this;
|
2225
|
-
}
|
2226
|
-
|
2227
|
-
// Calculate container dimensions
|
2228
|
-
containerRect = container.getBoundingClientRect();
|
2229
|
-
containerTop = containerRect.top + scrollTop;
|
2230
|
-
containerCenter = (containerRect.left + (containerRect.width / 2));
|
2231
|
-
|
2232
|
-
// position the toolbar at left 0, so we can get the real width of the toolbar
|
2233
|
-
this.toolbar.style.left = '0';
|
2234
|
-
toolbarWidth = this.toolbar.offsetWidth;
|
2235
|
-
toolbarHeight = this.toolbar.offsetHeight;
|
2236
|
-
halfOffsetWidth = toolbarWidth / 2;
|
2237
|
-
defaultLeft = this.options.diffLeft - halfOffsetWidth;
|
2238
|
-
|
2239
|
-
if (this.options.staticToolbar) {
|
2240
|
-
this.showToolbar();
|
2241
|
-
|
2242
|
-
if (this.options.stickyToolbar) {
|
2243
|
-
// If it's beyond the height of the editor, position it at the bottom of the editor
|
2244
|
-
if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight)) {
|
2245
|
-
this.toolbar.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px';
|
2246
|
-
this.toolbar.classList.remove('sticky-toolbar');
|
2247
|
-
|
2248
|
-
// Stick the toolbar to the top of the window
|
2249
|
-
} else if (scrollTop > (containerTop - toolbarHeight)) {
|
2250
|
-
this.toolbar.classList.add('sticky-toolbar');
|
2251
|
-
this.toolbar.style.top = "0px";
|
2252
|
-
|
2253
|
-
// Normal static toolbar position
|
2254
|
-
} else {
|
2255
|
-
this.toolbar.classList.remove('sticky-toolbar');
|
2256
|
-
this.toolbar.style.top = containerTop - toolbarHeight + "px";
|
2257
|
-
}
|
2258
|
-
} else {
|
2259
|
-
this.toolbar.style.top = containerTop - toolbarHeight + "px";
|
2260
|
-
}
|
2261
|
-
|
2262
|
-
if (this.options.toolbarAlign === 'left') {
|
2263
|
-
targetLeft = containerRect.left;
|
2264
|
-
} else if (this.options.toolbarAlign === 'center') {
|
2265
|
-
targetLeft = containerCenter - halfOffsetWidth;
|
2266
|
-
} else if (this.options.toolbarAlign === 'right') {
|
2267
|
-
targetLeft = containerRect.right - toolbarWidth;
|
2268
|
-
}
|
2269
|
-
|
2270
|
-
if (targetLeft < 0) {
|
2271
|
-
targetLeft = 0;
|
2272
|
-
} else if ((targetLeft + toolbarWidth) > windowWidth) {
|
2273
|
-
targetLeft = windowWidth - toolbarWidth;
|
2274
|
-
}
|
2275
|
-
|
2276
|
-
this.toolbar.style.left = targetLeft + 'px';
|
2277
|
-
|
2278
|
-
} else if (!selection.isCollapsed) {
|
2279
|
-
this.showToolbar();
|
2280
|
-
|
2281
|
-
range = selection.getRangeAt(0);
|
2282
|
-
boundary = range.getBoundingClientRect();
|
2283
|
-
middleBoundary = (boundary.left + boundary.right) / 2;
|
2284
|
-
|
2285
|
-
if (boundary.top < buttonHeight) {
|
2286
|
-
this.toolbar.classList.add('medium-toolbar-arrow-over');
|
2287
|
-
this.toolbar.classList.remove('medium-toolbar-arrow-under');
|
2288
|
-
this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
|
2289
|
-
} else {
|
2290
|
-
this.toolbar.classList.add('medium-toolbar-arrow-under');
|
2291
|
-
this.toolbar.classList.remove('medium-toolbar-arrow-over');
|
2292
|
-
this.toolbar.style.top = boundary.top + this.options.diffTop + this.options.contentWindow.pageYOffset - toolbarHeight + 'px';
|
2293
|
-
}
|
2294
|
-
if (middleBoundary < halfOffsetWidth) {
|
2295
|
-
this.toolbar.style.left = defaultLeft + halfOffsetWidth + 'px';
|
2296
|
-
} else if ((windowWidth - middleBoundary) < halfOffsetWidth) {
|
2297
|
-
this.toolbar.style.left = windowWidth + defaultLeft - halfOffsetWidth + 'px';
|
2298
|
-
} else {
|
2299
|
-
this.toolbar.style.left = defaultLeft + middleBoundary + 'px';
|
2300
|
-
}
|
2301
|
-
}
|
2918
|
+
sel.removeAllRanges();
|
2919
|
+
sel.addRange(range);
|
2302
2920
|
|
2303
|
-
|
2921
|
+
node.previousElementSibling.parentNode.removeChild(node);
|
2304
2922
|
|
2305
|
-
|
2923
|
+
e.preventDefault();
|
2924
|
+
}
|
2306
2925
|
},
|
2307
2926
|
|
2308
|
-
|
2309
|
-
this.
|
2310
|
-
|
2311
|
-
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2927
|
+
initToolbar: function () {
|
2928
|
+
if (this.toolbar) {
|
2929
|
+
return this;
|
2930
|
+
}
|
2931
|
+
this.toolbar = new Toolbar(this);
|
2932
|
+
this.options.elementsContainer.appendChild(this.toolbar.getToolbarElement());
|
2933
|
+
|
2315
2934
|
return this;
|
2316
2935
|
},
|
2317
2936
|
|
2318
|
-
|
2319
|
-
var
|
2320
|
-
manualStateChecks = [],
|
2321
|
-
queryState = null,
|
2322
|
-
parentNode,
|
2323
|
-
checkExtension = function (extension) {
|
2324
|
-
if (typeof extension.checkState === 'function') {
|
2325
|
-
extension.checkState(parentNode);
|
2326
|
-
} else if (typeof extension.isActive === 'function' &&
|
2327
|
-
typeof extension.isAlreadyApplied === 'function') {
|
2328
|
-
if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) {
|
2329
|
-
extension.setActive();
|
2330
|
-
}
|
2331
|
-
}
|
2332
|
-
};
|
2937
|
+
bindDragDrop: function () {
|
2938
|
+
var self = this, i, className, onDrag, onDrop, element;
|
2333
2939
|
|
2334
|
-
if (!
|
2335
|
-
return;
|
2940
|
+
if (!self.options.imageDragging) {
|
2941
|
+
return this;
|
2336
2942
|
}
|
2337
|
-
parentNode = Selection.getSelectedParentElement(this.selectionRange);
|
2338
2943
|
|
2339
|
-
|
2340
|
-
|
2341
|
-
|
2342
|
-
|
2343
|
-
|
2344
|
-
|
2345
|
-
|
2346
|
-
|
2347
|
-
|
2348
|
-
|
2944
|
+
className = 'medium-editor-dragover';
|
2945
|
+
|
2946
|
+
onDrag = function (e) {
|
2947
|
+
e.preventDefault();
|
2948
|
+
e.dataTransfer.dropEffect = "copy";
|
2949
|
+
|
2950
|
+
if (e.type === "dragover") {
|
2951
|
+
this.classList.add(className);
|
2952
|
+
} else {
|
2953
|
+
this.classList.remove(className);
|
2954
|
+
}
|
2955
|
+
};
|
2956
|
+
|
2957
|
+
onDrop = function (e) {
|
2958
|
+
var files;
|
2959
|
+
e.preventDefault();
|
2960
|
+
e.stopPropagation();
|
2961
|
+
// IE9 does not support the File API, so prevent file from opening in a new window
|
2962
|
+
// but also don't try to actually get the file
|
2963
|
+
if (e.dataTransfer.files) {
|
2964
|
+
files = Array.prototype.slice.call(e.dataTransfer.files, 0);
|
2965
|
+
files.some(function (file) {
|
2966
|
+
if (file.type.match("image")) {
|
2967
|
+
var fileReader, id;
|
2968
|
+
fileReader = new FileReader();
|
2969
|
+
fileReader.readAsDataURL(file);
|
2970
|
+
|
2971
|
+
id = 'medium-img-' + (+new Date());
|
2972
|
+
Util.insertHTMLCommand(self.options.ownerDocument, '<img class="medium-image-loading" id="' + id + '" />');
|
2973
|
+
|
2974
|
+
fileReader.onload = function () {
|
2975
|
+
var img = document.getElementById(id);
|
2976
|
+
if (img) {
|
2977
|
+
img.removeAttribute('id');
|
2978
|
+
img.removeAttribute('class');
|
2979
|
+
img.src = fileReader.result;
|
2980
|
+
}
|
2981
|
+
};
|
2349
2982
|
}
|
2350
|
-
|
2351
|
-
}
|
2983
|
+
});
|
2352
2984
|
}
|
2353
|
-
|
2354
|
-
|
2355
|
-
});
|
2985
|
+
this.classList.remove(className);
|
2986
|
+
};
|
2356
2987
|
|
2357
|
-
|
2358
|
-
|
2359
|
-
manualStateChecks.forEach(checkExtension.bind(this));
|
2988
|
+
for (i = 0; i < this.elements.length; i += 1) {
|
2989
|
+
element = this.elements[i];
|
2360
2990
|
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
}
|
2365
|
-
parentNode = parentNode.parentNode;
|
2991
|
+
this.on(element, 'dragover', onDrag);
|
2992
|
+
this.on(element, 'dragleave', onDrag);
|
2993
|
+
this.on(element, 'drop', onDrop);
|
2366
2994
|
}
|
2995
|
+
return this;
|
2367
2996
|
},
|
2368
2997
|
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
|
2998
|
+
stopSelectionUpdates: function () {
|
2999
|
+
this.preventSelectionUpdates = true;
|
3000
|
+
},
|
3001
|
+
|
3002
|
+
startSelectionUpdates: function () {
|
3003
|
+
this.preventSelectionUpdates = false;
|
3004
|
+
},
|
3005
|
+
|
3006
|
+
checkSelection: function () {
|
3007
|
+
if (this.toolbar) {
|
3008
|
+
this.toolbar.checkState();
|
2374
3009
|
}
|
2375
3010
|
return this;
|
2376
3011
|
},
|
@@ -2447,8 +3082,11 @@ function MediumEditor(elements, options) {
|
|
2447
3082
|
return this.options.ownerDocument.execCommand(action, false, null);
|
2448
3083
|
},
|
2449
3084
|
|
2450
|
-
getSelectedParentElement: function () {
|
2451
|
-
|
3085
|
+
getSelectedParentElement: function (range) {
|
3086
|
+
if (range === undefined) {
|
3087
|
+
range = this.options.contentWindow.getSelection().getRangeAt(0);
|
3088
|
+
}
|
3089
|
+
return Selection.getSelectedParentElement(range);
|
2452
3090
|
},
|
2453
3091
|
|
2454
3092
|
execFormatBlock: function (el) {
|
@@ -2476,79 +3114,19 @@ function MediumEditor(elements, options) {
|
|
2476
3114
|
return this.options.ownerDocument.execCommand('formatBlock', false, el);
|
2477
3115
|
},
|
2478
3116
|
|
2479
|
-
isToolbarDefaultActionsShown: function () {
|
2480
|
-
return !!this.toolbarActions && this.toolbarActions.style.display === 'block';
|
2481
|
-
},
|
2482
|
-
|
2483
3117
|
hideToolbarDefaultActions: function () {
|
2484
|
-
if (this.
|
2485
|
-
this.
|
2486
|
-
if (extension.onHide && typeof extension.onHide === 'function') {
|
2487
|
-
extension.onHide();
|
2488
|
-
}
|
2489
|
-
});
|
2490
|
-
this.toolbarActions.style.display = 'none';
|
2491
|
-
}
|
2492
|
-
},
|
2493
|
-
|
2494
|
-
showToolbarDefaultActions: function () {
|
2495
|
-
this.hideExtensionForms();
|
2496
|
-
|
2497
|
-
if (this.toolbarActions && !this.isToolbarDefaultActionsShown()) {
|
2498
|
-
this.toolbarActions.style.display = 'block';
|
3118
|
+
if (this.toolbar) {
|
3119
|
+
this.toolbar.hideToolbarDefaultActions();
|
2499
3120
|
}
|
2500
|
-
|
2501
|
-
this.keepToolbarAlive = false;
|
2502
|
-
// Using setTimeout + options.delay because:
|
2503
|
-
// We will actually be displaying the toolbar, which should be controlled by options.delay
|
2504
|
-
this.delay(function () {
|
2505
|
-
this.showToolbar();
|
2506
|
-
}.bind(this));
|
2507
|
-
|
2508
3121
|
return this;
|
2509
3122
|
},
|
2510
3123
|
|
2511
|
-
|
2512
|
-
|
2513
|
-
|
2514
|
-
if (extension.hasForm && extension.isDisplayed()) {
|
2515
|
-
extension.hideForm();
|
2516
|
-
}
|
2517
|
-
});
|
2518
|
-
},
|
2519
|
-
|
2520
|
-
isToolbarShown: function () {
|
2521
|
-
return this.toolbar && this.toolbar.classList.contains('medium-editor-toolbar-active');
|
2522
|
-
},
|
2523
|
-
|
2524
|
-
showToolbar: function () {
|
2525
|
-
if (this.toolbar && !this.isToolbarShown()) {
|
2526
|
-
this.toolbar.classList.add('medium-editor-toolbar-active');
|
2527
|
-
if (typeof this.options.onShowToolbar === 'function') {
|
2528
|
-
this.options.onShowToolbar();
|
2529
|
-
}
|
2530
|
-
}
|
2531
|
-
},
|
2532
|
-
|
2533
|
-
hideToolbar: function () {
|
2534
|
-
if (this.isToolbarShown()) {
|
2535
|
-
this.toolbar.classList.remove('medium-editor-toolbar-active');
|
2536
|
-
if (typeof this.options.onHideToolbar === 'function') {
|
2537
|
-
this.options.onHideToolbar();
|
2538
|
-
}
|
3124
|
+
setToolbarPosition: function () {
|
3125
|
+
if (this.toolbar) {
|
3126
|
+
this.toolbar.setToolbarPosition();
|
2539
3127
|
}
|
2540
3128
|
},
|
2541
3129
|
|
2542
|
-
hideToolbarActions: function () {
|
2543
|
-
this.commands.forEach(function (extension) {
|
2544
|
-
if (extension.onHide && typeof extension.onHide === 'function') {
|
2545
|
-
extension.onHide();
|
2546
|
-
}
|
2547
|
-
});
|
2548
|
-
this.keepToolbarAlive = false;
|
2549
|
-
this.hideToolbar();
|
2550
|
-
},
|
2551
|
-
|
2552
3130
|
selectAllContents: function () {
|
2553
3131
|
var range = this.options.ownerDocument.createRange(),
|
2554
3132
|
sel = this.options.contentWindow.getSelection(),
|
@@ -2656,181 +3234,6 @@ function MediumEditor(elements, options) {
|
|
2656
3234
|
sel.addRange(range);
|
2657
3235
|
},
|
2658
3236
|
|
2659
|
-
hideAnchorPreview: function () {
|
2660
|
-
this.anchorPreview.classList.remove('medium-editor-anchor-preview-active');
|
2661
|
-
},
|
2662
|
-
|
2663
|
-
// TODO: break method
|
2664
|
-
showAnchorPreview: function (anchorEl) {
|
2665
|
-
if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')
|
2666
|
-
|| anchorEl.getAttribute('data-disable-preview')) {
|
2667
|
-
return true;
|
2668
|
-
}
|
2669
|
-
|
2670
|
-
var self = this,
|
2671
|
-
buttonHeight = 40,
|
2672
|
-
boundary = anchorEl.getBoundingClientRect(),
|
2673
|
-
middleBoundary = (boundary.left + boundary.right) / 2,
|
2674
|
-
halfOffsetWidth,
|
2675
|
-
defaultLeft;
|
2676
|
-
|
2677
|
-
self.anchorPreview.querySelector('i').textContent = anchorEl.attributes.href.value;
|
2678
|
-
halfOffsetWidth = self.anchorPreview.offsetWidth / 2;
|
2679
|
-
defaultLeft = self.options.diffLeft - halfOffsetWidth;
|
2680
|
-
|
2681
|
-
self.observeAnchorPreview(anchorEl);
|
2682
|
-
|
2683
|
-
self.anchorPreview.classList.add('medium-toolbar-arrow-over');
|
2684
|
-
self.anchorPreview.classList.remove('medium-toolbar-arrow-under');
|
2685
|
-
self.anchorPreview.style.top = Math.round(buttonHeight + boundary.bottom - self.options.diffTop + this.options.contentWindow.pageYOffset - self.anchorPreview.offsetHeight) + 'px';
|
2686
|
-
if (middleBoundary < halfOffsetWidth) {
|
2687
|
-
self.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px';
|
2688
|
-
} else if ((this.options.contentWindow.innerWidth - middleBoundary) < halfOffsetWidth) {
|
2689
|
-
self.anchorPreview.style.left = this.options.contentWindow.innerWidth + defaultLeft - halfOffsetWidth + 'px';
|
2690
|
-
} else {
|
2691
|
-
self.anchorPreview.style.left = defaultLeft + middleBoundary + 'px';
|
2692
|
-
}
|
2693
|
-
|
2694
|
-
if (this.anchorPreview && !this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) {
|
2695
|
-
this.anchorPreview.classList.add('medium-editor-anchor-preview-active');
|
2696
|
-
}
|
2697
|
-
|
2698
|
-
return this;
|
2699
|
-
},
|
2700
|
-
|
2701
|
-
// TODO: break method
|
2702
|
-
observeAnchorPreview: function (anchorEl) {
|
2703
|
-
var self = this,
|
2704
|
-
lastOver = (new Date()).getTime(),
|
2705
|
-
over = true,
|
2706
|
-
stamp = function () {
|
2707
|
-
lastOver = (new Date()).getTime();
|
2708
|
-
over = true;
|
2709
|
-
},
|
2710
|
-
unstamp = function (e) {
|
2711
|
-
if (!e.relatedTarget || !/anchor-preview/.test(e.relatedTarget.className)) {
|
2712
|
-
over = false;
|
2713
|
-
}
|
2714
|
-
},
|
2715
|
-
interval_timer = setInterval(function () {
|
2716
|
-
if (over) {
|
2717
|
-
return true;
|
2718
|
-
}
|
2719
|
-
var durr = (new Date()).getTime() - lastOver;
|
2720
|
-
if (durr > self.options.anchorPreviewHideDelay) {
|
2721
|
-
// hide the preview 1/2 second after mouse leaves the link
|
2722
|
-
self.hideAnchorPreview();
|
2723
|
-
|
2724
|
-
// cleanup
|
2725
|
-
clearInterval(interval_timer);
|
2726
|
-
self.off(self.anchorPreview, 'mouseover', stamp);
|
2727
|
-
self.off(self.anchorPreview, 'mouseout', unstamp);
|
2728
|
-
self.off(anchorEl, 'mouseover', stamp);
|
2729
|
-
self.off(anchorEl, 'mouseout', unstamp);
|
2730
|
-
|
2731
|
-
}
|
2732
|
-
}, 200);
|
2733
|
-
|
2734
|
-
this.on(self.anchorPreview, 'mouseover', stamp);
|
2735
|
-
this.on(self.anchorPreview, 'mouseout', unstamp);
|
2736
|
-
this.on(anchorEl, 'mouseover', stamp);
|
2737
|
-
this.on(anchorEl, 'mouseout', unstamp);
|
2738
|
-
},
|
2739
|
-
|
2740
|
-
createAnchorPreview: function () {
|
2741
|
-
var self = this,
|
2742
|
-
anchorPreview = this.options.ownerDocument.createElement('div');
|
2743
|
-
|
2744
|
-
anchorPreview.id = 'medium-editor-anchor-preview-' + this.id;
|
2745
|
-
anchorPreview.className = 'medium-editor-anchor-preview';
|
2746
|
-
anchorPreview.innerHTML = this.anchorPreviewTemplate();
|
2747
|
-
this.options.elementsContainer.appendChild(anchorPreview);
|
2748
|
-
|
2749
|
-
this.on(anchorPreview, 'click', function () {
|
2750
|
-
self.anchorPreviewClickHandler();
|
2751
|
-
});
|
2752
|
-
|
2753
|
-
return anchorPreview;
|
2754
|
-
},
|
2755
|
-
|
2756
|
-
anchorPreviewTemplate: function () {
|
2757
|
-
return '<div class="medium-editor-toolbar-anchor-preview" id="medium-editor-toolbar-anchor-preview">' +
|
2758
|
-
' <i class="medium-editor-toolbar-anchor-preview-inner"></i>' +
|
2759
|
-
'</div>';
|
2760
|
-
},
|
2761
|
-
|
2762
|
-
anchorPreviewClickHandler: function (event) {
|
2763
|
-
var range,
|
2764
|
-
sel,
|
2765
|
-
anchorExtension = this.getExtensionByName('anchor');
|
2766
|
-
|
2767
|
-
if (anchorExtension && this.activeAnchor) {
|
2768
|
-
range = this.options.ownerDocument.createRange();
|
2769
|
-
range.selectNodeContents(this.activeAnchor);
|
2770
|
-
|
2771
|
-
sel = this.options.contentWindow.getSelection();
|
2772
|
-
sel.removeAllRanges();
|
2773
|
-
sel.addRange(range);
|
2774
|
-
// Using setTimeout + options.delay because:
|
2775
|
-
// We may actually be displaying the anchor form, which should be controlled by options.delay
|
2776
|
-
this.delay(function () {
|
2777
|
-
if (this.activeAnchor) {
|
2778
|
-
anchorExtension.showForm(this.activeAnchor.attributes.href.value);
|
2779
|
-
}
|
2780
|
-
this.keepToolbarAlive = false;
|
2781
|
-
}.bind(this));
|
2782
|
-
}
|
2783
|
-
|
2784
|
-
this.hideAnchorPreview();
|
2785
|
-
},
|
2786
|
-
|
2787
|
-
editorAnchorObserver: function (e) {
|
2788
|
-
var self = this,
|
2789
|
-
overAnchor = true,
|
2790
|
-
leaveAnchor = function () {
|
2791
|
-
// mark the anchor as no longer hovered, and stop listening
|
2792
|
-
overAnchor = false;
|
2793
|
-
self.off(self.activeAnchor, 'mouseout', leaveAnchor);
|
2794
|
-
};
|
2795
|
-
|
2796
|
-
if (e.target && e.target.tagName.toLowerCase() === 'a') {
|
2797
|
-
|
2798
|
-
// Detect empty href attributes
|
2799
|
-
// The browser will make href="" or href="#top"
|
2800
|
-
// into absolute urls when accessed as e.targed.href, so check the html
|
2801
|
-
if (!/href=["']\S+["']/.test(e.target.outerHTML) || /href=["']#\S+["']/.test(e.target.outerHTML)) {
|
2802
|
-
return true;
|
2803
|
-
}
|
2804
|
-
|
2805
|
-
// only show when hovering on anchors
|
2806
|
-
if (this.isToolbarShown()) {
|
2807
|
-
// only show when toolbar is not present
|
2808
|
-
return true;
|
2809
|
-
}
|
2810
|
-
this.activeAnchor = e.target;
|
2811
|
-
this.on(this.activeAnchor, 'mouseout', leaveAnchor);
|
2812
|
-
// Using setTimeout + options.delay because:
|
2813
|
-
// - We're going to show the anchor preview according to the configured delay
|
2814
|
-
// if the mouse has not left the anchor tag in that time
|
2815
|
-
this.delay(function () {
|
2816
|
-
if (overAnchor) {
|
2817
|
-
self.showAnchorPreview(e.target);
|
2818
|
-
}
|
2819
|
-
});
|
2820
|
-
}
|
2821
|
-
},
|
2822
|
-
|
2823
|
-
bindAnchorPreview: function (index) {
|
2824
|
-
var i, self = this;
|
2825
|
-
this.editorAnchorObserverWrapper = function (e) {
|
2826
|
-
self.editorAnchorObserver(e);
|
2827
|
-
};
|
2828
|
-
for (i = 0; i < this.elements.length; i += 1) {
|
2829
|
-
this.on(this.elements[i], 'mouseover', this.editorAnchorObserverWrapper);
|
2830
|
-
}
|
2831
|
-
return this;
|
2832
|
-
},
|
2833
|
-
|
2834
3237
|
createLink: function (opts) {
|
2835
3238
|
var customEvent,
|
2836
3239
|
i;
|
@@ -2875,32 +3278,6 @@ function MediumEditor(elements, options) {
|
|
2875
3278
|
}
|
2876
3279
|
},
|
2877
3280
|
|
2878
|
-
positionToolbarIfShown: function () {
|
2879
|
-
if (this.isToolbarShown()) {
|
2880
|
-
this.setToolbarPosition();
|
2881
|
-
}
|
2882
|
-
},
|
2883
|
-
|
2884
|
-
bindWindowActions: function () {
|
2885
|
-
var self = this;
|
2886
|
-
|
2887
|
-
// Add a scroll event for sticky toolbar
|
2888
|
-
if (this.options.staticToolbar && this.options.stickyToolbar) {
|
2889
|
-
// On scroll, re-position the toolbar
|
2890
|
-
this.on(this.options.contentWindow, 'scroll', function () {
|
2891
|
-
self.positionToolbarIfShown();
|
2892
|
-
}, true);
|
2893
|
-
}
|
2894
|
-
|
2895
|
-
this.on(this.options.contentWindow, 'resize', function () {
|
2896
|
-
self.handleResize();
|
2897
|
-
});
|
2898
|
-
|
2899
|
-
this.bindBlur();
|
2900
|
-
|
2901
|
-
return this;
|
2902
|
-
},
|
2903
|
-
|
2904
3281
|
activate: function () {
|
2905
3282
|
if (this.isActive) {
|
2906
3283
|
return;
|
@@ -2918,10 +3295,8 @@ function MediumEditor(elements, options) {
|
|
2918
3295
|
this.isActive = false;
|
2919
3296
|
|
2920
3297
|
if (this.toolbar !== undefined) {
|
2921
|
-
this.
|
2922
|
-
this.options.elementsContainer.removeChild(this.toolbar);
|
3298
|
+
this.toolbar.deactivate();
|
2923
3299
|
delete this.toolbar;
|
2924
|
-
delete this.anchorPreview;
|
2925
3300
|
}
|
2926
3301
|
|
2927
3302
|
for (i = 0; i < this.elements.length; i += 1) {
|