PluginProbe ʕ •ᴥ•ʔ
SiteOrigin CSS / 1.0.2
SiteOrigin CSS v1.0.2
1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.10 1.5.11 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 trunk 1.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0
so-css / js / css.js
so-css / js Last commit date
css.js 11 years ago css.min.js 11 years ago csslint.js 11 years ago csslint.min.js 11 years ago editor.js 11 years ago editor.min.js 11 years ago inspector.js 11 years ago inspector.min.js 11 years ago jquery.sizes.js 11 years ago jquery.sizes.min.js 11 years ago specificity.js 11 years ago specificity.min.js 11 years ago
css.js
698 lines
1 /*
2 The MIT License (MIT)
3 Copyright (c) 2015 JotForm
4
5 Modifications made by SiteOrigin
6 */
7
8 /* jshint unused:false */
9 /* global base64_decode, CSSWizardView, window, console, jQuery */
10 (function ($) {
11 'use strict';
12 var fi = function () {
13
14 this.cssImportStatements = [];
15 this.cssKeyframeStatements = [];
16
17 this.cssRegex = new RegExp('([\\s\\S]*?){([\\s\\S]*?)}', 'gi');
18 this.cssMediaQueryRegex = '((@media [\\s\\S]*?){([\\s\\S]*?}\\s*?)})';
19 this.cssKeyframeRegex = '((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})';
20 this.combinedCSSRegex = '((\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})'; //to match css & media queries together
21 this.cssCommentsRegex = '(\\/\\*[\\s\\S]*?\\*\\/)';
22 this.cssImportStatementRegex = new RegExp('@import .*?;', 'gi');
23 };
24
25 /*
26 Strip outs css comments and returns cleaned css string
27
28 @param css, the original css string to be stipped out of comments
29
30 @return cleanedCSS contains no css comments
31 */
32 fi.prototype.stripComments = function (cssString) {
33 var regex = new RegExp(this.cssCommentsRegex, 'gi');
34
35 return cssString.replace(regex, '');
36 };
37
38 /*
39 Parses given css string, and returns css object
40 keys as selectors and values are css rules
41
42 @param source css string to be parsed
43
44 @return object css
45 */
46 fi.prototype.parseCSS = function (source) {
47
48 if (source === undefined) {
49 return [];
50 }
51
52 var css = [];
53
54 //get import statements
55 while (true) {
56 var imports = this.cssImportStatementRegex.exec(source);
57 if (imports !== null) {
58 this.cssImportStatements.push(imports[0]);
59 css.push({
60 selector: '@imports',
61 type: 'imports',
62 styles: imports[0]
63 });
64 }
65 else {
66 break;
67 }
68 }
69 source = source.replace(this.cssImportStatementRegex, '');
70 //get keyframe statements
71 var keyframesRegex = new RegExp(this.cssKeyframeRegex, 'gi');
72 var arr;
73 while (true) {
74 arr = keyframesRegex.exec(source);
75 if (arr === null) {
76 break;
77 }
78 css.push({
79 selector: '@keyframes',
80 type: 'keyframes',
81 styles: arr[0]
82 });
83 }
84 source = source.replace(keyframesRegex, '');
85
86 //unified regex
87 var unified = new RegExp(this.combinedCSSRegex, 'gi');
88
89 while (true) {
90 arr = unified.exec(source);
91 if (arr === null) {
92 break;
93 }
94 var selector = '';
95 if (arr[2] === undefined) {
96 selector = arr[5].split('\r\n').join('\n').trim();
97 }
98 else {
99 selector = arr[2].split('\r\n').join('\n').trim();
100 }
101
102 /*
103 fetch comments and associate it with current selector
104 */
105 var commentsRegex = new RegExp(this.cssCommentsRegex, 'gi');
106 var comments = commentsRegex.exec(selector);
107 if (comments !== null) {
108 selector = selector.replace(commentsRegex, '').trim();
109 }
110
111 // Never have more than a single line break in a row
112 selector = selector.replace(/\n+/, "\n");
113
114 //determine the type
115 if (selector.indexOf('@media') !== -1) {
116 //we have a media query
117 var cssObject = {
118 selector: selector,
119 type: 'media',
120 subStyles: this.parseCSS(arr[3] + '\n}') //recursively parse media query inner css
121 };
122 if (comments !== null) {
123 cssObject.comments = comments[0];
124 }
125 css.push(cssObject);
126 }
127 else {
128 //we have standard css
129 var rules = this.parseRules(arr[6]);
130 var style = {
131 selector: selector,
132 rules: rules
133 };
134 if (selector === '@font-face') {
135 style.type = 'font-face';
136 }
137 if (comments !== null) {
138 style.comments = comments[0];
139 }
140 css.push(style);
141 }
142 }
143
144 return css;
145 };
146
147 /*
148 parses given string containing css directives
149 and returns an array of objects containing ruleName:ruleValue pairs
150
151 @param rules, css directive string example
152 \n\ncolor:white;\n font-size:18px;\n
153 */
154 fi.prototype.parseRules = function (rules) {
155 //convert all windows style line endings to unix style line endings
156 rules = rules.split('\r\n').join('\n');
157 var ret = [];
158
159 rules = rules.split(';');
160
161 //proccess rules line by line
162 for (var i = 0; i < rules.length; i++) {
163 var line = rules[i];
164
165 //determine if line is a valid css directive, ie color:white;
166 line = line.trim();
167 if (line.indexOf(':') !== -1) {
168 //line contains :
169 line = line.split(':');
170 var cssDirective = line[0].trim();
171 var cssValue = line.slice(1).join(':').trim();
172
173 //push rule
174 ret.push({
175 directive: cssDirective,
176 value: cssValue
177 });
178 }
179 else {
180 //if there is no ':', but what if it was mis splitted value which starts with base64
181 if (line.trim().substr(0, 7) === 'base64,') { //hack :)
182 ret[ret.length - 1].value += line.trim();
183 }
184 else {
185 //add rule, even if it is defective
186 if (line.length > 0) {
187 ret.push({
188 directive: '',
189 value: line,
190 defective: true
191 });
192 }
193 }
194 }
195 }
196
197 return ret; //we are done!
198 };
199
200 /*
201 just returns the rule having given directive
202 if not found returns false;
203 */
204 fi.prototype.findCorrespondingRule = function (rules, directive, value) {
205 if (value === undefined) {
206 value = false;
207 }
208 var ret = false;
209 for (var i = 0; i < rules.length; i++) {
210 if (rules[i].directive == directive) {
211 ret = rules[i];
212 if (value === rules[i].value) {
213 break;
214 }
215 }
216 }
217 return ret;
218 };
219
220 /*
221 Finds styles that have given selector, compress them,
222 and returns them
223 */
224 fi.prototype.findBySelector = function (cssObjectArray, selector, contains) {
225 if (contains === undefined) {
226 contains = false;
227 }
228
229 var found = [];
230 for (var i = 0; i < cssObjectArray.length; i++) {
231 if (contains === false) {
232 if (cssObjectArray[i].selector === selector) {
233 found.push(cssObjectArray[i]);
234 }
235 }
236 else {
237 if (cssObjectArray[i].selector.indexOf(selector) !== -1) {
238 found.push(cssObjectArray[i]);
239 }
240 }
241
242 }
243 if (found.length < 2) {
244 return found;
245 }
246 else {
247 var base = found[0];
248 for (i = 1; i < found.length; i++) {
249 this.intelligentCSSPush([base], found[i]);
250 }
251 return [base]; //we are done!! all properties merged into base!
252 }
253 };
254
255 /*
256 deletes cssObjects having given selector, and returns new array
257 */
258 fi.prototype.deleteBySelector = function (cssObjectArray, selector) {
259 var ret = [];
260 for (var i = 0; i < cssObjectArray.length; i++) {
261 if (cssObjectArray[i].selector !== selector) {
262 ret.push(cssObjectArray[i]);
263 }
264 }
265 return ret;
266 };
267
268 /*
269 Compresses given cssObjectArray and tries to minimize
270 selector redundence.
271 */
272 fi.prototype.compressCSS = function (cssObjectArray) {
273 var compressed = [];
274 var done = {};
275 for (var i = 0; i < cssObjectArray.length; i++) {
276 var obj = cssObjectArray[i];
277 if (done[obj.selector] === true) {
278 continue;
279 }
280
281 var found = this.findBySelector(cssObjectArray, obj.selector); //found compressed
282 if ( found.length !== 0 ) {
283 compressed.push(found[0]);
284 done[obj.selector] = true;
285 }
286 }
287
288 return compressed;
289 };
290
291 /*
292 Received 2 css objects with following structure
293 {
294 rules : [{directive:"", value:""}, {directive:"", value:""}, ...]
295 selector : "SOMESELECTOR"
296 }
297
298 returns the changed(new,removed,updated) values on css1 parameter, on same structure
299
300 if two css objects are the same, then returns false
301
302 if a css directive exists in css1 and css2, and its value is different, it is included in diff
303 if a css directive exists in css1 and not css2, it is then included in diff
304 if a css directive exists in css2 but not css1, then it is deleted in css1, it would be included in diff but will be marked as type='DELETED'
305
306 @object css1 css object
307 @object css2 css object
308
309 @return diff css object contains changed values in css1 in regards to css2 see test input output in /test/data/css.js
310 */
311 fi.prototype.cssDiff = function (css1, css2) {
312 if (css1.selector !== css2.selector) {
313 return false;
314 }
315
316 //if one of them is media query return false, because diff function can not operate on media queries
317 if ((css1.type === 'media' || css2.type === 'media')) {
318 return false;
319 }
320
321 var diff = {
322 selector: css1.selector,
323 rules: []
324 };
325 var rule1, rule2;
326 for (var i = 0; i < css1.rules.length; i++) {
327 rule1 = css1.rules[i];
328 //find rule2 which has the same directive as rule1
329 rule2 = this.findCorrespondingRule(css2.rules, rule1.directive, rule1.value);
330 if (rule2 === false) {
331 //rule1 is a new rule in css1
332 diff.rules.push(rule1);
333 }
334 else {
335 //rule2 was found only push if its value is different too
336 if (rule1.value !== rule2.value) {
337 diff.rules.push(rule1);
338 }
339 }
340 }
341
342 //now for rules exists in css2 but not in css1, which means deleted rules
343 for (var ii = 0; ii < css2.rules.length; ii++) {
344 rule2 = css2.rules[ii];
345 //find rule2 which has the same directive as rule1
346 rule1 = this.findCorrespondingRule(css1.rules, rule2.directive);
347 if (rule1 === false) {
348 //rule1 is a new rule
349 rule2.type = 'DELETED'; //mark it as a deleted rule, so that other merge operations could be true
350 diff.rules.push(rule2);
351 }
352 }
353
354
355 if (diff.rules.length === 0) {
356 return false;
357 }
358 return diff;
359 };
360
361 /*
362 Merges 2 different css objects together
363 using intelligentCSSPush,
364
365 @param cssObjectArray, target css object array
366 @param newArray, source array that will be pushed into cssObjectArray parameter
367 @param reverse, [optional], if given true, first parameter will be traversed on reversed order
368 effectively giving priority to the styles in newArray
369 */
370 fi.prototype.intelligentMerge = function (cssObjectArray, newArray, reverse) {
371 if (reverse === undefined) {
372 reverse = false;
373 }
374
375
376 for (var i = 0; i < newArray.length; i++) {
377 this.intelligentCSSPush(cssObjectArray, newArray[i], reverse);
378 }
379 for (i = 0; i < cssObjectArray.length; i++) {
380 var cobj = cssObjectArray[i];
381 if (cobj.type === 'media' || (cobj.type === 'keyframes')) {
382 continue;
383 }
384 cobj.rules = this.compactRules(cobj.rules);
385 }
386 };
387
388 /*
389 inserts new css objects into a bigger css object
390 with same selectors grouped together
391
392 @param cssObjectArray, array of bigger css object to be pushed into
393 @param minimalObject, single css object
394 @param reverse [optional] default is false, if given, cssObjectArray will be reverse traversed
395 resulting more priority in minimalObject's styles
396 */
397 fi.prototype.intelligentCSSPush = function (cssObjectArray, minimalObject, reverse) {
398 var pushSelector = minimalObject.selector;
399 //find correct selector if not found just push minimalObject into cssObject
400 var cssObject = false;
401
402 if (reverse === undefined) {
403 reverse = false;
404 }
405
406 if (reverse === false) {
407 for (var i = 0; i < cssObjectArray.length; i++) {
408 if (cssObjectArray[i].selector === minimalObject.selector) {
409 cssObject = cssObjectArray[i];
410 break;
411 }
412 }
413 }
414 else {
415 for (var j = cssObjectArray.length - 1; j > -1; j--) {
416 if (cssObjectArray[j].selector === minimalObject.selector) {
417 cssObject = cssObjectArray[j];
418 break;
419 }
420 }
421 }
422
423 if (cssObject === false) {
424 cssObjectArray.push(minimalObject); //just push, because cssSelector is new
425 }
426 else {
427 if (minimalObject.type !== 'media') {
428 for (var ii = 0; ii < minimalObject.rules.length; ii++) {
429 var rule = minimalObject.rules[ii];
430 //find rule inside cssObject
431 var oldRule = this.findCorrespondingRule(cssObject.rules, rule.directive);
432 if (oldRule === false) {
433 cssObject.rules.push(rule);
434 } else if (rule.type === 'DELETED') {
435 oldRule.type = 'DELETED';
436 }
437 else {
438 //rule found just update value
439
440 oldRule.value = rule.value;
441 }
442 }
443 }
444 else {
445 cssObject.subStyles = minimalObject.subStyles; //TODO, make this intelligent too
446 }
447
448 }
449 };
450
451 /*
452 filter outs rule objects whose type param equal to DELETED
453
454 @param rules, array of rules
455
456 @returns rules array, compacted by deleting all unnecessary rules
457 */
458 fi.prototype.compactRules = function (rules) {
459 var newRules = [];
460 for (var i = 0; i < rules.length; i++) {
461 if (rules[i].type !== 'DELETED') {
462 newRules.push(rules[i]);
463 }
464 }
465 return newRules;
466 };
467 /*
468 computes string for ace editor using this.css or given cssBase optional parameter
469
470 @param [optional] cssBase, if given computes cssString from cssObject array
471 */
472 fi.prototype.getCSSForEditor = function (cssBase, depth) {
473 if (depth === undefined) {
474 depth = 0;
475 }
476 var ret = '';
477 if (cssBase === undefined) {
478 cssBase = this.css;
479 }
480 //append imports
481 for (var i = 0; i < cssBase.length; i++) {
482 if (cssBase[i].type === 'imports') {
483 ret += cssBase[i].styles + '\n\n';
484 }
485 }
486 for (i = 0; i < cssBase.length; i++) {
487 var tmp = cssBase[i];
488 if (tmp.selector === undefined) { //temporarily omit media queries
489 continue;
490 }
491 var comments = "";
492 if (tmp.comments !== undefined) {
493 comments = tmp.comments + '\n';
494 }
495
496 if (tmp.type === 'media') { //also put media queries to output
497 ret += comments + tmp.selector + '{\n';
498 ret += this.getCSSForEditor(tmp.subStyles, depth + 1);
499 ret += '}\n\n';
500 }
501 else if (tmp.type !== 'keyframes' && tmp.type !== 'imports') {
502 ret += this.getSpaces(depth) + comments + tmp.selector + ' {\n';
503 ret += this.getCSSOfRules(tmp.rules, depth + 1);
504 ret += this.getSpaces(depth) + '}\n\n';
505 }
506 }
507
508 //append keyFrames
509 for (i = 0; i < cssBase.length; i++) {
510 if (cssBase[i].type === 'keyframes') {
511 ret += cssBase[i].styles + '\n\n';
512 }
513 }
514
515 return ret;
516 };
517
518 fi.prototype.getImports = function (cssObjectArray) {
519 var imps = [];
520 for (var i = 0; i < cssObjectArray.length; i++) {
521 if (cssObjectArray[i].type === 'imports') {
522 imps.push(cssObjectArray[i].styles);
523 }
524 }
525 return imps;
526 };
527
528 /*
529 given rules array, returns visually formatted css string
530 to be used inside editor
531 */
532 fi.prototype.getCSSOfRules = function (rules, depth) {
533 var ret = '';
534 for (var i = 0; i < rules.length; i++) {
535 if (rules[i] === undefined) {
536 continue;
537 }
538 if( rules[i].value === '' ) {
539 continue;
540 }
541
542 if (rules[i].defective === undefined) {
543 ret += this.getSpaces(depth) + rules[i].directive + ': ' + rules[i].value + ';\n';
544 }
545 else {
546 ret += this.getSpaces(depth) + rules[i].value + ';\n';
547 }
548
549 }
550 return ret || '\n';
551 };
552
553 /*
554 A very simple helper function returns number of spaces appended in a single string,
555 the number depends input parameter, namely input*2
556 */
557 fi.prototype.getSpaces = function (num) {
558 var ret = '';
559 for (var i = 0; i < num * 2; i++) {
560 ret += ' ';
561 }
562 return ret;
563 };
564
565 /*
566 Given css string or objectArray, parses it and then for every selector,
567 prepends this.cssPreviewNamespace to prevent css collision issues
568
569 @returns css string in which this.cssPreviewNamespace prepended
570 */
571 fi.prototype.applyNamespacing = function (css, forcedNamespace) {
572 var cssObjectArray = css;
573 var namespaceClass = '.' + this.cssPreviewNamespace;
574 if (forcedNamespace !== undefined) {
575 namespaceClass = forcedNamespace;
576 }
577
578 if (typeof css === 'string') {
579 cssObjectArray = this.parseCSS(css);
580 }
581
582 for (var i = 0; i < cssObjectArray.length; i++) {
583 var obj = cssObjectArray[i];
584
585 //bypass namespacing for @font-face @keyframes @import
586 if (obj.selector.indexOf('@font-face') > -1 || obj.selector.indexOf('keyframes') > -1 || obj.selector.indexOf('@import') > -1 || obj.selector.indexOf('.form-all') > -1 || obj.selector.indexOf('#stage') > -1) {
587 continue;
588 }
589
590 if (obj.type !== 'media') {
591 var selector = obj.selector.split(',');
592 var newSelector = [];
593 for (var j = 0; j < selector.length; j++) {
594 if (selector[j].indexOf('.supernova') === -1) { //do not apply namespacing to selectors including supernova
595 newSelector.push(namespaceClass + ' ' + selector[j]);
596 }
597 else {
598 newSelector.push(selector[j]);
599 }
600 }
601 obj.selector = newSelector.join(',');
602 }
603 else {
604 obj.subStyles = this.applyNamespacing(obj.subStyles, forcedNamespace); //handle media queries as well
605 }
606 }
607
608 return cssObjectArray;
609 };
610
611 /*
612 given css string or object array, clears possible namespacing from
613 all of the selectors inside the css
614 */
615 fi.prototype.clearNamespacing = function (css, returnObj) {
616 if (returnObj === undefined) {
617 returnObj = false;
618 }
619 var cssObjectArray = css;
620 var namespaceClass = '.' + this.cssPreviewNamespace;
621 if (typeof css === 'string') {
622 cssObjectArray = this.parseCSS(css);
623 }
624
625 for (var i = 0; i < cssObjectArray.length; i++) {
626 var obj = cssObjectArray[i];
627
628 if (obj.type !== 'media') {
629 var selector = obj.selector.split(',');
630 var newSelector = [];
631 for (var j = 0; j < selector.length; j++) {
632 newSelector.push(selector[j].split(namespaceClass + ' ').join(''));
633 }
634 obj.selector = newSelector.join(',');
635 }
636 else {
637 obj.subStyles = this.clearNamespacing(obj.subStyles, true); //handle media queries as well
638 }
639 }
640 if (returnObj === false) {
641 return this.getCSSForEditor(cssObjectArray);
642 }
643 else {
644 return cssObjectArray;
645 }
646
647 };
648
649 /*
650 creates a new style tag (also destroys the previous one)
651 and injects given css string into that css tag
652 */
653 fi.prototype.createStyleElement = function (id, css, format) {
654 if (format === undefined) {
655 format = false;
656 }
657
658 if (this.testMode === false && format !== 'nonamespace') {
659 //apply namespacing classes
660 css = this.applyNamespacing(css);
661 }
662
663 if (typeof css !== 'string') {
664 css = this.getCSSForEditor(css);
665 }
666 //apply formatting for css
667 if (format === true) {
668 css = this.getCSSForEditor(this.parseCSS(css));
669 }
670
671 if (this.testMode !== false) {
672 return this.testMode('create style #' + id, css); //if test mode, just pass result to callback
673 }
674
675 var __el = document.getElementById(id);
676 if (__el) {
677 __el.parentNode.removeChild(__el);
678 }
679
680 var head = document.head || document.getElementsByTagName('head')[0],
681 style = document.createElement('style');
682
683 style.id = id;
684 style.type = 'text/css';
685
686 head.appendChild(style);
687
688 if (style.styleSheet && !style.sheet) {
689 style.styleSheet.cssText = css;
690 }
691 else {
692 style.appendChild(document.createTextNode(css));
693 }
694 };
695
696 window.cssjs = fi;
697
698 })();