odoo.define('web_editor.transcoder', function (require) {
'use strict';
var base = require('web_editor.base');
var rulesCache = [];
/**
* Returns the css rules which applies on an element, tweaked so that they are
* browser/mail client ok.
*
* @param {DOMElement} a
* @returns {Object} css property name -> css property value
*/
function getMatchedCSSRules(a) {
var i, r, k;
if (!rulesCache.length) {
var sheets = document.styleSheets;
for (i = sheets.length-1 ; i >= 0 ; i--) {
var rules;
if (sheets[i].rules) {
rules = sheets[i].rules;
} else {
// try...catch because Firefox not able to enumerate
// document.styleSheets[].cssRules[] for cross-domain sheets.
try {
rules = sheets[i].cssRules;
} catch (e) {
console.warn("Can't read the css rules of: " + sheets[i].href, e);
continue;
}
rules = sheets[i].cssRules;
}
if (rules) {
for (r = rules.length-1; r >= 0; r--) {
var selectorText = rules[r].selectorText;
if (selectorText &&
rules[r].cssText &&
selectorText !== '*' &&
selectorText.indexOf(':hover') === -1 &&
selectorText.indexOf(':before') === -1 &&
selectorText.indexOf(':after') === -1 &&
selectorText.indexOf(':active') === -1 &&
selectorText.indexOf(':link') === -1 &&
selectorText.indexOf('::') === -1 &&
selectorText.indexOf('"') === -1 &&
selectorText.indexOf("'") === -1) {
var st = selectorText.split(/\s*,\s*/);
for (k = 0 ; k < st.length ; k++) {
rulesCache.push({ 'selector': st[k], 'style': rules[r].style });
}
}
}
}
}
rulesCache.reverse();
}
var css = [];
var style;
a.matches = a.matches || a.webkitMatchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector;
for (r = 0; r < rulesCache.length; r++) {
if (a.matches(rulesCache[r].selector)) {
style = rulesCache[r].style;
if (style.parentRule) {
var style_obj = {};
var len;
for (k = 0, len = style.length ; k < len ; k++) {
if (style[k].indexOf('animation') !== -1) {
continue;
}
style_obj[style[k]] = style[style[k].replace(/-(.)/g, function (a, b) { return b.toUpperCase(); })];
if (new RegExp(style[k] + '\s*:[^:;]+!important' ).test(style.cssText)) {
style_obj[style[k]] += ' !important';
}
}
rulesCache[r].style = style = style_obj;
}
css.push([rulesCache[r].selector, style]);
}
}
function specificity(selector) {
// http://www.w3.org/TR/css3-selectors/#specificity
var a = 0;
selector = selector.replace(/#[a-z0-9_-]+/gi, function () { a++; return ''; });
var b = 0;
selector = selector.replace(/(\.[a-z0-9_-]+)|(\[.*?\])/gi, function () { b++; return ''; });
var c = 0;
selector = selector.replace(/(^|\s+|:+)[a-z0-9_-]+/gi, function (a) { if (a.indexOf(':not(')===-1) c++; return ''; });
return a*100 + b*10 + c;
}
css.sort(function (a, b) { return specificity(a[0]) - specificity(b[0]); });
style = {};
_.each(css, function (v,k) {
_.each(v[1], function (v,k) {
if (v && _.isString(v) && k.indexOf('-webkit') === -1 && (!style[k] || style[k].indexOf('important') === -1 || v.indexOf('important') !== -1)) {
style[k] = v;
}
});
});
_.each(style, function (v,k) {
if (v.indexOf('important') !== -1) {
style[k] = v.slice(0, v.length-11);
}
});
if (style.display === 'block') {
delete style.display;
}
_.each(['margin', 'padding'], function (p) {
if (style[p+'-top'] || style[p+'-right'] || style[p+'-bottom'] || style[p+'-left']) {
if (style[p+'-top'] === style[p+'-right'] && style[p+'-top'] === style[p+'-bottom'] && style[p+'-top'] === style[p+'-left']) {
// keep => property: [top/right/bottom/left value];
style[p] = style[p+'-top'];
}
else {
// keep => property: [top value] [right value] [bottom value] [left value];
style[p] = (style[p+'-top'] || 0) + ' ' + (style[p+'-right'] || 0) + ' ' + (style[p+'-bottom'] || 0) + ' ' + (style[p+'-left'] || 0);
if (style[p].indexOf('inherit') !== -1 || style[p].indexOf('initial') !== -1) {
// keep => property-top: [top value]; property-right: [right value]; property-bottom: [bottom value]; property-left: [left value];
delete style[p];
return;
}
}
delete style[p+'-top'];
delete style[p+'-right'];
delete style[p+'-bottom'];
delete style[p+'-left'];
}
});
// text-decoration rule is decomposed in -line, -color and -style. This is
// however not supported by many browser/mail clients and the editor does
// not allow to change -color and -style rule anyway
if (style['text-decoration-line']) {
style['text-decoration'] = style['text-decoration-line'];
delete style['text-decoration-line'];
delete style['text-decoration-color'];
delete style['text-decoration-style'];
}
return style;
}
/**
* Converts font icons to images.
*
* @param {jQuery} $editable - the element in which the font icons have to be
* converted to images
*/
function fontToImg($editable) {
$editable.find('.fa').each(function () {
var $font = $(this);
var icon, content;
_.find(base.fontIcons, function (font) {
return _.find(base.getCssSelectors(font.parser), function (data) {
if ($font.is(data.selector.replace(/::?before/g, ''))) {
icon = data.names[0].split('-').shift();
content = data.css.match(/content:\s*['"]?(.)['"]?/)[1];
return true;
}
});
});
if (content) {
var color = $font.css('color').replace(/\s/g, '');
$font.replaceWith($('
', {
src: _.str.sprintf('/web_editor/font_to_img/%s/%s/%s', content.charCodeAt(0), window.encodeURI(color), Math.max(1, $font.height())),
'data-class': $font.attr('class'),
'data-style': $font.attr('style'),
class: $font.attr('class').replace(new RegExp('(^|\\s+)' + icon + '(-[^\\s]+)?', 'gi'), ''), // remove inline font-awsome style
style: $font.attr('style'),
}).css({height: 'auto', width: 'auto'}));
} else {
$font.remove();
}
});
}
/**
* Converts images which were the result of a font icon convertion to a font
* icon again.
*
* @param {jQuery} $editable - the element in which the images will be converted
* back to font icons
*/
function imgToFont($editable) {
$editable.find('img[src*="/web_editor/font_to_img/"]').each(function () {
var $img = $(this);
$img.replaceWith($('', {
class: $img.data('class'),
style: $img.data('style')
}));
});
}
/**
* Converts css style to inline style (leave the classes on elements but forces
* the style they give as inline style).
*
* @param {jQuery} $editable
*/
function classToStyle($editable) {
if (!rulesCache.length) {
getMatchedCSSRules($editable[0]);
}
$editable.find('*').each(function () {
var $target = $(this);
var css = getMatchedCSSRules(this);
var style = $target.attr('style') || '';
_.each(css, function (v,k) {
if (!(new RegExp('(^|;)\s*' + k).test(style))) {
style = k+':'+v+';'+style;
}
});
if (_.isEmpty(style)) {
$target.removeAttr('style');
} else {
$target.attr('style', style);
}
});
}
/**
* Removes the inline style which is not necessary (because, for example, a
* class on an element will induce the same style).
*
* @param {jQuery} $editable
*/
function styleToClass($editable) {
getMatchedCSSRules($editable[0]);
var $c = $('').appendTo(document.body);
$editable.find('*').each(function () {
var $target = $(this);
var css = getMatchedCSSRules(this);
var style = '';
_.each(css, function (v,k) {
if (!(new RegExp('(^|;)\s*' + k).test(style))) {
style = k+':'+v+';'+style;
}
});
css = ($c.attr('style', style).attr('style') || '').split(/\s*;\s*/);
style = $target.attr('style') || '';
_.each(css, function (v) {
style = style.replace(v, '');
});
style = style.replace(/;+(\s;)*/g, ';').replace(/^;/g, '');
if (style !== '') {
$target.attr('style', style);
} else {
$target.removeAttr('style');
}
});
$c.remove();
}
return {
fontToImg: fontToImg,
imgToFont: imgToFont,
classToStyle: classToStyle,
styleToClass: styleToClass,
};
});