Browse Source

Merge branch 'dev' into symbols-api

symbols-api
Artur Arseniev 2 years ago
parent
commit
95e71ce8b4
  1. 5
      src/css_composer/index.ts
  2. 28
      src/dom_components/model/Component.ts
  3. 5
      src/dom_components/model/types.ts
  4. 6
      src/domain_abstract/model/StyleableModel.ts
  5. 6
      src/editor/config/config.ts
  6. 2
      src/navigator/index.ts
  7. 22
      src/navigator/view/ItemView.ts
  8. 32
      src/style_manager/index.ts
  9. 24
      test/specs/dom_components/model/Component.ts
  10. 66
      test/specs/style_manager/index.ts

5
src/css_composer/index.ts

@ -39,6 +39,7 @@ import { ItemManagerModule } from '../abstract/Module';
import EditorModel from '../editor/model/Editor';
import Component from '../dom_components/model/Component';
import { ObjectAny, PrevToNewIdMap } from '../common';
import { UpdateStyleOptions } from '../domain_abstract/model/StyleableModel';
/** @private */
interface RuleOptions {
@ -53,7 +54,7 @@ interface RuleOptions {
}
/** @private */
interface SetRuleOptions extends RuleOptions {
interface SetRuleOptions extends RuleOptions, UpdateStyleOptions {
/**
* If the rule exists already, merge passed styles instead of replacing them.
*/
@ -61,7 +62,7 @@ interface SetRuleOptions extends RuleOptions {
}
/** @private */
export interface GetSetRuleOptions {
export interface GetSetRuleOptions extends UpdateStyleOptions {
state?: string;
mediaText?: string;
addOpts?: ObjectAny;

28
src/dom_components/model/Component.ts

@ -12,7 +12,7 @@ import {
keys,
} from 'underscore';
import { shallowDiff, capitalize, isEmptyObj, isObject, toLowerCase } from '../../utils/mixins';
import StyleableModel, { StyleProps } from '../../domain_abstract/model/StyleableModel';
import StyleableModel, { StyleProps, UpdateStyleOptions } from '../../domain_abstract/model/StyleableModel';
import { Model } from 'backbone';
import Components from './Components';
import Selector from '../../selector_manager/model/Selector';
@ -54,6 +54,8 @@ import {
export interface IComponent extends ExtractMethods<Component> {}
export interface SetAttrOptions extends SetOptions, UpdateStyleOptions {}
const escapeRegExp = (str: string) => {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
};
@ -588,7 +590,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
* Emit changes for each updated attribute
* @private
*/
attrUpdated(m: any, v: any, opts: any = {}) {
attrUpdated(m: any, v: any, opts: SetAttrOptions = {}) {
const attrs = this.get('attributes')!;
// Handle classes
const classes = attrs.class;
@ -597,7 +599,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
// Handle style
const style = attrs.style;
style && this.setStyle(style);
style && this.setStyle(style, opts);
delete attrs.style;
const attrPrev = { ...this.previous('attributes') };
@ -617,7 +619,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @example
* component.setAttributes({ id: 'test', 'data-key': 'value' });
*/
setAttributes(attrs: ObjectAny, opts: SetOptions = {}) {
setAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) {
this.set('attributes', { ...attrs }, opts);
return this;
}
@ -630,7 +632,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @example
* component.addAttributes({ 'data-key': 'value' });
*/
addAttributes(attrs: ObjectAny, opts: SetOptions = {}) {
addAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) {
return this.setAttributes(
{
...this.getAttributes({ noClass: true }),
@ -674,6 +676,10 @@ export default class Component extends StyleableModel<ComponentProperties> {
if (rule) {
return rule.getStyle(prop);
}
// Return empty style if not rule have been found. We cannot return inline style with the next return
// because else on load inline style is set a #id or .class style
return {};
}
return super.getStyle.call(this, prop);
@ -686,7 +692,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @example
* component.setStyle({ color: 'red' });
*/
setStyle(prop: StyleProps = {}, opts: any = {}) {
setStyle(prop: StyleProps = {}, opts: UpdateStyleOptions = {}) {
const { opt, em } = this;
if (avoidInline(em) && !opt.temporary && !opts.inline) {
@ -732,9 +738,9 @@ export default class Component extends StyleableModel<ComponentProperties> {
// Add style
if (!opts.noStyle) {
const style = this.get('style');
const style = this.getStyle({ inline: true });
if (isObject(style) && !isEmptyObj(style)) {
attributes.style = this.styleToString({ inline: 1 });
attributes.style = this.styleToString({ inline: true });
}
}
@ -1388,7 +1394,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
const tag = customTag || model.get('tagName');
const sTag = model.get('void');
const customAttr = opts.attributes;
let attributes = this.getAttrToHTML();
let attributes = this.getAttrToHTML(opts);
delete opts.tag;
// Get custom attributes if requested
@ -1459,10 +1465,10 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @return {Object}
* @private
*/
getAttrToHTML() {
getAttrToHTML(opts?: ToHTMLOptions) {
const attrs = this.getAttributes();
if (avoidInline(this.em)) {
if (avoidInline(this.em) && opts?.keepInlineStyle !== true) {
delete attrs.style;
}

5
src/dom_components/model/types.ts

@ -288,6 +288,11 @@ export interface ToHTMLOptions extends OptionAsDocument {
*/
altQuoteAttr?: boolean;
/**
* Keep inline style set intentionally by users with `setStyle({}, { inline: true })`
*/
keepInlineStyle?: boolean;
/**
* You can pass an object of custom attributes to replace with the current ones
* or you can even pass a function to generate attributes dynamically.

6
src/domain_abstract/model/StyleableModel.ts

@ -1,14 +1,16 @@
import { isArray, isString, keys } from 'underscore';
import { Model, ObjectAny, ObjectHash } from '../../common';
import { Model, ObjectAny, ObjectHash, SetOptions } from '../../common';
import ParserHtml from '../../parser/model/ParserHtml';
import Selectors from '../../selector_manager/model/Selectors';
import { shallowDiff } from '../../utils/mixins';
export type StyleProps = Record<string, string | string[]>;
export type UpdateStyleOptions = ObjectAny & {
export type UpdateStyleOptions = SetOptions & {
partial?: boolean;
addStyle?: StyleProps;
inline?: boolean;
noEvent?: boolean;
};
const parserHtml = ParserHtml();

6
src/editor/config/config.ts

@ -7,6 +7,7 @@ import { DeviceManagerConfig } from '../../device_manager/config/config';
import { I18nConfig } from '../../i18n/config';
import { ModalConfig } from '../../modal_dialog/config/config';
import { LayerManagerConfig } from '../../navigator/config/config';
import { KeymapsConfig } from '../../keymaps/config';
import { PageManagerConfig } from '../../pages/types';
import { PanelsConfig } from '../../panels/config/config';
import { ParserConfig } from '../../parser/config/config';
@ -356,6 +357,11 @@ export interface EditorConfig {
*/
commands?: CommandsConfig;
/**
* Configurations for keymaps
*/
keymaps?: KeymapsConfig;
/**
* Configurations for Css Composer.
*/

2
src/navigator/index.ts

@ -195,7 +195,7 @@ export default class LayerManager extends Module<LayerManagerConfig> {
style.display = 'none';
}
component.setStyle(style, styleOpts);
component.setStyle(style, styleOpts as any);
this.updateLayer(component);
this.em.trigger('component:toggled'); // Updates Style Manager #2938
}

22
src/navigator/view/ItemView.ts

@ -20,12 +20,13 @@ export type ItemViewProps = ViewOptions & {
};
const inputProp = 'contentEditable';
const dataToggleMove = 'data-toggle-move';
export default class ItemView extends View {
events() {
return {
'mousedown [data-toggle-move]': 'startSort',
'touchstart [data-toggle-move]': 'startSort',
[`mousedown [${dataToggleMove}]`]: 'startSort',
[`touchstart [${dataToggleMove}]`]: 'startSort',
'click [data-toggle-visible]': 'toggleVisibility',
'click [data-toggle-open]': 'toggleOpening',
'click [data-toggle-select]': 'handleSelect',
@ -76,7 +77,7 @@ export default class ItemView extends View {
</div>
<div class="${pfx}layer-item-right">
<div class="${this.clsCount}" data-count>${count || ''}</div>
<div class="${this.clsMove}" data-toggle-move>${move || ''}</div>
<div class="${this.clsMove}" ${dataToggleMove}>${move || ''}</div>
</div>
</div>
<div class="${this.clsChildren}"></div>
@ -151,6 +152,7 @@ export default class ItemView extends View {
['change:open', this.updateOpening],
['change:layerable', this.updateLayerable],
['change:style:display', this.updateVisibility],
['change:draggable', this.updateMove],
['rerender:layer', this.render],
['change:name change:custom-name', this.updateName],
// @ts-ignore
@ -183,6 +185,15 @@ export default class ItemView extends View {
this.getVisibilityEl()[method](`${pfx}layer-off`);
}
updateMove() {
const { model, config } = this;
const el = this.getItemContainer().find(`[${dataToggleMove}]`)[0];
if (el) {
const isDraggable = model.get('draggable') && config.sortable;
el.style.display = isDraggable ? '' : 'none';
}
}
/**
* Toggle visibility
* @param Event
@ -421,16 +432,13 @@ export default class ItemView extends View {
el.find(`.${this.clsChildren}`).append(children);
}
if (!model.get('draggable') || !config.sortable) {
el.children(`.${this.clsMove}`).remove();
}
!module.isVisible(model) && (this.className += ` ${pfx}hide`);
hidden && (this.className += ` ${ppfx}hidden`);
el.attr('class', this.className!);
this.updateStatus();
this.updateOpening();
this.updateVisibility();
this.updateMove();
this.__render();
this._rendered = true;
return this;

32
src/style_manager/index.ts

@ -597,6 +597,8 @@ export default class StyleManager extends ItemManagerModule<
const cmp = target.toHTML ? target : target.getComponent();
const optsSel = { array: true } as const;
let cmpRules: CssRule[] = [];
let tagNameRules: CssRule[] = [];
let invisibleAndOtherRules: CssRule[] = [];
let otherRules: CssRule[] = [];
let rules: CssRule[] = [];
@ -605,19 +607,41 @@ export default class StyleManager extends ItemManagerModule<
? []
: cssC.getRules().filter(rule => {
const rSels = rule.getSelectors().map(s => s.getFullName());
return !!rSels.length && rSels.every(rSel => values.indexOf(rSel) >= 0);
// rSels is equal to 0 when rule selectors contain tagName like : p {}, .logo path {}, ul li {}
if (rSels.length === 0) {
return false;
}
return rSels.every(rSel => values.indexOf(rSel) >= 0);
});
};
const rulesByTagName = (tagName: string) => {
return !tagName ? [] : cssC.getRules().filter(rule => rule.selectorsToString() === tagName);
};
// Componente related rule
if (cmp) {
cmpRules = cssC.getRules(`#${cmp.getId()}`);
tagNameRules = rulesByTagName(cmp.get('tagName'));
otherRules = sel ? rulesBySelectors(sel.getSelectors().getFullName(optsSel)) : [];
rules = otherRules.concat(cmpRules);
rules = otherRules.concat(tagNameRules).concat(cmpRules);
} else {
cmpRules = sel ? cssC.getRules(`#${sel.getId()}`) : [];
otherRules = rulesBySelectors(target.getSelectors().getFullName(optsSel));
rules = cmpRules.concat(otherRules);
tagNameRules = rulesByTagName(sel?.get('tagName') || '');
// Get rules set on invisible selectors like private one
const allCmpClasses = sel?.getSelectors().getFullName(optsSel) || [];
const invisibleSel = allCmpClasses.filter(
(item: string) =>
target
.getSelectors()
.getFullName(optsSel)
.findIndex(sel => sel === item) === -1
);
// Get rules set on active and visible selectors
invisibleAndOtherRules = rulesBySelectors(invisibleSel.concat(target.getSelectors().getFullName(optsSel)));
rules = tagNameRules.concat(cmpRules).concat(invisibleAndOtherRules);
}
const all = rules

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

@ -19,7 +19,9 @@ let em: Editor;
describe('Component', () => {
beforeEach(() => {
em = new Editor({ avoidDefaults: true });
// FIXME: avoidInlineStyle is deprecated and when running in dev or prod, `avoidInlineStyle` is set to true
// The following tests ran with `avoidInlineStyle` to false (this is why I add the parameter here)
em = new Editor({ avoidDefaults: true, avoidInlineStyle: true });
dcomp = em.Components;
em.Pages.onLoad();
compOpts = {
@ -278,10 +280,10 @@ describe('Component', () => {
class: 'class1 class2',
style: 'color: white; background: #fff',
});
// Style is not in attributes because it has not been set as inline
expect(obj.getAttributes()).toEqual({
id: 'test',
class: 'class1 class2',
style: 'color:white;background:#fff;',
'data-test': 'value',
});
expect(obj.classes.length).toEqual(2);
@ -291,13 +293,25 @@ describe('Component', () => {
});
});
test('set inline style with multiple values of the same key', () => {
test('set 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 });
test('set style on id and inline style', () => {
obj.setStyle({ color: 'red' }); // Should be set on id
obj.setStyle({ display: 'flex' }, { inline: true }); // Should be set as inline
expect(obj.getStyle()).toEqual({
color: 'red',
});
expect(obj.getStyle({ inline: true })).toEqual({
display: 'flex',
});
});
test('get proper style from style with multiple values of the same key', () => {
obj.setAttributes({ style: CSS_BG_STR }, { inline: true });
expect(obj.getAttributes()).toEqual({
style: CSS_BG_STR.split('\n').join(''),
});

66
test/specs/style_manager/index.ts

@ -195,6 +195,72 @@ describe('StyleManager', () => {
expect(obj.getSelectedParents()).toEqual([rule1]);
});
test('With multiple classes, should both be in parents list', () => {
const cmp = domc.addComponent('<div class="cls cls2"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
.cls2 { color: blue; }
`);
em.setSelected(cmp);
obj.__upSel();
expect(obj.getSelected()).not.toBe(rule1);
expect(obj.getSelected()).not.toBe(rule2);
expect(obj.getSelectedParents()).toEqual([rule2, rule1]);
});
test('With tagName + class, class first', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
`);
em.setSelected(cmp);
obj.__upSel();
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule2]);
});
test('Should ignore rules with tagName in the selector path but the rule is not apply on the tagName', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
div .child { padding: 10px; }
`);
em.setSelected(cmp);
obj.__upSel();
// getSelectedParents should only have 1 rule as the third one is not applied on the div
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule2]);
});
test('Should tagName rules if the selectors does not contain only the tagNale', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
.child div { padding: 10px; }
`);
em.setSelected(cmp);
obj.__upSel();
// getSelectedParents should only have 1 rule as the third one is not applied on the div
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule2]);
});
test('With tagName + ID + class, class first, ID second', () => {
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');
const [rule1, rule2, rule3] = cssc.addRules(`
.cls { color: red; }
div { color: yellow; }
#id-test { color: blue; }
`);
em.setSelected(cmp);
obj.__upSel();
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([rule3, rule2]);
});
test('With ID + class, multiple devices', () => {
sm.setComponentFirst(true);
const cmp = domc.addComponent('<div class="cls" id="id-test"></div>');

Loading…
Cancel
Save