medium-editor-rails 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/medium-editor-rails/version.rb +2 -2
- data/vendor/assets/javascripts/medium-editor.js +891 -247
- data/vendor/assets/stylesheets/medium-editor/medium-editor.css +20 -9
- data/vendor/assets/stylesheets/medium-editor/themes/flat.css +1 -1
- data/vendor/assets/stylesheets/medium-editor/themes/flat.min.css +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0241fca73542f5f5867edc588778dbcb10ad3f28
|
|
4
|
+
data.tar.gz: 842d1ca2ac98a5c6a45336d1f35816d6017d090a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7a183c756bba1a1ac215348813f707b15e748cf94d8476fd214540dec9fdeefd89e98b8619f866845f1b22dd2368223d0bd1620cf32d758e843d06090f287cd
|
|
7
|
+
data.tar.gz: 4f03baf7dc8dbc0e08f5aa6e65273df20d0122160101901e7c34694913c482517b5599fae11ab9cbdb5f55e76646bdc09df89e3a28ba0ef02e670564a01e9463
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ This gem integrates [Medium Editor](https://github.com/yabwe/medium-editor) with
|
|
|
8
8
|
|
|
9
9
|
## Version
|
|
10
10
|
|
|
11
|
-
The latest version of Medium Editor bundled by this gem is [5.
|
|
11
|
+
The latest version of Medium Editor bundled by this gem is [5.15.0](https://github.com/yabwe/medium-editor/releases)
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
@@ -385,7 +385,8 @@ if (!("classList" in document.createElement("_"))) {
|
|
|
385
385
|
|
|
386
386
|
(function (root, factory) {
|
|
387
387
|
'use strict';
|
|
388
|
-
|
|
388
|
+
var isElectron = typeof module === 'object' && process && process.versions && process.versions.electron;
|
|
389
|
+
if (!isElectron && typeof module === 'object') {
|
|
389
390
|
module.exports = factory;
|
|
390
391
|
} else if (typeof define === 'function' && define.amd) {
|
|
391
392
|
define(function () {
|
|
@@ -454,6 +455,7 @@ MediumEditor.extensions = {};
|
|
|
454
455
|
isMac: (window.navigator.platform.toUpperCase().indexOf('MAC') >= 0),
|
|
455
456
|
|
|
456
457
|
// https://github.com/jashkenas/underscore
|
|
458
|
+
// Lonely letter MUST USE the uppercase code
|
|
457
459
|
keyCode: {
|
|
458
460
|
BACKSPACE: 8,
|
|
459
461
|
TAB: 9,
|
|
@@ -462,7 +464,8 @@ MediumEditor.extensions = {};
|
|
|
462
464
|
SPACE: 32,
|
|
463
465
|
DELETE: 46,
|
|
464
466
|
K: 75, // K keycode, and not k
|
|
465
|
-
M: 77
|
|
467
|
+
M: 77,
|
|
468
|
+
V: 86
|
|
466
469
|
},
|
|
467
470
|
|
|
468
471
|
/**
|
|
@@ -897,8 +900,7 @@ MediumEditor.extensions = {};
|
|
|
897
900
|
range = range.cloneRange();
|
|
898
901
|
range.setStartAfter(lastNode);
|
|
899
902
|
range.collapse(true);
|
|
900
|
-
selection.
|
|
901
|
-
selection.addRange(range);
|
|
903
|
+
MediumEditor.selection.selectRange(doc, range);
|
|
902
904
|
}
|
|
903
905
|
res = true;
|
|
904
906
|
}
|
|
@@ -1428,11 +1430,15 @@ MediumEditor.extensions = {};
|
|
|
1428
1430
|
},
|
|
1429
1431
|
|
|
1430
1432
|
cleanupTags: function (el, tags) {
|
|
1431
|
-
tags.
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1433
|
+
if (tags.indexOf(el.nodeName.toLowerCase()) !== -1) {
|
|
1434
|
+
el.parentNode.removeChild(el);
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
|
|
1438
|
+
unwrapTags: function (el, tags) {
|
|
1439
|
+
if (tags.indexOf(el.nodeName.toLowerCase()) !== -1) {
|
|
1440
|
+
MediumEditor.util.unwrap(el, document);
|
|
1441
|
+
}
|
|
1436
1442
|
},
|
|
1437
1443
|
|
|
1438
1444
|
// get the closest parent
|
|
@@ -1457,6 +1463,17 @@ MediumEditor.extensions = {};
|
|
|
1457
1463
|
} else {
|
|
1458
1464
|
el.parentNode.removeChild(el);
|
|
1459
1465
|
}
|
|
1466
|
+
},
|
|
1467
|
+
|
|
1468
|
+
guid: function () {
|
|
1469
|
+
function _s4() {
|
|
1470
|
+
return Math
|
|
1471
|
+
.floor((1 + Math.random()) * 0x10000)
|
|
1472
|
+
.toString(16)
|
|
1473
|
+
.substring(1);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
return _s4() + _s4() + '-' + _s4() + '-' + _s4() + '-' + _s4() + '-' + _s4() + _s4() + _s4();
|
|
1460
1477
|
}
|
|
1461
1478
|
};
|
|
1462
1479
|
|
|
@@ -1641,6 +1658,19 @@ MediumEditor.extensions = {};
|
|
|
1641
1658
|
*/
|
|
1642
1659
|
setInactive: undefined,
|
|
1643
1660
|
|
|
1661
|
+
/* getInteractionElements: [function ()]
|
|
1662
|
+
*
|
|
1663
|
+
* If the extension renders any elements that the user can interact with,
|
|
1664
|
+
* this method should be implemented and return the root element or an array
|
|
1665
|
+
* containing all of the root elements. MediumEditor will call this function
|
|
1666
|
+
* during interaction to see if the user clicked on something outside of the editor.
|
|
1667
|
+
* The elements are used to check if the target element of a click or
|
|
1668
|
+
* other user event is a descendant of any extension elements.
|
|
1669
|
+
* This way, the editor can also count user interaction within editor elements as
|
|
1670
|
+
* interactions with the editor, and thus not trigger 'blur'
|
|
1671
|
+
*/
|
|
1672
|
+
getInteractionElements: undefined,
|
|
1673
|
+
|
|
1644
1674
|
/************************ Helpers ************************
|
|
1645
1675
|
* The following are helpers that are either set by MediumEditor
|
|
1646
1676
|
* during initialization, or are helper methods which either
|
|
@@ -1938,9 +1968,7 @@ MediumEditor.extensions = {};
|
|
|
1938
1968
|
range = this.importSelectionMoveCursorPastAnchor(selectionState, range);
|
|
1939
1969
|
}
|
|
1940
1970
|
|
|
1941
|
-
|
|
1942
|
-
sel.removeAllRanges();
|
|
1943
|
-
sel.addRange(range);
|
|
1971
|
+
this.selectRange(doc, range);
|
|
1944
1972
|
},
|
|
1945
1973
|
|
|
1946
1974
|
// Utility method called from importSelection only
|
|
@@ -2333,16 +2361,12 @@ MediumEditor.extensions = {};
|
|
|
2333
2361
|
},
|
|
2334
2362
|
|
|
2335
2363
|
selectNode: function (node, doc) {
|
|
2336
|
-
var range = doc.createRange()
|
|
2337
|
-
sel = doc.getSelection();
|
|
2338
|
-
|
|
2364
|
+
var range = doc.createRange();
|
|
2339
2365
|
range.selectNodeContents(node);
|
|
2340
|
-
|
|
2341
|
-
sel.addRange(range);
|
|
2366
|
+
this.selectRange(doc, range);
|
|
2342
2367
|
},
|
|
2343
2368
|
|
|
2344
2369
|
select: function (doc, startNode, startOffset, endNode, endOffset) {
|
|
2345
|
-
doc.getSelection().removeAllRanges();
|
|
2346
2370
|
var range = doc.createRange();
|
|
2347
2371
|
range.setStart(startNode, startOffset);
|
|
2348
2372
|
if (endNode) {
|
|
@@ -2350,7 +2374,7 @@ MediumEditor.extensions = {};
|
|
|
2350
2374
|
} else {
|
|
2351
2375
|
range.collapse(true);
|
|
2352
2376
|
}
|
|
2353
|
-
|
|
2377
|
+
this.selectRange(doc, range);
|
|
2354
2378
|
return range;
|
|
2355
2379
|
},
|
|
2356
2380
|
|
|
@@ -2387,6 +2411,13 @@ MediumEditor.extensions = {};
|
|
|
2387
2411
|
return selection.getRangeAt(0);
|
|
2388
2412
|
},
|
|
2389
2413
|
|
|
2414
|
+
selectRange: function (ownerDocument, range) {
|
|
2415
|
+
var selection = ownerDocument.getSelection();
|
|
2416
|
+
|
|
2417
|
+
selection.removeAllRanges();
|
|
2418
|
+
selection.addRange(range);
|
|
2419
|
+
},
|
|
2420
|
+
|
|
2390
2421
|
// http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi
|
|
2391
2422
|
// by You
|
|
2392
2423
|
getSelectionStart: function (ownerDocument) {
|
|
@@ -2403,6 +2434,26 @@ MediumEditor.extensions = {};
|
|
|
2403
2434
|
(function () {
|
|
2404
2435
|
'use strict';
|
|
2405
2436
|
|
|
2437
|
+
function isElementDescendantOfExtension(extensions, element) {
|
|
2438
|
+
return extensions.some(function (extension) {
|
|
2439
|
+
if (typeof extension.getInteractionElements !== 'function') {
|
|
2440
|
+
return false;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
var extensionElements = extension.getInteractionElements();
|
|
2444
|
+
if (!extensionElements) {
|
|
2445
|
+
return false;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
if (!Array.isArray(extensionElements)) {
|
|
2449
|
+
extensionElements = [extensionElements];
|
|
2450
|
+
}
|
|
2451
|
+
return extensionElements.some(function (el) {
|
|
2452
|
+
return MediumEditor.util.isDescendant(el, element, true);
|
|
2453
|
+
});
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2406
2457
|
var Events = function (instance) {
|
|
2407
2458
|
this.base = instance;
|
|
2408
2459
|
this.options = this.base.options;
|
|
@@ -2417,18 +2468,32 @@ MediumEditor.extensions = {};
|
|
|
2417
2468
|
|
|
2418
2469
|
// Helpers for event handling
|
|
2419
2470
|
|
|
2420
|
-
attachDOMEvent: function (
|
|
2421
|
-
|
|
2422
|
-
|
|
2471
|
+
attachDOMEvent: function (targets, event, listener, useCapture) {
|
|
2472
|
+
var win = this.base.options.contentWindow,
|
|
2473
|
+
doc = this.base.options.ownerDocument;
|
|
2474
|
+
|
|
2475
|
+
targets = MediumEditor.util.isElement(targets) || [win, doc].indexOf(targets) > -1 ? [targets] : targets;
|
|
2476
|
+
|
|
2477
|
+
Array.prototype.forEach.call(targets, function (target) {
|
|
2478
|
+
target.addEventListener(event, listener, useCapture);
|
|
2479
|
+
this.events.push([target, event, listener, useCapture]);
|
|
2480
|
+
}.bind(this));
|
|
2423
2481
|
},
|
|
2424
2482
|
|
|
2425
|
-
detachDOMEvent: function (
|
|
2426
|
-
var index
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2483
|
+
detachDOMEvent: function (targets, event, listener, useCapture) {
|
|
2484
|
+
var index, e,
|
|
2485
|
+
win = this.base.options.contentWindow,
|
|
2486
|
+
doc = this.base.options.ownerDocument;
|
|
2487
|
+
|
|
2488
|
+
targets = MediumEditor.util.isElement(targets) || [win, doc].indexOf(targets) > -1 ? [targets] : targets;
|
|
2489
|
+
|
|
2490
|
+
Array.prototype.forEach.call(targets, function (target) {
|
|
2491
|
+
index = this.indexOfListener(target, event, listener, useCapture);
|
|
2492
|
+
if (index !== -1) {
|
|
2493
|
+
e = this.events.splice(index, 1)[0];
|
|
2494
|
+
e[0].removeEventListener(e[1], e[2], e[3]);
|
|
2495
|
+
}
|
|
2496
|
+
}.bind(this));
|
|
2432
2497
|
},
|
|
2433
2498
|
|
|
2434
2499
|
indexOfListener: function (target, event, listener, useCapture) {
|
|
@@ -2450,6 +2515,30 @@ MediumEditor.extensions = {};
|
|
|
2450
2515
|
}
|
|
2451
2516
|
},
|
|
2452
2517
|
|
|
2518
|
+
detachAllEventsFromElement: function (element) {
|
|
2519
|
+
var filtered = this.events.filter(function (e) {
|
|
2520
|
+
return e && e[0].getAttribute && e[0].getAttribute('medium-editor-index') === element.getAttribute('medium-editor-index');
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2523
|
+
for (var i = 0, len = filtered.length; i < len; i++) {
|
|
2524
|
+
var e = filtered[i];
|
|
2525
|
+
this.detachDOMEvent(e[0], e[1], e[2], e[3]);
|
|
2526
|
+
}
|
|
2527
|
+
},
|
|
2528
|
+
|
|
2529
|
+
// Attach all existing handlers to a new element
|
|
2530
|
+
attachAllEventsToElement: function (element) {
|
|
2531
|
+
if (this.listeners['editableInput']) {
|
|
2532
|
+
this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
if (this.eventsCache) {
|
|
2536
|
+
this.eventsCache.forEach(function (e) {
|
|
2537
|
+
this.attachDOMEvent(element, e['name'], e['handler'].bind(this));
|
|
2538
|
+
}, this);
|
|
2539
|
+
}
|
|
2540
|
+
},
|
|
2541
|
+
|
|
2453
2542
|
enableCustomEvent: function (event) {
|
|
2454
2543
|
if (this.disabledEvents[event] !== undefined) {
|
|
2455
2544
|
delete this.disabledEvents[event];
|
|
@@ -2564,23 +2653,23 @@ MediumEditor.extensions = {};
|
|
|
2564
2653
|
|
|
2565
2654
|
// Helper method to call all listeners to execCommand
|
|
2566
2655
|
var callListeners = function (args, result) {
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
});
|
|
2656
|
+
if (doc.execCommand.listeners) {
|
|
2657
|
+
doc.execCommand.listeners.forEach(function (listener) {
|
|
2658
|
+
listener({
|
|
2659
|
+
command: args[0],
|
|
2660
|
+
value: args[2],
|
|
2661
|
+
args: args,
|
|
2662
|
+
result: result
|
|
2575
2663
|
});
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
},
|
|
2578
2667
|
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2668
|
+
// Create a wrapper method for execCommand which will:
|
|
2669
|
+
// 1) Call document.execCommand with the correct arguments
|
|
2670
|
+
// 2) Loop through any listeners and notify them that execCommand was called
|
|
2671
|
+
// passing extra info on the call
|
|
2672
|
+
// 3) Return the result
|
|
2584
2673
|
wrapper = function () {
|
|
2585
2674
|
var result = doc.execCommand.orig.apply(this, arguments);
|
|
2586
2675
|
|
|
@@ -2641,15 +2730,15 @@ MediumEditor.extensions = {};
|
|
|
2641
2730
|
break;
|
|
2642
2731
|
case 'editableInput':
|
|
2643
2732
|
// setup cache for knowing when the content has changed
|
|
2644
|
-
this.contentCache =
|
|
2733
|
+
this.contentCache = {};
|
|
2645
2734
|
this.base.elements.forEach(function (element) {
|
|
2646
2735
|
this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML;
|
|
2736
|
+
}, this);
|
|
2647
2737
|
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
}.bind(this));
|
|
2738
|
+
// Attach to the 'oninput' event, handled correctly by most browsers
|
|
2739
|
+
if (this.InputEventOnContenteditableSupported) {
|
|
2740
|
+
this.attachToEachElement('input', this.handleInput);
|
|
2741
|
+
}
|
|
2653
2742
|
|
|
2654
2743
|
// For browsers which don't support the input event on contenteditable (IE)
|
|
2655
2744
|
// we'll attach to 'selectionchange' on the document and 'keypress' on the editables
|
|
@@ -2710,6 +2799,8 @@ MediumEditor.extensions = {};
|
|
|
2710
2799
|
// Detecting drop on the contenteditables
|
|
2711
2800
|
this.attachToEachElement('drop', this.handleDrop);
|
|
2712
2801
|
break;
|
|
2802
|
+
// TODO: We need to have a custom 'paste' event separate from 'editablePaste'
|
|
2803
|
+
// Need to think about the way to introduce this without breaking folks
|
|
2713
2804
|
case 'editablePaste':
|
|
2714
2805
|
// Detecting paste on the contenteditables
|
|
2715
2806
|
this.attachToEachElement('paste', this.handlePaste);
|
|
@@ -2719,9 +2810,26 @@ MediumEditor.extensions = {};
|
|
|
2719
2810
|
},
|
|
2720
2811
|
|
|
2721
2812
|
attachToEachElement: function (name, handler) {
|
|
2813
|
+
// build our internal cache to know which element got already what handler attached
|
|
2814
|
+
if (!this.eventsCache) {
|
|
2815
|
+
this.eventsCache = [];
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2722
2818
|
this.base.elements.forEach(function (element) {
|
|
2723
2819
|
this.attachDOMEvent(element, name, handler.bind(this));
|
|
2724
2820
|
}, this);
|
|
2821
|
+
|
|
2822
|
+
this.eventsCache.push({ 'name': name, 'handler': handler });
|
|
2823
|
+
},
|
|
2824
|
+
|
|
2825
|
+
cleanupElement: function (element) {
|
|
2826
|
+
var index = element.getAttribute('medium-editor-index');
|
|
2827
|
+
if (index) {
|
|
2828
|
+
this.detachAllEventsFromElement(element);
|
|
2829
|
+
if (this.contentCache) {
|
|
2830
|
+
delete this.contentCache[index];
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2725
2833
|
},
|
|
2726
2834
|
|
|
2727
2835
|
focusElement: function (element) {
|
|
@@ -2730,21 +2838,16 @@ MediumEditor.extensions = {};
|
|
|
2730
2838
|
},
|
|
2731
2839
|
|
|
2732
2840
|
updateFocus: function (target, eventObj) {
|
|
2733
|
-
var
|
|
2734
|
-
toolbarEl = toolbar ? toolbar.getToolbarElement() : null,
|
|
2735
|
-
anchorPreview = this.base.getExtensionByName('anchor-preview'),
|
|
2736
|
-
previewEl = (anchorPreview && anchorPreview.getPreviewElement) ? anchorPreview.getPreviewElement() : null,
|
|
2737
|
-
hadFocus = this.base.getFocusedElement(),
|
|
2841
|
+
var hadFocus = this.base.getFocusedElement(),
|
|
2738
2842
|
toFocus;
|
|
2739
2843
|
|
|
2740
|
-
// For clicks, we need to know if the mousedown that caused the click happened inside the existing focused element
|
|
2741
|
-
// If so, we don't want to focus another element
|
|
2844
|
+
// For clicks, we need to know if the mousedown that caused the click happened inside the existing focused element
|
|
2845
|
+
// or one of the extension elements. If so, we don't want to focus another element
|
|
2742
2846
|
if (hadFocus &&
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
MediumEditor.util.isDescendant(previewEl, this.lastMousedownTarget, true))) {
|
|
2847
|
+
eventObj.type === 'click' &&
|
|
2848
|
+
this.lastMousedownTarget &&
|
|
2849
|
+
(MediumEditor.util.isDescendant(hadFocus, this.lastMousedownTarget, true) ||
|
|
2850
|
+
isElementDescendantOfExtension(this.base.extensions, this.lastMousedownTarget))) {
|
|
2748
2851
|
toFocus = hadFocus;
|
|
2749
2852
|
}
|
|
2750
2853
|
|
|
@@ -2760,10 +2863,9 @@ MediumEditor.extensions = {};
|
|
|
2760
2863
|
}, this);
|
|
2761
2864
|
}
|
|
2762
2865
|
|
|
2763
|
-
// Check if the target is external (not part of the editor, toolbar, or
|
|
2866
|
+
// Check if the target is external (not part of the editor, toolbar, or any other extension)
|
|
2764
2867
|
var externalEvent = !MediumEditor.util.isDescendant(hadFocus, target, true) &&
|
|
2765
|
-
|
|
2766
|
-
!MediumEditor.util.isDescendant(previewEl, target, true);
|
|
2868
|
+
!isElementDescendantOfExtension(this.base.extensions, target);
|
|
2767
2869
|
|
|
2768
2870
|
if (toFocus !== hadFocus) {
|
|
2769
2871
|
// If element has focus, and focus is going outside of editor
|
|
@@ -2793,12 +2895,14 @@ MediumEditor.extensions = {};
|
|
|
2793
2895
|
}
|
|
2794
2896
|
// An event triggered which signifies that the user may have changed someting
|
|
2795
2897
|
// Look in our cache of input for the contenteditables to see if something changed
|
|
2796
|
-
var index = target.getAttribute('medium-editor-index')
|
|
2797
|
-
|
|
2898
|
+
var index = target.getAttribute('medium-editor-index'),
|
|
2899
|
+
html = target.innerHTML;
|
|
2900
|
+
|
|
2901
|
+
if (html !== this.contentCache[index]) {
|
|
2798
2902
|
// The content has changed since the last time we checked, fire the event
|
|
2799
2903
|
this.triggerCustomEvent('editableInput', eventObj, target);
|
|
2800
2904
|
}
|
|
2801
|
-
this.contentCache[index] =
|
|
2905
|
+
this.contentCache[index] = html;
|
|
2802
2906
|
},
|
|
2803
2907
|
|
|
2804
2908
|
handleDocumentSelectionChange: function (event) {
|
|
@@ -3679,12 +3783,12 @@ MediumEditor.extensions = {};
|
|
|
3679
3783
|
targetCheckbox = this.getAnchorTargetCheckbox(),
|
|
3680
3784
|
buttonCheckbox = this.getAnchorButtonCheckbox();
|
|
3681
3785
|
|
|
3682
|
-
opts = opts || {
|
|
3786
|
+
opts = opts || { value: '' };
|
|
3683
3787
|
// TODO: This is for backwards compatability
|
|
3684
3788
|
// We don't need to support the 'string' argument in 6.0.0
|
|
3685
3789
|
if (typeof opts === 'string') {
|
|
3686
3790
|
opts = {
|
|
3687
|
-
|
|
3791
|
+
value: opts
|
|
3688
3792
|
};
|
|
3689
3793
|
}
|
|
3690
3794
|
|
|
@@ -3693,7 +3797,7 @@ MediumEditor.extensions = {};
|
|
|
3693
3797
|
MediumEditor.extensions.form.prototype.showForm.apply(this);
|
|
3694
3798
|
this.setToolbarPosition();
|
|
3695
3799
|
|
|
3696
|
-
input.value = opts.
|
|
3800
|
+
input.value = opts.value;
|
|
3697
3801
|
input.focus();
|
|
3698
3802
|
|
|
3699
3803
|
// If we have a target checkbox, we want it to be checked/unchecked
|
|
@@ -3730,11 +3834,11 @@ MediumEditor.extensions = {};
|
|
|
3730
3834
|
var targetCheckbox = this.getAnchorTargetCheckbox(),
|
|
3731
3835
|
buttonCheckbox = this.getAnchorButtonCheckbox(),
|
|
3732
3836
|
opts = {
|
|
3733
|
-
|
|
3837
|
+
value: this.getInput().value.trim()
|
|
3734
3838
|
};
|
|
3735
3839
|
|
|
3736
3840
|
if (this.linkValidation) {
|
|
3737
|
-
opts.
|
|
3841
|
+
opts.value = this.checkLinkFormat(opts.value);
|
|
3738
3842
|
}
|
|
3739
3843
|
|
|
3740
3844
|
opts.target = '_self';
|
|
@@ -3764,14 +3868,15 @@ MediumEditor.extensions = {};
|
|
|
3764
3868
|
// Matches any alphabetical characters followed by ://
|
|
3765
3869
|
// Matches protocol relative "//"
|
|
3766
3870
|
// Matches common external protocols "mailto:" "tel:" "maps:"
|
|
3767
|
-
|
|
3871
|
+
// Matches relative hash link, begins with "#"
|
|
3872
|
+
var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i,
|
|
3768
3873
|
// var te is a regex for checking if the string is a telephone number
|
|
3769
3874
|
telRegex = /^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/;
|
|
3770
3875
|
if (telRegex.test(value)) {
|
|
3771
3876
|
return 'tel:' + value;
|
|
3772
3877
|
} else {
|
|
3773
3878
|
// Check for URL scheme and default to http:// if none found
|
|
3774
|
-
return (urlSchemeRegex.test(value) ? '' : 'http://') + value;
|
|
3879
|
+
return (urlSchemeRegex.test(value) ? '' : 'http://') + encodeURI(value);
|
|
3775
3880
|
}
|
|
3776
3881
|
},
|
|
3777
3882
|
|
|
@@ -3884,6 +3989,11 @@ MediumEditor.extensions = {};
|
|
|
3884
3989
|
*/
|
|
3885
3990
|
showWhenToolbarIsVisible: false,
|
|
3886
3991
|
|
|
3992
|
+
/* showOnEmptyLinks: [boolean]
|
|
3993
|
+
* determines whether the anchor tag preview shows up on links with href="" or href="#something"
|
|
3994
|
+
*/
|
|
3995
|
+
showOnEmptyLinks: true,
|
|
3996
|
+
|
|
3887
3997
|
init: function () {
|
|
3888
3998
|
this.anchorPreview = this.createPreview();
|
|
3889
3999
|
|
|
@@ -3892,6 +4002,11 @@ MediumEditor.extensions = {};
|
|
|
3892
4002
|
this.attachToEditables();
|
|
3893
4003
|
},
|
|
3894
4004
|
|
|
4005
|
+
getInteractionElements: function () {
|
|
4006
|
+
return this.getPreviewElement();
|
|
4007
|
+
},
|
|
4008
|
+
|
|
4009
|
+
// TODO: Remove this function in 6.0.0
|
|
3895
4010
|
getPreviewElement: function () {
|
|
3896
4011
|
return this.anchorPreview;
|
|
3897
4012
|
},
|
|
@@ -3956,13 +4071,15 @@ MediumEditor.extensions = {};
|
|
|
3956
4071
|
|
|
3957
4072
|
positionPreview: function (activeAnchor) {
|
|
3958
4073
|
activeAnchor = activeAnchor || this.activeAnchor;
|
|
3959
|
-
var
|
|
4074
|
+
var containerWidth = this.window.innerWidth,
|
|
4075
|
+
buttonHeight = this.anchorPreview.offsetHeight,
|
|
3960
4076
|
boundary = activeAnchor.getBoundingClientRect(),
|
|
3961
|
-
middleBoundary = (boundary.left + boundary.right) / 2,
|
|
3962
4077
|
diffLeft = this.diffLeft,
|
|
3963
4078
|
diffTop = this.diffTop,
|
|
3964
|
-
|
|
3965
|
-
|
|
4079
|
+
elementsContainer = this.getEditorOption('elementsContainer'),
|
|
4080
|
+
elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
|
|
4081
|
+
relativeBoundary = {},
|
|
4082
|
+
halfOffsetWidth, defaultLeft, middleBoundary, elementsContainerBoundary, top;
|
|
3966
4083
|
|
|
3967
4084
|
halfOffsetWidth = this.anchorPreview.offsetWidth / 2;
|
|
3968
4085
|
var toolbarExtension = this.base.getExtensionByName('toolbar');
|
|
@@ -3972,12 +4089,35 @@ MediumEditor.extensions = {};
|
|
|
3972
4089
|
}
|
|
3973
4090
|
defaultLeft = diffLeft - halfOffsetWidth;
|
|
3974
4091
|
|
|
3975
|
-
|
|
4092
|
+
// If container element is absolute / fixed, recalculate boundaries to be relative to the container
|
|
4093
|
+
if (elementsContainerAbsolute) {
|
|
4094
|
+
elementsContainerBoundary = elementsContainer.getBoundingClientRect();
|
|
4095
|
+
['top', 'left'].forEach(function (key) {
|
|
4096
|
+
relativeBoundary[key] = boundary[key] - elementsContainerBoundary[key];
|
|
4097
|
+
});
|
|
4098
|
+
|
|
4099
|
+
relativeBoundary.width = boundary.width;
|
|
4100
|
+
relativeBoundary.height = boundary.height;
|
|
4101
|
+
boundary = relativeBoundary;
|
|
4102
|
+
|
|
4103
|
+
containerWidth = elementsContainerBoundary.width;
|
|
4104
|
+
|
|
4105
|
+
// Adjust top position according to container scroll position
|
|
4106
|
+
top = elementsContainer.scrollTop;
|
|
4107
|
+
} else {
|
|
4108
|
+
// Adjust top position according to window scroll position
|
|
4109
|
+
top = this.window.pageYOffset;
|
|
4110
|
+
}
|
|
4111
|
+
|
|
4112
|
+
middleBoundary = boundary.left + boundary.width / 2;
|
|
4113
|
+
top += buttonHeight + boundary.top + boundary.height - diffTop - this.anchorPreview.offsetHeight;
|
|
4114
|
+
|
|
4115
|
+
this.anchorPreview.style.top = Math.round(top) + 'px';
|
|
3976
4116
|
this.anchorPreview.style.right = 'initial';
|
|
3977
4117
|
if (middleBoundary < halfOffsetWidth) {
|
|
3978
4118
|
this.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px';
|
|
3979
4119
|
this.anchorPreview.style.right = 'initial';
|
|
3980
|
-
} else if ((
|
|
4120
|
+
} else if ((containerWidth - middleBoundary) < halfOffsetWidth) {
|
|
3981
4121
|
this.anchorPreview.style.left = 'auto';
|
|
3982
4122
|
this.anchorPreview.style.right = 0;
|
|
3983
4123
|
} else {
|
|
@@ -3988,6 +4128,15 @@ MediumEditor.extensions = {};
|
|
|
3988
4128
|
|
|
3989
4129
|
attachToEditables: function () {
|
|
3990
4130
|
this.subscribe('editableMouseover', this.handleEditableMouseover.bind(this));
|
|
4131
|
+
this.subscribe('positionedToolbar', this.handlePositionedToolbar.bind(this));
|
|
4132
|
+
},
|
|
4133
|
+
|
|
4134
|
+
handlePositionedToolbar: function () {
|
|
4135
|
+
// If the toolbar is visible and positioned, we don't need to hide the preview
|
|
4136
|
+
// when showWhenToolbarIsVisible is true
|
|
4137
|
+
if (!this.showWhenToolbarIsVisible) {
|
|
4138
|
+
this.hidePreview();
|
|
4139
|
+
}
|
|
3991
4140
|
},
|
|
3992
4141
|
|
|
3993
4142
|
handleClick: function (event) {
|
|
@@ -4004,7 +4153,7 @@ MediumEditor.extensions = {};
|
|
|
4004
4153
|
this.base.delay(function () {
|
|
4005
4154
|
if (activeAnchor) {
|
|
4006
4155
|
var opts = {
|
|
4007
|
-
|
|
4156
|
+
value: activeAnchor.attributes.href.value,
|
|
4008
4157
|
target: activeAnchor.getAttribute('target'),
|
|
4009
4158
|
buttonClass: activeAnchor.getAttribute('class')
|
|
4010
4159
|
};
|
|
@@ -4033,7 +4182,8 @@ MediumEditor.extensions = {};
|
|
|
4033
4182
|
// Detect empty href attributes
|
|
4034
4183
|
// The browser will make href="" or href="#top"
|
|
4035
4184
|
// into absolute urls when accessed as event.target.href, so check the html
|
|
4036
|
-
if (
|
|
4185
|
+
if (!this.showOnEmptyLinks &&
|
|
4186
|
+
(!/href=["']\S+["']/.test(target.outerHTML) || /href=["']#\S+["']/.test(target.outerHTML))) {
|
|
4037
4187
|
return true;
|
|
4038
4188
|
}
|
|
4039
4189
|
|
|
@@ -4537,8 +4687,12 @@ MediumEditor.extensions = {};
|
|
|
4537
4687
|
event.preventDefault();
|
|
4538
4688
|
event.stopPropagation();
|
|
4539
4689
|
|
|
4690
|
+
// command can be a function to execute
|
|
4691
|
+
if (typeof data.command === 'function') {
|
|
4692
|
+
data.command.apply(this);
|
|
4693
|
+
}
|
|
4540
4694
|
// command can be false so the shortcut is just disabled
|
|
4541
|
-
if (false !== data.command) {
|
|
4695
|
+
else if (false !== data.command) {
|
|
4542
4696
|
this.execAction(data.command);
|
|
4543
4697
|
}
|
|
4544
4698
|
}
|
|
@@ -4709,7 +4863,7 @@ MediumEditor.extensions = {};
|
|
|
4709
4863
|
if (font === '') {
|
|
4710
4864
|
this.clearFontName();
|
|
4711
4865
|
} else {
|
|
4712
|
-
this.execAction('fontName', {
|
|
4866
|
+
this.execAction('fontName', { value: font });
|
|
4713
4867
|
}
|
|
4714
4868
|
},
|
|
4715
4869
|
|
|
@@ -4887,7 +5041,7 @@ MediumEditor.extensions = {};
|
|
|
4887
5041
|
if (size === '4') {
|
|
4888
5042
|
this.clearFontSize();
|
|
4889
5043
|
} else {
|
|
4890
|
-
this.execAction('fontSize', {
|
|
5044
|
+
this.execAction('fontSize', { value: size });
|
|
4891
5045
|
}
|
|
4892
5046
|
},
|
|
4893
5047
|
|
|
@@ -4913,6 +5067,16 @@ MediumEditor.extensions = {};
|
|
|
4913
5067
|
}());
|
|
4914
5068
|
(function () {
|
|
4915
5069
|
'use strict';
|
|
5070
|
+
|
|
5071
|
+
/* Helpers and internal variables that don't need to be members of actual paste handler */
|
|
5072
|
+
|
|
5073
|
+
var pasteBinDefaultContent = '%ME_PASTEBIN%',
|
|
5074
|
+
lastRange = null,
|
|
5075
|
+
keyboardPasteEditable = null,
|
|
5076
|
+
stopProp = function (event) {
|
|
5077
|
+
event.stopPropagation();
|
|
5078
|
+
};
|
|
5079
|
+
|
|
4916
5080
|
/*jslint regexp: true*/
|
|
4917
5081
|
/*
|
|
4918
5082
|
jslint does not allow character negation, because the negation
|
|
@@ -4922,6 +5086,15 @@ MediumEditor.extensions = {};
|
|
|
4922
5086
|
*/
|
|
4923
5087
|
function createReplacements() {
|
|
4924
5088
|
return [
|
|
5089
|
+
// Remove anything but the contents within the BODY element
|
|
5090
|
+
[new RegExp(/^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g), ''],
|
|
5091
|
+
|
|
5092
|
+
// cleanup comments added by Chrome when pasting html
|
|
5093
|
+
[new RegExp(/<!--StartFragment-->|<!--EndFragment-->/g), ''],
|
|
5094
|
+
|
|
5095
|
+
// Trailing BR elements
|
|
5096
|
+
[new RegExp(/<br>$/i), ''],
|
|
5097
|
+
|
|
4925
5098
|
// replace two bogus tags that begin pastes from google docs
|
|
4926
5099
|
[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ''],
|
|
4927
5100
|
[new RegExp(/<\/b>(<br[^>]*>)?$/gi), ''],
|
|
@@ -4931,13 +5104,13 @@ MediumEditor.extensions = {};
|
|
|
4931
5104
|
[new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],
|
|
4932
5105
|
|
|
4933
5106
|
// replace google docs italics+bold with a span to be replaced once the html is inserted
|
|
4934
|
-
[new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
|
|
5107
|
+
[new RegExp(/<span[^>]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
|
|
4935
5108
|
|
|
4936
5109
|
// replace google docs italics with a span to be replaced once the html is inserted
|
|
4937
5110
|
[new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],
|
|
4938
5111
|
|
|
4939
5112
|
//[replace google docs bolds with a span to be replaced once the html is inserted
|
|
4940
|
-
[new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'],
|
|
5113
|
+
[new RegExp(/<span[^>]*font-weight:(bold|700)[^>]*>/gi), '<span class="replace-with bold">'],
|
|
4941
5114
|
|
|
4942
5115
|
// replace manually entered b/i/a tags with real ones
|
|
4943
5116
|
[new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'],
|
|
@@ -4953,13 +5126,47 @@ MediumEditor.extensions = {};
|
|
|
4953
5126
|
// Microsoft Word makes these odd tags, like <o:p></o:p>
|
|
4954
5127
|
[new RegExp(/<\/?o:[a-z]*>/gi), ''],
|
|
4955
5128
|
|
|
4956
|
-
//
|
|
4957
|
-
[
|
|
4958
|
-
['<!--StartFragment-->', '']
|
|
5129
|
+
// Microsoft Word adds some special elements around list items
|
|
5130
|
+
[new RegExp(/<!\[if !supportLists\]>(((?!<!).)*)<!\[endif]\>/gi), '$1']
|
|
4959
5131
|
];
|
|
4960
5132
|
}
|
|
4961
5133
|
/*jslint regexp: false*/
|
|
4962
5134
|
|
|
5135
|
+
/**
|
|
5136
|
+
* Gets various content types out of the Clipboard API. It will also get the
|
|
5137
|
+
* plain text using older IE and WebKit API.
|
|
5138
|
+
*
|
|
5139
|
+
* @param {event} event Event fired on paste.
|
|
5140
|
+
* @param {win} reference to window
|
|
5141
|
+
* @param {doc} reference to document
|
|
5142
|
+
* @return {Object} Object with mime types and data for those mime types.
|
|
5143
|
+
*/
|
|
5144
|
+
function getClipboardContent(event, win, doc) {
|
|
5145
|
+
var dataTransfer = event.clipboardData || win.clipboardData || doc.dataTransfer,
|
|
5146
|
+
data = {};
|
|
5147
|
+
|
|
5148
|
+
if (!dataTransfer) {
|
|
5149
|
+
return data;
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
// Use old WebKit/IE API
|
|
5153
|
+
if (dataTransfer.getData) {
|
|
5154
|
+
var legacyText = dataTransfer.getData('Text');
|
|
5155
|
+
if (legacyText && legacyText.length > 0) {
|
|
5156
|
+
data['text/plain'] = legacyText;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
if (dataTransfer.types) {
|
|
5161
|
+
for (var i = 0; i < dataTransfer.types.length; i++) {
|
|
5162
|
+
var contentType = dataTransfer.types[i];
|
|
5163
|
+
data[contentType] = dataTransfer.getData(contentType);
|
|
5164
|
+
}
|
|
5165
|
+
}
|
|
5166
|
+
|
|
5167
|
+
return data;
|
|
5168
|
+
}
|
|
5169
|
+
|
|
4963
5170
|
var PasteHandler = MediumEditor.Extension.extend({
|
|
4964
5171
|
/* Paste Options */
|
|
4965
5172
|
|
|
@@ -4999,65 +5206,247 @@ MediumEditor.extensions = {};
|
|
|
4999
5206
|
*/
|
|
5000
5207
|
cleanTags: ['meta'],
|
|
5001
5208
|
|
|
5209
|
+
/* unwrapTags: [Array]
|
|
5210
|
+
* list of element tag names to unwrap (remove the element tag but retain its child elements)
|
|
5211
|
+
* during paste when __cleanPastedHTML__ is `true` or when
|
|
5212
|
+
* calling `cleanPaste(text)` or `pasteHTML(html, options)` helper methods.
|
|
5213
|
+
*/
|
|
5214
|
+
unwrapTags: [],
|
|
5215
|
+
|
|
5002
5216
|
init: function () {
|
|
5003
5217
|
MediumEditor.Extension.prototype.init.apply(this, arguments);
|
|
5004
5218
|
|
|
5005
5219
|
if (this.forcePlainText || this.cleanPastedHTML) {
|
|
5006
|
-
this.subscribe('
|
|
5220
|
+
this.subscribe('editableKeydown', this.handleKeydown.bind(this));
|
|
5221
|
+
// We need access to the full event data in paste
|
|
5222
|
+
// so we can't use the editablePaste event here
|
|
5223
|
+
this.getEditorElements().forEach(function (element) {
|
|
5224
|
+
this.on(element, 'paste', this.handlePaste.bind(this));
|
|
5225
|
+
}, this);
|
|
5226
|
+
this.subscribe('addElement', this.handleAddElement.bind(this));
|
|
5007
5227
|
}
|
|
5008
5228
|
},
|
|
5009
5229
|
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5230
|
+
handleAddElement: function (event, editable) {
|
|
5231
|
+
this.on(editable, 'paste', this.handlePaste.bind(this));
|
|
5232
|
+
},
|
|
5233
|
+
|
|
5234
|
+
destroy: function () {
|
|
5235
|
+
// Make sure pastebin is destroyed in case it's still around for some reason
|
|
5236
|
+
if (this.forcePlainText || this.cleanPastedHTML) {
|
|
5237
|
+
this.removePasteBin();
|
|
5238
|
+
}
|
|
5239
|
+
},
|
|
5240
|
+
|
|
5241
|
+
handlePaste: function (event, editable) {
|
|
5242
|
+
if (event.defaultPrevented) {
|
|
5243
|
+
return;
|
|
5244
|
+
}
|
|
5245
|
+
|
|
5246
|
+
var clipboardContent = getClipboardContent(event, this.window, this.document),
|
|
5247
|
+
pastedHTML = clipboardContent['text/html'],
|
|
5248
|
+
pastedPlain = clipboardContent['text/plain'];
|
|
5249
|
+
|
|
5250
|
+
if (this.window.clipboardData && event.clipboardData === undefined && !pastedHTML) {
|
|
5021
5251
|
// If window.clipboardData exists, but event.clipboardData doesn't exist,
|
|
5022
5252
|
// we're probably in IE. IE only has two possibilities for clipboard
|
|
5023
5253
|
// data format: 'Text' and 'URL'.
|
|
5024
5254
|
//
|
|
5025
|
-
//
|
|
5026
|
-
|
|
5027
|
-
dataFormatPlain = 'Text';
|
|
5255
|
+
// For IE, we'll fallback to 'Text' for text/html
|
|
5256
|
+
pastedHTML = pastedPlain;
|
|
5028
5257
|
}
|
|
5029
5258
|
|
|
5030
|
-
if (
|
|
5031
|
-
event.clipboardData.getData &&
|
|
5032
|
-
!event.defaultPrevented) {
|
|
5259
|
+
if (pastedHTML || pastedPlain) {
|
|
5033
5260
|
event.preventDefault();
|
|
5034
5261
|
|
|
5035
|
-
pastedHTML
|
|
5036
|
-
|
|
5262
|
+
this.doPaste(pastedHTML, pastedPlain, editable);
|
|
5263
|
+
}
|
|
5264
|
+
},
|
|
5037
5265
|
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5266
|
+
doPaste: function (pastedHTML, pastedPlain, editable) {
|
|
5267
|
+
var paragraphs,
|
|
5268
|
+
html = '',
|
|
5269
|
+
p;
|
|
5041
5270
|
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5271
|
+
if (this.cleanPastedHTML && pastedHTML) {
|
|
5272
|
+
return this.cleanPaste(pastedHTML);
|
|
5273
|
+
}
|
|
5274
|
+
|
|
5275
|
+
if (!(this.getEditorOption('disableReturn') || (editable && editable.getAttribute('data-disable-return')))) {
|
|
5276
|
+
paragraphs = pastedPlain.split(/[\r\n]+/g);
|
|
5277
|
+
// If there are no \r\n in data, don't wrap in <p>
|
|
5278
|
+
if (paragraphs.length > 1) {
|
|
5279
|
+
for (p = 0; p < paragraphs.length; p += 1) {
|
|
5280
|
+
if (paragraphs[p] !== '') {
|
|
5281
|
+
html += '<p>' + MediumEditor.util.htmlEntities(paragraphs[p]) + '</p>';
|
|
5050
5282
|
}
|
|
5051
|
-
} else {
|
|
5052
|
-
html = MediumEditor.util.htmlEntities(paragraphs[0]);
|
|
5053
5283
|
}
|
|
5054
5284
|
} else {
|
|
5055
|
-
html = MediumEditor.util.htmlEntities(
|
|
5285
|
+
html = MediumEditor.util.htmlEntities(paragraphs[0]);
|
|
5056
5286
|
}
|
|
5057
|
-
|
|
5287
|
+
} else {
|
|
5288
|
+
html = MediumEditor.util.htmlEntities(pastedPlain);
|
|
5289
|
+
}
|
|
5290
|
+
MediumEditor.util.insertHTMLCommand(this.document, html);
|
|
5291
|
+
},
|
|
5292
|
+
|
|
5293
|
+
handlePasteBinPaste: function (event) {
|
|
5294
|
+
if (event.defaultPrevented) {
|
|
5295
|
+
this.removePasteBin();
|
|
5296
|
+
return;
|
|
5297
|
+
}
|
|
5298
|
+
|
|
5299
|
+
var clipboardContent = getClipboardContent(event, this.window, this.document),
|
|
5300
|
+
pastedHTML = clipboardContent['text/html'],
|
|
5301
|
+
pastedPlain = clipboardContent['text/plain'],
|
|
5302
|
+
editable = keyboardPasteEditable;
|
|
5303
|
+
|
|
5304
|
+
// If we have valid html already, or we're not in cleanPastedHTML mode
|
|
5305
|
+
// we can ignore the paste bin and just paste now
|
|
5306
|
+
if (!this.cleanPastedHTML || pastedHTML) {
|
|
5307
|
+
event.preventDefault();
|
|
5308
|
+
this.removePasteBin();
|
|
5309
|
+
this.doPaste(pastedHTML, pastedPlain, editable);
|
|
5310
|
+
|
|
5311
|
+
// The event handling code listens for paste on the editable element
|
|
5312
|
+
// in order to trigger the editablePaste event. Since this paste event
|
|
5313
|
+
// is happening on the pastebin, the event handling code never knows about it
|
|
5314
|
+
// So, we have to trigger editablePaste manually
|
|
5315
|
+
this.trigger('editablePaste', { currentTarget: editable, target: editable }, editable);
|
|
5316
|
+
return;
|
|
5317
|
+
}
|
|
5318
|
+
|
|
5319
|
+
// We need to look at the paste bin, so do a setTimeout to let the paste
|
|
5320
|
+
// fall through into the paste bin
|
|
5321
|
+
setTimeout(function () {
|
|
5322
|
+
// Only look for HTML if we're in cleanPastedHTML mode
|
|
5323
|
+
if (this.cleanPastedHTML) {
|
|
5324
|
+
// If clipboard didn't have HTML, try the paste bin
|
|
5325
|
+
pastedHTML = this.getPasteBinHtml();
|
|
5326
|
+
}
|
|
5327
|
+
|
|
5328
|
+
// If we needed the paste bin, we're done with it now, remove it
|
|
5329
|
+
this.removePasteBin();
|
|
5330
|
+
|
|
5331
|
+
// Handle the paste with the html from the paste bin
|
|
5332
|
+
this.doPaste(pastedHTML, pastedPlain, editable);
|
|
5333
|
+
|
|
5334
|
+
// The event handling code listens for paste on the editable element
|
|
5335
|
+
// in order to trigger the editablePaste event. Since this paste event
|
|
5336
|
+
// is happening on the pastebin, the event handling code never knows about it
|
|
5337
|
+
// So, we have to trigger editablePaste manually
|
|
5338
|
+
this.trigger('editablePaste', { currentTarget: editable, target: editable }, editable);
|
|
5339
|
+
}.bind(this), 0);
|
|
5340
|
+
},
|
|
5341
|
+
|
|
5342
|
+
handleKeydown: function (event, editable) {
|
|
5343
|
+
// if it's not Ctrl+V, do nothing
|
|
5344
|
+
if (!(MediumEditor.util.isKey(event, MediumEditor.util.keyCode.V) && MediumEditor.util.isMetaCtrlKey(event))) {
|
|
5345
|
+
return;
|
|
5346
|
+
}
|
|
5347
|
+
|
|
5348
|
+
event.stopImmediatePropagation();
|
|
5349
|
+
|
|
5350
|
+
this.removePasteBin();
|
|
5351
|
+
this.createPasteBin(editable);
|
|
5352
|
+
},
|
|
5353
|
+
|
|
5354
|
+
createPasteBin: function (editable) {
|
|
5355
|
+
var rects,
|
|
5356
|
+
range = MediumEditor.selection.getSelectionRange(this.document),
|
|
5357
|
+
top = this.window.pageYOffset;
|
|
5358
|
+
|
|
5359
|
+
keyboardPasteEditable = editable;
|
|
5360
|
+
|
|
5361
|
+
if (range) {
|
|
5362
|
+
rects = range.getClientRects();
|
|
5363
|
+
|
|
5364
|
+
// on empty line, rects is empty so we grab information from the first container of the range
|
|
5365
|
+
if (rects.length) {
|
|
5366
|
+
top += rects[0].top;
|
|
5367
|
+
} else {
|
|
5368
|
+
top += range.startContainer.getBoundingClientRect().top;
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5372
|
+
lastRange = range;
|
|
5373
|
+
|
|
5374
|
+
var pasteBinElm = this.document.createElement('div');
|
|
5375
|
+
pasteBinElm.id = this.pasteBinId = 'medium-editor-pastebin-' + (+Date.now());
|
|
5376
|
+
pasteBinElm.setAttribute('style', 'border: 1px red solid; position: absolute; top: ' + top + 'px; width: 10px; height: 10px; overflow: hidden; opacity: 0');
|
|
5377
|
+
pasteBinElm.setAttribute('contentEditable', true);
|
|
5378
|
+
pasteBinElm.innerHTML = pasteBinDefaultContent;
|
|
5379
|
+
|
|
5380
|
+
this.document.body.appendChild(pasteBinElm);
|
|
5381
|
+
|
|
5382
|
+
// avoid .focus() to stop other event (actually the paste event)
|
|
5383
|
+
this.on(pasteBinElm, 'focus', stopProp);
|
|
5384
|
+
this.on(pasteBinElm, 'focusin', stopProp);
|
|
5385
|
+
this.on(pasteBinElm, 'focusout', stopProp);
|
|
5386
|
+
|
|
5387
|
+
pasteBinElm.focus();
|
|
5388
|
+
|
|
5389
|
+
MediumEditor.selection.selectNode(pasteBinElm, this.document);
|
|
5390
|
+
|
|
5391
|
+
if (!this.boundHandlePaste) {
|
|
5392
|
+
this.boundHandlePaste = this.handlePasteBinPaste.bind(this);
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5395
|
+
this.on(pasteBinElm, 'paste', this.boundHandlePaste);
|
|
5396
|
+
},
|
|
5397
|
+
|
|
5398
|
+
removePasteBin: function () {
|
|
5399
|
+
if (null !== lastRange) {
|
|
5400
|
+
MediumEditor.selection.selectRange(this.document, lastRange);
|
|
5401
|
+
lastRange = null;
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
if (null !== keyboardPasteEditable) {
|
|
5405
|
+
keyboardPasteEditable = null;
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5408
|
+
var pasteBinElm = this.getPasteBin();
|
|
5409
|
+
if (!pasteBinElm) {
|
|
5410
|
+
return;
|
|
5411
|
+
}
|
|
5412
|
+
|
|
5413
|
+
if (pasteBinElm) {
|
|
5414
|
+
this.off(pasteBinElm, 'focus', stopProp);
|
|
5415
|
+
this.off(pasteBinElm, 'focusin', stopProp);
|
|
5416
|
+
this.off(pasteBinElm, 'focusout', stopProp);
|
|
5417
|
+
this.off(pasteBinElm, 'paste', this.boundHandlePaste);
|
|
5418
|
+
pasteBinElm.parentElement.removeChild(pasteBinElm);
|
|
5058
5419
|
}
|
|
5059
5420
|
},
|
|
5060
5421
|
|
|
5422
|
+
getPasteBin: function () {
|
|
5423
|
+
return this.document.getElementById(this.pasteBinId);
|
|
5424
|
+
},
|
|
5425
|
+
|
|
5426
|
+
getPasteBinHtml: function () {
|
|
5427
|
+
var pasteBinElm = this.getPasteBin();
|
|
5428
|
+
|
|
5429
|
+
if (!pasteBinElm) {
|
|
5430
|
+
return false;
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
// WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
|
|
5434
|
+
// so we need to force plain text mode in this case
|
|
5435
|
+
if (pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
|
|
5436
|
+
return false;
|
|
5437
|
+
}
|
|
5438
|
+
|
|
5439
|
+
var pasteBinHtml = pasteBinElm.innerHTML;
|
|
5440
|
+
|
|
5441
|
+
// If paste bin is empty try using plain text mode
|
|
5442
|
+
// since that is better than nothing right
|
|
5443
|
+
if (!pasteBinHtml || pasteBinHtml === pasteBinDefaultContent) {
|
|
5444
|
+
return false;
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5447
|
+
return pasteBinHtml;
|
|
5448
|
+
},
|
|
5449
|
+
|
|
5061
5450
|
cleanPaste: function (text) {
|
|
5062
5451
|
var i, elList, tmp, workEl,
|
|
5063
5452
|
multiline = /<p|<br|<div/.test(text),
|
|
@@ -5107,7 +5496,8 @@ MediumEditor.extensions = {};
|
|
|
5107
5496
|
pasteHTML: function (html, options) {
|
|
5108
5497
|
options = MediumEditor.util.defaults({}, options, {
|
|
5109
5498
|
cleanAttrs: this.cleanAttrs,
|
|
5110
|
-
cleanTags: this.cleanTags
|
|
5499
|
+
cleanTags: this.cleanTags,
|
|
5500
|
+
unwrapTags: this.unwrapTags
|
|
5111
5501
|
});
|
|
5112
5502
|
|
|
5113
5503
|
var elList, workEl, i, fragmentBody, pasteBlock = this.document.createDocumentFragment();
|
|
@@ -5129,21 +5519,25 @@ MediumEditor.extensions = {};
|
|
|
5129
5519
|
|
|
5130
5520
|
MediumEditor.util.cleanupAttrs(workEl, options.cleanAttrs);
|
|
5131
5521
|
MediumEditor.util.cleanupTags(workEl, options.cleanTags);
|
|
5522
|
+
MediumEditor.util.unwrapTags(workEl, options.unwrapTags);
|
|
5132
5523
|
}
|
|
5133
5524
|
|
|
5134
5525
|
MediumEditor.util.insertHTMLCommand(this.document, fragmentBody.innerHTML.replace(/ /g, ' '));
|
|
5135
5526
|
},
|
|
5136
5527
|
|
|
5528
|
+
// TODO (6.0): Make this an internal helper instead of member of paste handler
|
|
5137
5529
|
isCommonBlock: function (el) {
|
|
5138
5530
|
return (el && (el.nodeName.toLowerCase() === 'p' || el.nodeName.toLowerCase() === 'div'));
|
|
5139
5531
|
},
|
|
5140
5532
|
|
|
5533
|
+
// TODO (6.0): Make this an internal helper instead of member of paste handler
|
|
5141
5534
|
filterCommonBlocks: function (el) {
|
|
5142
5535
|
if (/^\s*$/.test(el.textContent) && el.parentNode) {
|
|
5143
5536
|
el.parentNode.removeChild(el);
|
|
5144
5537
|
}
|
|
5145
5538
|
},
|
|
5146
5539
|
|
|
5540
|
+
// TODO (6.0): Make this an internal helper instead of member of paste handler
|
|
5147
5541
|
filterLineBreak: function (el) {
|
|
5148
5542
|
if (this.isCommonBlock(el.previousElementSibling)) {
|
|
5149
5543
|
// remove stray br's following common block elements
|
|
@@ -5157,6 +5551,7 @@ MediumEditor.extensions = {};
|
|
|
5157
5551
|
}
|
|
5158
5552
|
},
|
|
5159
5553
|
|
|
5554
|
+
// TODO (6.0): Make this an internal helper instead of member of paste handler
|
|
5160
5555
|
// remove an element, including its parent, if it is the only element within its parent
|
|
5161
5556
|
removeWithParent: function (el) {
|
|
5162
5557
|
if (el && el.parentNode) {
|
|
@@ -5168,6 +5563,7 @@ MediumEditor.extensions = {};
|
|
|
5168
5563
|
}
|
|
5169
5564
|
},
|
|
5170
5565
|
|
|
5566
|
+
// TODO (6.0): Make this an internal helper instead of member of paste handler
|
|
5171
5567
|
cleanupSpans: function (containerEl) {
|
|
5172
5568
|
var i,
|
|
5173
5569
|
el,
|
|
@@ -5234,37 +5630,61 @@ MediumEditor.extensions = {};
|
|
|
5234
5630
|
},
|
|
5235
5631
|
|
|
5236
5632
|
initPlaceholders: function () {
|
|
5237
|
-
this.getEditorElements().forEach(
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5633
|
+
this.getEditorElements().forEach(this.initElement, this);
|
|
5634
|
+
},
|
|
5635
|
+
|
|
5636
|
+
handleAddElement: function (event, editable) {
|
|
5637
|
+
this.initElement(editable);
|
|
5638
|
+
},
|
|
5639
|
+
|
|
5640
|
+
initElement: function (el) {
|
|
5641
|
+
if (!el.getAttribute('data-placeholder')) {
|
|
5642
|
+
el.setAttribute('data-placeholder', this.text);
|
|
5643
|
+
}
|
|
5644
|
+
this.updatePlaceholder(el);
|
|
5243
5645
|
},
|
|
5244
5646
|
|
|
5245
5647
|
destroy: function () {
|
|
5246
|
-
this.getEditorElements().forEach(
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5648
|
+
this.getEditorElements().forEach(this.cleanupElement, this);
|
|
5649
|
+
},
|
|
5650
|
+
|
|
5651
|
+
handleRemoveElement: function (event, editable) {
|
|
5652
|
+
this.cleanupElement(editable);
|
|
5653
|
+
},
|
|
5654
|
+
|
|
5655
|
+
cleanupElement: function (el) {
|
|
5656
|
+
if (el.getAttribute('data-placeholder') === this.text) {
|
|
5657
|
+
el.removeAttribute('data-placeholder');
|
|
5658
|
+
}
|
|
5251
5659
|
},
|
|
5252
5660
|
|
|
5253
5661
|
showPlaceholder: function (el) {
|
|
5254
5662
|
if (el) {
|
|
5255
|
-
|
|
5663
|
+
// https://github.com/yabwe/medium-editor/issues/234
|
|
5664
|
+
// In firefox, styling the placeholder with an absolutely positioned
|
|
5665
|
+
// pseudo element causes the cursor to appear in a bad location
|
|
5666
|
+
// when the element is completely empty, so apply a different class to
|
|
5667
|
+
// style it with a relatively positioned pseudo element
|
|
5668
|
+
if (MediumEditor.util.isFF && el.childNodes.length === 0) {
|
|
5669
|
+
el.classList.add('medium-editor-placeholder-relative');
|
|
5670
|
+
el.classList.remove('medium-editor-placeholder');
|
|
5671
|
+
} else {
|
|
5672
|
+
el.classList.add('medium-editor-placeholder');
|
|
5673
|
+
el.classList.remove('medium-editor-placeholder-relative');
|
|
5674
|
+
}
|
|
5256
5675
|
}
|
|
5257
5676
|
},
|
|
5258
5677
|
|
|
5259
5678
|
hidePlaceholder: function (el) {
|
|
5260
5679
|
if (el) {
|
|
5261
5680
|
el.classList.remove('medium-editor-placeholder');
|
|
5681
|
+
el.classList.remove('medium-editor-placeholder-relative');
|
|
5262
5682
|
}
|
|
5263
5683
|
},
|
|
5264
5684
|
|
|
5265
5685
|
updatePlaceholder: function (el, dontShow) {
|
|
5266
5686
|
// If the element has content, hide the placeholder
|
|
5267
|
-
if (el.querySelector('img, blockquote, ul, ol') || (el.textContent.replace(/^\s+|\s+$/g, '') !== '')) {
|
|
5687
|
+
if (el.querySelector('img, blockquote, ul, ol, table') || (el.textContent.replace(/^\s+|\s+$/g, '') !== '')) {
|
|
5268
5688
|
return this.hidePlaceholder(el);
|
|
5269
5689
|
}
|
|
5270
5690
|
|
|
@@ -5284,6 +5704,10 @@ MediumEditor.extensions = {};
|
|
|
5284
5704
|
|
|
5285
5705
|
// When the editor loses focus, check if the placeholder should be visible
|
|
5286
5706
|
this.subscribe('blur', this.handleBlur.bind(this));
|
|
5707
|
+
|
|
5708
|
+
// Need to know when elements are added/removed from the editor
|
|
5709
|
+
this.subscribe('addElement', this.handleAddElement.bind(this));
|
|
5710
|
+
this.subscribe('removeElement', this.handleRemoveElement.bind(this));
|
|
5287
5711
|
},
|
|
5288
5712
|
|
|
5289
5713
|
handleInput: function (event, element) {
|
|
@@ -5500,6 +5924,10 @@ MediumEditor.extensions = {};
|
|
|
5500
5924
|
|
|
5501
5925
|
// Toolbar accessors
|
|
5502
5926
|
|
|
5927
|
+
getInteractionElements: function () {
|
|
5928
|
+
return this.getToolbarElement();
|
|
5929
|
+
},
|
|
5930
|
+
|
|
5503
5931
|
getToolbarElement: function () {
|
|
5504
5932
|
if (!this.toolbar) {
|
|
5505
5933
|
this.toolbar = this.createToolbar();
|
|
@@ -5825,30 +6253,26 @@ MediumEditor.extensions = {};
|
|
|
5825
6253
|
|
|
5826
6254
|
setToolbarPosition: function () {
|
|
5827
6255
|
var container = this.base.getFocusedElement(),
|
|
5828
|
-
selection = this.window.getSelection()
|
|
5829
|
-
anchorPreview;
|
|
6256
|
+
selection = this.window.getSelection();
|
|
5830
6257
|
|
|
5831
6258
|
// If there isn't a valid selection, bail
|
|
5832
6259
|
if (!container) {
|
|
5833
6260
|
return this;
|
|
5834
6261
|
}
|
|
5835
6262
|
|
|
5836
|
-
if (this.static
|
|
5837
|
-
this.showToolbar();
|
|
5838
|
-
this.positionStaticToolbar(container);
|
|
5839
|
-
} else if (!selection.isCollapsed) {
|
|
6263
|
+
if (this.static || !selection.isCollapsed) {
|
|
5840
6264
|
this.showToolbar();
|
|
5841
6265
|
|
|
5842
6266
|
// we don't need any absolute positioning if relativeContainer is set
|
|
5843
6267
|
if (!this.relativeContainer) {
|
|
5844
|
-
this.
|
|
6268
|
+
if (this.static) {
|
|
6269
|
+
this.positionStaticToolbar(container);
|
|
6270
|
+
} else {
|
|
6271
|
+
this.positionToolbar(selection);
|
|
6272
|
+
}
|
|
5845
6273
|
}
|
|
5846
|
-
}
|
|
5847
|
-
|
|
5848
|
-
anchorPreview = this.base.getExtensionByName('anchor-preview');
|
|
5849
6274
|
|
|
5850
|
-
|
|
5851
|
-
anchorPreview.hidePreview();
|
|
6275
|
+
this.trigger('positionedToolbar', {}, this.base.getFocusedElement());
|
|
5852
6276
|
}
|
|
5853
6277
|
},
|
|
5854
6278
|
|
|
@@ -5927,35 +6351,66 @@ MediumEditor.extensions = {};
|
|
|
5927
6351
|
}
|
|
5928
6352
|
}
|
|
5929
6353
|
|
|
5930
|
-
var
|
|
5931
|
-
middleBoundary = (boundary.left + boundary.right) / 2,
|
|
6354
|
+
var containerWidth = this.window.innerWidth,
|
|
5932
6355
|
toolbarElement = this.getToolbarElement(),
|
|
5933
6356
|
toolbarHeight = toolbarElement.offsetHeight,
|
|
5934
6357
|
toolbarWidth = toolbarElement.offsetWidth,
|
|
5935
6358
|
halfOffsetWidth = toolbarWidth / 2,
|
|
5936
6359
|
buttonHeight = 50,
|
|
5937
|
-
defaultLeft = this.diffLeft - halfOffsetWidth
|
|
6360
|
+
defaultLeft = this.diffLeft - halfOffsetWidth,
|
|
6361
|
+
elementsContainer = this.getEditorOption('elementsContainer'),
|
|
6362
|
+
elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1,
|
|
6363
|
+
positions = {},
|
|
6364
|
+
relativeBoundary = {},
|
|
6365
|
+
middleBoundary, elementsContainerBoundary;
|
|
6366
|
+
|
|
6367
|
+
// If container element is absolute / fixed, recalculate boundaries to be relative to the container
|
|
6368
|
+
if (elementsContainerAbsolute) {
|
|
6369
|
+
elementsContainerBoundary = elementsContainer.getBoundingClientRect();
|
|
6370
|
+
['top', 'left'].forEach(function (key) {
|
|
6371
|
+
relativeBoundary[key] = boundary[key] - elementsContainerBoundary[key];
|
|
6372
|
+
});
|
|
6373
|
+
|
|
6374
|
+
relativeBoundary.width = boundary.width;
|
|
6375
|
+
relativeBoundary.height = boundary.height;
|
|
6376
|
+
boundary = relativeBoundary;
|
|
6377
|
+
|
|
6378
|
+
containerWidth = elementsContainerBoundary.width;
|
|
6379
|
+
|
|
6380
|
+
// Adjust top position according to container scroll position
|
|
6381
|
+
positions.top = elementsContainer.scrollTop;
|
|
6382
|
+
} else {
|
|
6383
|
+
// Adjust top position according to window scroll position
|
|
6384
|
+
positions.top = this.window.pageYOffset;
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6387
|
+
middleBoundary = boundary.left + boundary.width / 2;
|
|
6388
|
+
positions.top += boundary.top - toolbarHeight;
|
|
5938
6389
|
|
|
5939
6390
|
if (boundary.top < buttonHeight) {
|
|
5940
6391
|
toolbarElement.classList.add('medium-toolbar-arrow-over');
|
|
5941
6392
|
toolbarElement.classList.remove('medium-toolbar-arrow-under');
|
|
5942
|
-
|
|
6393
|
+
positions.top += buttonHeight + boundary.height - this.diffTop;
|
|
5943
6394
|
} else {
|
|
5944
6395
|
toolbarElement.classList.add('medium-toolbar-arrow-under');
|
|
5945
6396
|
toolbarElement.classList.remove('medium-toolbar-arrow-over');
|
|
5946
|
-
|
|
6397
|
+
positions.top += this.diffTop;
|
|
5947
6398
|
}
|
|
5948
6399
|
|
|
5949
6400
|
if (middleBoundary < halfOffsetWidth) {
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
} else if ((
|
|
5953
|
-
|
|
5954
|
-
|
|
6401
|
+
positions.left = defaultLeft + halfOffsetWidth;
|
|
6402
|
+
positions.right = 'initial';
|
|
6403
|
+
} else if ((containerWidth - middleBoundary) < halfOffsetWidth) {
|
|
6404
|
+
positions.left = 'auto';
|
|
6405
|
+
positions.right = 0;
|
|
5955
6406
|
} else {
|
|
5956
|
-
|
|
5957
|
-
|
|
6407
|
+
positions.left = defaultLeft + middleBoundary;
|
|
6408
|
+
positions.right = 'initial';
|
|
5958
6409
|
}
|
|
6410
|
+
|
|
6411
|
+
['top', 'left', 'right'].forEach(function (key) {
|
|
6412
|
+
toolbarElement.style[key] = positions[key] + (isNaN(positions[key]) ? '' : 'px');
|
|
6413
|
+
});
|
|
5959
6414
|
}
|
|
5960
6415
|
});
|
|
5961
6416
|
|
|
@@ -6162,6 +6617,31 @@ MediumEditor.extensions = {};
|
|
|
6162
6617
|
// then pressing backspace key should change the <blockquote> to a <p> tag
|
|
6163
6618
|
event.preventDefault();
|
|
6164
6619
|
MediumEditor.util.execFormatBlock(this.options.ownerDocument, 'p');
|
|
6620
|
+
} else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) &&
|
|
6621
|
+
(MediumEditor.util.getClosestTag(node, 'blockquote') !== false) &&
|
|
6622
|
+
MediumEditor.selection.getCaretOffsets(node).right === 0) {
|
|
6623
|
+
|
|
6624
|
+
// when cursor is at the end of <blockquote>,
|
|
6625
|
+
// then pressing enter key should create <p> tag, not <blockquote>
|
|
6626
|
+
p = this.options.ownerDocument.createElement('p');
|
|
6627
|
+
p.innerHTML = '<br>';
|
|
6628
|
+
node.parentElement.insertBefore(p, node.nextSibling);
|
|
6629
|
+
|
|
6630
|
+
// move the cursor into the new paragraph
|
|
6631
|
+
MediumEditor.selection.moveCursor(this.options.ownerDocument, p);
|
|
6632
|
+
|
|
6633
|
+
event.preventDefault();
|
|
6634
|
+
} else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) &&
|
|
6635
|
+
MediumEditor.util.isMediumEditorElement(node.parentElement) &&
|
|
6636
|
+
!node.previousElementSibling &&
|
|
6637
|
+
node.nextElementSibling &&
|
|
6638
|
+
isEmpty.test(node.innerHTML)) {
|
|
6639
|
+
|
|
6640
|
+
// when cursor is in the first element, it's empty and user presses backspace,
|
|
6641
|
+
// do delete action instead to get rid of the first element and move caret to 2nd
|
|
6642
|
+
event.preventDefault();
|
|
6643
|
+
MediumEditor.selection.moveCursor(this.options.ownerDocument, node.nextSibling);
|
|
6644
|
+
node.parentElement.removeChild(node);
|
|
6165
6645
|
}
|
|
6166
6646
|
}
|
|
6167
6647
|
|
|
@@ -6173,7 +6653,9 @@ MediumEditor.extensions = {};
|
|
|
6173
6653
|
return;
|
|
6174
6654
|
}
|
|
6175
6655
|
|
|
6176
|
-
|
|
6656
|
+
// https://github.com/yabwe/medium-editor/issues/994
|
|
6657
|
+
// Firefox thrown an error when calling `formatBlock` on an empty editable blockContainer that's not a <div>
|
|
6658
|
+
if (MediumEditor.util.isMediumEditorElement(node) && node.children.length === 0 && !MediumEditor.util.isBlockContainer(node)) {
|
|
6177
6659
|
this.options.ownerDocument.execCommand('formatBlock', false, 'p');
|
|
6178
6660
|
}
|
|
6179
6661
|
|
|
@@ -6194,6 +6676,13 @@ MediumEditor.extensions = {};
|
|
|
6194
6676
|
}
|
|
6195
6677
|
}
|
|
6196
6678
|
|
|
6679
|
+
function handleEditableInput(event, editable) {
|
|
6680
|
+
var textarea = editable.parentNode.querySelector('textarea[medium-editor-textarea-id="' + editable.getAttribute('medium-editor-textarea-id') + '"]');
|
|
6681
|
+
if (textarea) {
|
|
6682
|
+
textarea.value = editable.innerHTML.trim();
|
|
6683
|
+
}
|
|
6684
|
+
}
|
|
6685
|
+
|
|
6197
6686
|
// Internal helper methods which shouldn't be exposed externally
|
|
6198
6687
|
|
|
6199
6688
|
function addToEditors(win) {
|
|
@@ -6227,30 +6716,50 @@ MediumEditor.extensions = {};
|
|
|
6227
6716
|
win._mediumEditors[this.id] = null;
|
|
6228
6717
|
}
|
|
6229
6718
|
|
|
6230
|
-
function createElementsArray(selector) {
|
|
6719
|
+
function createElementsArray(selector, doc, filterEditorElements) {
|
|
6720
|
+
var elements = [];
|
|
6721
|
+
|
|
6231
6722
|
if (!selector) {
|
|
6232
6723
|
selector = [];
|
|
6233
6724
|
}
|
|
6234
6725
|
// If string, use as query selector
|
|
6235
6726
|
if (typeof selector === 'string') {
|
|
6236
|
-
selector =
|
|
6727
|
+
selector = doc.querySelectorAll(selector);
|
|
6237
6728
|
}
|
|
6238
6729
|
// If element, put into array
|
|
6239
6730
|
if (MediumEditor.util.isElement(selector)) {
|
|
6240
6731
|
selector = [selector];
|
|
6241
6732
|
}
|
|
6242
|
-
// Convert NodeList (or other array like object) into an array
|
|
6243
|
-
var elements = Array.prototype.slice.apply(selector);
|
|
6244
6733
|
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6734
|
+
if (filterEditorElements) {
|
|
6735
|
+
// Remove elements that have already been initialized by the editor
|
|
6736
|
+
// selecotr might not be an array (ie NodeList) so use for loop
|
|
6737
|
+
for (var i = 0; i < selector.length; i++) {
|
|
6738
|
+
var el = selector[i];
|
|
6739
|
+
if (MediumEditor.util.isElement(el) &&
|
|
6740
|
+
!el.getAttribute('data-medium-editor-element') &&
|
|
6741
|
+
!el.getAttribute('medium-editor-textarea-id')) {
|
|
6742
|
+
elements.push(el);
|
|
6743
|
+
}
|
|
6252
6744
|
}
|
|
6253
|
-
}
|
|
6745
|
+
} else {
|
|
6746
|
+
// Convert NodeList (or other array like object) into an array
|
|
6747
|
+
elements = Array.prototype.slice.apply(selector);
|
|
6748
|
+
}
|
|
6749
|
+
|
|
6750
|
+
return elements;
|
|
6751
|
+
}
|
|
6752
|
+
|
|
6753
|
+
function cleanupTextareaElement(element) {
|
|
6754
|
+
var textarea = element.parentNode.querySelector('textarea[medium-editor-textarea-id="' + element.getAttribute('medium-editor-textarea-id') + '"]');
|
|
6755
|
+
if (textarea) {
|
|
6756
|
+
// Un-hide the textarea
|
|
6757
|
+
textarea.classList.remove('medium-editor-hidden');
|
|
6758
|
+
textarea.removeAttribute('medium-editor-textarea-id');
|
|
6759
|
+
}
|
|
6760
|
+
if (element.parentNode) {
|
|
6761
|
+
element.parentNode.removeChild(element);
|
|
6762
|
+
}
|
|
6254
6763
|
}
|
|
6255
6764
|
|
|
6256
6765
|
function setExtensionDefaults(extension, defaults) {
|
|
@@ -6328,17 +6837,17 @@ MediumEditor.extensions = {};
|
|
|
6328
6837
|
return !this.options.extensions['imageDragging'];
|
|
6329
6838
|
}
|
|
6330
6839
|
|
|
6331
|
-
function createContentEditable(textarea
|
|
6840
|
+
function createContentEditable(textarea) {
|
|
6332
6841
|
var div = this.options.ownerDocument.createElement('div'),
|
|
6333
6842
|
now = Date.now(),
|
|
6334
|
-
uniqueId = 'medium-editor-' + now
|
|
6843
|
+
uniqueId = 'medium-editor-' + now,
|
|
6335
6844
|
atts = textarea.attributes;
|
|
6336
6845
|
|
|
6337
6846
|
// Some browsers can move pretty fast, since we're using a timestamp
|
|
6338
6847
|
// to make a unique-id, ensure that the id is actually unique on the page
|
|
6339
6848
|
while (this.options.ownerDocument.getElementById(uniqueId)) {
|
|
6340
6849
|
now++;
|
|
6341
|
-
uniqueId = 'medium-editor-' + now
|
|
6850
|
+
uniqueId = 'medium-editor-' + now;
|
|
6342
6851
|
}
|
|
6343
6852
|
|
|
6344
6853
|
div.className = textarea.className;
|
|
@@ -6355,6 +6864,16 @@ MediumEditor.extensions = {};
|
|
|
6355
6864
|
}
|
|
6356
6865
|
}
|
|
6357
6866
|
|
|
6867
|
+
// If textarea has a form, listen for reset on the form to clear
|
|
6868
|
+
// the content of the created div
|
|
6869
|
+
if (textarea.form) {
|
|
6870
|
+
this.on(textarea.form, 'reset', function (event) {
|
|
6871
|
+
if (!event.defaultPrevented) {
|
|
6872
|
+
this.resetContent(this.options.ownerDocument.getElementById(uniqueId));
|
|
6873
|
+
}
|
|
6874
|
+
}.bind(this));
|
|
6875
|
+
}
|
|
6876
|
+
|
|
6358
6877
|
textarea.classList.add('medium-editor-hidden');
|
|
6359
6878
|
textarea.parentNode.insertBefore(
|
|
6360
6879
|
div,
|
|
@@ -6364,37 +6883,57 @@ MediumEditor.extensions = {};
|
|
|
6364
6883
|
return div;
|
|
6365
6884
|
}
|
|
6366
6885
|
|
|
6367
|
-
function
|
|
6368
|
-
|
|
6886
|
+
function initElement(element, editorId) {
|
|
6887
|
+
if (!element.getAttribute('data-medium-editor-element')) {
|
|
6888
|
+
if (element.nodeName.toLowerCase() === 'textarea') {
|
|
6889
|
+
element = createContentEditable.call(this, element);
|
|
6890
|
+
|
|
6891
|
+
// Make sure we only attach to editableInput once for <textarea> elements
|
|
6892
|
+
if (!this.instanceHandleEditableInput) {
|
|
6893
|
+
this.instanceHandleEditableInput = handleEditableInput.bind(this);
|
|
6894
|
+
this.subscribe('editableInput', this.instanceHandleEditableInput);
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6369
6897
|
|
|
6370
|
-
this.elements.forEach(function (element, index) {
|
|
6371
6898
|
if (!this.options.disableEditing && !element.getAttribute('data-disable-editing')) {
|
|
6372
6899
|
element.setAttribute('contentEditable', true);
|
|
6373
6900
|
element.setAttribute('spellcheck', this.options.spellcheck);
|
|
6374
6901
|
}
|
|
6375
|
-
element.setAttribute('data-medium-editor-element', true);
|
|
6376
|
-
element.setAttribute('role', 'textbox');
|
|
6377
|
-
element.setAttribute('aria-multiline', true);
|
|
6378
|
-
element.setAttribute('medium-editor-index', index);
|
|
6379
6902
|
|
|
6380
|
-
|
|
6381
|
-
|
|
6903
|
+
// Make sure we only attach to editableKeydownEnter once for disable-return options
|
|
6904
|
+
if (!this.instanceHandleEditableKeydownEnter) {
|
|
6905
|
+
if (element.getAttribute('data-disable-return') || element.getAttribute('data-disable-double-return')) {
|
|
6906
|
+
this.instanceHandleEditableKeydownEnter = handleDisabledEnterKeydown.bind(this);
|
|
6907
|
+
this.subscribe('editableKeydownEnter', this.instanceHandleEditableKeydownEnter);
|
|
6908
|
+
}
|
|
6382
6909
|
}
|
|
6383
|
-
}, this);
|
|
6384
6910
|
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6911
|
+
// if we're not disabling return, add a handler to help handle cleanup
|
|
6912
|
+
// for certain cases when enter is pressed
|
|
6913
|
+
if (!this.options.disableReturn && !element.getAttribute('data-disable-return')) {
|
|
6914
|
+
this.on(element, 'keyup', handleKeyup.bind(this));
|
|
6915
|
+
}
|
|
6916
|
+
|
|
6917
|
+
var elementId = MediumEditor.util.guid();
|
|
6918
|
+
|
|
6919
|
+
element.setAttribute('data-medium-editor-element', true);
|
|
6920
|
+
element.classList.add('medium-editor-element');
|
|
6921
|
+
element.setAttribute('role', 'textbox');
|
|
6922
|
+
element.setAttribute('aria-multiline', true);
|
|
6923
|
+
element.setAttribute('data-medium-editor-editor-index', editorId);
|
|
6924
|
+
// TODO: Merge data-medium-editor-element and medium-editor-index attributes for 6.0.0
|
|
6925
|
+
// medium-editor-index is not named correctly anymore and can be re-purposed to signify
|
|
6926
|
+
// whether the element has been initialized or not
|
|
6927
|
+
element.setAttribute('medium-editor-index', elementId);
|
|
6928
|
+
initialContent[elementId] = element.innerHTML;
|
|
6929
|
+
|
|
6930
|
+
this.events.attachAllEventsToElement(element);
|
|
6392
6931
|
}
|
|
6932
|
+
|
|
6933
|
+
return element;
|
|
6393
6934
|
}
|
|
6394
6935
|
|
|
6395
6936
|
function attachHandlers() {
|
|
6396
|
-
var i;
|
|
6397
|
-
|
|
6398
6937
|
// attach to tabs
|
|
6399
6938
|
this.subscribe('editableKeydownTab', handleTabKeydown.bind(this));
|
|
6400
6939
|
|
|
@@ -6407,27 +6946,14 @@ MediumEditor.extensions = {};
|
|
|
6407
6946
|
this.subscribe('editableKeydownSpace', handleDisableExtraSpaces.bind(this));
|
|
6408
6947
|
}
|
|
6409
6948
|
|
|
6410
|
-
//
|
|
6411
|
-
if (this.
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
this.subscribe('editableKeydownEnter', handleDisabledEnterKeydown.bind(this));
|
|
6417
|
-
break;
|
|
6418
|
-
}
|
|
6949
|
+
// Make sure we only attach to editableKeydownEnter once for disable-return options
|
|
6950
|
+
if (!this.instanceHandleEditableKeydownEnter) {
|
|
6951
|
+
// disabling return or double return
|
|
6952
|
+
if (this.options.disableReturn || this.options.disableDoubleReturn) {
|
|
6953
|
+
this.instanceHandleEditableKeydownEnter = handleDisabledEnterKeydown.bind(this);
|
|
6954
|
+
this.subscribe('editableKeydownEnter', this.instanceHandleEditableKeydownEnter);
|
|
6419
6955
|
}
|
|
6420
6956
|
}
|
|
6421
|
-
|
|
6422
|
-
// if we're not disabling return, add a handler to help handle cleanup
|
|
6423
|
-
// for certain cases when enter is pressed
|
|
6424
|
-
if (!this.options.disableReturn) {
|
|
6425
|
-
this.elements.forEach(function (element) {
|
|
6426
|
-
if (!element.getAttribute('data-disable-return')) {
|
|
6427
|
-
this.on(element, 'keyup', handleKeyup.bind(this));
|
|
6428
|
-
}
|
|
6429
|
-
}, this);
|
|
6430
|
-
}
|
|
6431
6957
|
}
|
|
6432
6958
|
|
|
6433
6959
|
function initExtensions() {
|
|
@@ -6519,7 +7045,8 @@ MediumEditor.extensions = {};
|
|
|
6519
7045
|
/*jslint regexp: true*/
|
|
6520
7046
|
var appendAction = /^append-(.+)$/gi,
|
|
6521
7047
|
justifyAction = /justify([A-Za-z]*)$/g, /* Detecting if is justifyCenter|Right|Left */
|
|
6522
|
-
match
|
|
7048
|
+
match,
|
|
7049
|
+
cmdValueArgument;
|
|
6523
7050
|
/*jslint regexp: false*/
|
|
6524
7051
|
|
|
6525
7052
|
// Actions starting with 'append-' should attempt to format a block of text ('formatBlock') using a specific
|
|
@@ -6530,11 +7057,21 @@ MediumEditor.extensions = {};
|
|
|
6530
7057
|
}
|
|
6531
7058
|
|
|
6532
7059
|
if (action === 'fontSize') {
|
|
6533
|
-
|
|
7060
|
+
// TODO: Deprecate support for opts.size in 6.0.0
|
|
7061
|
+
if (opts.size) {
|
|
7062
|
+
MediumEditor.util.deprecated('.size option for fontSize command', '.value', '6.0.0');
|
|
7063
|
+
}
|
|
7064
|
+
cmdValueArgument = opts.value || opts.size;
|
|
7065
|
+
return this.options.ownerDocument.execCommand('fontSize', false, cmdValueArgument);
|
|
6534
7066
|
}
|
|
6535
7067
|
|
|
6536
7068
|
if (action === 'fontName') {
|
|
6537
|
-
|
|
7069
|
+
// TODO: Deprecate support for opts.name in 6.0.0
|
|
7070
|
+
if (opts.name) {
|
|
7071
|
+
MediumEditor.util.deprecated('.name option for fontName command', '.value', '6.0.0');
|
|
7072
|
+
}
|
|
7073
|
+
cmdValueArgument = opts.value || opts.name;
|
|
7074
|
+
return this.options.ownerDocument.execCommand('fontName', false, cmdValueArgument);
|
|
6538
7075
|
}
|
|
6539
7076
|
|
|
6540
7077
|
if (action === 'createLink') {
|
|
@@ -6558,7 +7095,8 @@ MediumEditor.extensions = {};
|
|
|
6558
7095
|
return result;
|
|
6559
7096
|
}
|
|
6560
7097
|
|
|
6561
|
-
|
|
7098
|
+
cmdValueArgument = opts && opts.value;
|
|
7099
|
+
return this.options.ownerDocument.execCommand(action, false, cmdValueArgument);
|
|
6562
7100
|
}
|
|
6563
7101
|
|
|
6564
7102
|
/* If we've just justified text within a container block
|
|
@@ -6606,6 +7144,8 @@ MediumEditor.extensions = {};
|
|
|
6606
7144
|
}
|
|
6607
7145
|
}
|
|
6608
7146
|
|
|
7147
|
+
var initialContent = {};
|
|
7148
|
+
|
|
6609
7149
|
MediumEditor.prototype = {
|
|
6610
7150
|
// NOT DOCUMENTED - exposed for backwards compatability
|
|
6611
7151
|
init: function (elements, options) {
|
|
@@ -6624,19 +7164,19 @@ MediumEditor.extensions = {};
|
|
|
6624
7164
|
return;
|
|
6625
7165
|
}
|
|
6626
7166
|
|
|
6627
|
-
|
|
7167
|
+
addToEditors.call(this, this.options.contentWindow);
|
|
7168
|
+
this.events = new MediumEditor.Events(this);
|
|
7169
|
+
this.elements = [];
|
|
7170
|
+
|
|
7171
|
+
this.addElements(this.origElements);
|
|
6628
7172
|
|
|
6629
7173
|
if (this.elements.length === 0) {
|
|
6630
7174
|
return;
|
|
6631
7175
|
}
|
|
6632
7176
|
|
|
6633
7177
|
this.isActive = true;
|
|
6634
|
-
addToEditors.call(this, this.options.contentWindow);
|
|
6635
|
-
|
|
6636
|
-
this.events = new MediumEditor.Events(this);
|
|
6637
7178
|
|
|
6638
7179
|
// Call initialization helpers
|
|
6639
|
-
initElements.call(this);
|
|
6640
7180
|
initExtensions.call(this);
|
|
6641
7181
|
attachHandlers.call(this);
|
|
6642
7182
|
},
|
|
@@ -6666,45 +7206,52 @@ MediumEditor.extensions = {};
|
|
|
6666
7206
|
element.removeAttribute('contentEditable');
|
|
6667
7207
|
element.removeAttribute('spellcheck');
|
|
6668
7208
|
element.removeAttribute('data-medium-editor-element');
|
|
7209
|
+
element.classList.remove('medium-editor-element');
|
|
6669
7210
|
element.removeAttribute('role');
|
|
6670
7211
|
element.removeAttribute('aria-multiline');
|
|
6671
7212
|
element.removeAttribute('medium-editor-index');
|
|
7213
|
+
element.removeAttribute('data-medium-editor-editor-index');
|
|
6672
7214
|
|
|
6673
7215
|
// Remove any elements created for textareas
|
|
6674
|
-
if (element.
|
|
6675
|
-
|
|
6676
|
-
if (textarea) {
|
|
6677
|
-
// Un-hide the textarea
|
|
6678
|
-
textarea.classList.remove('medium-editor-hidden');
|
|
6679
|
-
}
|
|
6680
|
-
if (element.parentNode) {
|
|
6681
|
-
element.parentNode.removeChild(element);
|
|
6682
|
-
}
|
|
7216
|
+
if (element.getAttribute('medium-editor-textarea-id')) {
|
|
7217
|
+
cleanupTextareaElement(element);
|
|
6683
7218
|
}
|
|
6684
7219
|
}, this);
|
|
6685
7220
|
this.elements = [];
|
|
7221
|
+
this.instanceHandleEditableKeydownEnter = null;
|
|
7222
|
+
this.instanceHandleEditableInput = null;
|
|
6686
7223
|
|
|
6687
7224
|
removeFromEditors.call(this, this.options.contentWindow);
|
|
6688
7225
|
},
|
|
6689
7226
|
|
|
6690
7227
|
on: function (target, event, listener, useCapture) {
|
|
6691
7228
|
this.events.attachDOMEvent(target, event, listener, useCapture);
|
|
7229
|
+
|
|
7230
|
+
return this;
|
|
6692
7231
|
},
|
|
6693
7232
|
|
|
6694
7233
|
off: function (target, event, listener, useCapture) {
|
|
6695
7234
|
this.events.detachDOMEvent(target, event, listener, useCapture);
|
|
7235
|
+
|
|
7236
|
+
return this;
|
|
6696
7237
|
},
|
|
6697
7238
|
|
|
6698
7239
|
subscribe: function (event, listener) {
|
|
6699
7240
|
this.events.attachCustomEvent(event, listener);
|
|
7241
|
+
|
|
7242
|
+
return this;
|
|
6700
7243
|
},
|
|
6701
7244
|
|
|
6702
7245
|
unsubscribe: function (event, listener) {
|
|
6703
7246
|
this.events.detachCustomEvent(event, listener);
|
|
7247
|
+
|
|
7248
|
+
return this;
|
|
6704
7249
|
},
|
|
6705
7250
|
|
|
6706
7251
|
trigger: function (name, data, editable) {
|
|
6707
7252
|
this.events.triggerCustomEvent(name, data, editable);
|
|
7253
|
+
|
|
7254
|
+
return this;
|
|
6708
7255
|
},
|
|
6709
7256
|
|
|
6710
7257
|
delay: function (fn) {
|
|
@@ -6719,8 +7266,10 @@ MediumEditor.extensions = {};
|
|
|
6719
7266
|
serialize: function () {
|
|
6720
7267
|
var i,
|
|
6721
7268
|
elementid,
|
|
6722
|
-
content = {}
|
|
6723
|
-
|
|
7269
|
+
content = {},
|
|
7270
|
+
len = this.elements.length;
|
|
7271
|
+
|
|
7272
|
+
for (i = 0; i < len; i += 1) {
|
|
6724
7273
|
elementid = (this.elements[i].id !== '') ? this.elements[i].id : 'element-' + i;
|
|
6725
7274
|
content[elementid] = {
|
|
6726
7275
|
value: this.elements[i].innerHTML.trim()
|
|
@@ -6954,7 +7503,8 @@ MediumEditor.extensions = {};
|
|
|
6954
7503
|
|
|
6955
7504
|
createLink: function (opts) {
|
|
6956
7505
|
var currentEditor = MediumEditor.selection.getSelectionElement(this.options.contentWindow),
|
|
6957
|
-
customEvent = {}
|
|
7506
|
+
customEvent = {},
|
|
7507
|
+
targetUrl;
|
|
6958
7508
|
|
|
6959
7509
|
// Make sure the selection is within an element this editor is tracking
|
|
6960
7510
|
if (this.elements.indexOf(currentEditor) === -1) {
|
|
@@ -6963,7 +7513,12 @@ MediumEditor.extensions = {};
|
|
|
6963
7513
|
|
|
6964
7514
|
try {
|
|
6965
7515
|
this.events.disableCustomEvent('editableInput');
|
|
6966
|
-
|
|
7516
|
+
// TODO: Deprecate support for opts.url in 6.0.0
|
|
7517
|
+
if (opts.url) {
|
|
7518
|
+
MediumEditor.util.deprecated('.url option for createLink', '.value', '6.0.0');
|
|
7519
|
+
}
|
|
7520
|
+
targetUrl = opts.url || opts.value;
|
|
7521
|
+
if (targetUrl && targetUrl.trim().length > 0) {
|
|
6967
7522
|
var currentSelection = this.options.contentWindow.getSelection();
|
|
6968
7523
|
if (currentSelection) {
|
|
6969
7524
|
var currRange = currentSelection.getRangeAt(0),
|
|
@@ -7055,7 +7610,7 @@ MediumEditor.extensions = {};
|
|
|
7055
7610
|
}
|
|
7056
7611
|
|
|
7057
7612
|
// Creates the link in the document fragment
|
|
7058
|
-
MediumEditor.util.createLink(this.options.ownerDocument, textNodes,
|
|
7613
|
+
MediumEditor.util.createLink(this.options.ownerDocument, textNodes, targetUrl.trim());
|
|
7059
7614
|
|
|
7060
7615
|
// Chrome trims the leading whitespaces when inserting HTML, which messes up restoring the selection.
|
|
7061
7616
|
var leadingWhitespacesCount = (fragment.firstChild.innerHTML.match(/^\s+/) || [''])[0].length;
|
|
@@ -7067,13 +7622,13 @@ MediumEditor.extensions = {};
|
|
|
7067
7622
|
|
|
7068
7623
|
this.importSelection(exportedSelection);
|
|
7069
7624
|
} else {
|
|
7070
|
-
this.options.ownerDocument.execCommand('createLink', false,
|
|
7625
|
+
this.options.ownerDocument.execCommand('createLink', false, targetUrl);
|
|
7071
7626
|
}
|
|
7072
7627
|
|
|
7073
7628
|
if (this.options.targetBlank || opts.target === '_blank') {
|
|
7074
|
-
MediumEditor.util.setTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument),
|
|
7629
|
+
MediumEditor.util.setTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument), targetUrl);
|
|
7075
7630
|
} else {
|
|
7076
|
-
MediumEditor.util.removeTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument),
|
|
7631
|
+
MediumEditor.util.removeTargetBlank(MediumEditor.selection.getSelectionStart(this.options.ownerDocument), targetUrl);
|
|
7077
7632
|
}
|
|
7078
7633
|
|
|
7079
7634
|
if (opts.buttonClass) {
|
|
@@ -7085,7 +7640,7 @@ MediumEditor.extensions = {};
|
|
|
7085
7640
|
if (this.options.targetBlank || opts.target === '_blank' || opts.buttonClass) {
|
|
7086
7641
|
customEvent = this.options.ownerDocument.createEvent('HTMLEvents');
|
|
7087
7642
|
customEvent.initEvent('input', true, true, this.options.contentWindow);
|
|
7088
|
-
for (var i = 0
|
|
7643
|
+
for (var i = 0, len = this.elements.length; i < len; i += 1) {
|
|
7089
7644
|
this.elements[i].dispatchEvent(customEvent);
|
|
7090
7645
|
}
|
|
7091
7646
|
}
|
|
@@ -7110,9 +7665,98 @@ MediumEditor.extensions = {};
|
|
|
7110
7665
|
if (this.elements[index]) {
|
|
7111
7666
|
var target = this.elements[index];
|
|
7112
7667
|
target.innerHTML = html;
|
|
7113
|
-
this.
|
|
7668
|
+
this.checkContentChanged(target);
|
|
7669
|
+
}
|
|
7670
|
+
},
|
|
7671
|
+
|
|
7672
|
+
getContent: function (index) {
|
|
7673
|
+
index = index || 0;
|
|
7674
|
+
|
|
7675
|
+
if (this.elements[index]) {
|
|
7676
|
+
return this.elements[index].innerHTML.trim();
|
|
7677
|
+
}
|
|
7678
|
+
return null;
|
|
7679
|
+
},
|
|
7680
|
+
|
|
7681
|
+
checkContentChanged: function (editable) {
|
|
7682
|
+
editable = editable || MediumEditor.selection.getSelectionElement(this.options.contentWindow);
|
|
7683
|
+
this.events.updateInput(editable, { target: editable, currentTarget: editable });
|
|
7684
|
+
},
|
|
7685
|
+
|
|
7686
|
+
resetContent: function (element) {
|
|
7687
|
+
// For all elements that exist in the this.elements array, we can assume:
|
|
7688
|
+
// - Its initial content has been set in the initialContent object
|
|
7689
|
+
// - It has a medium-editor-index attribute which is the key value in the initialContent object
|
|
7690
|
+
|
|
7691
|
+
if (element) {
|
|
7692
|
+
var index = this.elements.indexOf(element);
|
|
7693
|
+
if (index !== -1) {
|
|
7694
|
+
this.setContent(initialContent[element.getAttribute('medium-editor-index')], index);
|
|
7695
|
+
}
|
|
7696
|
+
return;
|
|
7697
|
+
}
|
|
7698
|
+
|
|
7699
|
+
this.elements.forEach(function (el, idx) {
|
|
7700
|
+
this.setContent(initialContent[el.getAttribute('medium-editor-index')], idx);
|
|
7701
|
+
}, this);
|
|
7702
|
+
},
|
|
7703
|
+
|
|
7704
|
+
addElements: function (selector) {
|
|
7705
|
+
// Convert elements into an array
|
|
7706
|
+
var elements = createElementsArray(selector, this.options.ownerDocument, true);
|
|
7707
|
+
|
|
7708
|
+
// Do we have elements to add now?
|
|
7709
|
+
if (elements.length === 0) {
|
|
7710
|
+
return false;
|
|
7114
7711
|
}
|
|
7712
|
+
|
|
7713
|
+
elements.forEach(function (element) {
|
|
7714
|
+
// Initialize all new elements (we check that in those functions don't worry)
|
|
7715
|
+
element = initElement.call(this, element, this.id);
|
|
7716
|
+
|
|
7717
|
+
// Add new elements to our internal elements array
|
|
7718
|
+
this.elements.push(element);
|
|
7719
|
+
|
|
7720
|
+
// Trigger event so extensions can know when an element has been added
|
|
7721
|
+
this.trigger('addElement', { target: element, currentTarget: element }, element);
|
|
7722
|
+
}, this);
|
|
7723
|
+
},
|
|
7724
|
+
|
|
7725
|
+
removeElements: function (selector) {
|
|
7726
|
+
// Convert elements into an array
|
|
7727
|
+
var elements = createElementsArray(selector, this.options.ownerDocument),
|
|
7728
|
+
toRemove = elements.map(function (el) {
|
|
7729
|
+
// For textareas, make sure we're looking at the editor div and not the textarea itself
|
|
7730
|
+
if (el.getAttribute('medium-editor-textarea-id') && el.parentNode) {
|
|
7731
|
+
return el.parentNode.querySelector('div[medium-editor-textarea-id="' + el.getAttribute('medium-editor-textarea-id') + '"]');
|
|
7732
|
+
} else {
|
|
7733
|
+
return el;
|
|
7734
|
+
}
|
|
7735
|
+
});
|
|
7736
|
+
|
|
7737
|
+
this.elements = this.elements.filter(function (element) {
|
|
7738
|
+
// If this is an element we want to remove
|
|
7739
|
+
if (toRemove.indexOf(element) !== -1) {
|
|
7740
|
+
this.events.cleanupElement(element);
|
|
7741
|
+
if (element.getAttribute('medium-editor-textarea-id')) {
|
|
7742
|
+
cleanupTextareaElement(element);
|
|
7743
|
+
}
|
|
7744
|
+
// Trigger event so extensions can clean-up elements that are being removed
|
|
7745
|
+
this.trigger('removeElement', { target: element, currentTarget: element }, element);
|
|
7746
|
+
return false;
|
|
7747
|
+
}
|
|
7748
|
+
return true;
|
|
7749
|
+
}, this);
|
|
7750
|
+
}
|
|
7751
|
+
};
|
|
7752
|
+
|
|
7753
|
+
MediumEditor.getEditorFromElement = function (element) {
|
|
7754
|
+
var index = element.getAttribute('data-medium-editor-editor-index'),
|
|
7755
|
+
win = element && element.ownerDocument && (element.ownerDocument.defaultView || element.ownerDocument.parentWindow);
|
|
7756
|
+
if (win && win._mediumEditors && win._mediumEditors[index]) {
|
|
7757
|
+
return win._mediumEditors[index];
|
|
7115
7758
|
}
|
|
7759
|
+
return null;
|
|
7116
7760
|
};
|
|
7117
7761
|
}());
|
|
7118
7762
|
|
|
@@ -7154,7 +7798,7 @@ MediumEditor.parseVersionString = function (release) {
|
|
|
7154
7798
|
|
|
7155
7799
|
MediumEditor.version = MediumEditor.parseVersionString.call(this, ({
|
|
7156
7800
|
// grunt-bump looks for this:
|
|
7157
|
-
'version': '5.
|
|
7801
|
+
'version': '5.22.0'
|
|
7158
7802
|
}).version);
|
|
7159
7803
|
|
|
7160
7804
|
return MediumEditor;
|