Current File : /home/kelaby89/sergio-cuchi.tattoo/wp-content/plugins/so-css/js/inspector.js |
/* globals jQuery, Backbone, _, socssOptions, SPECIFICITY, console */
( function( $, _, socssOptions ){
var socss = {
model : { },
collection : { },
view : { },
fn : {}
};
var getSelectorSpecificity = function(selector, useParts) {
var specificities = [];
var ruleSpecificity = SPECIFICITY.calculate( selector );
for (var i = 0; i < ruleSpecificity.length; i++) {
var specificity = ruleSpecificity[ i ];
if ( useParts ) {
for ( var j = 0; j < specificity.parts.length; j++ ) {
var specificityPart = specificity.parts[ j ];
// Recursive call to add specificities for parts.
specificities = specificities.concat(getSelectorSpecificity(specificityPart.selector));
}
} else {
specificities.push({
'selector': specificity.selector.trim(),
'specificity': parseInt(specificity.specificity.replace(/,/g, ''))
});
}
}
return specificities;
};
/**
* This is the main view for the app
*/
socss.view.inspector = Backbone.View.extend( {
active: false,
hl: false,
hoverEl: false,
pageSelectors: [],
selectorTemplate: _.template('<div class="socss-selector"><%= selector %></div>'),
importantClasses: [
'menu-item-',
'postid-',
'page-id-',
],
initialize: function(){
var thisView = this;
this.hl = new socss.view.highlighter();
this.hl.initialize();
this.pageSelectors = socss.fn.pageSelectors();
// Setup hovering
$('body').on('mouseover', '*', function(e){
if( !thisView.active ) {
return true;
}
var $$ = $(this);
if( $$.closest('.socss-element').length === 0 ) {
e.stopPropagation();
thisView.setHoverEl( $(this) );
}
});
// Setup the click event
var wcCheck = $( '.single-product' ).length;
$('body *').on( 'click', function( e ) {
if ( ! thisView.active || $( e.target ).parent( '.socss-link' ).length ) {
return true;
}
var $$ = $( this );
if ( ! wcCheck || ! $$.parents( '.wc-tabs' ).length ) {
e.preventDefault();
$$.trigger( 'blur' );
thisView.setActiveEl( thisView.hoverEl );
}
});
this.$('.socss-enable-inspector').on( 'click', function(){
thisView.toggleActive();
} );
this.$el.on( 'mouseenter', function() {
thisView.hl.clear();
} );
// Try register this inspector with the parent editor
try {
parent.socss.mainEditor.setInspector( this );
}
catch( err ){
console.log( 'No editor to register this inspector with' );
}
},
/**
* Set the element that's currently being hovered
*
* @param hoverEl
*/
setHoverEl: function( hoverEl ){
this.hoverEl = hoverEl;
this.hl.highlight( hoverEl );
},
activate: function(){
this.active = true;
$('body').addClass('socss-active');
$('body').removeClass('socss-inactive');
},
deactivate: function(){
this.active = false;
$('body').addClass('socss-inactive');
$('body').removeClass('socss-active');
this.hl.clear();
this.$('.socss-hierarchy').empty();
},
/**
* Toggle the active status
*/
toggleActive: function(){
if( this.active ) {
this.deactivate();
}
else {
this.activate();
}
},
/**
* Set the element that we're busy inspecting
* @param el
*/
setActiveEl: function( el ) {
var thisView = this;
var $h = this.$('.socss-hierarchy');
$h.empty();
if ( !el ) {
return;
}
if (el.prop('tagName').toLowerCase() !== 'body') {
var cel = $(el);
do {
var selector = socss.fn.elSelector( cel );
thisView.importantClasses.forEach( function( importantClass ) {
if ( selector.indexOf( importantClass ) >= 0 ) {
var selectorRegex = new RegExp( '(' + importantClass + '\\d+)', 'g' );
selector = selector.replace( selectorRegex, "<strong>$1</strong>");
}
} );
$( this.selectorTemplate( { selector: selector } ) )
.prependTo($h)
.data('el', cel);
cel = cel.parent();
} while (cel.prop('tagName').toLowerCase() !== 'body');
$(this.selectorTemplate({selector: 'body'}))
.prependTo($h)
.data('el', $('body'));
this.$('.socss-hierarchy .socss-selector')
.on( 'mouseenter', function () {
thisView.hl.highlight($(this).data('el'));
})
.on( 'click', function(e) {
e.preventDefault();
e.stopPropagation();
thisView.setActiveEl($(this).data('el'));
});
}
// Scroll all the way left...
$h.scrollLeft( 99999 );
// Now lets add all the CSS selectors
var selectors = this.pageSelectors.filter( function(a){
// Use try to catch any malformed selectors
try {
return el.is( a.selector );
}
catch(err) {
return false;
}
} );
// If the selected element is a link, check if it's a menu item.
if ( el.is( 'a' ) ) {
const parent = el.closest( '.menu-item' );
if ( parent.length ) {
// Certain themes don't use the menu-item class.
// To make it easier for users, we add the parent classes to the selectors array.
const parentClasses = parent.attr( 'class' ).split( /\s+/ );
const existingSelectors = new Set( selectors.map( s => s.selector ) );
// If a class isn't already present, add it to the selectors array.
parentClasses.forEach( cls => {
if ( ! existingSelectors.has( selector ) ) {
selectors.push( {
selector: '.' + cls,
specificity: getSelectorSpecificity( '.' + cls )
} );
}
} );
// Move the * selector to the end of the selectors list.
selectors.sort( ( a, b ) => {
if ( a.selector === '*' ) {
return 1;
}
if ( b.selector === '*' ) {
return -1;
}
return 0;
} );
}
}
var container = this.$('.socss-selectors-window').empty();
_.each( selectors, function( selector ){
container.append(
$( thisView.selectorTemplate( selector ) )
.data( selector )
);
} );
container.find('> div')
.on( 'mouseenter', function() {
thisView.hl.highlight( $(this).data('selector') );
} )
.on( 'click', function( e ) {
e.preventDefault();
e.stopPropagation();
thisView.trigger( 'click_selector', $(this).data('selector') );
} );
// And the CSS attributes
var attributes = socss.fn.elementAttributes(el);
container = this.$('.socss-properties-window').empty();
_.each( attributes, function(v, k){
container.append(
$( thisView.selectorTemplate( { selector: '<strong>' + k + '</strong>: ' + v } ) )
.data( 'property', k + ': ' + v )
);
} );
container.find('> div')
.on( 'click', function( e ) {
e.preventDefault();
e.stopPropagation();
thisView.trigger( 'click_property', $(this).data('property') );
});
// Display the link
var link = el.closest('a[href]');
var linkContainer = this.$('.socss-link');
if( link.length ) {
linkContainer.show().find('a')
.html( link.attr('href').replace(/[\?&]*so_css_preview=1/, '') )
.attr('href', link.attr('href') );
}
else {
linkContainer.hide();
}
this.trigger('set_active_element', el, selectors);
}
} );
socss.view.highlighter = Backbone.View.extend( {
template: _.template( $('#socss-template-hover').html().trim() ),
highlighted: [ ],
highlight: function( els ){
this.clear();
var thisView = this;
$(els).each(function(i, el){
el = $(el);
if( !el.is(':visible') ) {
// Skip over invisible elements
return true;
}
var hl = $( thisView.template() );
hl.css({
'top' : el.offset().top,
'left' : el.offset().left,
'width' : el.outerWidth(),
'height' : el.outerHeight()
}).appendTo( 'body' );
var g;
var padding = el.padding();
for( var k in padding ) {
if( parseInt( padding[k] ) > 0 ) {
g = hl.find( '.socss-guide-padding.socss-guide-' + k ).show();
if( k === 'top' || k === 'bottom' ) {
g.css('height', padding[k]);
}
else {
g.css('width', padding[k]);
g.css({
'width': padding[k],
'top' : padding.top,
'bottom' : padding.bottom
});
}
}
}
var margin = el.margin();
for( var k in margin ) {
if( parseInt( margin[k] ) > 0 ) {
g = hl.find( '.socss-guide-margin.socss-guide-' + k ).show();
if( k === 'top' || k === 'bottom' ) {
g.css('height', margin[k]);
}
else {
g.css('width', margin[k]);
}
}
}
thisView.highlighted.push( hl );
} );
},
clear: function(){
while( this.highlighted.length ) {
this.highlighted.pop().remove();
}
}
} );
socss.parsedCss = {};
socss.fn.getParsedCss = function(){
// Load all the parsed CSS
if( Object.keys(socss.parsedCss).length === 0 ) {
var parser = window.css;
$('.socss-theme-styles').each(function(){
var $$ = $(this);
var p = parser.parse( $$.html(), {
silent: true
} );
socss.parsedCss[ $$.attr('id') ] = p;
});
}
return socss.parsedCss;
};
/**
* Function to get all the available page selectors
*/
socss.fn.pageSelectors = function(){
var selectors = [];
var parsedCss = socss.fn.getParsedCss();
for( var k in parsedCss ) {
var rules = parsedCss[k].stylesheet.rules;
for( var i = 0; i < rules.length; i++ ) {
if (typeof rules[i].selectors === 'undefined') {
continue;
}
for(var j = 0; j < rules[i].selectors.length; j++) {
selectors = selectors.concat( getSelectorSpecificity( rules[i].selectors[j] ) );
}
}
}
// Also add selectors for all the elements in the
$('body *').each(function(){
var $$ = $(this);
var elName = socss.fn.elSelector( $$ );
selectors = selectors.concat(getSelectorSpecificity(elName));
});
var $body = $('body');
var bName = socss.fn.elSelector($body);
selectors = selectors.concat(getSelectorSpecificity(bName, true));
selectors = _.uniq( selectors, false, function( a ){
return a.selector;
} );
selectors.sort(function(a, b){
return a.specificity > b.specificity ? -1 : 1;
});
return selectors;
};
socss.fn.elementAttributes = function( el ) {
if( !document.styleSheets ) {
return [];
}
var elProperties = [];
var trimFunc = function(e) {
return e.trim();
};
var filterFunc = function(e){
return e !== '';
};
var splitFunc = function(e) {
return e.split(':').map( trimFunc );
};
var parsedCss = socss.fn.getParsedCss();
var isAtRule = function (ruleType) {
switch(ruleType) {
case 'charset':
case 'custom-media':
case 'document':
case 'font-face':
case 'host':
case 'import':
case 'keyframes':
case 'keyframe':
case 'media':
case 'namespace':
case 'page':
case 'supports':
return true;
}
return false;
};
for( var k in parsedCss ) {
var rules = parsedCss[k].stylesheet.rules;
for( var i = 0; i < rules.length; i++ ) {
var rule = rules[i];
if (
typeof rule.selectors === 'undefined' || isAtRule(rule.type)
) {
continue;
}
for(var j = 0; j < rule.selectors.length; j++) {
var ruleSpecificity = SPECIFICITY.calculate( rule.selectors[j] );
for (var l = 0; l < ruleSpecificity.length; l++) {
try {
if ( el.is( ruleSpecificity[l].selector ) ) {
var declarations = rule.declarations;
for (var l = 0; l < declarations.length; l++) {
elProperties.push({
'name': declarations[l].property,
'value': declarations[l].value,
'specificity': parseInt( ruleSpecificity[l].specificity.replace( /,/g, '' ) )
});
}
}
}
catch (e) {
// For now, we're just going to ignore rules that trigger errors
}
}
}
}
}
elProperties.sort( function(a,b) {
return a.specificity > b.specificity ? 1 : -1;
}).reverse();
var returnProperties = {};
for( var pi = 0; pi < elProperties.length; pi++ ) {
if( typeof returnProperties[elProperties[pi].name] === 'undefined' ) {
returnProperties[elProperties[pi].name] = elProperties[pi].value;
}
}
return returnProperties;
};
socss.fn.elSelector = function( el ){
var elName = '';
if( el.attr('id') !== undefined ) {
elName += '#' + el.attr('id');
}
if( el.attr('class') !== undefined ) {
elName += '.' + el.attr('class').replace(/\s+/g, '.');
}
if( elName === '' ) {
elName = el.prop('tagName').toLowerCase();
}
return elName;
};
window.socssInspector = socss;
} ) ( jQuery, _, socssOptions );
jQuery( function($){
var socss = window.socssInspector;
// Setup the editor
var inspector = new socss.view.inspector( {
el : $('#socss-inspector-interface').get(0)
} );
inspector.activate();
window.socssInspector.mainInspector = inspector;
} );