-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
Copy pathCssRulesView.ts
150 lines (124 loc) · 4.32 KB
/
CssRulesView.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { bindAll } from 'underscore';
import { View } from '../../common';
import { createEl } from '../../utils/dom';
import CssRuleView from './CssRuleView';
import CssGroupRuleView from './CssGroupRuleView';
import EditorModel from '../../editor/model/Editor';
import CssRule from '../model/CssRule';
const getBlockId = (pfx: string, order?: string | number) => `${pfx}${order ? `-${parseFloat(order as string)}` : ''}`;
export default class CssRulesView extends View {
atRules: Record<string, any>;
config: Record<string, any>;
em: EditorModel;
pfx: string;
renderStarted?: boolean;
constructor(o: any) {
super(o);
bindAll(this, 'sortRules');
const config = o.config || {};
this.atRules = {};
this.config = config;
this.em = config.em;
this.pfx = config.stylePrefix || '';
this.className = this.pfx + 'rules';
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.render);
}
/**
* Add to collection
* @param {Object} model
* @private
* */
addTo(model: CssRule) {
this.addToCollection(model);
}
/**
* Add new object to collection
* @param {Object} model
* @param {Object} fragmentEl
* @return {Object}
* @private
* */
addToCollection(model: CssRule, fragmentEl?: DocumentFragment) {
// If the render is not yet started
if (!this.renderStarted) {
return;
}
const fragment = fragmentEl || null;
const { config } = this;
const opts = { model, config };
let rendered, view;
// I have to render keyframes of the same name together
// Unfortunately at the moment I didn't find the way of appending them
// if not staticly, via appendData
if (model.get('atRuleType') === 'keyframes') {
const atRule = model.getAtRule();
let atRuleEl = this.atRules[atRule];
if (!atRuleEl) {
const styleEl = document.createElement('style');
atRuleEl = document.createTextNode('');
styleEl.appendChild(document.createTextNode(`${atRule}{`));
styleEl.appendChild(atRuleEl);
styleEl.appendChild(document.createTextNode('}'));
this.atRules[atRule] = atRuleEl;
rendered = styleEl;
}
view = new CssGroupRuleView(opts);
atRuleEl.appendData(view.render().el.textContent);
} else {
view = new CssRuleView(opts);
rendered = view.render().el;
}
const clsName = this.className!;
const mediaText = model.get('mediaText');
const defaultBlockId = getBlockId(clsName);
let blockId = defaultBlockId;
// If the rule contains a media query it might have a different container
// for it (eg. rules created with Device Manager)
if (mediaText) {
blockId = getBlockId(clsName, this.getMediaWidth(mediaText));
}
if (rendered) {
const container = fragment || this.el;
let contRules;
// Try to find a specific container for the rule (if it
// containes a media query), otherwise get the default one
try {
contRules = container.querySelector(`#${blockId}`);
} catch (e) {}
if (!contRules) {
contRules = container.querySelector(`#${defaultBlockId}`);
}
contRules?.appendChild(rendered);
}
return rendered;
}
getMediaWidth(mediaText: string) {
return mediaText && mediaText.replace(`(${this.em.getConfig().mediaCondition}: `, '').replace(')', '');
}
sortRules(a: number, b: number) {
const { em } = this;
const isMobFirst = (em.getConfig().mediaCondition || '').indexOf('min-width') !== -1;
if (!isMobFirst) return 0;
const left = isMobFirst ? a : b;
const right = isMobFirst ? b : a;
return left - right;
}
render() {
this.renderStarted = true;
this.atRules = {};
const { em, $el, collection } = this;
const cls = this.className!;
const frag = document.createDocumentFragment();
$el.empty();
// Create devices related DOM structure, ensure also to have a default container
const prs = em.get('DeviceManager').getAll().pluck('priority').sort(this.sortRules) as number[];
prs.every(pr => pr) && prs.unshift(0);
prs.forEach(pr => frag.appendChild(createEl('div', { id: getBlockId(cls, pr) })));
collection.each(model => this.addToCollection(model, frag));
$el.append(frag);
$el.attr('class', cls);
return this;
}
}