Browse Source

Support multiple style values for the same property. Closes #4434

canvas-spot
Artur Arseniev 3 years ago
parent
commit
3a7c85f6af
  1. 5
      src/dom_components/model/Component.ts
  2. 19
      src/domain_abstract/model/StyleableModel.ts
  3. 2
      src/parser/config/config.ts
  4. 5
      src/parser/model/BrowserParserCss.ts
  5. 18
      src/parser/model/ParserHtml.ts
  6. 13
      test/specs/dom_components/model/Component.ts
  7. 77
      test/specs/parser/model/ParserCss.ts
  8. 5
      test/specs/parser/model/ParserHtml.ts

5
src/dom_components/model/Component.ts

@ -598,7 +598,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
if (avoidInline(em) && !opt.temporary && !opts.inline) {
const style = this.get('style') || {};
prop = isString(prop) ? this.parseStyle(prop) : prop;
prop = isString(prop) ? (this.parseStyle(prop) as ObjectStrings) : prop;
prop = { ...prop, ...style };
const state = em.get('state');
const cc = em.Css;
@ -608,8 +608,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
this.set('style', '', { silent: true });
keys(diff).forEach(pr => this.trigger(`change:style:${pr}`));
} else {
// @ts-ignore
prop = super.setStyle.apply(this, arguments);
prop = super.setStyle.apply(this, arguments as any);
}
return prop;

19
src/domain_abstract/model/StyleableModel.ts

