medium-editor-rails 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +1 -1
- data/lib/medium-editor-rails/version.rb +2 -2
- data/vendor/assets/javascripts/medium-editor.js +359 -58
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c733d77c3745960169613a029d7f9bbc675b21ae
|
4
|
+
data.tar.gz: 2b3d08541f21c65ba1cc5551a7cf01bdccbe3167
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae76fe9f412d723b6b7b1645501067aa87be9fefc84da9f265ad0c82d2c014e8e3b71979ec9e1e020855b214f19130f8df87714e96d23b7844de344f1a8d14f4
|
7
|
+
data.tar.gz: 4e819110c8fad3e1a4fd2df083a5d322a601c5e730d164a2adb44e82fb7e5c88d632823cb4fdf5b68fde69684dd6a8e981a2ea014a027fd2d63dc3401bda863b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
|
2
2
|
#### [Current]
|
3
|
+
* [fe79879](../../commit/fe79879) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
|
+
|
5
|
+
#### 0.7.0
|
6
|
+
* [5ebaca5](../../commit/5ebaca5) - __(Ahmet Sezgin Duran)__ Bump versions 0.7.0 and 1.7.5
|
3
7
|
* [99ba025](../../commit/99ba025) - __(Ahmet Sezgin Duran)__ Update Medium Editor files
|
4
8
|
|
5
9
|
#### 0.6.0
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ This gem integrates [Medium Editor](https://github.com/daviferreira/medium-edito
|
|
7
7
|
|
8
8
|
## Version
|
9
9
|
|
10
|
-
The latest version of Medium Editor bundled by this gem is [1.
|
10
|
+
The latest version of Medium Editor bundled by this gem is [1.8.0](https://github.com/daviferreira/medium-editor/releases)
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
@@ -85,12 +85,20 @@ if (typeof module === 'object') {
|
|
85
85
|
return html;
|
86
86
|
}
|
87
87
|
|
88
|
+
// https://github.com/jashkenas/underscore
|
89
|
+
function isElement(obj) {
|
90
|
+
return !!(obj && obj.nodeType === 1);
|
91
|
+
}
|
92
|
+
|
88
93
|
MediumEditor.prototype = {
|
89
94
|
defaults: {
|
90
95
|
allowMultiParagraphSelection: true,
|
91
96
|
anchorInputPlaceholder: 'Paste or type a link',
|
97
|
+
anchorPreviewHideDelay: 500,
|
92
98
|
buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote'],
|
93
99
|
buttonLabels: false,
|
100
|
+
checkLinkFormat: false,
|
101
|
+
cleanPastedHTML: false,
|
94
102
|
delay: 0,
|
95
103
|
diffLeft: 0,
|
96
104
|
diffTop: -10,
|
@@ -102,7 +110,7 @@ if (typeof module === 'object') {
|
|
102
110
|
placeholder: 'Type your text',
|
103
111
|
secondHeader: 'h4',
|
104
112
|
targetBlank: false,
|
105
|
-
|
113
|
+
extensions: {}
|
106
114
|
},
|
107
115
|
|
108
116
|
// http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562
|
@@ -141,7 +149,7 @@ if (typeof module === 'object') {
|
|
141
149
|
this.elements[i].setAttribute('data-placeholder', this.options.placeholder);
|
142
150
|
}
|
143
151
|
this.elements[i].setAttribute('data-medium-element', true);
|
144
|
-
this.bindParagraphCreation(i).bindReturn(i).bindTab(i)
|
152
|
+
this.bindParagraphCreation(i).bindReturn(i).bindTab(i);
|
145
153
|
if (!this.options.disableToolbar && !this.elements[i].getAttribute('data-disable-toolbar')) {
|
146
154
|
addToolbar = true;
|
147
155
|
}
|
@@ -150,7 +158,8 @@ if (typeof module === 'object') {
|
|
150
158
|
if (addToolbar) {
|
151
159
|
this.initToolbar()
|
152
160
|
.bindButtons()
|
153
|
-
.bindAnchorForm()
|
161
|
+
.bindAnchorForm()
|
162
|
+
.bindAnchorPreview();
|
154
163
|
}
|
155
164
|
return this;
|
156
165
|
},
|
@@ -168,6 +177,32 @@ if (typeof module === 'object') {
|
|
168
177
|
return content;
|
169
178
|
},
|
170
179
|
|
180
|
+
/**
|
181
|
+
* Helper function to call a method with a number of parameters on all registered extensions.
|
182
|
+
* The function assures that the function exists before calling.
|
183
|
+
*
|
184
|
+
* @param {string} funcName name of the function to call
|
185
|
+
* @param [args] arguments passed into funcName
|
186
|
+
*/
|
187
|
+
callExtensions: function (funcName) {
|
188
|
+
if (arguments.length < 1) {
|
189
|
+
return;
|
190
|
+
}
|
191
|
+
|
192
|
+
var args = Array.prototype.slice.call(arguments, 1),
|
193
|
+
ext,
|
194
|
+
name;
|
195
|
+
|
196
|
+
for (name in this.options.extensions) {
|
197
|
+
if (this.options.extensions.hasOwnProperty(name)) {
|
198
|
+
ext = this.options.extensions[name];
|
199
|
+
if (ext[funcName] !== undefined) {
|
200
|
+
ext[funcName].apply(ext, args);
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
},
|
205
|
+
|
171
206
|
bindParagraphCreation: function (index) {
|
172
207
|
var self = this;
|
173
208
|
this.elements[index].addEventListener('keyup', function (e) {
|
@@ -244,22 +279,22 @@ if (typeof module === 'object') {
|
|
244
279
|
buttonTemplate: function (btnType) {
|
245
280
|
var buttonLabels = this.getButtonLabels(this.options.buttonLabels),
|
246
281
|
buttonTemplates = {
|
247
|
-
'bold': '<
|
248
|
-
'italic': '<
|
249
|
-
'underline': '<
|
250
|
-
'strikethrough': '<
|
251
|
-
'superscript': '<
|
252
|
-
'subscript': '<
|
253
|
-
'anchor': '<
|
254
|
-
'image': '<
|
255
|
-
'header1': '<
|
256
|
-
'header2': '<
|
257
|
-
'quote': '<
|
258
|
-
'orderedlist': '<
|
259
|
-
'unorderedlist': '<
|
260
|
-
'pre': '<
|
261
|
-
'indent': '<
|
262
|
-
'outdent': '<
|
282
|
+
'bold': '<button class="medium-editor-action medium-editor-action-bold" data-action="bold" data-element="b">' + buttonLabels.bold + '</button>',
|
283
|
+
'italic': '<button class="medium-editor-action medium-editor-action-italic" data-action="italic" data-element="i">' + buttonLabels.italic + '</button>',
|
284
|
+
'underline': '<button class="medium-editor-action medium-editor-action-underline" data-action="underline" data-element="u">' + buttonLabels.underline + '</button>',
|
285
|
+
'strikethrough': '<button class="medium-editor-action medium-editor-action-strikethrough" data-action="strikethrough" data-element="strike"><strike>A</strike></button>',
|
286
|
+
'superscript': '<button class="medium-editor-action medium-editor-action-superscript" data-action="superscript" data-element="sup">' + buttonLabels.superscript + '</button>',
|
287
|
+
'subscript': '<button class="medium-editor-action medium-editor-action-subscript" data-action="subscript" data-element="sub">' + buttonLabels.subscript + '</button>',
|
288
|
+
'anchor': '<button class="medium-editor-action medium-editor-action-anchor" data-action="anchor" data-element="a">' + buttonLabels.anchor + '</button>',
|
289
|
+
'image': '<button class="medium-editor-action medium-editor-action-image" data-action="image" data-element="img">' + buttonLabels.image + '</button>',
|
290
|
+
'header1': '<button class="medium-editor-action medium-editor-action-header1" data-action="append-' + this.options.firstHeader + '" data-element="' + this.options.firstHeader + '">' + buttonLabels.header1 + '</button>',
|
291
|
+
'header2': '<button class="medium-editor-action medium-editor-action-header2" data-action="append-' + this.options.secondHeader + '" data-element="' + this.options.secondHeader + '">' + buttonLabels.header2 + '</button>',
|
292
|
+
'quote': '<button class="medium-editor-action medium-editor-action-quote" data-action="append-blockquote" data-element="blockquote">' + buttonLabels.quote + '</button>',
|
293
|
+
'orderedlist': '<button class="medium-editor-action medium-editor-action-orderedlist" data-action="insertorderedlist" data-element="ol">' + buttonLabels.orderedlist + '</button>',
|
294
|
+
'unorderedlist': '<button class="medium-editor-action medium-editor-action-unorderedlist" data-action="insertunorderedlist" data-element="ul">' + buttonLabels.unorderedlist + '</button>',
|
295
|
+
'pre': '<button class="medium-editor-action medium-editor-action-pre" data-action="append-pre" data-element="pre">' + buttonLabels.pre + '</button>',
|
296
|
+
'indent': '<button class="medium-editor-action medium-editor-action-indent" data-action="indent" data-element="ul">' + buttonLabels.indent + '</button>',
|
297
|
+
'outdent': '<button class="medium-editor-action medium-editor-action-outdent" data-action="outdent" data-element="ul">' + buttonLabels.outdent + '</button>'
|
263
298
|
};
|
264
299
|
return buttonTemplates[btnType] || false;
|
265
300
|
},
|
@@ -314,27 +349,6 @@ if (typeof module === 'object') {
|
|
314
349
|
return buttonLabels;
|
315
350
|
},
|
316
351
|
|
317
|
-
//TODO: actionTemplate
|
318
|
-
toolbarTemplate: function () {
|
319
|
-
var btns = this.options.buttons,
|
320
|
-
html = '<ul id="medium-editor-toolbar-actions" class="medium-editor-toolbar-actions clearfix">',
|
321
|
-
i,
|
322
|
-
tpl;
|
323
|
-
|
324
|
-
for (i = 0; i < btns.length; i += 1) {
|
325
|
-
tpl = this.buttonTemplate(btns[i]);
|
326
|
-
if (tpl) {
|
327
|
-
html += tpl;
|
328
|
-
}
|
329
|
-
}
|
330
|
-
html += '</ul>' +
|
331
|
-
'<div class="medium-editor-toolbar-form-anchor" id="medium-editor-toolbar-form-anchor">' +
|
332
|
-
' <input type="text" value="" placeholder="' + this.options.anchorInputPlaceholder + '">' +
|
333
|
-
' <a href="#">×</a>' +
|
334
|
-
'</div>';
|
335
|
-
return html;
|
336
|
-
},
|
337
|
-
|
338
352
|
initToolbar: function () {
|
339
353
|
if (this.toolbar) {
|
340
354
|
return this;
|
@@ -353,11 +367,65 @@ if (typeof module === 'object') {
|
|
353
367
|
var toolbar = document.createElement('div');
|
354
368
|
toolbar.id = 'medium-editor-toolbar-' + this.id;
|
355
369
|
toolbar.className = 'medium-editor-toolbar';
|
356
|
-
toolbar.
|
370
|
+
toolbar.appendChild(this.toolbarButtons());
|
371
|
+
toolbar.appendChild(this.toolbarFormAnchor());
|
357
372
|
document.body.appendChild(toolbar);
|
358
373
|
return toolbar;
|
359
374
|
},
|
360
375
|
|
376
|
+
//TODO: actionTemplate
|
377
|
+
toolbarButtons: function () {
|
378
|
+
var btns = this.options.buttons,
|
379
|
+
ul = document.createElement('ul'),
|
380
|
+
li,
|
381
|
+
i,
|
382
|
+
btn,
|
383
|
+
ext;
|
384
|
+
|
385
|
+
ul.id = 'medium-editor-toolbar-actions';
|
386
|
+
ul.className = 'medium-editor-toolbar-actions clearfix';
|
387
|
+
|
388
|
+
for (i = 0; i < btns.length; i += 1) {
|
389
|
+
if (this.options.extensions.hasOwnProperty(btns[i])) {
|
390
|
+
ext = this.options.extensions[btns[i]];
|
391
|
+
btn = ext.getButton !== undefined ? ext.getButton() : null;
|
392
|
+
} else {
|
393
|
+
btn = this.buttonTemplate(btns[i]);
|
394
|
+
}
|
395
|
+
|
396
|
+
if (btn) {
|
397
|
+
li = document.createElement('li');
|
398
|
+
if (isElement(btn)) {
|
399
|
+
li.appendChild(btn);
|
400
|
+
} else {
|
401
|
+
li.innerHTML = btn;
|
402
|
+
}
|
403
|
+
ul.appendChild(li);
|
404
|
+
}
|
405
|
+
}
|
406
|
+
|
407
|
+
return ul;
|
408
|
+
},
|
409
|
+
|
410
|
+
toolbarFormAnchor: function () {
|
411
|
+
var anchor = document.createElement('div'),
|
412
|
+
input = document.createElement('input'),
|
413
|
+
a = document.createElement('a');
|
414
|
+
|
415
|
+
a.setAttribute('href', '#');
|
416
|
+
a.innerHTML = '×';
|
417
|
+
|
418
|
+
input.setAttribute('type', 'text');
|
419
|
+
input.setAttribute('placeholder', this.options.anchorInputPlaceholder);
|
420
|
+
|
421
|
+
anchor.className = 'medium-editor-toolbar-form-anchor';
|
422
|
+
anchor.id = 'medium-editor-toolbar-form-anchor';
|
423
|
+
anchor.appendChild(input);
|
424
|
+
anchor.appendChild(a);
|
425
|
+
|
426
|
+
return anchor;
|
427
|
+
},
|
428
|
+
|
361
429
|
bindSelect: function () {
|
362
430
|
var self = this,
|
363
431
|
timer = '',
|
@@ -438,9 +506,7 @@ if (typeof module === 'object') {
|
|
438
506
|
|
439
507
|
getSelectionElement: function () {
|
440
508
|
var selection = window.getSelection(),
|
441
|
-
range
|
442
|
-
current = range.commonAncestorContainer,
|
443
|
-
parent = current.parentNode,
|
509
|
+
range, current, parent,
|
444
510
|
result,
|
445
511
|
getMediumElement = function (e) {
|
446
512
|
var localParent = e;
|
@@ -455,6 +521,10 @@ if (typeof module === 'object') {
|
|
455
521
|
};
|
456
522
|
// First try on current node
|
457
523
|
try {
|
524
|
+
range = selection.getRangeAt(0);
|
525
|
+
current = range.commonAncestorContainer;
|
526
|
+
parent = current.parentNode;
|
527
|
+
|
458
528
|
if (current.getAttribute('data-medium-element')) {
|
459
529
|
result = current;
|
460
530
|
} else {
|
@@ -508,12 +578,19 @@ if (typeof module === 'object') {
|
|
508
578
|
},
|
509
579
|
|
510
580
|
checkActiveButtons: function () {
|
511
|
-
var
|
581
|
+
var elements = Array.prototype.slice.call(this.elements),
|
582
|
+
parentNode = this.selection.anchorNode;
|
512
583
|
if (!parentNode.tagName) {
|
513
584
|
parentNode = this.selection.anchorNode.parentNode;
|
514
585
|
}
|
515
586
|
while (parentNode.tagName !== undefined && this.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) {
|
516
587
|
this.activateButton(parentNode.tagName.toLowerCase());
|
588
|
+
this.callExtensions('checkState', parentNode);
|
589
|
+
|
590
|
+
// we can abort the search upwards if we leave the contentEditable element
|
591
|
+
if (elements.indexOf(parentNode) !== -1) {
|
592
|
+
break;
|
593
|
+
}
|
517
594
|
parentNode = parentNode.parentNode;
|
518
595
|
}
|
519
596
|
},
|
@@ -540,7 +617,9 @@ if (typeof module === 'object') {
|
|
540
617
|
} else {
|
541
618
|
this.className += ' medium-editor-button-active';
|
542
619
|
}
|
543
|
-
|
620
|
+
if (this.hasAttribute('data-action')) {
|
621
|
+
self.execAction(this.getAttribute('data-action'), e);
|
622
|
+
}
|
544
623
|
};
|
545
624
|
for (i = 0; i < buttons.length; i += 1) {
|
546
625
|
buttons[i].addEventListener('click', triggerAction);
|
@@ -550,8 +629,10 @@ if (typeof module === 'object') {
|
|
550
629
|
},
|
551
630
|
|
552
631
|
setFirstAndLastItems: function (buttons) {
|
553
|
-
buttons
|
554
|
-
|
632
|
+
if (buttons.length > 0) {
|
633
|
+
buttons[0].className += ' medium-editor-button-first';
|
634
|
+
buttons[buttons.length - 1].className += ' medium-editor-button-last';
|
635
|
+
}
|
555
636
|
return this;
|
556
637
|
},
|
557
638
|
|
@@ -638,7 +719,9 @@ if (typeof module === 'object') {
|
|
638
719
|
|
639
720
|
hideToolbarActions: function () {
|
640
721
|
this.keepToolbarAlive = false;
|
641
|
-
this.toolbar
|
722
|
+
if (this.toolbar !== undefined) {
|
723
|
+
this.toolbar.classList.remove('medium-editor-toolbar-active');
|
724
|
+
}
|
642
725
|
},
|
643
726
|
|
644
727
|
showToolbarActions: function () {
|
@@ -649,7 +732,7 @@ if (typeof module === 'object') {
|
|
649
732
|
this.keepToolbarAlive = false;
|
650
733
|
clearTimeout(timer);
|
651
734
|
timer = setTimeout(function () {
|
652
|
-
if (!self.toolbar.classList.contains('medium-editor-toolbar-active')) {
|
735
|
+
if (self.toolbar && !self.toolbar.classList.contains('medium-editor-toolbar-active')) {
|
653
736
|
self.toolbar.classList.add('medium-editor-toolbar-active');
|
654
737
|
}
|
655
738
|
}, 100);
|
@@ -718,7 +801,7 @@ if (typeof module === 'object') {
|
|
718
801
|
|
719
802
|
clearTimeout(timer);
|
720
803
|
timer = setTimeout(function () {
|
721
|
-
if (!self.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) {
|
804
|
+
if (self.anchorPreview && !self.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) {
|
722
805
|
self.anchorPreview.classList.add('medium-editor-anchor-preview-active');
|
723
806
|
}
|
724
807
|
}, 100);
|
@@ -811,7 +894,9 @@ if (typeof module === 'object') {
|
|
811
894
|
sel.removeAllRanges();
|
812
895
|
sel.addRange(range);
|
813
896
|
setTimeout(function () {
|
814
|
-
|
897
|
+
if (self.activeAnchor) {
|
898
|
+
self.showAnchorForm(self.activeAnchor.href);
|
899
|
+
}
|
815
900
|
self.keepToolbarAlive = false;
|
816
901
|
}, 100 + self.options.delay);
|
817
902
|
|
@@ -832,7 +917,7 @@ if (typeof module === 'object') {
|
|
832
917
|
if (e.target && e.target.tagName.toLowerCase() === 'a') {
|
833
918
|
|
834
919
|
// Detect empty href attributes
|
835
|
-
// The browser will make href="" or href="#top"
|
920
|
+
// The browser will make href="" or href="#top"
|
836
921
|
// into absolute urls when accessed as e.targed.href, so check the html
|
837
922
|
if (!/href=["']\S+["']/.test(e.target.outerHTML) || /href=["']#\S+["']/.test(e.target.outerHTML)) {
|
838
923
|
return true;
|
@@ -858,13 +943,24 @@ if (typeof module === 'object') {
|
|
858
943
|
},
|
859
944
|
|
860
945
|
bindAnchorPreview: function (index) {
|
861
|
-
var self = this;
|
862
|
-
this.
|
946
|
+
var i, self = this;
|
947
|
+
this.editorAnchorObserverWrapper = function (e) {
|
863
948
|
self.editorAnchorObserver(e);
|
864
|
-
}
|
949
|
+
};
|
950
|
+
for (i = 0; i < this.elements.length; i += 1) {
|
951
|
+
this.elements[i].addEventListener('mouseover', this.editorAnchorObserverWrapper);
|
952
|
+
}
|
865
953
|
return this;
|
866
954
|
},
|
867
955
|
|
956
|
+
checkLinkFormat: function (value) {
|
957
|
+
var re = /^https?:\/\//;
|
958
|
+
if (value.match(re)) {
|
959
|
+
return value;
|
960
|
+
}
|
961
|
+
return "http://" + value;
|
962
|
+
},
|
963
|
+
|
868
964
|
setTargetBlank: function () {
|
869
965
|
var el = getSelectionStart(),
|
870
966
|
i;
|
@@ -880,6 +976,9 @@ if (typeof module === 'object') {
|
|
880
976
|
|
881
977
|
createLink: function (input) {
|
882
978
|
restoreSelection(this.savedSelection);
|
979
|
+
if (this.options.checkLinkFormat) {
|
980
|
+
input.value = this.checkLinkFormat(input.value);
|
981
|
+
}
|
883
982
|
document.execCommand('createLink', false, input.value);
|
884
983
|
if (this.options.targetBlank) {
|
885
984
|
this.setTargetBlank();
|
@@ -930,6 +1029,7 @@ if (typeof module === 'object') {
|
|
930
1029
|
window.removeEventListener('resize', this.windowResizeHandler);
|
931
1030
|
|
932
1031
|
for (i = 0; i < this.elements.length; i += 1) {
|
1032
|
+
this.elements[i].removeEventListener('mouseover', this.editorAnchorObserverWrapper);
|
933
1033
|
this.elements[i].removeEventListener('keyup', this.checkSelectionWrapper);
|
934
1034
|
this.elements[i].removeEventListener('blur', this.checkSelectionWrapper);
|
935
1035
|
this.elements[i].removeEventListener('paste', this.pasteWrapper);
|
@@ -939,6 +1039,12 @@ if (typeof module === 'object') {
|
|
939
1039
|
|
940
1040
|
},
|
941
1041
|
|
1042
|
+
htmlEntities: function (str) {
|
1043
|
+
// converts special characters (like <) into their escaped/encoded values (like <).
|
1044
|
+
// This allows you to show to display the string without the browser reading it as HTML.
|
1045
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
1046
|
+
},
|
1047
|
+
|
942
1048
|
bindPaste: function () {
|
943
1049
|
var i, self = this;
|
944
1050
|
this.pasteWrapper = function (e) {
|
@@ -947,17 +1053,25 @@ if (typeof module === 'object') {
|
|
947
1053
|
p;
|
948
1054
|
|
949
1055
|
this.classList.remove('medium-editor-placeholder');
|
950
|
-
if (!self.options.forcePlainText) {
|
1056
|
+
if (!self.options.forcePlainText && !self.options.cleanPastedHTML) {
|
951
1057
|
return this;
|
952
1058
|
}
|
953
1059
|
|
954
1060
|
if (e.clipboardData && e.clipboardData.getData && !e.defaultPrevented) {
|
955
1061
|
e.preventDefault();
|
1062
|
+
|
1063
|
+
if (self.options.cleanPastedHTML && e.clipboardData.getData('text/html')) {
|
1064
|
+
return self.cleanPaste(e.clipboardData.getData('text/html'));
|
1065
|
+
}
|
956
1066
|
if (!self.options.disableReturn) {
|
957
1067
|
paragraphs = e.clipboardData.getData('text/plain').split(/[\r\n]/g);
|
958
1068
|
for (p = 0; p < paragraphs.length; p += 1) {
|
959
1069
|
if (paragraphs[p] !== '') {
|
960
|
-
|
1070
|
+
if (navigator.userAgent.match(/firefox/i) && p === 0) {
|
1071
|
+
html += self.htmlEntities(paragraphs[p]);
|
1072
|
+
} else {
|
1073
|
+
html += '<p>' + self.htmlEntities(paragraphs[p]) + '</p>';
|
1074
|
+
}
|
961
1075
|
}
|
962
1076
|
}
|
963
1077
|
document.execCommand('insertHTML', false, html);
|
@@ -991,6 +1105,193 @@ if (typeof module === 'object') {
|
|
991
1105
|
this.elements[i].addEventListener('keypress', placeholderWrapper);
|
992
1106
|
}
|
993
1107
|
return this;
|
1108
|
+
},
|
1109
|
+
|
1110
|
+
cleanPaste: function (text) {
|
1111
|
+
|
1112
|
+
/*jslint regexp: true*/
|
1113
|
+
/*
|
1114
|
+
jslint does not allow character negation, because the negation
|
1115
|
+
will not match any unicode characters. In the regexes in this
|
1116
|
+
block, negation is used specifically to match the end of an html
|
1117
|
+
tag, and in fact unicode characters *should* be allowed.
|
1118
|
+
*/
|
1119
|
+
var i, elList, workEl,
|
1120
|
+
el = this.getSelectionElement(),
|
1121
|
+
multiline = /<p|<br|<div/.test(text),
|
1122
|
+
replacements = [
|
1123
|
+
|
1124
|
+
// replace two bogus tags that begin pastes from google docs
|
1125
|
+
[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""],
|
1126
|
+
[new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""],
|
1127
|
+
|
1128
|
+
// un-html spaces and newlines inserted by OS X
|
1129
|
+
[new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '],
|
1130
|
+
[new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'],
|
1131
|
+
|
1132
|
+
// replace google docs italics+bold with a span to be replaced once the html is inserted
|
1133
|
+
[new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'],
|
1134
|
+
|
1135
|
+
// replace google docs italics with a span to be replaced once the html is inserted
|
1136
|
+
[new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'],
|
1137
|
+
|
1138
|
+
//[replace google docs bolds with a span to be replaced once the html is inserted
|
1139
|
+
[new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'],
|
1140
|
+
|
1141
|
+
// replace manually entered b/i/a tags with real ones
|
1142
|
+
[new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'],
|
1143
|
+
|
1144
|
+
// replace manually a tags with real ones, converting smart-quotes from google docs
|
1145
|
+
[new RegExp(/<a\s+href=("|”|“|“|”)([^&]+)("|”|“|“|”)>/gi), '<a href="$2">']
|
1146
|
+
|
1147
|
+
];
|
1148
|
+
/*jslint regexp: false*/
|
1149
|
+
|
1150
|
+
for (i = 0; i < replacements.length; i += 1) {
|
1151
|
+
text = text.replace(replacements[i][0], replacements[i][1]);
|
1152
|
+
}
|
1153
|
+
|
1154
|
+
if (multiline) {
|
1155
|
+
|
1156
|
+
// double br's aren't converted to p tags, but we want paragraphs.
|
1157
|
+
elList = text.split('<br><br>');
|
1158
|
+
|
1159
|
+
this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>');
|
1160
|
+
document.execCommand('insertText', false, "\n");
|
1161
|
+
|
1162
|
+
// block element cleanup
|
1163
|
+
elList = el.querySelectorAll('p,div,br');
|
1164
|
+
for (i = 0; i < elList.length; i += 1) {
|
1165
|
+
|
1166
|
+
workEl = elList[i];
|
1167
|
+
|
1168
|
+
switch (workEl.tagName.toLowerCase()) {
|
1169
|
+
case 'p':
|
1170
|
+
case 'div':
|
1171
|
+
this.filterCommonBlocks(workEl);
|
1172
|
+
break;
|
1173
|
+
case 'br':
|
1174
|
+
this.filterLineBreak(workEl);
|
1175
|
+
break;
|
1176
|
+
}
|
1177
|
+
|
1178
|
+
}
|
1179
|
+
|
1180
|
+
|
1181
|
+
} else {
|
1182
|
+
|
1183
|
+
this.pasteHTML(text);
|
1184
|
+
|
1185
|
+
}
|
1186
|
+
|
1187
|
+
},
|
1188
|
+
|
1189
|
+
pasteHTML: function (html) {
|
1190
|
+
var elList, workEl, i, fragmentBody, pasteBlock = document.createDocumentFragment();
|
1191
|
+
|
1192
|
+
pasteBlock.appendChild(document.createElement('body'));
|
1193
|
+
|
1194
|
+
fragmentBody = pasteBlock.querySelector('body');
|
1195
|
+
fragmentBody.innerHTML = html;
|
1196
|
+
|
1197
|
+
this.cleanupSpans(fragmentBody);
|
1198
|
+
|
1199
|
+
elList = fragmentBody.querySelectorAll('*');
|
1200
|
+
for (i = 0; i < elList.length; i += 1) {
|
1201
|
+
|
1202
|
+
workEl = elList[i];
|
1203
|
+
|
1204
|
+
// delete ugly attributes
|
1205
|
+
workEl.removeAttribute('class');
|
1206
|
+
workEl.removeAttribute('style');
|
1207
|
+
workEl.removeAttribute('dir');
|
1208
|
+
|
1209
|
+
if (workEl.tagName.toLowerCase() === 'meta') {
|
1210
|
+
workEl.parentNode.removeChild(workEl);
|
1211
|
+
}
|
1212
|
+
|
1213
|
+
}
|
1214
|
+
document.execCommand('insertHTML', false, fragmentBody.innerHTML.replace(/ /g, ' '));
|
1215
|
+
},
|
1216
|
+
isCommonBlock: function (el) {
|
1217
|
+
return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div'));
|
1218
|
+
},
|
1219
|
+
filterCommonBlocks: function (el) {
|
1220
|
+
if (/^\s*$/.test(el.innerText)) {
|
1221
|
+
el.parentNode.removeChild(el);
|
1222
|
+
}
|
1223
|
+
},
|
1224
|
+
filterLineBreak: function (el) {
|
1225
|
+
if (this.isCommonBlock(el.previousElementSibling)) {
|
1226
|
+
|
1227
|
+
// remove stray br's following common block elements
|
1228
|
+
el.parentNode.removeChild(el);
|
1229
|
+
|
1230
|
+
} else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) {
|
1231
|
+
|
1232
|
+
// remove br's just inside open or close tags of a div/p
|
1233
|
+
el.parentNode.removeChild(el);
|
1234
|
+
|
1235
|
+
} else if (el.parentNode.childElementCount === 1) {
|
1236
|
+
|
1237
|
+
// and br's that are the only child of a div/p
|
1238
|
+
this.removeWithParent(el);
|
1239
|
+
|
1240
|
+
}
|
1241
|
+
|
1242
|
+
},
|
1243
|
+
|
1244
|
+
// remove an element, including its parent, if it is the only element within its parent
|
1245
|
+
removeWithParent: function (el) {
|
1246
|
+
if (el && el.parentNode) {
|
1247
|
+
if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) {
|
1248
|
+
el.parentNode.parentNode.removeChild(el.parentNode);
|
1249
|
+
} else {
|
1250
|
+
el.parentNode.removeChild(el.parentNode);
|
1251
|
+
}
|
1252
|
+
}
|
1253
|
+
},
|
1254
|
+
|
1255
|
+
cleanupSpans: function (container_el) {
|
1256
|
+
|
1257
|
+
var i,
|
1258
|
+
el,
|
1259
|
+
new_el,
|
1260
|
+
spans = container_el.querySelectorAll('.replace-with');
|
1261
|
+
|
1262
|
+
for (i = 0; i < spans.length; i += 1) {
|
1263
|
+
|
1264
|
+
el = spans[i];
|
1265
|
+
new_el = document.createElement(el.classList.contains('bold') ? 'b' : 'i');
|
1266
|
+
|
1267
|
+
if (el.classList.contains('bold') && el.classList.contains('italic')) {
|
1268
|
+
|
1269
|
+
// add an i tag as well if this has both italics and bold
|
1270
|
+
new_el.innerHTML = '<i>' + el.innerHTML + '</i>';
|
1271
|
+
|
1272
|
+
} else {
|
1273
|
+
|
1274
|
+
new_el.innerHTML = el.innerHTML;
|
1275
|
+
|
1276
|
+
}
|
1277
|
+
el.parentNode.replaceChild(new_el, el);
|
1278
|
+
|
1279
|
+
}
|
1280
|
+
|
1281
|
+
spans = container_el.querySelectorAll('span');
|
1282
|
+
for (i = 0; i < spans.length; i += 1) {
|
1283
|
+
|
1284
|
+
el = spans[i];
|
1285
|
+
|
1286
|
+
// remove empty spans, replace others with their contents
|
1287
|
+
if (/^\s*$/.test()) {
|
1288
|
+
el.parentNode.removeChild(el);
|
1289
|
+
} else {
|
1290
|
+
el.parentNode.replaceChild(document.createTextNode(el.innerText), el);
|
1291
|
+
}
|
1292
|
+
|
1293
|
+
}
|
1294
|
+
|
994
1295
|
}
|
995
1296
|
|
996
1297
|
};
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: medium-editor-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ahmet Sezgin Duran
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Medium Editor integrated in Rails asset pipeline
|
14
14
|
email:
|
@@ -17,7 +17,7 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
- .gitignore
|
20
|
+
- ".gitignore"
|
21
21
|
- CHANGELOG.md
|
22
22
|
- Gemfile
|
23
23
|
- LICENSE.txt
|
@@ -45,17 +45,17 @@ require_paths:
|
|
45
45
|
- lib
|
46
46
|
required_ruby_version: !ruby/object:Gem::Requirement
|
47
47
|
requirements:
|
48
|
-
- -
|
48
|
+
- - ">="
|
49
49
|
- !ruby/object:Gem::Version
|
50
50
|
version: '0'
|
51
51
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- -
|
53
|
+
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
requirements: []
|
57
57
|
rubyforge_project:
|
58
|
-
rubygems_version: 2.2.
|
58
|
+
rubygems_version: 2.2.2
|
59
59
|
signing_key:
|
60
60
|
specification_version: 4
|
61
61
|
summary: Medium Editor integrated in Rails asset pipeline
|