Skip to content

Commit

Permalink
PRDEV-944 #comment squashed/applied patch from FirebaseExtended#237 a…
Browse files Browse the repository at this point in the history
…nd added changes for copy/cut/paste buttons using same functionality
  • Loading branch information
Alexey Lukin committed Jun 15, 2016
1 parent 838736f commit 9e0e864
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 21 deletions.
39 changes: 31 additions & 8 deletions lib/firepad.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,32 @@ 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)) continue;
if (prop==ATTR.LINE_SENTINEL) continue;
for(var validAttr in firepad.AttributeConstants) if (firepad.AttributeConstants[validAttr] === prop) return true; // found one
}
}

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);
Expand All @@ -254,7 +280,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);
};

Expand All @@ -263,7 +289,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);
};

Expand Down Expand Up @@ -460,23 +486,20 @@ firepad.Firepad = (function (global) {

Firepad.prototype.cut = function () {
var doc = this.codeMirror_.doc;
var selection = doc.getSelection();
var selection = this.editorAdapter_.rtcm.copyHtml();
if (selection) {
this.copyText = selection;
doc.replaceSelection('');
}
};
Firepad.prototype.copy = function () {
var doc = this.codeMirror_.doc;
var selection = doc.getSelection();
var selection = this.editorAdapter_.rtcm.copyHtml();
if (selection) {
this.copyText = selection;
}
};
Firepad.prototype.paste = function () {
var cm = this.codeMirror_,
index = cm.indexFromPos(cm.getCursor('head'));
this.richTextCodeMirror_.insertText(index, this.copyText);
this.insertHtmlAtCursor(this.copyText);
};

Firepad.prototype.markQuickmark = function (id) {
Expand Down
2 changes: 1 addition & 1 deletion lib/headless.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ firepad.Headless = (function() {
var self = this;

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();

Expand Down
51 changes: 42 additions & 9 deletions lib/parse-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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.
Expand All @@ -101,14 +103,18 @@ firepad.ParseHtml = (function () {
}
};

var entityManager_;
function parseHtml(html, entityManager) {
var entityManager_, codeMirror_;
function parseHtml(html, entityManager, codeMirror) {
html=html.replace(/(\r\n|\n|\r)?<html>(\r\n|\n|\r)?<body>(\r\n|\n|\r)?/, ''); // remove <html><body>
html=html.replace(/(\r\n|\n|\r)?<\/body>(\r\n|\n|\r)?<\/html>(\r\n|\n|\r)?/, ''); // remove </body></html>

// 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();
Expand Down Expand Up @@ -138,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 &nbsp; 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:
Expand Down Expand Up @@ -240,7 +246,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(';');
Expand All @@ -265,6 +293,7 @@ firepad.ParseHtml = (function () {
textFormatting = textFormatting.italic(italic);
break;
case 'color':
if (styleEqual(val, this.firepadDefaultStyles.color)) break;
textFormatting = textFormatting.color(val);
break;
case 'qmid':
Expand All @@ -274,12 +303,15 @@ firepad.ParseHtml = (function () {
textFormatting = textFormatting.qmclass();
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)) {
Expand All @@ -293,6 +325,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() });
Expand Down
75 changes: 73 additions & 2 deletions lib/rich-text-codemirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,21 @@ firepad.RichTextCodeMirror = (function () {
bind(this, 'onCodeMirrorChange_');
bind(this, 'onCursorActivity_');

if (/^4\./.test(CodeMirror.version)) {
bind(this, 'onCodeMirrorCopyCut_');
bind(this, 'onCodeMirrorPaste_');

if (parseInt(CodeMirror.version) >= 4) {
this.codeMirror.on('changes', this.onCodeMirrorChange_);
} else {
this.codeMirror.on('change', this.onCodeMirrorChange_);
}
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_ = [];
Expand All @@ -59,6 +66,11 @@ 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_();
};

Expand Down Expand Up @@ -581,6 +593,65 @@ firepad.RichTextCodeMirror = (function () {
}
};

RichTextCodeMirror.prototype.copyHtml = function() {
// 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 val = '';

// if selection has attributes try to get html
if ( fp.selectionHasAttributes() ) {
val = fp.getHtmlFromSelection();
if ( val ) {
val = '<span style="' + this.firepadStyleWrapper + '">' + val + '</span>';
}
}

return val;
};

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

var gotHtml = false,
val = this.copyHtml();
if (val) {
gotHtml = true;
}
// if we couldn't get html try to get text
else {
// remove sentinels
val = this.codeMirror.getSelections().join('\n').replace(new RegExp('[' + LineSentinelCharacter + EntitySentinelCharacter + ']', 'g'), '');
// no html or text - something went wrong
if ( !val ) return;
}

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;
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();
};

function cmpPos (a, b) {
return (a.line - b.line) || (a.ch - b.ch);
}
Expand Down Expand Up @@ -1153,7 +1224,7 @@ firepad.RichTextCodeMirror = (function () {
function bind (obj, method) {
var fn = obj[method];
obj[method] = function () {
fn.apply(obj, arguments);
return fn.apply(obj, arguments);
};
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"karma-coverage": "^0.2.6",
"karma-failed-reporter": "0.0.2",
"karma-jasmine": "~0.1.3",
"karma-phantomjs-launcher": "~0.1.0",
"phantomjs-prebuilt": "2.1.4",
"karma-phantomjs-launcher": "~1.0.0",
"karma-spec-reporter": "0.0.13"
},
"scripts": {
Expand Down

0 comments on commit 9e0e864

Please sign in to comment.