PluginProbe ʕ •ᴥ•ʔ
SiteOrigin CSS / 1.6.4
SiteOrigin CSS v1.6.4
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 / inspector.js
so-css / js Last commit date
URI.js 9 years ago URI.min.js 6 years ago css.js 1 year ago css.min.js 1 year ago csslint.js 9 years ago csslint.min.js 1 year ago editor.js 2 years ago editor.min.js 1 year ago inspector.js 1 year ago inspector.min.js 1 year ago jquery.sizes.js 11 years ago jquery.sizes.min.js 6 years ago specificity.js 11 years ago specificity.min.js 6 years ago
inspector.js
539 lines
1
2 /* globals jQuery, Backbone, _, socssOptions, SPECIFICITY, console */
3
4 ( function( $, _, socssOptions ){
5
6 var socss = {
7 model : { },
8 collection : { },
9 view : { },
10 fn : {}
11 };
12
13 var getSelectorSpecificity = function(selector, useParts) {
14 var specificities = [];
15 var ruleSpecificity = SPECIFICITY.calculate( selector );
16 for (var i = 0; i < ruleSpecificity.length; i++) {
17 var specificity = ruleSpecificity[ i ];
18 if ( useParts ) {
19 for ( var j = 0; j < specificity.parts.length; j++ ) {
20 var specificityPart = specificity.parts[ j ];
21 // Recursive call to add specificities for parts.
22 specificities = specificities.concat(getSelectorSpecificity(specificityPart.selector));
23 }
24 } else {
25 specificities.push({
26 'selector': specificity.selector.trim(),
27 'specificity': parseInt(specificity.specificity.replace(/,/g, ''))
28 });
29 }
30 }
31 return specificities;
32 };
33
34 /**
35 * This is the main view for the app
36 */
37 socss.view.inspector = Backbone.View.extend( {
38
39 active: false,
40 hl: false,
41 hoverEl: false,
42 pageSelectors: [],
43
44 selectorTemplate: _.template('<div class="socss-selector"><%= selector %></div>'),
45 importantClasses: [
46 'menu-item-',
47 'postid-',
48 'page-id-',
49 ],
50
51 initialize: function(){
52 var thisView = this;
53
54 this.hl = new socss.view.highlighter();
55 this.hl.initialize();
56
57 this.pageSelectors = socss.fn.pageSelectors();
58
59 // Setup hovering
60 $('body').on('mouseover', '*', function(e){
61 if( !thisView.active ) {
62 return true;
63 }
64
65 var $$ = $(this);
66 if( $$.closest('.socss-element').length === 0 ) {
67 e.stopPropagation();
68 thisView.setHoverEl( $(this) );
69 }
70 });
71
72 // Setup the click event
73 var wcCheck = $( '.single-product' ).length;
74 $('body *').on( 'click', function( e ) {
75 if ( ! thisView.active || $( e.target ).parent( '.socss-link' ).length ) {
76 return true;
77 }
78
79 var $$ = $( this );
80 if ( ! wcCheck || ! $$.parents( '.wc-tabs' ).length ) {
81 e.preventDefault();
82
83 $$.trigger( 'blur' );
84 thisView.setActiveEl( thisView.hoverEl );
85 }
86 });
87
88 this.$('.socss-enable-inspector').on( 'click', function(){
89 thisView.toggleActive();
90 } );
91
92 this.$el.on( 'mouseenter', function() {
93 thisView.hl.clear();
94 } );
95
96 // Try register this inspector with the parent editor
97 try {
98 parent.socss.mainEditor.setInspector( this );
99 }
100 catch( err ){
101 console.log( 'No editor to register this inspector with' );
102 }
103
104 },
105
106 /**
107 * Set the element that's currently being hovered
108 *
109 * @param hoverEl
110 */
111 setHoverEl: function( hoverEl ){
112 this.hoverEl = hoverEl;
113 this.hl.highlight( hoverEl );
114 },
115
116 activate: function(){
117 this.active = true;
118 $('body').addClass('socss-active');
119 $('body').removeClass('socss-inactive');
120 },
121
122 deactivate: function(){
123 this.active = false;
124 $('body').addClass('socss-inactive');
125 $('body').removeClass('socss-active');
126 this.hl.clear();
127 this.$('.socss-hierarchy').empty();
128 },
129
130 /**
131 * Toggle the active status
132 */
133 toggleActive: function(){
134 if( this.active ) {
135 this.deactivate();
136 }
137 else {
138 this.activate();
139 }
140 },
141
142 /**
143 * Set the element that we're busy inspecting
144 * @param el
145 */
146 setActiveEl: function( el ) {
147 var thisView = this;
148
149 var $h = this.$('.socss-hierarchy');
150 $h.empty();
151
152 if ( !el ) {
153 return;
154 }
155
156 if (el.prop('tagName').toLowerCase() !== 'body') {
157 var cel = $(el);
158 do {
159 var selector = socss.fn.elSelector( cel );
160 thisView.importantClasses.forEach( function( importantClass ) {
161 if ( selector.indexOf( importantClass ) >= 0 ) {
162 var selectorRegex = new RegExp( '(' + importantClass + '\\d+)', 'g' );
163 selector = selector.replace( selectorRegex, "<strong>$1</strong>");
164 }
165 } );
166
167 $( this.selectorTemplate( { selector: selector } ) )
168 .prependTo($h)
169 .data('el', cel);
170 cel = cel.parent();
171 } while (cel.prop('tagName').toLowerCase() !== 'body');
172
173 $(this.selectorTemplate({selector: 'body'}))
174 .prependTo($h)
175 .data('el', $('body'));
176
177 this.$('.socss-hierarchy .socss-selector')
178 .on( 'mouseenter', function () {
179 thisView.hl.highlight($(this).data('el'));
180 })
181 .on( 'click', function(e) {
182 e.preventDefault();
183 e.stopPropagation();
184 thisView.setActiveEl($(this).data('el'));
185 });
186 }
187
188 // Scroll all the way left...
189 $h.scrollLeft( 99999 );
190
191 // Now lets add all the CSS selectors
192 var selectors = this.pageSelectors.filter( function(a){
193 // Use try to catch any malformed selectors
194 try {
195 return el.is( a.selector );
196 }
197 catch(err) {
198 return false;
199 }
200 } );
201
202 // If the selected element is a link, check if it's a menu item.
203 if ( el.is( 'a' ) ) {
204 const parent = el.closest( '.menu-item' );
205 if ( parent.length ) {
206 // Certain themes don't use the menu-item class.
207 // To make it easier for users, we add the parent classes to the selectors array.
208 const parentClasses = parent.attr( 'class' ).split( /\s+/ );
209
210 const existingSelectors = new Set( selectors.map( s => s.selector ) );
211
212 // If a class isn't already present, add it to the selectors array.
213 parentClasses.forEach( cls => {
214 if ( ! existingSelectors.has( selector ) ) {
215 selectors.push( {
216 selector: '.' + cls,
217 specificity: getSelectorSpecificity( '.' + cls )
218 } );
219 }
220 } );
221
222 // Move the * selector to the end of the selectors list.
223 selectors.sort( ( a, b ) => {
224 if ( a.selector === '*' ) {
225 return 1;
226 }
227
228 if ( b.selector === '*' ) {
229 return -1;
230 }
231
232 return 0;
233 } );
234 }
235 }
236
237 var container = this.$('.socss-selectors-window').empty();
238
239 _.each( selectors, function( selector ){
240 container.append(
241 $( thisView.selectorTemplate( selector ) )
242 .data( selector )
243 );
244 } );
245 container.find('> div')
246 .on( 'mouseenter', function() {
247 thisView.hl.highlight( $(this).data('selector') );
248 } )
249 .on( 'click', function( e ) {
250 e.preventDefault();
251 e.stopPropagation();
252
253 thisView.trigger( 'click_selector', $(this).data('selector') );
254 } );
255
256 // And the CSS attributes
257 var attributes = socss.fn.elementAttributes(el);
258 container = this.$('.socss-properties-window').empty();
259
260 _.each( attributes, function(v, k){
261 container.append(
262 $( thisView.selectorTemplate( { selector: '<strong>' + k + '</strong>: ' + v } ) )
263 .data( 'property', k + ': ' + v )
264 );
265 } );
266
267 container.find('> div')
268 .on( 'click', function( e ) {
269 e.preventDefault();
270 e.stopPropagation();
271
272 thisView.trigger( 'click_property', $(this).data('property') );
273 });
274
275 // Display the link
276 var link = el.closest('a[href]');
277 var linkContainer = this.$('.socss-link');
278 if( link.length ) {
279 linkContainer.show().find('a')
280 .html( link.attr('href').replace(/[\?&]*so_css_preview=1/, '') )
281 .attr('href', link.attr('href') );
282 }
283 else {
284 linkContainer.hide();
285 }
286
287 this.trigger('set_active_element', el, selectors);
288 }
289
290 } );
291
292 socss.view.highlighter = Backbone.View.extend( {
293 template: _.template( $('#socss-template-hover').html().trim() ),
294 highlighted: [ ],
295
296 highlight: function( els ){
297 this.clear();
298 var thisView = this;
299
300 $(els).each(function(i, el){
301 el = $(el);
302
303 if( !el.is(':visible') ) {
304 // Skip over invisible elements
305 return true;
306 }
307
308 var hl = $( thisView.template() );
309 hl.css({
310 'top' : el.offset().top,
311 'left' : el.offset().left,
312 'width' : el.outerWidth(),
313 'height' : el.outerHeight()
314 }).appendTo( 'body' );
315
316 var g;
317
318 var padding = el.padding();
319 for( var k in padding ) {
320 if( parseInt( padding[k] ) > 0 ) {
321 g = hl.find( '.socss-guide-padding.socss-guide-' + k ).show();
322 if( k === 'top' || k === 'bottom' ) {
323 g.css('height', padding[k]);
324 }
325 else {
326 g.css('width', padding[k]);
327 g.css({
328 'width': padding[k],
329 'top' : padding.top,
330 'bottom' : padding.bottom
331 });
332 }
333 }
334 }
335
336 var margin = el.margin();
337 for( var k in margin ) {
338 if( parseInt( margin[k] ) > 0 ) {
339 g = hl.find( '.socss-guide-margin.socss-guide-' + k ).show();
340 if( k === 'top' || k === 'bottom' ) {
341 g.css('height', margin[k]);
342 }
343 else {
344 g.css('width', margin[k]);
345 }
346 }
347 }
348
349 thisView.highlighted.push( hl );
350 } );
351 },
352
353 clear: function(){
354 while( this.highlighted.length ) {
355 this.highlighted.pop().remove();
356 }
357 }
358 } );
359
360 socss.parsedCss = {};
361 socss.fn.getParsedCss = function(){
362 // Load all the parsed CSS
363 if( Object.keys(socss.parsedCss).length === 0 ) {
364 var parser = window.css;
365 $('.socss-theme-styles').each(function(){
366 var $$ = $(this);
367 var p = parser.parse( $$.html(), {
368 silent: true
369 } );
370 socss.parsedCss[ $$.attr('id') ] = p;
371 });
372 }
373 return socss.parsedCss;
374 };
375
376 /**
377 * Function to get all the available page selectors
378 */
379 socss.fn.pageSelectors = function(){
380 var selectors = [];
381 var parsedCss = socss.fn.getParsedCss();
382
383 for( var k in parsedCss ) {
384 var rules = parsedCss[k].stylesheet.rules;
385 for( var i = 0; i < rules.length; i++ ) {
386 if (typeof rules[i].selectors === 'undefined') {
387 continue;
388 }
389
390 for(var j = 0; j < rules[i].selectors.length; j++) {
391 selectors = selectors.concat( getSelectorSpecificity( rules[i].selectors[j] ) );
392 }
393 }
394 }
395
396 // Also add selectors for all the elements in the
397 $('body *').each(function(){
398 var $$ = $(this);
399 var elName = socss.fn.elSelector( $$ );
400
401 selectors = selectors.concat(getSelectorSpecificity(elName));
402 });
403
404 var $body = $('body');
405 var bName = socss.fn.elSelector($body);
406 selectors = selectors.concat(getSelectorSpecificity(bName, true));
407
408 selectors = _.uniq( selectors, false, function( a ){
409 return a.selector;
410 } );
411
412 selectors.sort(function(a, b){
413 return a.specificity > b.specificity ? -1 : 1;
414 });
415
416 return selectors;
417 };
418
419 socss.fn.elementAttributes = function( el ) {
420 if( !document.styleSheets ) {
421 return [];
422 }
423
424 var elProperties = [];
425
426 var trimFunc = function(e) {
427 return e.trim();
428 };
429
430 var filterFunc = function(e){
431 return e !== '';
432 };
433
434 var splitFunc = function(e) {
435 return e.split(':').map( trimFunc );
436 };
437
438 var parsedCss = socss.fn.getParsedCss();
439
440 var isAtRule = function (ruleType) {
441 switch(ruleType) {
442 case 'charset':
443 case 'custom-media':
444 case 'document':
445 case 'font-face':
446 case 'host':
447 case 'import':
448 case 'keyframes':
449 case 'keyframe':
450 case 'media':
451 case 'namespace':
452 case 'page':
453 case 'supports':
454 return true;
455
456 }
457 return false;
458 };
459
460 for( var k in parsedCss ) {
461 var rules = parsedCss[k].stylesheet.rules;
462 for( var i = 0; i < rules.length; i++ ) {
463 var rule = rules[i];
464 if (
465 typeof rule.selectors === 'undefined' || isAtRule(rule.type)
466 ) {
467 continue;
468 }
469
470 for(var j = 0; j < rule.selectors.length; j++) {
471 var ruleSpecificity = SPECIFICITY.calculate( rule.selectors[j] );
472 for (var l = 0; l < ruleSpecificity.length; l++) {
473 try {
474 if ( el.is( ruleSpecificity[l].selector ) ) {
475 var declarations = rule.declarations;
476 for (var l = 0; l < declarations.length; l++) {
477 elProperties.push({
478 'name': declarations[l].property,
479 'value': declarations[l].value,
480 'specificity': parseInt( ruleSpecificity[l].specificity.replace( /,/g, '' ) )
481 });
482 }
483 }
484 }
485 catch (e) {
486 // For now, we're just going to ignore rules that trigger errors
487 }
488 }
489 }
490
491 }
492 }
493
494 elProperties.sort( function(a,b) {
495 return a.specificity > b.specificity ? 1 : -1;
496 }).reverse();
497
498 var returnProperties = {};
499 for( var pi = 0; pi < elProperties.length; pi++ ) {
500 if( typeof returnProperties[elProperties[pi].name] === 'undefined' ) {
501 returnProperties[elProperties[pi].name] = elProperties[pi].value;
502 }
503 }
504
505 return returnProperties;
506 };
507
508 socss.fn.elSelector = function( el ){
509 var elName = '';
510 if( el.attr('id') !== undefined ) {
511 elName += '#' + el.attr('id');
512 }
513 if( el.attr('class') !== undefined ) {
514 elName += '.' + el.attr('class').replace(/\s+/g, '.');
515 }
516
517 if( elName === '' ) {
518 elName = el.prop('tagName').toLowerCase();
519 }
520
521 return elName;
522 };
523
524 window.socssInspector = socss;
525
526 } ) ( jQuery, _, socssOptions );
527
528 jQuery( function($){
529 var socss = window.socssInspector;
530
531 // Setup the editor
532 var inspector = new socss.view.inspector( {
533 el : $('#socss-inspector-interface').get(0)
534 } );
535 inspector.activate();
536
537 window.socssInspector.mainInspector = inspector;
538 } );
539