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 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