From 5ba7bee2d04f20cd7a6a2562bf86a400c1a486b1 Mon Sep 17 00:00:00 2001 From: MikaGenic Date: Tue, 16 Feb 2016 11:51:24 +1100 Subject: [PATCH 01/29] Rich text copy/cut/paste --- examples/firepad.js | 131 +++++++++++++++++++++++++++++++++++- lib/firepad.js | 29 +++++++- lib/rich-text-codemirror.js | 102 +++++++++++++++++++++++++++- 3 files changed, 258 insertions(+), 4 deletions(-) diff --git a/examples/firepad.js b/examples/firepad.js index f494b3470..491c6e211 100644 --- a/examples/firepad.js +++ b/examples/firepad.js @@ -2923,6 +2923,10 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); + bind(this, 'onCopy_'); + bind(this, 'onCut_'); + bind(this, 'onPaste_'); + if (parseInt(CodeMirror.version) >= 4) { this.codeMirror.on('changes', this.onCodeMirrorChange_); } else { @@ -2946,6 +2950,11 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.off('change', this.onCodeMirrorChange_); this.codeMirror.off('changes', this.onCodeMirrorChange_); this.codeMirror.off('cursorActivity', this.onCursorActivity_); + + this.codeMirror.getWrapperElement().removeEventListener('copy'); + this.codeMirror.getWrapperElement().removeEventListener('cut'); + this.codeMirror.off('paste', this.onCodeMirrorPaste_); + this.clearAnnotations_(); }; @@ -3457,6 +3466,97 @@ firepad.RichTextCodeMirror = (function () { } }; + // return true if e.preventDefault() was called + RichTextCodeMirror.prototype.onCopy_ = function(fp, e) { + if (!e.clipboardData) return false; // sanity, revert to CM copy + + var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); + if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 + if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge + if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy + + if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy + + var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html + if (!html) return false; // something went wrong, revert to CM copy + + // caching some default styles needed later + var cm=fp.codeMirror_; + if (!this.defaultStyles) { + var style = window.getComputedStyle(cm.getWrapperElement()); + this.defaultStyles={ + fontFamily: style.getPropertyValue('font-family'), + fontSize: style.getPropertyValue('font-size'), + bgColor: style.getPropertyValue('background-color') + }; + } + + // add default font to the html + html=''+html+''; + if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy + //console.log('html copy '+html); + e.preventDefault(); + return true; + }; + + RichTextCodeMirror.prototype.onCut_ = function(fp, e) { + if (!e.clipboardData) return; // older browsers + if (!this.onCopy_(fp, e)) return; // default to cm cut + fp.codeMirror_.replaceSelection(''); + }; + + RichTextCodeMirror.prototype.onPaste_ = function(fp, e) { + if (!e.clipboardData) return; // sanity, revert to CM paste + + var html = e.clipboardData.getData('text/html'); + if (!html) return null; // something went wrong, revert to CM paste + + // setting bg color when not needed can break selection, so removing it here + if (this.defaultStyles) html = html.split('background-color: '+this.defaultStyles.bgColor).join('background-color:transparent'); + + fp.codeMirror_.replaceSelection(''); + fp.insertHtmlAtCursor(html); + e.preventDefault(); + }; + + function copyHtmlToClipboard(html) { + var div = document.createElement('div'); + div.style.opacity = 0; + div.style.position = 'absolute'; + div.style.pointerEvents = 'none'; + div.style.zIndex = -1; + div.setAttribute('tabindex', '-1'); // so it can be focused + div.innerHTML = html; + document.body.appendChild(div); + + var focused=document.activeElement; + div.focus(); + + window.getSelection().removeAllRanges(); + var range = document.createRange(); + // not using range.selectNode(div) as that makes chrome add an extra
+ range.setStartBefore(div.firstChild); + range.setEndAfter(div.lastChild); + window.getSelection().addRange(range); + + var ok=false; + try { + ok = document.execCommand('copy'); + } catch (err) { + console.log(err); + } + if (!ok) { + console.log('execCommand failed!'); + return false; + } + + window.getSelection().removeAllRanges(); + document.body.removeChild(div); + + focused.focus(); + return true; + } + function cmpPos (a, b) { return (a.line - b.line) || (a.ch - b.ch); } @@ -4024,7 +4124,7 @@ firepad.RichTextCodeMirror = (function () { function bind (obj, method) { var fn = obj[method]; obj[method] = function () { - fn.apply(obj, arguments); + return fn.apply(obj, arguments); }; } @@ -5351,6 +5451,8 @@ firepad.Firepad = (function(global) { var ace = global.ace; function Firepad(ref, place, options) { + var self = this; + if (!(this instanceof Firepad)) { return new Firepad(ref, place, options); } if (!CodeMirror && !ace) { @@ -5394,6 +5496,10 @@ firepad.Firepad = (function(global) { } this.codeMirror_.setOption('keyMap', 'richtext'); this.firepadWrapper_.className += ' firepad-richtext'; + + this.codeMirror_.getWrapperElement().addEventListener('copy', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCopy_(self, e); }); + this.codeMirror_.getWrapperElement().addEventListener('cut', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCut_(self, e); }); + this.codeMirror_.on('paste', function(cm, e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onPaste_(self, e); }); } this.imageInsertionUI = this.getOption('imageInsertionUI', true); @@ -5423,7 +5529,6 @@ firepad.Firepad = (function(global) { } this.client_ = new EditorClient(this.firebaseAdapter_, this.editorAdapter_); - var self = this; this.firebaseAdapter_.on('cursor', function() { self.trigger.apply(self, ['cursor'].concat([].slice.call(arguments))); }); @@ -5568,6 +5673,28 @@ firepad.Firepad = (function(global) { return this.getHtmlFromRange(null, null); }; + Firepad.prototype.selectionHasAttributes = function() { + var startPos = this.codeMirror_.getCursor('start'), endPos = this.codeMirror_.getCursor('end'); + var startIndex = this.codeMirror_.indexFromPos(startPos), endIndex = this.codeMirror_.indexFromPos(endPos); + return this.rangeHasAttributes(startIndex, endIndex); + }; + + Firepad.prototype.rangeHasAttributes = function(start, end) { + this.assertReady_('rangeHasAttributes'); + var doc = (start != null && end != null) ? + this.getOperationForSpan(start, end) : + this.getOperationForSpan(0, this.codeMirror_.getValue().length); + + var op; + for (var i = 0; i < doc.ops.length; i++) { + op = doc.ops[i]; + for(var prop in op.attributes) if (op.attributes.hasOwnProperty(prop) && prop!=ATTR.LINE_SENTINEL) return true; // found an attribute + } + + return false; + }; + + Firepad.prototype.getHtmlFromSelection = function() { var startPos = this.codeMirror_.getCursor('start'), endPos = this.codeMirror_.getCursor('end'); var startIndex = this.codeMirror_.indexFromPos(startPos), endIndex = this.codeMirror_.indexFromPos(endPos); diff --git a/lib/firepad.js b/lib/firepad.js index 2b887a6f1..d4d421bc1 100644 --- a/lib/firepad.js +++ b/lib/firepad.js @@ -18,6 +18,8 @@ firepad.Firepad = (function(global) { var ace = global.ace; function Firepad(ref, place, options) { + var self = this; + if (!(this instanceof Firepad)) { return new Firepad(ref, place, options); } if (!CodeMirror && !ace) { @@ -61,6 +63,10 @@ firepad.Firepad = (function(global) { } this.codeMirror_.setOption('keyMap', 'richtext'); this.firepadWrapper_.className += ' firepad-richtext'; + + this.codeMirror_.getWrapperElement().addEventListener('copy', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCopy_(self, e); }); + this.codeMirror_.getWrapperElement().addEventListener('cut', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCut_(self, e); }); + this.codeMirror_.on('paste', function(cm, e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onPaste_(self, e); }); } this.imageInsertionUI = this.getOption('imageInsertionUI', true); @@ -90,7 +96,6 @@ firepad.Firepad = (function(global) { } this.client_ = new EditorClient(this.firebaseAdapter_, this.editorAdapter_); - var self = this; this.firebaseAdapter_.on('cursor', function() { self.trigger.apply(self, ['cursor'].concat([].slice.call(arguments))); }); @@ -235,6 +240,28 @@ firepad.Firepad = (function(global) { return this.getHtmlFromRange(null, null); }; + Firepad.prototype.selectionHasAttributes = function() { + var startPos = this.codeMirror_.getCursor('start'), endPos = this.codeMirror_.getCursor('end'); + var startIndex = this.codeMirror_.indexFromPos(startPos), endIndex = this.codeMirror_.indexFromPos(endPos); + return this.rangeHasAttributes(startIndex, endIndex); + }; + + Firepad.prototype.rangeHasAttributes = function(start, end) { + this.assertReady_('rangeHasAttributes'); + var doc = (start != null && end != null) ? + this.getOperationForSpan(start, end) : + this.getOperationForSpan(0, this.codeMirror_.getValue().length); + + var op; + for (var i = 0; i < doc.ops.length; i++) { + op = doc.ops[i]; + for(var prop in op.attributes) if (op.attributes.hasOwnProperty(prop) && prop!=ATTR.LINE_SENTINEL) return true; // found an attribute + } + + return false; + }; + + Firepad.prototype.getHtmlFromSelection = function() { var startPos = this.codeMirror_.getCursor('start'), endPos = this.codeMirror_.getCursor('end'); var startIndex = this.codeMirror_.indexFromPos(startPos), endIndex = this.codeMirror_.indexFromPos(endPos); diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index 38cc64304..e22de2d89 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -36,6 +36,10 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); + bind(this, 'onCopy_'); + bind(this, 'onCut_'); + bind(this, 'onPaste_'); + if (parseInt(CodeMirror.version) >= 4) { this.codeMirror.on('changes', this.onCodeMirrorChange_); } else { @@ -59,6 +63,11 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.off('change', this.onCodeMirrorChange_); this.codeMirror.off('changes', this.onCodeMirrorChange_); this.codeMirror.off('cursorActivity', this.onCursorActivity_); + + this.codeMirror.getWrapperElement().removeEventListener('copy'); + this.codeMirror.getWrapperElement().removeEventListener('cut'); + this.codeMirror.off('paste', this.onCodeMirrorPaste_); + this.clearAnnotations_(); }; @@ -570,6 +579,97 @@ firepad.RichTextCodeMirror = (function () { } }; + // return true if e.preventDefault() was called + RichTextCodeMirror.prototype.onCopy_ = function(fp, e) { + if (!e.clipboardData) return false; // sanity, revert to CM copy + + var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); + if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 + if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge + if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy + + if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy + + var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html + if (!html) return false; // something went wrong, revert to CM copy + + // caching some default styles needed later + var cm=fp.codeMirror_; + if (!this.defaultStyles) { + var style = window.getComputedStyle(cm.getWrapperElement()); + this.defaultStyles={ + fontFamily: style.getPropertyValue('font-family'), + fontSize: style.getPropertyValue('font-size'), + bgColor: style.getPropertyValue('background-color') + }; + } + + // add default font to the html + html=''+html+''; + if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy + //console.log('html copy '+html); + e.preventDefault(); + return true; + }; + + RichTextCodeMirror.prototype.onCut_ = function(fp, e) { + if (!e.clipboardData) return; // older browsers + if (!this.onCopy_(fp, e)) return; // default to cm cut + fp.codeMirror_.replaceSelection(''); + }; + + RichTextCodeMirror.prototype.onPaste_ = function(fp, e) { + if (!e.clipboardData) return; // sanity, revert to CM paste + + var html = e.clipboardData.getData('text/html'); + if (!html) return null; // something went wrong, revert to CM paste + + // setting bg color when not needed can break selection, so removing it here + if (this.defaultStyles) html = html.split('background-color: '+this.defaultStyles.bgColor).join('background-color:transparent'); + + fp.codeMirror_.replaceSelection(''); + fp.insertHtmlAtCursor(html); + e.preventDefault(); + }; + + function copyHtmlToClipboard(html) { + var div = document.createElement('div'); + div.style.opacity = 0; + div.style.position = 'absolute'; + div.style.pointerEvents = 'none'; + div.style.zIndex = -1; + div.setAttribute('tabindex', '-1'); // so it can be focused + div.innerHTML = html; + document.body.appendChild(div); + + var focused=document.activeElement; + div.focus(); + + window.getSelection().removeAllRanges(); + var range = document.createRange(); + // not using range.selectNode(div) as that makes chrome add an extra
+ range.setStartBefore(div.firstChild); + range.setEndAfter(div.lastChild); + window.getSelection().addRange(range); + + var ok=false; + try { + ok = document.execCommand('copy'); + } catch (err) { + console.log(err); + } + if (!ok) { + console.log('execCommand failed!'); + return false; + } + + window.getSelection().removeAllRanges(); + document.body.removeChild(div); + + focused.focus(); + return true; + } + function cmpPos (a, b) { return (a.line - b.line) || (a.ch - b.ch); } @@ -1137,7 +1237,7 @@ firepad.RichTextCodeMirror = (function () { function bind (obj, method) { var fn = obj[method]; obj[method] = function () { - fn.apply(obj, arguments); + return fn.apply(obj, arguments); }; } From 6ce1dc9134840cbae6d6f5e87d6bc64365c83836 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Wed, 23 Mar 2016 15:51:49 +1100 Subject: [PATCH 02/29] Update firepad.js --- examples/firepad.js | 110 ++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/examples/firepad.js b/examples/firepad.js index 491c6e211..f2bee0507 100644 --- a/examples/firepad.js +++ b/examples/firepad.js @@ -4,7 +4,7 @@ * it requires no server-side code and can be added to any web app simply by * including a couple JavaScript files. * - * Firepad 0.0.0 + * Firepad 1.3.0 * http://www.firepad.io/ * License: MIT * Copyright: 2014 Firebase @@ -2903,6 +2903,8 @@ firepad.RichTextCodeMirror = (function () { 'li' : function(indent) { return 'padding-left: ' + (indent * 40) + 'px'; } }; + var firepadDefaultStyles=null; + // A cache of dynamically-created styles so we can re-use them. var StyleCache_ = {}; @@ -2923,9 +2925,9 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); - bind(this, 'onCopy_'); - bind(this, 'onCut_'); - bind(this, 'onPaste_'); + bind(this, 'onCodeMirrorCopy_'); + bind(this, 'onCodeMirrorCut_'); + bind(this, 'onCodeMirrorPaste_'); if (parseInt(CodeMirror.version) >= 4) { this.codeMirror.on('changes', this.onCodeMirrorChange_); @@ -2935,9 +2937,23 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.on('beforeChange', this.onCodeMirrorBeforeChange_); this.codeMirror.on('cursorActivity', this.onCursorActivity_); + this.codeMirror.on('copy', this.onCodeMirrorCopy_); + this.codeMirror.on('cut', this.onCodeMirrorCut_); + this.codeMirror.on('paste', this.onCodeMirrorPaste_); + this.changeId_ = 0; this.outstandingChanges_ = { }; this.dirtyLines_ = []; + + // caching some default styles needed later + var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); + firepadDefaultStyles=[ + 'font-family: '+style.getPropertyValue('font-family')+';', + 'font-size: '+style.getPropertyValue('font-size')+';', + 'background-color: '+style.getPropertyValue('background-color')+';', + 'color: '+style.getPropertyValue('color')+';', + 'text-align: '+style.getPropertyValue('text-align')+';' + ]; } utils.makeEventEmitter(RichTextCodeMirror, ['change', 'attributesChange', 'newLine']); @@ -2951,8 +2967,8 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.off('changes', this.onCodeMirrorChange_); this.codeMirror.off('cursorActivity', this.onCursorActivity_); - this.codeMirror.getWrapperElement().removeEventListener('copy'); - this.codeMirror.getWrapperElement().removeEventListener('cut'); + this.codeMirror.off('copy', this.onCodeMirrorCopy_); + this.codeMirror.off('cut', this.onCodeMirrorCut_); this.codeMirror.off('paste', this.onCodeMirrorPaste_); this.clearAnnotations_(); @@ -3467,54 +3483,47 @@ firepad.RichTextCodeMirror = (function () { }; // return true if e.preventDefault() was called - RichTextCodeMirror.prototype.onCopy_ = function(fp, e) { - if (!e.clipboardData) return false; // sanity, revert to CM copy - - var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); - if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 - if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge - if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy - - if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy - - var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html - if (!html) return false; // something went wrong, revert to CM copy - - // caching some default styles needed later - var cm=fp.codeMirror_; - if (!this.defaultStyles) { - var style = window.getComputedStyle(cm.getWrapperElement()); - this.defaultStyles={ - fontFamily: style.getPropertyValue('font-family'), - fontSize: style.getPropertyValue('font-size'), - bgColor: style.getPropertyValue('background-color') - }; - } - - // add default font to the html - html=''+html+''; - if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy - //console.log('html copy '+html); - e.preventDefault(); - return true; + RichTextCodeMirror.prototype.onCodeMirrorCopy_ = function(cm, e) { + var fp=this.codeMirror.firepad; + if (!e.clipboardData) return false; // sanity, revert to CM copy + + var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); + if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 + if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge + if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy + + if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy + + var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html + if (!html) return false; // something went wrong, revert to CM copy + + // add default styles to html + html=''+html+''; + + if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy + //console.log('html copy '+html); + e.preventDefault(); + return true; }; - RichTextCodeMirror.prototype.onCut_ = function(fp, e) { + RichTextCodeMirror.prototype.onCodeMirrorCut_ = function(cm, e) { if (!e.clipboardData) return; // older browsers - if (!this.onCopy_(fp, e)) return; // default to cm cut - fp.codeMirror_.replaceSelection(''); + //var fp=this.codeMirror.firepad; + if (!this.onCodeMirrorCopy_(cm, e)) return; // default to cm cut + cm.replaceSelection(''); }; - RichTextCodeMirror.prototype.onPaste_ = function(fp, e) { + RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { if (!e.clipboardData) return; // sanity, revert to CM paste + var fp=this.codeMirror.firepad; var html = e.clipboardData.getData('text/html'); if (!html) return null; // something went wrong, revert to CM paste - // setting bg color when not needed can break selection, so removing it here - if (this.defaultStyles) html = html.split('background-color: '+this.defaultStyles.bgColor).join('background-color:transparent'); + // removing default styles + firepadDefaultStyles.forEach(function(s) { html = html.split(s).join(''); }); - fp.codeMirror_.replaceSelection(''); + cm.replaceSelection(''); fp.insertHtmlAtCursor(html); e.preventDefault(); }; @@ -5451,8 +5460,6 @@ firepad.Firepad = (function(global) { var ace = global.ace; function Firepad(ref, place, options) { - var self = this; - if (!(this instanceof Firepad)) { return new Firepad(ref, place, options); } if (!CodeMirror && !ace) { @@ -5496,10 +5503,6 @@ firepad.Firepad = (function(global) { } this.codeMirror_.setOption('keyMap', 'richtext'); this.firepadWrapper_.className += ' firepad-richtext'; - - this.codeMirror_.getWrapperElement().addEventListener('copy', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCopy_(self, e); }); - this.codeMirror_.getWrapperElement().addEventListener('cut', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCut_(self, e); }); - this.codeMirror_.on('paste', function(cm, e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onPaste_(self, e); }); } this.imageInsertionUI = this.getOption('imageInsertionUI', true); @@ -5529,6 +5532,7 @@ firepad.Firepad = (function(global) { } this.client_ = new EditorClient(this.firebaseAdapter_, this.editorAdapter_); + var self = this; this.firebaseAdapter_.on('cursor', function() { self.trigger.apply(self, ['cursor'].concat([].slice.call(arguments))); }); @@ -5641,8 +5645,7 @@ firepad.Firepad = (function(global) { textPieces = [textPieces]; } - var self = this; - self.codeMirror_.operation(function() { + // TODO: Batch this all into a single operation. // HACK: We should check if we're actually at the beginning of a line; but checking for index == 0 is sufficient // for the setText() case. var atNewLine = index === 0; @@ -5651,10 +5654,9 @@ firepad.Firepad = (function(global) { for (var i = 0; i < inserts.length; i++) { var string = inserts[i].string; var attributes = inserts[i].attributes; - self.richTextCodeMirror_.insertText(index, string, attributes); + this.richTextCodeMirror_.insertText(index, string, attributes); index += string.length; } - }); }; Firepad.prototype.getOperationForSpan = function(start, end) { @@ -5995,4 +5997,4 @@ firepad.Firepad.Headless = firepad.Headless; firepad.Firepad.RichTextCodeMirrorAdapter = firepad.RichTextCodeMirrorAdapter; firepad.Firepad.ACEAdapter = firepad.ACEAdapter; -return firepad.Firepad; }, this); \ No newline at end of file +return firepad.Firepad; }, this); From 88032a541b9b220a8ddf9c1b664a5249ebd2c0d6 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Wed, 23 Mar 2016 16:08:51 +1100 Subject: [PATCH 03/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 91 ++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index e22de2d89..9a016f180 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -16,6 +16,8 @@ firepad.RichTextCodeMirror = (function () { 'li' : function(indent) { return 'padding-left: ' + (indent * 40) + 'px'; } }; + var firepadDefaultStyles=null; + // A cache of dynamically-created styles so we can re-use them. var StyleCache_ = {}; @@ -36,9 +38,9 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); - bind(this, 'onCopy_'); - bind(this, 'onCut_'); - bind(this, 'onPaste_'); + bind(this, 'onCodeMirrorCopy_'); + bind(this, 'onCodeMirrorCut_'); + bind(this, 'onCodeMirrorPaste_'); if (parseInt(CodeMirror.version) >= 4) { this.codeMirror.on('changes', this.onCodeMirrorChange_); @@ -48,9 +50,23 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.on('beforeChange', this.onCodeMirrorBeforeChange_); this.codeMirror.on('cursorActivity', this.onCursorActivity_); + this.codeMirror.on('copy', this.onCodeMirrorCopy_); + this.codeMirror.on('cut', this.onCodeMirrorCut_); + this.codeMirror.on('paste', this.onCodeMirrorPaste_); + this.changeId_ = 0; this.outstandingChanges_ = { }; this.dirtyLines_ = []; + + // caching some default styles needed later + var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); + firepadDefaultStyles=[ + 'font-family: '+style.getPropertyValue('font-family')+';', + 'font-size: '+style.getPropertyValue('font-size')+';', + 'background-color: '+style.getPropertyValue('background-color')+';', + 'color: '+style.getPropertyValue('color')+';', + 'text-align: '+style.getPropertyValue('text-align')+';' + ]; } utils.makeEventEmitter(RichTextCodeMirror, ['change', 'attributesChange', 'newLine']); @@ -64,8 +80,8 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.off('changes', this.onCodeMirrorChange_); this.codeMirror.off('cursorActivity', this.onCursorActivity_); - this.codeMirror.getWrapperElement().removeEventListener('copy'); - this.codeMirror.getWrapperElement().removeEventListener('cut'); + this.codeMirror.off('copy', this.onCodeMirrorCopy_); + this.codeMirror.off('cut', this.onCodeMirrorCut_); this.codeMirror.off('paste', this.onCodeMirrorPaste_); this.clearAnnotations_(); @@ -580,54 +596,47 @@ firepad.RichTextCodeMirror = (function () { }; // return true if e.preventDefault() was called - RichTextCodeMirror.prototype.onCopy_ = function(fp, e) { - if (!e.clipboardData) return false; // sanity, revert to CM copy - - var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); - if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 - if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge - if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy - - if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy - - var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html - if (!html) return false; // something went wrong, revert to CM copy - - // caching some default styles needed later - var cm=fp.codeMirror_; - if (!this.defaultStyles) { - var style = window.getComputedStyle(cm.getWrapperElement()); - this.defaultStyles={ - fontFamily: style.getPropertyValue('font-family'), - fontSize: style.getPropertyValue('font-size'), - bgColor: style.getPropertyValue('background-color') - }; - } + RichTextCodeMirror.prototype.onCodeMirrorCopy_ = function(cm, e) { + var fp=this.codeMirror.firepad; + if (!e.clipboardData) return false; // sanity, revert to CM copy + + var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); + if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 + if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge + if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy - // add default font to the html - html=''+html+''; - if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy - //console.log('html copy '+html); - e.preventDefault(); - return true; + if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy + + var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html + if (!html) return false; // something went wrong, revert to CM copy + + // add default styles to html + html=''+html+''; + + if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy + //console.log('html copy '+html); + e.preventDefault(); + return true; }; - RichTextCodeMirror.prototype.onCut_ = function(fp, e) { + RichTextCodeMirror.prototype.onCodeMirrorCut_ = function(cm, e) { if (!e.clipboardData) return; // older browsers - if (!this.onCopy_(fp, e)) return; // default to cm cut - fp.codeMirror_.replaceSelection(''); + //var fp=this.codeMirror.firepad; + if (!this.onCodeMirrorCopy_(cm, e)) return; // default to cm cut + cm.replaceSelection(''); }; - RichTextCodeMirror.prototype.onPaste_ = function(fp, e) { + RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { if (!e.clipboardData) return; // sanity, revert to CM paste + var fp=this.codeMirror.firepad; var html = e.clipboardData.getData('text/html'); if (!html) return null; // something went wrong, revert to CM paste - // setting bg color when not needed can break selection, so removing it here - if (this.defaultStyles) html = html.split('background-color: '+this.defaultStyles.bgColor).join('background-color:transparent'); + // removing default styles + firepadDefaultStyles.forEach(function(s) { html = html.split(s).join(''); }); - fp.codeMirror_.replaceSelection(''); + cm.replaceSelection(''); fp.insertHtmlAtCursor(html); e.preventDefault(); }; From 30ff490104ce79d072fc30016921fbd721c68693 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Wed, 23 Mar 2016 16:09:31 +1100 Subject: [PATCH 04/29] Update firepad.js --- lib/firepad.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/firepad.js b/lib/firepad.js index d4d421bc1..6ab8a2c72 100644 --- a/lib/firepad.js +++ b/lib/firepad.js @@ -18,8 +18,6 @@ firepad.Firepad = (function(global) { var ace = global.ace; function Firepad(ref, place, options) { - var self = this; - if (!(this instanceof Firepad)) { return new Firepad(ref, place, options); } if (!CodeMirror && !ace) { @@ -63,10 +61,6 @@ firepad.Firepad = (function(global) { } this.codeMirror_.setOption('keyMap', 'richtext'); this.firepadWrapper_.className += ' firepad-richtext'; - - this.codeMirror_.getWrapperElement().addEventListener('copy', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCopy_(self, e); }); - this.codeMirror_.getWrapperElement().addEventListener('cut', function(e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onCut_(self, e); }); - this.codeMirror_.on('paste', function(cm, e) { if (self.richTextCodeMirror_) self.richTextCodeMirror_.onPaste_(self, e); }); } this.imageInsertionUI = this.getOption('imageInsertionUI', true); From af53b7a4b28de20539fdba8f232efd2da1e3d047 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 5 Apr 2016 11:00:36 +1000 Subject: [PATCH 05/29] Update firepad.js --- examples/firepad.js | 163 ++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 96 deletions(-) diff --git a/examples/firepad.js b/examples/firepad.js index f2bee0507..e1b6da6dc 100644 --- a/examples/firepad.js +++ b/examples/firepad.js @@ -2903,8 +2903,6 @@ firepad.RichTextCodeMirror = (function () { 'li' : function(indent) { return 'padding-left: ' + (indent * 40) + 'px'; } }; - var firepadDefaultStyles=null; - // A cache of dynamically-created styles so we can re-use them. var StyleCache_ = {}; @@ -2925,8 +2923,7 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); - bind(this, 'onCodeMirrorCopy_'); - bind(this, 'onCodeMirrorCut_'); + bind(this, 'onCodeMirrorCopyCut_'); bind(this, 'onCodeMirrorPaste_'); if (parseInt(CodeMirror.version) >= 4) { @@ -2937,23 +2934,13 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.on('beforeChange', this.onCodeMirrorBeforeChange_); this.codeMirror.on('cursorActivity', this.onCursorActivity_); - this.codeMirror.on('copy', this.onCodeMirrorCopy_); - this.codeMirror.on('cut', this.onCodeMirrorCut_); + this.codeMirror.on('copy', this.onCodeMirrorCopyCut_); + this.codeMirror.on('cut', this.onCodeMirrorCopyCut_); this.codeMirror.on('paste', this.onCodeMirrorPaste_); this.changeId_ = 0; this.outstandingChanges_ = { }; this.dirtyLines_ = []; - - // caching some default styles needed later - var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); - firepadDefaultStyles=[ - 'font-family: '+style.getPropertyValue('font-family')+';', - 'font-size: '+style.getPropertyValue('font-size')+';', - 'background-color: '+style.getPropertyValue('background-color')+';', - 'color: '+style.getPropertyValue('color')+';', - 'text-align: '+style.getPropertyValue('text-align')+';' - ]; } utils.makeEventEmitter(RichTextCodeMirror, ['change', 'attributesChange', 'newLine']); @@ -2967,8 +2954,8 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.off('changes', this.onCodeMirrorChange_); this.codeMirror.off('cursorActivity', this.onCursorActivity_); - this.codeMirror.off('copy', this.onCodeMirrorCopy_); - this.codeMirror.off('cut', this.onCodeMirrorCut_); + this.codeMirror.off('copy', this.onCodeMirrorCopyCut_); + this.codeMirror.off('cut', this.onCodeMirrorCopyCut_); this.codeMirror.off('paste', this.onCodeMirrorPaste_); this.clearAnnotations_(); @@ -3482,90 +3469,44 @@ firepad.RichTextCodeMirror = (function () { } }; - // return true if e.preventDefault() was called - RichTextCodeMirror.prototype.onCodeMirrorCopy_ = function(cm, e) { + RichTextCodeMirror.prototype.onCodeMirrorCopyCut_ = function(cm, e) { var fp=this.codeMirror.firepad; - if (!e.clipboardData) return false; // sanity, revert to CM copy + if (!fp.selectionHasAttributes()) return ; // not rich text - var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); - if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 - if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge - if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy + var html=fp.getHtmlFromSelection(); + if (!html) return; // something went wrong - if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy + if (!this.firepadStyleWrapper) { + var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); + this.firepadStyleWrapper= + 'font-family:'+style.getPropertyValue('font-family')+';'+ + 'font-size:'+style.getPropertyValue('font-size')+';'+ + 'background-color:'+style.getPropertyValue('background-color')+';'+ + 'color:'+style.getPropertyValue('color')+';'+ + 'text-align:'+style.getPropertyValue('text-align')+';'; + } + html=''+html+''; - var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html - if (!html) return false; // something went wrong, revert to CM copy + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + if (!e.clipboardData || ios) return; // clipboard ops not supported - // add default styles to html - html=''+html+''; + if (e.type == "cut") cm.replaceSelection("", null, "cut"); - if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy - //console.log('html copy '+html); - e.preventDefault(); - return true; - }; - - RichTextCodeMirror.prototype.onCodeMirrorCut_ = function(cm, e) { - if (!e.clipboardData) return; // older browsers - //var fp=this.codeMirror.firepad; - if (!this.onCodeMirrorCopy_(cm, e)) return; // default to cm cut - cm.replaceSelection(''); - }; - - RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { - if (!e.clipboardData) return; // sanity, revert to CM paste + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData("text/html", html); + }; - var fp=this.codeMirror.firepad; - var html = e.clipboardData.getData('text/html'); - if (!html) return null; // something went wrong, revert to CM paste - - // removing default styles - firepadDefaultStyles.forEach(function(s) { html = html.split(s).join(''); }); + RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { + var html = e.clipboardData ? e.clipboardData.getData('text/html') : null; + if (!html) return; // something went wrong, revert to CM paste cm.replaceSelection(''); + var fp=this.codeMirror.firepad; fp.insertHtmlAtCursor(html); e.preventDefault(); }; - function copyHtmlToClipboard(html) { - var div = document.createElement('div'); - div.style.opacity = 0; - div.style.position = 'absolute'; - div.style.pointerEvents = 'none'; - div.style.zIndex = -1; - div.setAttribute('tabindex', '-1'); // so it can be focused - div.innerHTML = html; - document.body.appendChild(div); - - var focused=document.activeElement; - div.focus(); - - window.getSelection().removeAllRanges(); - var range = document.createRange(); - // not using range.selectNode(div) as that makes chrome add an extra
- range.setStartBefore(div.firstChild); - range.setEndAfter(div.lastChild); - window.getSelection().addRange(range); - - var ok=false; - try { - ok = document.execCommand('copy'); - } catch (err) { - console.log(err); - } - if (!ok) { - console.log('execCommand failed!'); - return false; - } - - window.getSelection().removeAllRanges(); - document.body.removeChild(div); - - focused.focus(); - return true; - } - function cmpPos (a, b) { return (a.line - b.line) || (a.ch - b.ch); } @@ -4842,14 +4783,15 @@ firepad.ParseHtml = (function () { } }; - var entityManager_; - function parseHtml(html, entityManager) { + var entityManager_, codeMirror_; + function parseHtml(html, entityManager, codeMirror) { // Create DIV with HTML (as a convenient way to parse it). var div = (firepad.document || document).createElement('div'); div.innerHTML = html; // HACK until I refactor this. entityManager_ = entityManager; + codeMirror_ = codeMirror; var output = new ParseOutput(); var state = new ParseState(); @@ -4981,7 +4923,29 @@ firepad.ParseHtml = (function () { } } + function styleEqual(s1,s2) { + s1=s1.toLowerCase(); // lower + s1=s1.split(' ').join(''); // remove spaces + s1=s1.lastIndexOf(";") == s1.length - 1 ? s1.substring(0, s1.length -1 ) : s1; // remove trailing ; + s2=s2.toLowerCase(); // lower + s2=s2.split(' ').join(''); // remove spaces + s2=s2.lastIndexOf(";") == s2.length - 1 ? s2.substring(0, s2.length -1 ) : s2; // remove trailing ; + return s1==s2; + } + function parseStyle(state, styleString) { + if (!this.firepadDefaultStyles) { + // caching some default styles needed later + var style = window.getComputedStyle(codeMirror_.getWrapperElement()); + firepadDefaultStyles={ + fontFamily: style.getPropertyValue('font-family'), + fontSize: style.getPropertyValue('font-size'), + backgroundColor: style.getPropertyValue('background-color'), + color: style.getPropertyValue('color'), + textAlign: style.getPropertyValue('text-align') + }; + } + var textFormatting = state.textFormatting; var lineFormatting = state.lineFormatting; var styles = styleString.split(';'); @@ -5006,15 +4970,19 @@ firepad.ParseHtml = (function () { textFormatting = textFormatting.italic(italic); break; case 'color': + if (styleEqual(val, this.firepadDefaultStyles.color)) break; textFormatting = textFormatting.color(val); break; case 'background-color': + if (styleEqual(val, this.firepadDefaultStyles.backgroundColor)) break; textFormatting = textFormatting.backgroundColor(val); break; case 'text-align': + if (styleEqual(val, this.firepadDefaultStyles.textAlign)) break; lineFormatting = lineFormatting.align(val); break; case 'font-size': + if (styleEqual(val, this.firepadDefaultStyles.fontSize)) break; var size = null; var allowedValues = ['px','pt','%','em','xx-small','x-small','small','medium','large','x-large','xx-large','smaller','larger']; if (firepad.utils.stringEndsWith(val, allowedValues)) { @@ -5028,6 +4996,7 @@ firepad.ParseHtml = (function () { } break; case 'font-family': + if (styleEqual(val, this.firepadDefaultStyles.fontFamily)) break; var font = firepad.utils.trim(val.split(',')[0]); // get first font. font = font.replace(/['"]/g, ''); // remove quotes. font = font.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() }); @@ -5402,7 +5371,7 @@ firepad.Headless = (function() { } self.initializeFakeDom(function() { - var textPieces = ParseHtml(html, self.entityManager_); + var textPieces = ParseHtml(html, self.entityManager_, self.codeMirror_); var inserts = firepad.textPiecesToInserts(true, textPieces); var op = new TextOperation(); @@ -5645,7 +5614,8 @@ firepad.Firepad = (function(global) { textPieces = [textPieces]; } - // TODO: Batch this all into a single operation. + var self = this; + self.codeMirror_.operation(function() { // HACK: We should check if we're actually at the beginning of a line; but checking for index == 0 is sufficient // for the setText() case. var atNewLine = index === 0; @@ -5654,9 +5624,10 @@ firepad.Firepad = (function(global) { for (var i = 0; i < inserts.length; i++) { var string = inserts[i].string; var attributes = inserts[i].attributes; - this.richTextCodeMirror_.insertText(index, string, attributes); + self.richTextCodeMirror_.insertText(index, string, attributes); index += string.length; } + }); }; Firepad.prototype.getOperationForSpan = function(start, end) { @@ -5712,7 +5683,7 @@ firepad.Firepad = (function(global) { }; Firepad.prototype.insertHtml = function (index, html) { - var lines = firepad.ParseHtml(html, this.entityManager_); + var lines = firepad.ParseHtml(html, this.entityManager_, this.codeMirror_); this.insertText(index, lines); }; @@ -5721,7 +5692,7 @@ firepad.Firepad = (function(global) { }; Firepad.prototype.setHtml = function (html) { - var lines = firepad.ParseHtml(html, this.entityManager_); + var lines = firepad.ParseHtml(html, this.entityManager_, this.codeMirror_); this.setText(lines); }; From 59643cb03bc658d630516f71cd5602280b9dcdc1 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 5 Apr 2016 11:03:25 +1000 Subject: [PATCH 06/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 121 +++++++++--------------------------- 1 file changed, 31 insertions(+), 90 deletions(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index 9a016f180..b3654e1a2 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -16,8 +16,6 @@ firepad.RichTextCodeMirror = (function () { 'li' : function(indent) { return 'padding-left: ' + (indent * 40) + 'px'; } }; - var firepadDefaultStyles=null; - // A cache of dynamically-created styles so we can re-use them. var StyleCache_ = {}; @@ -38,8 +36,7 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); - bind(this, 'onCodeMirrorCopy_'); - bind(this, 'onCodeMirrorCut_'); + bind(this, 'onCodeMirrorCopyCut_'); bind(this, 'onCodeMirrorPaste_'); if (parseInt(CodeMirror.version) >= 4) { @@ -50,23 +47,13 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.on('beforeChange', this.onCodeMirrorBeforeChange_); this.codeMirror.on('cursorActivity', this.onCursorActivity_); - this.codeMirror.on('copy', this.onCodeMirrorCopy_); - this.codeMirror.on('cut', this.onCodeMirrorCut_); + this.codeMirror.on('copy', this.onCodeMirrorCopyCut_); + this.codeMirror.on('cut', this.onCodeMirrorCopyCut_); this.codeMirror.on('paste', this.onCodeMirrorPaste_); this.changeId_ = 0; this.outstandingChanges_ = { }; this.dirtyLines_ = []; - - // caching some default styles needed later - var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); - firepadDefaultStyles=[ - 'font-family: '+style.getPropertyValue('font-family')+';', - 'font-size: '+style.getPropertyValue('font-size')+';', - 'background-color: '+style.getPropertyValue('background-color')+';', - 'color: '+style.getPropertyValue('color')+';', - 'text-align: '+style.getPropertyValue('text-align')+';' - ]; } utils.makeEventEmitter(RichTextCodeMirror, ['change', 'attributesChange', 'newLine']); @@ -80,8 +67,8 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.off('changes', this.onCodeMirrorChange_); this.codeMirror.off('cursorActivity', this.onCursorActivity_); - this.codeMirror.off('copy', this.onCodeMirrorCopy_); - this.codeMirror.off('cut', this.onCodeMirrorCut_); + this.codeMirror.off('copy', this.onCodeMirrorCopyCut_); + this.codeMirror.off('cut', this.onCodeMirrorCopyCut_); this.codeMirror.off('paste', this.onCodeMirrorPaste_); this.clearAnnotations_(); @@ -595,90 +582,44 @@ firepad.RichTextCodeMirror = (function () { } }; - // return true if e.preventDefault() was called - RichTextCodeMirror.prototype.onCodeMirrorCopy_ = function(cm, e) { + RichTextCodeMirror.prototype.onCodeMirrorCopyCut_ = function(cm, e) { var fp=this.codeMirror.firepad; - if (!e.clipboardData) return false; // sanity, revert to CM copy - - var IE = !!navigator.userAgent.match(/MSIE\s([\d.]+)/); - if (navigator.userAgent.match(/Trident\/7.0/) && navigator.userAgent.match(/rv:11/)) IE=true; //IE 11 - if (navigator.userAgent.match(/Edge/g)) IE=true; //IE edge - if (IE) return false; // IE does not support document.execCommand('copy') of html, revert to CM copy + if (!fp.selectionHasAttributes()) return ; // not rich text + + var html=fp.getHtmlFromSelection(); + if (!html) return; // something went wrong + + if (!this.firepadStyleWrapper) { + var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); + this.firepadStyleWrapper= + 'font-family:'+style.getPropertyValue('font-family')+';'+ + 'font-size:'+style.getPropertyValue('font-size')+';'+ + 'background-color:'+style.getPropertyValue('background-color')+';'+ + 'color:'+style.getPropertyValue('color')+';'+ + 'text-align:'+style.getPropertyValue('text-align')+';'; + } + html=''+html+''; - if (!fp.selectionHasAttributes()) return false; // not rich text, revert to CM copy + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + if (!e.clipboardData || ios) return; // clipboard ops not supported - var html=fp.getHtmlFromSelection(); // IE does not support document.execCommand('copy') of html - if (!html) return false; // something went wrong, revert to CM copy + if (e.type == "cut") cm.replaceSelection("", null, "cut"); - // add default styles to html - html=''+html+''; + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData("text/html", html); + }; - if (!copyHtmlToClipboard(html)) return false; // something went wrong, revert to CM copy - //console.log('html copy '+html); - e.preventDefault(); - return true; - }; + RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { + var html = e.clipboardData ? e.clipboardData.getData('text/html') : null; + if (!html) return; // something went wrong, revert to CM paste - RichTextCodeMirror.prototype.onCodeMirrorCut_ = function(cm, e) { - if (!e.clipboardData) return; // older browsers - //var fp=this.codeMirror.firepad; - if (!this.onCodeMirrorCopy_(cm, e)) return; // default to cm cut cm.replaceSelection(''); - }; - - RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { - if (!e.clipboardData) return; // sanity, revert to CM paste - var fp=this.codeMirror.firepad; - var html = e.clipboardData.getData('text/html'); - if (!html) return null; // something went wrong, revert to CM paste - - // removing default styles - firepadDefaultStyles.forEach(function(s) { html = html.split(s).join(''); }); - - cm.replaceSelection(''); fp.insertHtmlAtCursor(html); e.preventDefault(); }; - function copyHtmlToClipboard(html) { - var div = document.createElement('div'); - div.style.opacity = 0; - div.style.position = 'absolute'; - div.style.pointerEvents = 'none'; - div.style.zIndex = -1; - div.setAttribute('tabindex', '-1'); // so it can be focused - div.innerHTML = html; - document.body.appendChild(div); - - var focused=document.activeElement; - div.focus(); - - window.getSelection().removeAllRanges(); - var range = document.createRange(); - // not using range.selectNode(div) as that makes chrome add an extra
- range.setStartBefore(div.firstChild); - range.setEndAfter(div.lastChild); - window.getSelection().addRange(range); - - var ok=false; - try { - ok = document.execCommand('copy'); - } catch (err) { - console.log(err); - } - if (!ok) { - console.log('execCommand failed!'); - return false; - } - - window.getSelection().removeAllRanges(); - document.body.removeChild(div); - - focused.focus(); - return true; - } - function cmpPos (a, b) { return (a.line - b.line) || (a.ch - b.ch); } From fdbd7d9e534f5cafabb5619d0ff9efeb512d292f Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 5 Apr 2016 11:04:41 +1000 Subject: [PATCH 07/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index b3654e1a2..70e2235ce 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -36,7 +36,7 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); - bind(this, 'onCodeMirrorCopyCut_'); + bind(this, 'onCodeMirrorCopyCut_'); bind(this, 'onCodeMirrorPaste_'); if (parseInt(CodeMirror.version) >= 4) { @@ -47,8 +47,8 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.on('beforeChange', this.onCodeMirrorBeforeChange_); this.codeMirror.on('cursorActivity', this.onCursorActivity_); - this.codeMirror.on('copy', this.onCodeMirrorCopyCut_); - this.codeMirror.on('cut', this.onCodeMirrorCopyCut_); + this.codeMirror.on('copy', this.onCodeMirrorCopyCut_); + this.codeMirror.on('cut', this.onCodeMirrorCopyCut_); this.codeMirror.on('paste', this.onCodeMirrorPaste_); this.changeId_ = 0; From acad413196118ee4d38f74a739fcf929f2270c9d Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 5 Apr 2016 11:29:54 +1000 Subject: [PATCH 08/29] Update parse-html.js --- lib/parse-html.js | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/parse-html.js b/lib/parse-html.js index 07ebb29d8..8e74d3ccc 100644 --- a/lib/parse-html.js +++ b/lib/parse-html.js @@ -101,14 +101,15 @@ firepad.ParseHtml = (function () { } }; - var entityManager_; - function parseHtml(html, entityManager) { + var entityManager_, codeMirror_; + function parseHtml(html, entityManager, codeMirror) { // Create DIV with HTML (as a convenient way to parse it). var div = (firepad.document || document).createElement('div'); div.innerHTML = html; // HACK until I refactor this. entityManager_ = entityManager; + codeMirror_ = codeMirror; var output = new ParseOutput(); var state = new ParseState(); @@ -240,7 +241,29 @@ firepad.ParseHtml = (function () { } } + function styleEqual(s1,s2) { + s1=s1.toLowerCase(); // lower + s1=s1.split(' ').join(''); // remove spaces + s1=s1.lastIndexOf(";") == s1.length - 1 ? s1.substring(0, s1.length -1 ) : s1; // remove trailing ; + s2=s2.toLowerCase(); // lower + s2=s2.split(' ').join(''); // remove spaces + s2=s2.lastIndexOf(";") == s2.length - 1 ? s2.substring(0, s2.length -1 ) : s2; // remove trailing ; + return s1==s2; + } + function parseStyle(state, styleString) { + if (!this.firepadDefaultStyles) { + // caching some default styles needed later + var style = window.getComputedStyle(codeMirror_.getWrapperElement()); + firepadDefaultStyles={ + fontFamily: style.getPropertyValue('font-family'), + fontSize: style.getPropertyValue('font-size'), + backgroundColor: style.getPropertyValue('background-color'), + color: style.getPropertyValue('color'), + textAlign: style.getPropertyValue('text-align') + }; + } + var textFormatting = state.textFormatting; var lineFormatting = state.lineFormatting; var styles = styleString.split(';'); @@ -265,15 +288,19 @@ firepad.ParseHtml = (function () { textFormatting = textFormatting.italic(italic); break; case 'color': + if (styleEqual(val, this.firepadDefaultStyles.color)) break; textFormatting = textFormatting.color(val); break; case 'background-color': + if (styleEqual(val, this.firepadDefaultStyles.backgroundColor)) break; textFormatting = textFormatting.backgroundColor(val); break; case 'text-align': + if (styleEqual(val, this.firepadDefaultStyles.textAlign)) break; lineFormatting = lineFormatting.align(val); break; case 'font-size': + if (styleEqual(val, this.firepadDefaultStyles.fontSize)) break; var size = null; var allowedValues = ['px','pt','%','em','xx-small','x-small','small','medium','large','x-large','xx-large','smaller','larger']; if (firepad.utils.stringEndsWith(val, allowedValues)) { @@ -287,6 +314,7 @@ firepad.ParseHtml = (function () { } break; case 'font-family': + if (styleEqual(val, this.firepadDefaultStyles.fontFamily)) break; var font = firepad.utils.trim(val.split(',')[0]); // get first font. font = font.replace(/['"]/g, ''); // remove quotes. font = font.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() }); From f703475ec15bd69cb16bbd0bb6af9b7dd2227785 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 5 Apr 2016 11:32:50 +1000 Subject: [PATCH 09/29] Update parse-html.js --- lib/parse-html.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/parse-html.js b/lib/parse-html.js index 8e74d3ccc..548f5456c 100644 --- a/lib/parse-html.js +++ b/lib/parse-html.js @@ -101,8 +101,8 @@ firepad.ParseHtml = (function () { } }; - var entityManager_, codeMirror_; - function parseHtml(html, entityManager, codeMirror) { + var entityManager_, codeMirror_; + function parseHtml(html, entityManager, codeMirror) { // Create DIV with HTML (as a convenient way to parse it). var div = (firepad.document || document).createElement('div'); div.innerHTML = html; @@ -300,7 +300,7 @@ firepad.ParseHtml = (function () { lineFormatting = lineFormatting.align(val); break; case 'font-size': - if (styleEqual(val, this.firepadDefaultStyles.fontSize)) break; + if (styleEqual(val, this.firepadDefaultStyles.fontSize)) break; var size = null; var allowedValues = ['px','pt','%','em','xx-small','x-small','small','medium','large','x-large','xx-large','smaller','larger']; if (firepad.utils.stringEndsWith(val, allowedValues)) { From f2f9131497e731ee4c6be5017d001b47f78cde56 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 5 Apr 2016 11:37:34 +1000 Subject: [PATCH 10/29] Update headless.js --- lib/headless.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/headless.js b/lib/headless.js index b93206a09..9de598159 100644 --- a/lib/headless.js +++ b/lib/headless.js @@ -110,7 +110,7 @@ firepad.Headless = (function() { } self.initializeFakeDom(function() { - var textPieces = ParseHtml(html, self.entityManager_); + var textPieces = ParseHtml(html, self.entityManager_, self.codeMirror_); var inserts = firepad.textPiecesToInserts(true, textPieces); var op = new TextOperation(); From 8366a573798afa0bda2af78e9c2910f34b9cd900 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 5 Apr 2016 11:41:12 +1000 Subject: [PATCH 11/29] Update firepad.js --- lib/firepad.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/firepad.js b/lib/firepad.js index 6ab8a2c72..9e40517c4 100644 --- a/lib/firepad.js +++ b/lib/firepad.js @@ -271,7 +271,7 @@ firepad.Firepad = (function(global) { }; Firepad.prototype.insertHtml = function (index, html) { - var lines = firepad.ParseHtml(html, this.entityManager_); + var lines = firepad.ParseHtml(html, this.entityManager_, this.codeMirror_); this.insertText(index, lines); }; @@ -280,7 +280,7 @@ firepad.Firepad = (function(global) { }; Firepad.prototype.setHtml = function (html) { - var lines = firepad.ParseHtml(html, this.entityManager_); + var lines = firepad.ParseHtml(html, this.entityManager_, this.codeMirror_); this.setText(lines); }; From 21ad1f71f9e289656de682338dadb444410ea932 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Tue, 19 Apr 2016 07:45:47 +1000 Subject: [PATCH 12/29] Update firepad.js --- lib/firepad.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/firepad.js b/lib/firepad.js index 9e40517c4..5e95da9f2 100644 --- a/lib/firepad.js +++ b/lib/firepad.js @@ -90,6 +90,7 @@ firepad.Firepad = (function(global) { } this.client_ = new EditorClient(this.firebaseAdapter_, this.editorAdapter_); + var self = this; this.firebaseAdapter_.on('cursor', function() { self.trigger.apply(self, ['cursor'].concat([].slice.call(arguments))); }); From ed47fe8477d2c7e29303a59415e3a9832861c2ae Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 17:46:17 +1000 Subject: [PATCH 13/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index 70e2235ce..6456e197a 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -612,7 +612,7 @@ firepad.RichTextCodeMirror = (function () { RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { var html = e.clipboardData ? e.clipboardData.getData('text/html') : null; - if (!html) return; // something went wrong, revert to CM paste + if (!html) return; // not html or something went wrong, revert to CM paste cm.replaceSelection(''); var fp=this.codeMirror.firepad; From 92ce779bb881730116e974c3d627bec4e9d1bc60 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 18:04:42 +1000 Subject: [PATCH 14/29] Update firepad.js --- lib/firepad.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/firepad.js b/lib/firepad.js index 5e95da9f2..7d9b36b32 100644 --- a/lib/firepad.js +++ b/lib/firepad.js @@ -250,7 +250,11 @@ firepad.Firepad = (function(global) { var op; for (var i = 0; i < doc.ops.length; i++) { op = doc.ops[i]; - for(var prop in op.attributes) if (op.attributes.hasOwnProperty(prop) && prop!=ATTR.LINE_SENTINEL) return true; // found an attribute + for (var prop in op.attributes) { + if (!op.attributes.hasOwnProperty(prop)) continue; + if (prop==ATTR.LINE_SENTINEL) continue; + for(var validAttr in firepad.AttributeConstants) if (firepad.AttributeConstants[validAttr] === prop) return true; // found one + } } return false; From d39f57301db76752f16f4c16578daa35282933e6 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 22:02:33 +1000 Subject: [PATCH 15/29] Update parse-html.js --- lib/parse-html.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/parse-html.js b/lib/parse-html.js index 548f5456c..bbd649cf9 100644 --- a/lib/parse-html.js +++ b/lib/parse-html.js @@ -55,14 +55,14 @@ firepad.ParseHtml = (function () { } ParseOutput.prototype.newlineIfNonEmpty = function(state) { - this.cleanLine_(); + this.cleanLine_(true); if (this.currentLine.length > 0) { this.newline(state); } }; ParseOutput.prototype.newlineIfNonEmptyOrListItem = function(state) { - this.cleanLine_(); + this.cleanLine_(true); if (this.currentLine.length > 0 || this.currentLineListItemType !== null) { this.newline(state); } @@ -84,15 +84,17 @@ firepad.ParseHtml = (function () { this.currentLineListItemType = type; }; - ParseOutput.prototype.cleanLine_ = function() { + ParseOutput.prototype.cleanLine_ = function(ignoreNbsps) { // Kinda' a hack, but we remove leading and trailing spaces (since these aren't significant in html) and // replaces nbsp's with normal spaces. if (this.currentLine.length > 0) { var last = this.currentLine.length - 1; this.currentLine[0].text = this.currentLine[0].text.replace(/^ +/, ''); this.currentLine[last].text = this.currentLine[last].text.replace(/ +$/g, ''); - for(var i = 0; i < this.currentLine.length; i++) { - this.currentLine[i].text = this.currentLine[i].text.replace(/\u00a0/g, ' '); + if (!ignoreNbsps) { + for(var i = 0; i < this.currentLine.length; i++) { + this.currentLine[i].text = this.currentLine[i].text.replace(/\u00a0/g, ' '); + } } } // If after stripping trailing whitespace, there's nothing left, clear currentLine out. @@ -103,6 +105,9 @@ firepad.ParseHtml = (function () { var entityManager_, codeMirror_; function parseHtml(html, entityManager, codeMirror) { + html=html.replace(/(\r\n|\n|\r)?(\r\n|\n|\r)?(\r\n|\n|\r)?/, ''); // remove + html=html.replace(/(\r\n|\n|\r)?<\/body>(\r\n|\n|\r)?<\/html>(\r\n|\n|\r)?/, ''); // remove + // Create DIV with HTML (as a convenient way to parse it). var div = (firepad.document || document).createElement('div'); div.innerHTML = html; From 1178b50e04a83d772913dd647c32aac8cb80e39d Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 22:04:40 +1000 Subject: [PATCH 16/29] Update parse-html.js --- lib/parse-html.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/parse-html.js b/lib/parse-html.js index bbd649cf9..c62c3141d 100644 --- a/lib/parse-html.js +++ b/lib/parse-html.js @@ -144,8 +144,8 @@ firepad.ParseHtml = (function () { switch (node.nodeType) { case Node.TEXT_NODE: - // This probably isn't exactly right, but mostly works... - var text = node.nodeValue.replace(/[ \n\t]+/g, ' '); + // replace spaces with   so they can withstand cleanLine_ + var text = node.nodeValue.replace(/ /g, '\u00a0'); output.currentLine.push(firepad.Text(text, state.textFormatting)); break; case Node.ELEMENT_NODE: From d405907f33f1d7d1d1c0a607823b668180298528 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 22:21:30 +1000 Subject: [PATCH 17/29] Update firepad.js --- examples/firepad.js | 114 +++----------------------------------------- 1 file changed, 7 insertions(+), 107 deletions(-) diff --git a/examples/firepad.js b/examples/firepad.js index e1b6da6dc..999caa8db 100644 --- a/examples/firepad.js +++ b/examples/firepad.js @@ -4,7 +4,7 @@ * it requires no server-side code and can be added to any web app simply by * including a couple JavaScript files. * - * Firepad 1.3.0 + * Firepad 0.0.0 * http://www.firepad.io/ * License: MIT * Copyright: 2014 Firebase @@ -2923,9 +2923,6 @@ firepad.RichTextCodeMirror = (function () { bind(this, 'onCodeMirrorChange_'); bind(this, 'onCursorActivity_'); - bind(this, 'onCodeMirrorCopyCut_'); - bind(this, 'onCodeMirrorPaste_'); - if (parseInt(CodeMirror.version) >= 4) { this.codeMirror.on('changes', this.onCodeMirrorChange_); } else { @@ -2934,10 +2931,6 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.on('beforeChange', this.onCodeMirrorBeforeChange_); this.codeMirror.on('cursorActivity', this.onCursorActivity_); - this.codeMirror.on('copy', this.onCodeMirrorCopyCut_); - this.codeMirror.on('cut', this.onCodeMirrorCopyCut_); - this.codeMirror.on('paste', this.onCodeMirrorPaste_); - this.changeId_ = 0; this.outstandingChanges_ = { }; this.dirtyLines_ = []; @@ -2953,11 +2946,6 @@ firepad.RichTextCodeMirror = (function () { this.codeMirror.off('change', this.onCodeMirrorChange_); this.codeMirror.off('changes', this.onCodeMirrorChange_); this.codeMirror.off('cursorActivity', this.onCursorActivity_); - - this.codeMirror.off('copy', this.onCodeMirrorCopyCut_); - this.codeMirror.off('cut', this.onCodeMirrorCopyCut_); - this.codeMirror.off('paste', this.onCodeMirrorPaste_); - this.clearAnnotations_(); }; @@ -3469,44 +3457,6 @@ firepad.RichTextCodeMirror = (function () { } }; - RichTextCodeMirror.prototype.onCodeMirrorCopyCut_ = function(cm, e) { - var fp=this.codeMirror.firepad; - if (!fp.selectionHasAttributes()) return ; // not rich text - - var html=fp.getHtmlFromSelection(); - if (!html) return; // something went wrong - - if (!this.firepadStyleWrapper) { - var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); - this.firepadStyleWrapper= - 'font-family:'+style.getPropertyValue('font-family')+';'+ - 'font-size:'+style.getPropertyValue('font-size')+';'+ - 'background-color:'+style.getPropertyValue('background-color')+';'+ - 'color:'+style.getPropertyValue('color')+';'+ - 'text-align:'+style.getPropertyValue('text-align')+';'; - } - html=''+html+''; - - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); - if (!e.clipboardData || ios) return; // clipboard ops not supported - - if (e.type == "cut") cm.replaceSelection("", null, "cut"); - - e.preventDefault(); - e.clipboardData.clearData(); - e.clipboardData.setData("text/html", html); - }; - - RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { - var html = e.clipboardData ? e.clipboardData.getData('text/html') : null; - if (!html) return; // something went wrong, revert to CM paste - - cm.replaceSelection(''); - var fp=this.codeMirror.firepad; - fp.insertHtmlAtCursor(html); - e.preventDefault(); - }; - function cmpPos (a, b) { return (a.line - b.line) || (a.ch - b.ch); } @@ -4074,7 +4024,7 @@ firepad.RichTextCodeMirror = (function () { function bind (obj, method) { var fn = obj[method]; obj[method] = function () { - return fn.apply(obj, arguments); + fn.apply(obj, arguments); }; } @@ -4783,15 +4733,14 @@ firepad.ParseHtml = (function () { } }; - var entityManager_, codeMirror_; - function parseHtml(html, entityManager, codeMirror) { + var entityManager_; + function parseHtml(html, entityManager) { // Create DIV with HTML (as a convenient way to parse it). var div = (firepad.document || document).createElement('div'); div.innerHTML = html; // HACK until I refactor this. entityManager_ = entityManager; - codeMirror_ = codeMirror; var output = new ParseOutput(); var state = new ParseState(); @@ -4923,29 +4872,7 @@ firepad.ParseHtml = (function () { } } - function styleEqual(s1,s2) { - s1=s1.toLowerCase(); // lower - s1=s1.split(' ').join(''); // remove spaces - s1=s1.lastIndexOf(";") == s1.length - 1 ? s1.substring(0, s1.length -1 ) : s1; // remove trailing ; - s2=s2.toLowerCase(); // lower - s2=s2.split(' ').join(''); // remove spaces - s2=s2.lastIndexOf(";") == s2.length - 1 ? s2.substring(0, s2.length -1 ) : s2; // remove trailing ; - return s1==s2; - } - function parseStyle(state, styleString) { - if (!this.firepadDefaultStyles) { - // caching some default styles needed later - var style = window.getComputedStyle(codeMirror_.getWrapperElement()); - firepadDefaultStyles={ - fontFamily: style.getPropertyValue('font-family'), - fontSize: style.getPropertyValue('font-size'), - backgroundColor: style.getPropertyValue('background-color'), - color: style.getPropertyValue('color'), - textAlign: style.getPropertyValue('text-align') - }; - } - var textFormatting = state.textFormatting; var lineFormatting = state.lineFormatting; var styles = styleString.split(';'); @@ -4970,19 +4897,15 @@ firepad.ParseHtml = (function () { textFormatting = textFormatting.italic(italic); break; case 'color': - if (styleEqual(val, this.firepadDefaultStyles.color)) break; textFormatting = textFormatting.color(val); break; case 'background-color': - if (styleEqual(val, this.firepadDefaultStyles.backgroundColor)) break; textFormatting = textFormatting.backgroundColor(val); break; case 'text-align': - if (styleEqual(val, this.firepadDefaultStyles.textAlign)) break; lineFormatting = lineFormatting.align(val); break; case 'font-size': - if (styleEqual(val, this.firepadDefaultStyles.fontSize)) break; var size = null; var allowedValues = ['px','pt','%','em','xx-small','x-small','small','medium','large','x-large','xx-large','smaller','larger']; if (firepad.utils.stringEndsWith(val, allowedValues)) { @@ -4996,7 +4919,6 @@ firepad.ParseHtml = (function () { } break; case 'font-family': - if (styleEqual(val, this.firepadDefaultStyles.fontFamily)) break; var font = firepad.utils.trim(val.split(',')[0]); // get first font. font = font.replace(/['"]/g, ''); // remove quotes. font = font.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() }); @@ -5371,7 +5293,7 @@ firepad.Headless = (function() { } self.initializeFakeDom(function() { - var textPieces = ParseHtml(html, self.entityManager_, self.codeMirror_); + var textPieces = ParseHtml(html, self.entityManager_); var inserts = firepad.textPiecesToInserts(true, textPieces); var op = new TextOperation(); @@ -5646,28 +5568,6 @@ firepad.Firepad = (function(global) { return this.getHtmlFromRange(null, null); }; - Firepad.prototype.selectionHasAttributes = function() { - var startPos = this.codeMirror_.getCursor('start'), endPos = this.codeMirror_.getCursor('end'); - var startIndex = this.codeMirror_.indexFromPos(startPos), endIndex = this.codeMirror_.indexFromPos(endPos); - return this.rangeHasAttributes(startIndex, endIndex); - }; - - Firepad.prototype.rangeHasAttributes = function(start, end) { - this.assertReady_('rangeHasAttributes'); - var doc = (start != null && end != null) ? - this.getOperationForSpan(start, end) : - this.getOperationForSpan(0, this.codeMirror_.getValue().length); - - var op; - for (var i = 0; i < doc.ops.length; i++) { - op = doc.ops[i]; - for(var prop in op.attributes) if (op.attributes.hasOwnProperty(prop) && prop!=ATTR.LINE_SENTINEL) return true; // found an attribute - } - - return false; - }; - - Firepad.prototype.getHtmlFromSelection = function() { var startPos = this.codeMirror_.getCursor('start'), endPos = this.codeMirror_.getCursor('end'); var startIndex = this.codeMirror_.indexFromPos(startPos), endIndex = this.codeMirror_.indexFromPos(endPos); @@ -5683,7 +5583,7 @@ firepad.Firepad = (function(global) { }; Firepad.prototype.insertHtml = function (index, html) { - var lines = firepad.ParseHtml(html, this.entityManager_, this.codeMirror_); + var lines = firepad.ParseHtml(html, this.entityManager_); this.insertText(index, lines); }; @@ -5692,7 +5592,7 @@ firepad.Firepad = (function(global) { }; Firepad.prototype.setHtml = function (html) { - var lines = firepad.ParseHtml(html, this.entityManager_, this.codeMirror_); + var lines = firepad.ParseHtml(html, this.entityManager_); this.setText(lines); }; From f08da69fe63d53b3d1231e641cfc3fb6e687e262 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 22:22:11 +1000 Subject: [PATCH 18/29] Update firepad.js From ed6d16cd36b5f5effd1c42813ef794b11e021692 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 22:24:13 +1000 Subject: [PATCH 19/29] Update firepad.js From 5f606e52b2eed9a41501f6b381fa6847b7779172 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Sun, 24 Apr 2016 22:25:21 +1000 Subject: [PATCH 20/29] Update firepad.js From 546d7a0179372f9bdf6947b49d83955dff29728f Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 29 Apr 2016 11:12:48 +1000 Subject: [PATCH 21/29] Update package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b2c419bc0..7dd3bddf6 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,8 @@ "karma-coverage": "^0.2.6", "karma-failed-reporter": "0.0.2", "karma-jasmine": "^0.3.6", - "karma-phantomjs-launcher": "~0.1.0", + "phantomjs-prebuilt": "2.1.4", + "karma-phantomjs-launcher": "~1.0.0", "karma-spec-reporter": "0.0.13" }, "scripts": { From 0e163e7edebcc9fe73b1dddb3da9758cc844fd9f Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 29 Apr 2016 12:16:17 +1000 Subject: [PATCH 22/29] Update firepad.js From 0e612ee6064deab4946b24f13307457ceb66cac7 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 29 Apr 2016 12:20:08 +1000 Subject: [PATCH 23/29] Update firepad.js From 2dce1d173eb78df5019999767cb58f1634dbbe23 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 20 May 2016 09:53:03 +1000 Subject: [PATCH 24/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 87 +++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index 6456e197a..dba3e14f2 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -7,6 +7,44 @@ firepad.RichTextCodeMirror = (function () { var ATTR = firepad.AttributeConstants; var RichTextClassPrefixDefault = 'cmrt-'; var RichTextOriginPrefix = 'cmrt-'; + RichTextCodeMirror.prototype.onCodeMirrorCopyCut_ = function(cm, e) { + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + if (!e.clipboardData || ios) return; // clipboard ops not supported + + // one time caching of html styles + if (!this.firepadStyleWrapper) { + var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); + this.firepadStyleWrapper= + 'font-family:'+style.getPropertyValue('font-family')+';'+ + 'font-size:'+style.getPropertyValue('font-size')+';'+ + 'background-color:'+style.getPropertyValue('background-color')+';'+ + 'color:'+style.getPropertyValue('color')+';'+ + 'text-align:'+style.getPropertyValue('text-align')+';'; + } + + var fp=this.codeMirror.firepad; + var gotHtml=false, val=''; + + // if selection has attributes try to get html + if (fp.selectionHasAttributes()) { + val=fp.getHtmlFromSelection(); + if (val) { + val=''+val+''; + gotHtml=true; + } + } + + // if we couldn't get html try to get text + if (!gotHtml) { + val=this.codeMirror.getSelections().join('\n').replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), ''); // remove sentinels + if (!val) return; // no html or text - something went wrong + } + + if (e.type == 'cut') cm.replaceSelection('', null, 'cut'); + e.clipboardData.clearData(); + e.clipboardData.setData(gotHtml ? 'text/html' : 'text', val); + e.preventDefault() + }; // These attributes will have styles generated dynamically in the page. var DynamicStyleAttributes = { @@ -583,35 +621,46 @@ firepad.RichTextCodeMirror = (function () { }; RichTextCodeMirror.prototype.onCodeMirrorCopyCut_ = function(cm, e) { - var fp=this.codeMirror.firepad; - if (!fp.selectionHasAttributes()) return ; // not rich text + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + if (!e.clipboardData || ios) return; // clipboard ops not supported - var html=fp.getHtmlFromSelection(); - if (!html) return; // something went wrong - - if (!this.firepadStyleWrapper) { - var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); - this.firepadStyleWrapper= + // one time caching of html styles + if (!this.firepadStyleWrapper) { + var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); + this.firepadStyleWrapper= 'font-family:'+style.getPropertyValue('font-family')+';'+ 'font-size:'+style.getPropertyValue('font-size')+';'+ 'background-color:'+style.getPropertyValue('background-color')+';'+ 'color:'+style.getPropertyValue('color')+';'+ 'text-align:'+style.getPropertyValue('text-align')+';'; - } - html=''+html+''; + } - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); - if (!e.clipboardData || ios) return; // clipboard ops not supported + var fp=this.codeMirror.firepad; + var gotHtml=false, val=''; + + // if selection has attributes try to get html + if (fp.selectionHasAttributes()) { + val=fp.getHtmlFromSelection(); + if (val) { + val=''+val+''; + gotHtml=true; + } + } - if (e.type == "cut") cm.replaceSelection("", null, "cut"); + // if we couldn't get html try to get text + if (!gotHtml) { + val=this.codeMirror.getSelections().join('\n').replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), ''); // remove sentinels + if (!val) return; // no html or text - something went wrong + } - e.preventDefault(); - e.clipboardData.clearData(); - e.clipboardData.setData("text/html", html); - }; + if (e.type == 'cut') cm.replaceSelection('', null, 'cut'); + e.clipboardData.clearData(); + e.clipboardData.setData(gotHtml ? 'text/html' : 'text', val); + e.preventDefault() + }; - RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { - var html = e.clipboardData ? e.clipboardData.getData('text/html') : null; + RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { + var html = e.clipboardData ? e.clipboardData.getData('text/html') : null; if (!html) return; // not html or something went wrong, revert to CM paste cm.replaceSelection(''); From 8fb5aaf1ef082488676461a2ff325b57f91c55b4 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 24 Jun 2016 09:38:03 +1000 Subject: [PATCH 25/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 78 ++++++------------------------------- 1 file changed, 12 insertions(+), 66 deletions(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index dba3e14f2..5e8f48bd3 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -7,44 +7,6 @@ firepad.RichTextCodeMirror = (function () { var ATTR = firepad.AttributeConstants; var RichTextClassPrefixDefault = 'cmrt-'; var RichTextOriginPrefix = 'cmrt-'; - RichTextCodeMirror.prototype.onCodeMirrorCopyCut_ = function(cm, e) { - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); - if (!e.clipboardData || ios) return; // clipboard ops not supported - - // one time caching of html styles - if (!this.firepadStyleWrapper) { - var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); - this.firepadStyleWrapper= - 'font-family:'+style.getPropertyValue('font-family')+';'+ - 'font-size:'+style.getPropertyValue('font-size')+';'+ - 'background-color:'+style.getPropertyValue('background-color')+';'+ - 'color:'+style.getPropertyValue('color')+';'+ - 'text-align:'+style.getPropertyValue('text-align')+';'; - } - - var fp=this.codeMirror.firepad; - var gotHtml=false, val=''; - - // if selection has attributes try to get html - if (fp.selectionHasAttributes()) { - val=fp.getHtmlFromSelection(); - if (val) { - val=''+val+''; - gotHtml=true; - } - } - - // if we couldn't get html try to get text - if (!gotHtml) { - val=this.codeMirror.getSelections().join('\n').replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), ''); // remove sentinels - if (!val) return; // no html or text - something went wrong - } - - if (e.type == 'cut') cm.replaceSelection('', null, 'cut'); - e.clipboardData.clearData(); - e.clipboardData.setData(gotHtml ? 'text/html' : 'text', val); - e.preventDefault() - }; // These attributes will have styles generated dynamically in the page. var DynamicStyleAttributes = { @@ -624,49 +586,33 @@ firepad.RichTextCodeMirror = (function () { var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); if (!e.clipboardData || ios) return; // clipboard ops not supported - // one time caching of html styles - if (!this.firepadStyleWrapper) { - var style = window.getComputedStyle(this.codeMirror.getWrapperElement()); - this.firepadStyleWrapper= - 'font-family:'+style.getPropertyValue('font-family')+';'+ - 'font-size:'+style.getPropertyValue('font-size')+';'+ - 'background-color:'+style.getPropertyValue('background-color')+';'+ - 'color:'+style.getPropertyValue('color')+';'+ - 'text-align:'+style.getPropertyValue('text-align')+';'; - } - var fp=this.codeMirror.firepad; - var gotHtml=false, val=''; - - // if selection has attributes try to get html - if (fp.selectionHasAttributes()) { - val=fp.getHtmlFromSelection(); - if (val) { - val=''+val+''; - gotHtml=true; - } - } + if (!fp.selectionHasAttributes()) return; // selection has attributes - not html - // if we couldn't get html try to get text - if (!gotHtml) { - val=this.codeMirror.getSelections().join('\n').replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), ''); // remove sentinels - if (!val) return; // no html or text - something went wrong - } + let textVal=this.codeMirror.getSelections().join('\n').replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), ''); // remove sentinels + if (!textVal) return; // something went wrong + //utils.log(textVal); + + var htmlVal=fp.getHtmlFromSelection(); + if (!htmlVal) return; // something went wrong + //utils.log(htmlVal); if (e.type == 'cut') cm.replaceSelection('', null, 'cut'); e.clipboardData.clearData(); - e.clipboardData.setData(gotHtml ? 'text/html' : 'text', val); + e.clipboardData.setData('text', textVal); + e.clipboardData.setData('text/html', htmlVal); e.preventDefault() }; RichTextCodeMirror.prototype.onCodeMirrorPaste_ = function(cm, e) { var html = e.clipboardData ? e.clipboardData.getData('text/html') : null; if (!html) return; // not html or something went wrong, revert to CM paste - + cm.replaceSelection(''); var fp=this.codeMirror.firepad; fp.insertHtmlAtCursor(html); e.preventDefault(); + //utils.log(html); }; function cmpPos (a, b) { From 173f625203980fffb502ea3a4fbc161770913ee0 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 24 Jun 2016 10:44:10 +1000 Subject: [PATCH 26/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index 5e8f48bd3..ae5b563d4 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -587,20 +587,19 @@ firepad.RichTextCodeMirror = (function () { if (!e.clipboardData || ios) return; // clipboard ops not supported var fp=this.codeMirror.firepad; - if (!fp.selectionHasAttributes()) return; // selection has attributes - not html let textVal=this.codeMirror.getSelections().join('\n').replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), ''); // remove sentinels if (!textVal) return; // something went wrong //utils.log(textVal); - var htmlVal=fp.getHtmlFromSelection(); - if (!htmlVal) return; // something went wrong - //utils.log(htmlVal); + var htmlVal; + if (fp.selectionHasAttributes()) htmlVal='
'+fp.getHtmlFromSelection() +'
'; + //if (htmlVal) utils.log(htmlVal); if (e.type == 'cut') cm.replaceSelection('', null, 'cut'); e.clipboardData.clearData(); e.clipboardData.setData('text', textVal); - e.clipboardData.setData('text/html', htmlVal); + if (htmlVal) e.clipboardData.setData('text/html', htmlVal); e.preventDefault() }; From 82504b428940ad6182d4f6ca322e75c58736dd24 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 24 Jun 2016 10:46:08 +1000 Subject: [PATCH 27/29] Update serialize-html.js --- lib/serialize-html.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/serialize-html.js b/lib/serialize-html.js index 1840191a2..41ad452b2 100644 --- a/lib/serialize-html.js +++ b/lib/serialize-html.js @@ -168,10 +168,6 @@ firepad.SerializeHtml = (function () { op = doc.ops[++i]; } - // Replace leading, trailing, and consecutive spaces with nbsp's to make sure they're preserved. - text = text.replace(/ +/g, function(str) { - return new Array(str.length + 1).join('\u00a0'); - }).replace(/^ /, '\u00a0').replace(/ $/, '\u00a0'); if (text.length > 0) { emptyLine = false; } From bf98f1b1ee819bd37d577874da16e848d179553d Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 24 Jun 2016 19:24:55 +1000 Subject: [PATCH 28/29] Update rich-text-codemirror.js --- lib/rich-text-codemirror.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rich-text-codemirror.js b/lib/rich-text-codemirror.js index ae5b563d4..0521cf8e9 100644 --- a/lib/rich-text-codemirror.js +++ b/lib/rich-text-codemirror.js @@ -593,7 +593,7 @@ firepad.RichTextCodeMirror = (function () { //utils.log(textVal); var htmlVal; - if (fp.selectionHasAttributes()) htmlVal='
'+fp.getHtmlFromSelection() +'
'; + if (fp.selectionHasAttributes()) htmlVal=fp.getHtmlFromSelection(); //if (htmlVal) utils.log(htmlVal); if (e.type == 'cut') cm.replaceSelection('', null, 'cut'); From 9c73df88d6865352632096bd81049f3862d56658 Mon Sep 17 00:00:00 2001 From: Kofifus Date: Fri, 24 Jun 2016 19:25:33 +1000 Subject: [PATCH 29/29] Update serialize-html.js --- lib/serialize-html.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/serialize-html.js b/lib/serialize-html.js index 41ad452b2..f0e9202f0 100644 --- a/lib/serialize-html.js +++ b/lib/serialize-html.js @@ -193,7 +193,7 @@ firepad.SerializeHtml = (function () { html = TODO_STYLE + html; } - return html; + return '
'+html+'
'; } return serializeHtml;