jsharmony-cms
Version:
627 lines (563 loc) • 26.8 kB
JavaScript
/*
Copyright 2019 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
var Helper = require('jsharmony/Helper');
var _ = require('lodash');
var async = require('async');
var DiffMatchPatch = require('diff-match-patch');
var diff2html = require('diff2html');
module.exports = exports = function(module, funcs){
var exports = {};
var _t = module._t;
var dmp = new DiffMatchPatch();
exports.diff = function (req, res, next) {
var verb = req.method.toLowerCase();
if (!req.body) req.body = {};
var Q = req.query;
var P = {};
if (req.body && ('data' in req.body)){
try{ P = JSON.parse(req.body.data); }
catch(ex){ Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
}
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var cms = module;
var XValidate = jsh.XValidate;
var dbtypes = appsrv.DB.types;
var model = jsh.getModel(req, module.namespace + 'Branch_Diff');
if (!Helper.hasModelAction(req, model, 'B')) { Helper.GenError(req, res, -11, _t('Invalid Model Access')); return; }
if (verb == 'get') {
if (!appsrv.ParamCheck('Q', Q, ['&branch_id'])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!appsrv.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var branch_id = Q.branch_id;
//Check if item is defined
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { 'branch_id': branch_id };
var validate = new XValidate();
var verrors = {};
validate.AddValidator('_obj.branch_id', 'Branch ID', 'B', [XValidate._v_IsNumeric(), XValidate._v_Required()]);
verrors = _.merge(verrors, validate.Validate('B', sql_params));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
var baseurl = req.baseurl;
if(baseurl.indexOf('//')<0) baseurl = req.protocol + '://' + req.get('host') + baseurl;
funcs.validateBranchAccess(req, res, branch_id, 'R%', undefined, function(){
var branch_data = {
_DBContext: req._DBContext,
baseurl: baseurl,
branch_id: branch_id,
site_id: null,
page_templates: null,
deployment_target_id: undefined,
};
var branch_diff = {};
async.waterfall([
//Get branch data
function(cb){
var sql = 'select site_editor deployment_target_id,v_my_branch_desc.site_id from '+(module.schema?module.schema+'.':'')+'v_my_branch_desc left outer join '+(module.schema?module.schema+'.':'')+'v_my_site on v_my_site.site_id = v_my_branch_desc.site_id where v_my_branch_desc.branch_id=@branch_id';
appsrv.ExecRow(req._DBContext, sql, sql_ptypes, sql_params, function (err, rslt) {
if (err != null) { err.sql = sql; return cb(err); }
if(rslt && rslt[0]){
try{
branch_data.deployment_target_id = rslt[0].deployment_target_id;
branch_data.site_id = rslt[0].site_id;
}
catch(ex){ /* Do nothing */ }
}
return cb();
});
},
//Run onBeforeDiff functions
function(cb){
async.eachOfSeries(cms.BranchItems, function(branch_item, branch_item_type, branch_item_cb){
if(!branch_item.diff) return branch_item_cb();
Helper.execif(branch_item.diff.onBeforeDiff,
function(f){
branch_item.diff.onBeforeDiff(branch_data, f);
},
branch_item_cb
);
}, cb);
},
//Get all branch data
function(cb){
async.eachOfSeries(cms.BranchItems, function(branch_item, branch_item_type, branch_item_cb){
if(!branch_item.diff) return branch_item_cb();
var sql = 'select branch_{item}.{item}_key, branch_{item}.branch_{item}_action, branch_{item}.{item}_id, branch_{item}.{item}_orig_id' +
_.map(branch_item.diff.columns || [], function(column_name){ return ', old_{item}.' + column_name + ' old_' + column_name + ', new_{item}.' + column_name + ' new_' + column_name + ' '; }).join('') +
'from {tbl_branch_item} branch_{item} \
left outer join {tbl_item} old_{item} on old_{item}.{item}_id=branch_{item}.{item}_orig_id \
left outer join {tbl_item} new_{item} on new_{item}.{item}_id=branch_{item}.{item}_id \
where branch_id=@branch_id and branch_{item}_action is not null' + (branch_item.diff.sqlwhere ? ' and (' + branch_item.diff.sqlwhere + ')' : '');
appsrv.ExecRecordset(req._DBContext, cms.applyBranchItemSQL(branch_item_type, sql), sql_ptypes, sql_params, function (err, rslt) {
if (err != null) { err.sql = sql; err.model = model; appsrv.AppDBError(req, res, err); return; }
if(rslt && rslt[0]) branch_diff[branch_item_type] = rslt[0];
//Run onDiff function
Helper.execif(branch_item.diff.onDiff,
function(f){
branch_item.diff.onDiff(branch_diff[branch_item_type], branch_data, f);
},
branch_item_cb
);
});
}, cb);
},
], function(err){
if(err) return Helper.GenError(req, res, -99999, err.toString());
var rslt = { _success: 1, branch_diff: _.pickBy(branch_diff, function(branch_items){ return !!branch_items.length; }) };
res.end(JSON.stringify(rslt));
});
});
return;
}
else {
return next();
}
};
//Add old and new pages to branch_data.page_keys
exports.diff_getPages = function(branch_data, callback){
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
branch_data.page_keys = {};
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { 'branch_id': branch_data.branch_id };
var sql = "\
select 'NEW' branch_diff_type,page_id,page_key,page_file_id,page_title,page_path,page_tags,page_author,page_template_id,page_template_path,page_seo_title,page_seo_canonical_url,page_seo_metadesc,page_seo_keywords,page_review_sts,page_lang \
from "+(module.schema?module.schema+'.':'')+'page page where page_is_folder = 0 and page.page_id in (select page_id from '+(module.schema?module.schema+'.':'')+"branch_page where branch_id=@branch_id) \
union all \
select 'PREV' branch_diff_type,page_id,page_key,page_file_id,page_title,page_path,page_tags,page_author,page_template_id,page_template_path,page_seo_title,page_seo_canonical_url,page_seo_metadesc,page_seo_keywords,page_review_sts,page_lang \
from "+(module.schema?module.schema+'.':'')+'page page where page_is_folder = 0 and page.page_id in (select page_orig_id from '+(module.schema?module.schema+'.':'')+'branch_page where branch_id=@branch_id) \
';
appsrv.ExecRecordset(branch_data._DBContext, sql, sql_ptypes, sql_params, function (err, rslt) {
if (err) return callback(err);
if(rslt && rslt[0]){
_.each(rslt[0], function(page){
branch_data.page_keys[page.page_key] = page;
if(
(page.branch_diff_type=='NEW') ||
(page.branch_diff_type=='PREV' && !(page.page_key in branch_data.page_keys))
) branch_data.page_keys[page.page_key] = page;
});
}
return callback();
});
};
//Add old and new media to branch_data.media_keys
exports.diff_getMedia = function(branch_data, callback){
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
branch_data.media_keys = {};
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { 'branch_id': branch_data.branch_id };
var sql = "\
select 'NEW' branch_diff_type,media_id,media_key,media_file_id,media_desc,media_path \
from "+(module.schema?module.schema+'.':'')+'media media where media_is_folder=0 and media.media_id in (select media_id from '+(module.schema?module.schema+'.':'')+"branch_media where branch_id=@branch_id)\
union all \
select 'PREV' branch_diff_type,media_id,media_key,media_file_id,media_desc,media_path \
from "+(module.schema?module.schema+'.':'')+'media media where media_is_folder=0 and media.media_id in (select media_orig_id from '+(module.schema?module.schema+'.':'')+'branch_media where branch_id=@branch_id)\
';
appsrv.ExecRecordset(branch_data._DBContext, sql, sql_ptypes, sql_params, function (err, rslt) {
if (err) return callback(err);
if(rslt && rslt[0]){
_.each(rslt[0], function(media){
if(
(media.branch_diff_type=='NEW') ||
(media.branch_diff_type=='PREV' && !(media.media_key in branch_data.media_keys))
) branch_data.media_keys[media.media_key] = media;
});
}
return callback();
});
};
exports.diff_page = function(branch_pages, branch_data, callback){
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
var updated_pages = {};
async.waterfall([
//Get updated pages
function(cb){
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { 'branch_id': branch_data.branch_id };
var sql = 'select page_id,page_key,page_file_id,page_title,page_path,page_tags,page_author,page_template_id,page_template_path,page_seo_title,page_seo_canonical_url,page_seo_metadesc,page_seo_keywords,page_review_sts,page_lang \
from '+(module.schema?module.schema+'.':'')+'page page \
where page_is_folder = 0 and (\
page.page_id in (select page_id from '+(module.schema?module.schema+'.':'')+'branch_page where branch_id=@branch_id and branch_page_action is not null) or \
page.page_id in (select page_orig_id from '+(module.schema?module.schema+'.':'')+"branch_page where branch_id=@branch_id and branch_page_action = 'UPDATE') \
)";
appsrv.ExecRecordset(branch_data._DBContext, sql, sql_ptypes, sql_params, function (err, rslt) {
if (err) return callback(err);
if(rslt && rslt[0]){
_.each(rslt[0], function(page){
updated_pages[page.page_id] = page;
});
}
return cb();
});
},
//Get page file content
function(cb){
async.eachOfSeries(updated_pages, function(page, page_id, page_cb){
funcs.getClientPage(branch_data._DBContext, page, null, branch_data.site_id, { includeExtraRemoteContent: true, pageTemplates: branch_data.page_templates, ignoreInvalidPageTemplate: true }, function(err, clientPage){
if(err) return page_cb(err);
if(!clientPage) return page_cb(null);
funcs.localizePageURLs(clientPage.page, branch_data.baseurl, !!clientPage.template.raw);
page.compiled = clientPage.page;
page.template = clientPage.template;
if(page.compiled.content){
var pretty_params = {
unformatted: ['code', 'pre'],
indent_inner_html: true,
indent_char: ' ',
indent_size: 2,
sep: '\n'
};
for(var key in page.compiled.content){
page.compiled.content[key] = funcs.prettyhtml(page.compiled.content[key], pretty_params);
}
}
page.template_title = clientPage.template.title;
return page_cb(null);
});
}, cb);
},
//Perform page diff
function(cb){
_.each(branch_pages, function(branch_page){
if(branch_page.branch_page_action.toUpperCase()=='UPDATE'){
branch_page.diff = funcs.diff_pageContent(updated_pages[branch_page.page_orig_id], updated_pages[branch_page.page_id]);
}
});
return cb();
},
], callback);
};
exports.diff_media = function(branch_media, branch_data, callback){
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
var media = {};
async.waterfall([
//Get all media
function(cb){
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { 'branch_id': branch_data.branch_id };
var sql = 'select media_id,media_key,media_file_id,media_desc,media_tags,media_type,media_path \
from '+(module.schema?module.schema+'.':'')+'media media \
where media.media_id in (select media_id from '+(module.schema?module.schema+'.':'')+'branch_media where branch_id=@branch_id and branch_media_action is not null) or \
media.media_id in (select media_orig_id from '+(module.schema?module.schema+'.':'')+"branch_media where branch_id=@branch_id and branch_media_action = 'UPDATE')";
appsrv.ExecRecordset(branch_data._DBContext, sql, sql_ptypes, sql_params, function (err, rslt) {
if (err) return callback(err);
if(rslt && rslt[0]){
_.each(rslt[0], function(media_item){
media[media_item.media_id] = media_item;
});
}
return cb();
});
},
//Perform media diff
function(cb){
_.each(branch_media, function(branch_media_item){
if(branch_media_item.branch_media_action.toUpperCase()=='UPDATE'){
branch_media_item.diff = funcs.mediaDiff(media[branch_media_item.media_orig_id], media[branch_media_item.media_id]);
}
});
return cb();
},
], callback);
};
exports.diff_menu = function(branch_menus, branch_data, callback){
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
var menus = {};
async.waterfall([
//Get all menus
function(cb){
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { 'branch_id': branch_data.branch_id };
var sql = 'select menu_id,menu_key,menu_file_id,menu_name,menu_tag \
from '+(module.schema?module.schema+'.':'')+'menu menu \
where menu.menu_id in (select menu_id from '+(module.schema?module.schema+'.':'')+'branch_menu where branch_id=@branch_id and branch_menu_action is not null) or \
menu.menu_id in (select menu_orig_id from '+(module.schema?module.schema+'.':'')+"branch_menu where branch_id=@branch_id and branch_menu_action = 'UPDATE')";
appsrv.ExecRecordset(branch_data._DBContext, sql, sql_ptypes, sql_params, function (err, rslt) {
if (err) return callback(err);
if(rslt && rslt[0]){
_.each(rslt[0], function(menu){
menus[menu.menu_id] = menu;
});
}
return cb();
});
},
//Get menu file content
function(cb){
async.eachOfSeries(menus, function(menu, menu_id, menu_cb){
funcs.getClientMenu(menu, function(err, menu_content){
if(err) return menu_cb(err);
if(!menu_content) return menu_cb(null);
menu.menu_items_text = funcs.prettyMenu(menu_content.menu_items, branch_data.page_keys, branch_data.media_keys);
return menu_cb();
});
}, cb);
},
//Perform menu diff
function(cb){
_.each(branch_menus, function(branch_menu){
if(branch_menu.branch_menu_action.toUpperCase()=='UPDATE'){
branch_menu.diff = funcs.menuDiff(menus[branch_menu.menu_orig_id], menus[branch_menu.menu_id]);
}
});
return cb();
},
], callback);
};
exports.diff_sitemap = function(branch_sitemaps, branch_data, callback){
var jsh = module.jsh;
var appsrv = jsh.AppSrv;
var dbtypes = appsrv.DB.types;
var sitemaps = {};
async.waterfall([
//Get all sitemaps
function(cb){
var sql_ptypes = [dbtypes.BigInt];
var sql_params = { 'branch_id': branch_data.branch_id };
var sql = 'select sitemap_id,sitemap_key,sitemap_file_id,sitemap_name,sitemap_type \
from '+(module.schema?module.schema+'.':'')+'sitemap sitemap \
where sitemap.sitemap_id in (select sitemap_id from '+(module.schema?module.schema+'.':'')+'branch_sitemap where branch_id=@branch_id and branch_sitemap_action is not null) or \
sitemap.sitemap_id in (select sitemap_orig_id from '+(module.schema?module.schema+'.':'')+"branch_sitemap where branch_id=@branch_id and branch_sitemap_action = 'UPDATE')";
appsrv.ExecRecordset(branch_data._DBContext, sql, sql_ptypes, sql_params, function (err, rslt) {
if (err) return callback(err);
if(rslt && rslt[0]){
_.each(rslt[0], function(sitemap){
sitemaps[sitemap.sitemap_id] = sitemap;
});
}
return cb();
});
},
//Get sitemap file content
function(cb){
async.eachOfSeries(sitemaps, function(sitemap, sitemap_id, sitemap_cb){
funcs.getClientSitemap(sitemap, function(err, sitemap_content){
if(err) return sitemap_cb(err);
if(!sitemap_content) return sitemap_cb(null);
sitemap.sitemap_items_text = funcs.prettySitemap(sitemap_content.sitemap_items, branch_data.page_keys, branch_data.media_keys);
return sitemap_cb();
});
}, cb);
},
//Perform sitemap diff
function(cb){
_.each(branch_sitemaps, function(branch_sitemap){
if(branch_sitemap.branch_sitemap_action.toUpperCase()=='UPDATE'){
branch_sitemap.diff = funcs.sitemapDiff(sitemaps[branch_sitemap.sitemap_orig_id], sitemaps[branch_sitemap.sitemap_id]);
}
});
return cb();
},
], callback);
};
exports.diff_pageContent = function(old_page, new_page){
if(!old_page || !new_page) return null;
var diff = {};
_.each(['css','header','footer'], function(key){
var key_diff = funcs.diffPageHTML(old_page.compiled[key], new_page.compiled[key]);
if(key_diff) diff[key] = key_diff;
});
_.each(['properties'], function(key){
var key_diff = funcs.diffPageHTML(JSON.stringify(old_page.compiled[key],null,4), JSON.stringify(new_page.compiled[key], null, 4));
if(key_diff) diff[key] = key_diff;
});
for(let key in old_page.compiled.content){ if(!(key in new_page.compiled.content)) new_page.compiled.content[key] = ''; }
for(let key in new_page.compiled.content){ if(!(key in old_page.compiled.content)) old_page.compiled.content[key] = ''; }
diff.content_elements = {};
for(let key in old_page.template.content_elements){ diff.content_elements[key] = old_page.template.content_elements[key].title; }
for(let key in new_page.template.content_elements){ diff.content_elements[key] = new_page.template.content_elements[key].title; }
for(let key in new_page.compiled.content){ if(!(key in diff.content_elements)) diff.content_elements[key] = key; }
for(let key in old_page.compiled.content){ if(!(key in diff.content_elements)) diff.content_elements[key] = key; }
diff.content = {};
for(let key in old_page.compiled.content){
var content_diff = funcs.diffPageHTML(old_page.compiled.content[key], new_page.compiled.content[key]);
diff.content[key] = content_diff;
}
_.each(['page_title','template_title','page_tags','page_template_path'], function(key){
if(old_page[key] != new_page[key]) diff[key] = new_page[key];
if(key=='page_template_path'){
if((old_page['template_title'] != '<Standalone>')||(new_page['template_title'] != '<Standalone>')){
delete diff[key];
}
}
});
diff.seo = {};
_.each(['title','keywords','metadesc','canonical_url'], function(key){
if(old_page.compiled.seo[key] != new_page.compiled.seo[key]) diff.seo[key] = new_page.compiled.seo[key];
});
return diff;
};
exports.mediaDiff = function(old_media, new_media){
var diff = {};
_.each(['media_desc','media_tags','media_type'], function(key){
if(old_media[key] != new_media[key]) diff[key] = new_media[key];
});
return diff;
};
exports.menuDiff = function(old_menu, new_menu){
var diff = {};
var menu_items_diff = funcs.diffText(old_menu.menu_items_text, new_menu.menu_items_text);
if(menu_items_diff) diff.menu_items = menu_items_diff;
_.each(['menu_name','menu_tag'], function(key){
if(old_menu[key] != new_menu[key]) diff[key] = new_menu[key];
});
return diff;
};
exports.sitemapDiff = function(old_sitemap, new_sitemap){
var diff = {};
var sitemap_items_diff = funcs.diffText(old_sitemap.sitemap_items_text, new_sitemap.sitemap_items_text);
if(sitemap_items_diff) diff.sitemap_items = sitemap_items_diff;
_.each(['sitemap_name','sitemap_type'], function(key){
if(old_sitemap[key] != new_sitemap[key]) diff[key] = new_sitemap[key];
});
return diff;
};
exports.diffPageHTML = function(a, b){
if(a==b) return '';
a = funcs.renderComponentsPretty(a);
b = funcs.renderComponentsPretty(b);
return funcs.diffText(a, b);
};
exports.diffText = function(a, b){
if(a==b) return '';
var dmp_diff_lines = dmp.diff_linesToChars_(a, b);
var diff = dmp.diff_main(dmp_diff_lines.chars1, dmp_diff_lines.chars2, false);
dmp.diff_charsToLines_(diff, dmp_diff_lines.lineArray);
//Generate patch
var patch_lines = [];
var source_line = 1;
var dest_line = 1;
var last_line = 0;
if(diff.length){
last_line = diff.length-1;
if(diff[last_line][0]==0){ /* Do nothing */ }
else{
if(diff.length > 1){
if((diff[last_line-1][0]==0)||(diff[last_line-1][0]==diff[last_line][0])){ /* Do nothing */ }
else last_line-=1;
}
}
}
for(let i=0;i<diff.length;i++){
var diff_line = diff[i];
var diff_line_type = diff_line[0];
var diff_line_text = diff_line[1]||'';
if((i < last_line) && diff_line_text && (diff_line_text[diff_line_text.length-1]=='\n')) diff_line_text = diff_line_text.substr(0,diff_line_text.length-1);
var diff_lines = diff_line_text.split('\n');
var diff_line_prefix = ' ';
if(diff_line_type==-1) diff_line_prefix = '-';
else if(diff_line_type==1) diff_line_prefix = '+';
_.each(diff_lines, function(diff_line){ patch_lines.push(diff_line_prefix + diff_line); });
diff_line[2] = diff_lines;
diff_line[3] = source_line;
diff_line[4] = dest_line;
if(diff_line_type==0){
source_line += diff_lines.length;
dest_line += diff_lines.length;
}
else if(diff_line_type==-1){
source_line += diff_lines.length;
}
else if(diff_line_type==1){
dest_line += diff_lines.length;
}
}
//Create patch
source_line = 0;
dest_line = 0;
var patch_batches = [];
var cur_patch_batch = null;
for(let i=0;i<patch_lines.length;i++){
var patch_line = patch_lines[i];
if(patch_line[0]==' '){
source_line++;
dest_line++;
}
else if(patch_line[0]=='-') source_line++;
else if(patch_line[0]=='+') dest_line++;
if(patch_line[0]==' '){
if(!cur_patch_batch) continue;
else{
cur_patch_batch.lines.push(patch_line);
}
}
else {
if(cur_patch_batch){ cur_patch_batch.lines.push(patch_line); }
else {
//Start patch batch
cur_patch_batch = {
lines: [patch_line],
source_start_line: source_line+1,
source_end_line: undefined,
dest_start_line: dest_line+1,
dest_end_line: undefined,
};
if(patch_line[0]=='-') cur_patch_batch.source_start_line--;
if(patch_line[0]=='+') cur_patch_batch.dest_start_line--;
if(i>0){
cur_patch_batch.source_start_line--;
cur_patch_batch.dest_start_line--;
cur_patch_batch.lines.unshift(patch_lines[i-1]);
}
}
}
//Check if at end of patch
if((i==(patch_lines.length-1)) || ((patch_line[0]==' ') && (patch_lines[i+1][0]==' '))){
//End patch batch
cur_patch_batch.source_end_line = source_line;
cur_patch_batch.dest_end_line = dest_line;
patch_batches.push(cur_patch_batch);
cur_patch_batch = null;
}
}
var patch = '';
for(var i=0;i<patch_batches.length;i++){
var patch_batch = patch_batches[i];
patch += '@@ -'+patch_batch.source_start_line+','+patch_batch.source_end_line+' +'+patch_batch.dest_start_line+','+patch_batch.dest_end_line+' @@\n';
patch += patch_batch.lines.join('\n')+'\n';
}
patch = '--- compare\n+++ compare\n' + patch;
return diff2html.html(patch, {
matching: 'lines'
});
};
exports.formatDiff = function(diff){
var html = [];
for (var x = 0; x < diff.length; x++) {
var op = diff[x][0];
var text = Helper.escapeHTMLBR(diff[x][1]);
if(op==DiffMatchPatch.DIFF_INSERT){
html[x] = '<ins style="background:#e6ffe6;">' + text + '</ins>';
}
else if(op==DiffMatchPatch.DIFF_DELETE){
html[x] = '<del style="background:#ffe6e6;">' + text + '</del>';
}
else if(op==DiffMatchPatch.DIFF_EQUAL){
html[x] = '<span>' + text + '</span>';
}
}
return html.join('');
};
return exports;
};