@ -111,15 +111,24 @@ export default class StyleableModel<T extends ObjectHash = any> extends Model<T>
* @return {String}
*/
styleToString(opts: ObjectAny = {}) {
const result = [];
const result: string[] = [];
const style = this.getStyle(opts);
const imp = opts.important;
for (let prop in style) {
const imp = opts.important;
const important = isArray(imp) ? imp.indexOf(prop) >= 0 : imp;
const value = `${style[prop]}${important ? ' !important' : ''}`;
const propPrv = prop.substr(0, 2) == '__';
value && !propPrv && result.push(`${prop}:${value};`);
const firstChars = prop.substring(0, 2);
const isPrivate = firstChars === '__';
if (isPrivate) continue;
const value = style[prop];
const values = isArray(value) ? (value as string[]) : [value];
values.forEach((val: string) => {
const value = `${val}${important ? ' !important' : ''}`;
value && result.push(`${prop}:${value};`);
});
}
return result.join('');

2
src/parser/config/config.ts

@ -1,7 +1,7 @@
import Editor from '../../editor';
export interface ParsedCssRule {
selectors: string;
selectors: string | string[];
style: Record<string, string>;
atRule?: string;
params?: string;

5
src/parser/model/BrowserParserCss.ts

@ -61,8 +61,7 @@ export const parseSelector = (str = '') => {
* @param {CSSRule} node
* @return {Object}
*/
export const parseStyle = (node: CSSRule) => {
// @ts-ignore
export const parseStyle = (node: CSSStyleRule) => {
const stl = node.style;
const style: Record<string, string> = {};
@ -158,7 +157,7 @@ export const parseNode = (el: CSSStyleSheet | CSSRule) => {
if (!sels && !isSingleAtRule) continue;
const style = parseStyle(node);
const style = parseStyle(node as CSSStyleRule);
const selsParsed = parseSelector(sels);
const selsAdd = selsParsed.add;
const selsArr: string[][] = selsParsed.result;

18
src/parser/model/ParserHtml.ts

@ -1,4 +1,4 @@
import { each, isFunction, isUndefined } from 'underscore';
import { each, isArray, isFunction, isUndefined } from 'underscore';
import { ObjectAny } from '../../common';
import { CssRuleJSON } from '../../css_composer/model/CssRule';
import { ComponentDefinitionDefined } from '../../dom_components/model/types';
@ -77,14 +77,26 @@ const ParserHtml = (em?: EditorModel, config: ParserConfig & { returnArray?: boo
* // {color: 'black', width: '100px', test: 'value'}
*/
parseStyle(str: string) {
const result: StringObject = {};
const result: Record<string, string | string[]> = {};
const decls = str.split(';');
for (let i = 0, len = decls.length; i < len; i++) {
const decl = decls[i].trim();
if (!decl) continue;
const prop = decl.split(':');
result[prop[0].trim()] = prop.slice(1).join(':').trim();
const key = prop[0].trim();
const value = prop.slice(1).join(':').trim();
// Support multiple values for the same key
if (result[key]) {
if (!isArray(result[key])) {
result[key] = [result[key] as string];
}
(result[key] as string[]).push(value);
} else {
result[key] = value;
}
}
return result;

13
test/specs/dom_components/model/Component.ts

@ -9,6 +9,7 @@ import ComponentVideo from '../../../../src/dom_components/model/ComponentVideo'
import Components from '../../../../src/dom_components/model/Components';
import Selector from '../../../../src/selector_manager/model/Selector';
import Editor from '../../../../src/editor/model/Editor';
import { CSS_BG_OBJ, CSS_BG_STR } from '../../parser/model/ParserCss';
const $ = Backbone.$;
let obj: Component;
@ -290,6 +291,18 @@ describe('Component', () => {
});
});
test('set inline style with multiple values of the same key', () => {
obj.setAttributes({ style: CSS_BG_STR });
expect(obj.getStyle()).toEqual(CSS_BG_OBJ);
});
test('get proper style from inline style with multiple values of the same key', () => {
obj.setAttributes({ style: CSS_BG_STR });
expect(obj.getAttributes()).toEqual({
style: CSS_BG_STR.split('\n').join(''),
});
});
test('setAttributes overwrites correctly', () => {
obj.setAttributes({ id: 'test', 'data-test': 'value', a: 'b', b: 'c' });
obj.setAttributes({ id: 'test2', 'data-test': 'value2' });

77
test/specs/parser/model/ParserCss.js → test/specs/parser/model/ParserCss.ts

@ -1,46 +1,59 @@
import { parseSelector } from 'parser/model/BrowserParserCss';
import ParserCss from 'parser/model/ParserCss';
import EditorModel from '../../../../src/editor/model/Editor';
import { parseSelector } from '../../../../src/parser/model/BrowserParserCss';
import ParserCss from '../../../../src/parser/model/ParserCss';
export const CSS_BG_STR = `background-image:url("a/b.png");
background-image:-webkit-linear-gradient(to bottom, #aaa, #bbb);
background-image:-moz-linear-gradient(to bottom, #aaa, #bbb);
background-image:linear-gradient(to bottom, #aaa, #bbb);`;
export const CSS_BG_OBJ = {
'background-image': [
'url("a/b.png")',
'-webkit-linear-gradient(to bottom, #aaa, #bbb)',
'-moz-linear-gradient(to bottom, #aaa, #bbb)',
'linear-gradient(to bottom, #aaa, #bbb)',
],
};
describe('ParserCss', () => {
let obj;
let obj: ReturnType<typeof ParserCss>;
let config;
let customParser;
let customParser: any;
let em = {
getCustomParserCss: () => customParser,
trigger: () => {},
};
} as unknown as EditorModel;
beforeEach(() => {
config = {};
obj = new ParserCss(em, config);
obj = ParserCss(em, config);
});
afterEach(() => {
obj = null;
});
test('Parse selector', () => {
var str = '.test';
var result = [['test']];
expect(parseSelector(str).result).toEqual(result);
});
describe('parseSelector', () => {
test('Parse selector', () => {
var str = '.test';
var result = [['test']];
expect(parseSelector(str).result).toEqual(result);
});
test('Parse selectors', () => {
var str = '.test1, .test1.test2, .test2.test3';
var result = [['test1'], ['test1', 'test2'], ['test2', 'test3']];
expect(parseSelector(str).result).toEqual(result);
});
test('Parse selectors', () => {
var str = '.test1, .test1.test2, .test2.test3';
var result = [['test1'], ['test1', 'test2'], ['test2', 'test3']];
expect(parseSelector(str).result).toEqual(result);
});
test('Ignore not valid selectors', () => {
var str = '.test1.test2, .test2 .test3, div > .test4, #test.test5, .test6';
var result = [['test1', 'test2'], ['test6']];
expect(parseSelector(str).result).toEqual(result);
});
test('Ignore not valid selectors', () => {
var str = '.test1.test2, .test2 .test3, div > .test4, #test.test5, .test6';
var result = [['test1', 'test2'], ['test6']];
expect(parseSelector(str).result).toEqual(result);
});
test('Parse selectors with state', () => {
var str = '.test1. test2, .test2>test3, .test4.test5:hover';
var result = [['test4', 'test5:hover']];
expect(parseSelector(str).result).toEqual(result);
test('Parse selectors with state', () => {
var str = '.test1. test2, .test2>test3, .test4.test5:hover';
var result = [['test4', 'test5:hover']];
expect(parseSelector(str).result).toEqual(result);
});
});
test('Parse simple rule', () => {
@ -132,7 +145,6 @@ describe('ParserCss', () => {
expect(obj.parse(str)).toEqual([result]);
});
// Phantom don't find 'node.conditionText' so will skip it
test('Parse rule inside media query', () => {
var str = '@media only screen and (max-width: 992px){ .test1.test2:hover{ color:red }}';
var result = {
@ -145,7 +157,6 @@ describe('ParserCss', () => {
expect(obj.parse(str)).toEqual([result]);
});
// Phantom don't find 'node.conditionText' so will skip it
test('Parse rule inside media query', () => {
var str = '@media (max-width: 992px){ .test1.test2:hover{ color:red }}';
var result = {
@ -344,7 +355,7 @@ describe('ParserCss', () => {
selectors: ['test1'],
style: { color: 'blue' },
};
obj = new ParserCss(em, {
obj = ParserCss(em, {
parserCss: () => [result],
});
expect(obj.parse(str)).toEqual([result]);
@ -385,7 +396,7 @@ describe('ParserCss', () => {
});
test('Check node with keyframes rule', () => {
const style = { opacity: 0 };
const style = { opacity: '0' };
expect(
obj.checkNode({
atRule: 'keyframes',

5
test/specs/parser/model/ParserHtml.ts

@ -2,6 +2,7 @@ import ParserHtml from '../../../../src/parser/model/ParserHtml';
import ParserCss from '../../../../src/parser/model/ParserCss';
import DomComponents from '../../../../src/dom_components';
import Editor from '../../../../src/editor/model/Editor';
import { CSS_BG_OBJ, CSS_BG_STR } from './ParserCss';
describe('ParserHtml', () => {
let obj: ReturnType<typeof ParserHtml>;
@ -63,6 +64,10 @@ describe('ParserHtml', () => {
expect(obj.parseStyle(str)).toEqual(result);
});
test('Parse style with multiple values of the same key', () => {
expect(obj.parseStyle(CSS_BG_STR)).toEqual(CSS_BG_OBJ);
});
test('Parse class string to array', () => {
var str = 'test1 test2 test3 test-4';
var result = ['test1', 'test2', 'test3', 'test-4'];

Loading…
Cancel
Save