medium-editor-rails 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 554e3a6e3408a75f8e89fc0371beac9b7325a35c
4
- data.tar.gz: 0f2118fc0e0f8a4bfb2a6b3f0d655c7257a10c51
3
+ metadata.gz: c733d77c3745960169613a029d7f9bbc675b21ae
4
+ data.tar.gz: 2b3d08541f21c65ba1cc5551a7cf01bdccbe3167
5
5
  SHA512:
6
- metadata.gz: 664af57a4d68e08d935f32bb42f302d47a70c455714d54783e516589a5682e7c55f9304b6bb7bab413c4518a4298688719f719aa723db76ca1337b340aec2c1a
7
- data.tar.gz: 1622122cb12a97fe15d3b3942c46d24aac89cc5f66fbc8285992a54401925eed360e4ec5ee0db525494df8e2531be1967e9a4ab45685a901c1e424537f8b9852
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.7.5](https://github.com/daviferreira/medium-editor/releases)
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
 
@@ -1,6 +1,6 @@
1
1
  module MediumEditorRails
2
2
  module Rails
3
- VERSION = '0.7.0'
4
- MEDIUM_EDITOR_VERSION = '1.7.5'
3
+ VERSION = '0.8.0'
4
+ MEDIUM_EDITOR_VERSION = '1.8.0'
5
5
  end
6
6
  end
@@ -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
- anchorPreviewHideDelay: 500
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).bindAnchorPreview(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': '<li><button class="medium-editor-action medium-editor-action-bold" data-action="bold" data-element="b">' + buttonLabels.bold + '</button></li>',
248
- 'italic': '<li><button class="medium-editor-action medium-editor-action-italic" data-action="italic" data-element="i">' + buttonLabels.italic + '</button></li>',
249
- 'underline': '<li><button class="medium-editor-action medium-editor-action-underline" data-action="underline" data-element="u">' + buttonLabels.underline + '</button></li>',
250
- 'strikethrough': '<li><button class="medium-editor-action medium-editor-action-strikethrough" data-action="strikethrough" data-element="strike"><strike>A</strike></button></li>',
251
- 'superscript': '<li><button class="medium-editor-action medium-editor-action-superscript" data-action="superscript" data-element="sup">' + buttonLabels.superscript + '</button></li>',
252
- 'subscript': '<li><button class="medium-editor-action medium-editor-action-subscript" data-action="subscript" data-element="sub">' + buttonLabels.subscript + '</button></li>',
253
- 'anchor': '<li><button class="medium-editor-action medium-editor-action-anchor" data-action="anchor" data-element="a">' + buttonLabels.anchor + '</button></li>',
254
- 'image': '<li><button class="medium-editor-action medium-editor-action-image" data-action="image" data-element="img">' + buttonLabels.image + '</button></li>',
255
- 'header1': '<li><button class="medium-editor-action medium-editor-action-header1" data-action="append-' + this.options.firstHeader + '" data-element="' + this.options.firstHeader + '">' + buttonLabels.header1 + '</button></li>',
256
- 'header2': '<li><button class="medium-editor-action medium-editor-action-header2" data-action="append-' + this.options.secondHeader + '" data-element="' + this.options.secondHeader + '">' + buttonLabels.header2 + '</button></li>',
257
- 'quote': '<li><button class="medium-editor-action medium-editor-action-quote" data-action="append-blockquote" data-element="blockquote">' + buttonLabels.quote + '</button></li>',
258
- 'orderedlist': '<li><button class="medium-editor-action medium-editor-action-orderedlist" data-action="insertorderedlist" data-element="ol">' + buttonLabels.orderedlist + '</button></li>',
259
- 'unorderedlist': '<li><button class="medium-editor-action medium-editor-action-unorderedlist" data-action="insertunorderedlist" data-element="ul">' + buttonLabels.unorderedlist + '</button></li>',
260
- 'pre': '<li><button class="medium-editor-action medium-editor-action-pre" data-action="append-pre" data-element="pre">' + buttonLabels.pre + '</button></li>',
261
- 'indent': '<li><button class="medium-editor-action medium-editor-action-indent" data-action="indent" data-element="ul">' + buttonLabels.indent + '</button></li>',
262
- 'outdent': '<li><button class="medium-editor-action medium-editor-action-outdent" data-action="outdent" data-element="ul">' + buttonLabels.outdent + '</button></li>'
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="#">&times;</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.innerHTML = this.toolbarTemplate();
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 = '&times;';
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 = selection.getRangeAt(0),
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 parentNode = this.selection.anchorNode;
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
- self.execAction(this.getAttribute('data-action'), e);
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[0].className += ' medium-editor-button-first';
554
- buttons[buttons.length - 1].className += ' medium-editor-button-last';
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.classList.remove('medium-editor-toolbar-active');
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
- self.showAnchorForm(self.activeAnchor.href);
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.elements[index].addEventListener('mouseover', function (e) {
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 &lt;).
1044
+ // This allows you to show to display the string without the browser reading it as HTML.
1045
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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
- html += '<p>' + paragraphs[p] + '</p>';
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(/&lt;(\/?)(i|b|a)&gt;/gi), '<$1$2>'],
1143
+
1144
+ // replace manually a tags with real ones, converting smart-quotes from google docs
1145
+ [new RegExp(/&lt;a\s+href=(&quot;|&rdquo;|&ldquo;|“|”)([^&]+)(&quot;|&rdquo;|&ldquo;|“|”)&gt;/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(/&nbsp;/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.7.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-03-29 00:00:00.000000000 Z
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.1
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