/**
* Preview.js
*
* Released under LGPL License.
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* Internal class for generating previews styles for formats.
*
* Example:
* Preview.getCssText(editor, 'bold');
*
* @private
* @class tinymce.fmt.Preview
*/
define(
'tinymce.core.fmt.Preview',
[
"tinymce.core.dom.DOMUtils",
"tinymce.core.util.Tools",
"tinymce.core.html.Schema"
],
function (DOMUtils, Tools, Schema) {
var each = Tools.each;
var dom = DOMUtils.DOM;
var parsedSelectorToHtml = function (ancestry, editor) {
var elm, item, fragment;
var schema = editor && editor.schema || new Schema({});
var decorate = function (elm, item) {
if (item.classes.length) {
dom.addClass(elm, item.classes.join(' '));
}
dom.setAttribs(elm, item.attrs);
};
var createElement = function (sItem) {
var elm;
item = typeof sItem === 'string' ? {
name: sItem,
classes: [],
attrs: {}
} : sItem;
elm = dom.create(item.name);
decorate(elm, item);
return elm;
};
var getRequiredParent = function (elm, candidate) {
var name = typeof elm !== 'string' ? elm.nodeName.toLowerCase() : elm;
var elmRule = schema.getElementRule(name);
var parentsRequired = elmRule && elmRule.parentsRequired;
if (parentsRequired && parentsRequired.length) {
return candidate && Tools.inArray(parentsRequired, candidate) !== -1 ? candidate : parentsRequired[0];
} else {
return false;
}
};
var wrapInHtml = function (elm, ancestry, siblings) {
var parent, parentCandidate, parentRequired;
var ancestor = ancestry.length > 0 && ancestry[0];
var ancestorName = ancestor && ancestor.name;
parentRequired = getRequiredParent(elm, ancestorName);
if (parentRequired) {
if (ancestorName === parentRequired) {
parentCandidate = ancestry[0];
ancestry = ancestry.slice(1);
} else {
parentCandidate = parentRequired;
}
} else if (ancestor) {
parentCandidate = ancestry[0];
ancestry = ancestry.slice(1);
} else if (!siblings) {
return elm;
}
if (parentCandidate) {
parent = createElement(parentCandidate);
parent.appendChild(elm);
}
if (siblings) {
if (!parent) {
// if no more ancestry, wrap in generic div
parent = dom.create('div');
parent.appendChild(elm);
}
Tools.each(siblings, function (sibling) {
var siblingElm = createElement(sibling);
parent.insertBefore(siblingElm, elm);
});
}
return wrapInHtml(parent, ancestry, parentCandidate && parentCandidate.siblings);
};
if (ancestry && ancestry.length) {
item = ancestry[0];
elm = createElement(item);
fragment = dom.create('div');
fragment.appendChild(wrapInHtml(elm, ancestry.slice(1), item.siblings));
return fragment;
} else {
return '';
}
};
var selectorToHtml = function (selector, editor) {
return parsedSelectorToHtml(parseSelector(selector), editor);
};
var parseSelectorItem = function (item) {
var tagName;
var obj = {
classes: [],
attrs: {}
};
item = obj.selector = Tools.trim(item);
if (item !== '*') {
// matching IDs, CLASSes, ATTRIBUTES and PSEUDOs
tagName = item.replace(/(?:([#\.]|::?)([\w\-]+)|(\[)([^\]]+)\]?)/g, function ($0, $1, $2, $3, $4) {
switch ($1) {
case '#':
obj.attrs.id = $2;
break;
case '.':
obj.classes.push($2);
break;
case ':':
if (Tools.inArray('checked disabled enabled read-only required'.split(' '), $2) !== -1) {
obj.attrs[$2] = $2;
}
break;
}
// atribute matched
if ($3 === '[') {
var m = $4.match(/([\w\-]+)(?:\=\"([^\"]+))?/);
if (m) {
obj.attrs[m[1]] = m[2];
}
}
return '';
});
}
obj.name = tagName || 'div';
return obj;
};
var parseSelector = function (selector) {
if (!selector || typeof selector !== 'string') {
return [];
}
// take into account only first one
selector = selector.split(/\s*,\s*/)[0];
// tighten
selector = selector.replace(/\s*(~\+|~|\+|>)\s*/g, '$1');
// split either on > or on space, but not the one inside brackets
return Tools.map(selector.split(/(?:>|\s+(?![^\[\]]+\]))/), function (item) {
// process each sibling selector separately
var siblings = Tools.map(item.split(/(?:~\+|~|\+)/), parseSelectorItem);
var obj = siblings.pop(); // the last one is our real target
if (siblings.length) {
obj.siblings = siblings;
}
return obj;
}).reverse();
};
var getCssText = function (editor, format) {
var name, previewFrag, previewElm, items;
var previewCss = '', parentFontSize, previewStyles;
previewStyles = editor.settings.preview_styles;
// No preview forced
if (previewStyles === false) {
return '';
}
// Default preview
if (typeof previewStyles !== 'string') {
previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
'text-transform color background-color border border-radius outline text-shadow';
}
// Removes any variables since these can't be previewed
var removeVars = function (val) {
return val.replace(/%(\w+)/g, '');
};
// Create block/inline element to use for preview
if (typeof format === "string") {
format = editor.formatter.get(format);
if (!format) {
return;
}
format = format[0];
}
// Format specific preview override
// TODO: This should probably be further reduced by the previewStyles option
if ('preview' in format) {
previewStyles = format.preview;
if (previewStyles === false) {
return '';
}
}
name = format.block || format.inline || 'span';
items = parseSelector(format.selector);
if (items.length) {
if (!items[0].name) { // e.g. something like ul > .someClass was provided
items[0].name = name;
}
name = format.selector;
previewFrag = parsedSelectorToHtml(items, editor);
} else {
previewFrag = parsedSelectorToHtml([name], editor);
}
previewElm = dom.select(name, previewFrag)[0] || previewFrag.firstChild;
// Add format styles to preview element
each(format.styles, function (value, name) {
value = removeVars(value);
if (value) {
dom.setStyle(previewElm, name, value);
}
});
// Add attributes to preview element
each(format.attributes, function (value, name) {
value = removeVars(value);
if (value) {
dom.setAttrib(previewElm, name, value);
}
});
// Add classes to preview element
each(format.classes, function (value) {
value = removeVars(value);
if (!dom.hasClass(previewElm, value)) {
dom.addClass(previewElm, value);
}
});
editor.fire('PreviewFormats');
// Add the previewElm outside the visual area
dom.setStyles(previewFrag, { position: 'absolute', left: -0xFFFF });
editor.getBody().appendChild(previewFrag);
// Get parent container font size so we can compute px values out of em/% for older IE:s
parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
each(previewStyles.split(' '), function (name) {
var value = dom.getStyle(previewElm, name, true);
// If background is transparent then check if the body has a background color we can use
if (name === 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
value = dom.getStyle(editor.getBody(), name, true);
// Ignore white since it's the default color, not the nicest fix
// TODO: Fix this by detecting runtime style
if (dom.toHex(value).toLowerCase() === '#ffffff') {
return;
}
}
if (name === 'color') {
// Ignore black since it's the default color, not the nicest fix
// TODO: Fix this by detecting runtime style
if (dom.toHex(value).toLowerCase() === '#000000') {
return;
}
}
// Old IE won't calculate the font size so we need to do that manually
if (name === 'font-size') {
if (/em|%$/.test(value)) {
if (parentFontSize === 0) {
return;
}
// Convert font size from em/% to px
value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
value = (value * parentFontSize) + 'px';
}
}
if (name === "border" && value) {
previewCss += 'padding:0 2px;';
}
previewCss += name + ':' + value + ';';
});
editor.fire('AfterPreviewFormats');
//previewCss += 'line-height:normal';
dom.remove(previewFrag);
return previewCss;
};
return {
getCssText: getCssText,
parseSelector: parseSelector,
selectorToHtml: selectorToHtml
};
}
);
|