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
inspector.js
444 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 | /** |
| 14 | * This is the main view for the app |
| 15 | */ |
| 16 | socss.view.inspector = Backbone.View.extend( { |
| 17 | |
| 18 | active: false, |
| 19 | hl: false, |
| 20 | hoverEl: false, |
| 21 | pageSelectors: [], |
| 22 | |
| 23 | selectorTemplate: _.template('<div class="socss-selector"><%= selector %></div>'), |
| 24 | |
| 25 | initialize: function(){ |
| 26 | var thisView = this; |
| 27 | |
| 28 | this.hl = new socss.view.highlighter(); |
| 29 | this.hl.initialize(); |
| 30 | |
| 31 | this.pageSelectors = socss.fn.pageSelectors(); |
| 32 | |
| 33 | // Setup hovering |
| 34 | $('body').on('mouseover', '*', function(e){ |
| 35 | if( !thisView.active ) { |
| 36 | return true; |
| 37 | } |
| 38 | |
| 39 | var $$ = $(this); |
| 40 | if( $$.closest('.socss-element').length === 0 ) { |
| 41 | e.stopPropagation(); |
| 42 | thisView.setHoverEl( $(this) ); |
| 43 | } |
| 44 | }); |
| 45 | |
| 46 | // Setup the click event |
| 47 | $('body *').click(function( e ){ |
| 48 | if( !thisView.active || thisView.$el.is(':hover') ) { |
| 49 | return true; |
| 50 | } |
| 51 | |
| 52 | e.preventDefault(); |
| 53 | e.stopPropagation(); |
| 54 | |
| 55 | var $$ = $(this); |
| 56 | $$.blur(); |
| 57 | thisView.setActiveEl( thisView.hoverEl ); |
| 58 | }); |
| 59 | |
| 60 | this.$('.socss-enable-inspector').click( function(){ |
| 61 | thisView.toggleActive(); |
| 62 | } ); |
| 63 | |
| 64 | this.$el.mouseenter( function(){ |
| 65 | thisView.hl.clear(); |
| 66 | } ); |
| 67 | |
| 68 | // Try register this inspector with the parent editor |
| 69 | try { |
| 70 | parent.socss.mainEditor.setInspector( this ); |
| 71 | } |
| 72 | catch( err ){ |
| 73 | console.log( 'No editor to register this inspector with' ); |
| 74 | } |
| 75 | |
| 76 | }, |
| 77 | |
| 78 | /** |
| 79 | * Set the element that's currently being hovered |
| 80 | * |
| 81 | * @param hoverEl |
| 82 | */ |
| 83 | setHoverEl: function( hoverEl ){ |
| 84 | this.hoverEl = hoverEl; |
| 85 | this.hl.highlight( hoverEl ); |
| 86 | }, |
| 87 | |
| 88 | activate: function(){ |
| 89 | this.active = true; |
| 90 | $('body').addClass('socss-active'); |
| 91 | $('body').removeClass('socss-inactive'); |
| 92 | }, |
| 93 | |
| 94 | deactivate: function(){ |
| 95 | this.active = false; |
| 96 | $('body').addClass('socss-inactive'); |
| 97 | $('body').removeClass('socss-active'); |
| 98 | this.hl.clear(); |
| 99 | this.$('.socss-hierarchy').empty(); |
| 100 | }, |
| 101 | |
| 102 | /** |
| 103 | * Toggle the active status |
| 104 | */ |
| 105 | toggleActive: function(){ |
| 106 | if( this.active ) { |
| 107 | this.deactivate(); |
| 108 | } |
| 109 | else { |
| 110 | this.activate(); |
| 111 | } |
| 112 | }, |
| 113 | |
| 114 | /** |
| 115 | * Set the element that we're busy inspecting |
| 116 | * @param el |
| 117 | */ |
| 118 | setActiveEl: function( el ) { |
| 119 | var thisView = this; |
| 120 | |
| 121 | var $h = this.$('.socss-hierarchy'); |
| 122 | $h.empty(); |
| 123 | |
| 124 | if (el.prop('tagName').toLowerCase() !== 'body') { |
| 125 | var cel = $(el); |
| 126 | do { |
| 127 | $(this.selectorTemplate({selector: socss.fn.elSelector(cel)})) |
| 128 | .prependTo($h) |
| 129 | .data('el', cel); |
| 130 | cel = cel.parent(); |
| 131 | } while (cel.prop('tagName').toLowerCase() !== 'body'); |
| 132 | |
| 133 | $(this.selectorTemplate({selector: 'body'})) |
| 134 | .prependTo($h) |
| 135 | .data('el', $('body')); |
| 136 | |
| 137 | this.$('.socss-hierarchy .socss-selector') |
| 138 | .hover(function () { |
| 139 | thisView.hl.highlight($(this).data('el')); |
| 140 | }) |
| 141 | .click(function (e) { |
| 142 | e.preventDefault(); |
| 143 | e.stopPropagation(); |
| 144 | thisView.setActiveEl($(this).data('el')); |
| 145 | }); |
| 146 | } |
| 147 | |
| 148 | // Scroll all the way left... |
| 149 | $h.scrollLeft( 99999 ); |
| 150 | |
| 151 | // Now lets add all the CSS selectors |
| 152 | var selectors = this.pageSelectors.filter( function(a){ |
| 153 | // Use try to catch any malformed selectors |
| 154 | try { |
| 155 | return el.is( a.selector ); |
| 156 | } |
| 157 | catch(err) { |
| 158 | return false; |
| 159 | } |
| 160 | } ); |
| 161 | |
| 162 | var container = this.$('.socss-selectors-window').empty(); |
| 163 | |
| 164 | _.each( selectors, function(selector){ |
| 165 | container.append( |
| 166 | $( thisView.selectorTemplate(selector) ) |
| 167 | .data( selector ) |
| 168 | ); |
| 169 | } ); |
| 170 | container.find('> div') |
| 171 | .mouseenter( function(){ |
| 172 | thisView.hl.highlight( $(this).data('selector') ); |
| 173 | } ) |
| 174 | .click( function(e){ |
| 175 | e.preventDefault(); |
| 176 | e.stopPropagation(); |
| 177 | |
| 178 | thisView.trigger( 'click_selector', $(this).data('selector') ); |
| 179 | } ); |
| 180 | |
| 181 | // And the CSS attributes |
| 182 | var attributes = socss.fn.elementAttributes(el); |
| 183 | container = this.$('.socss-properties-window').empty(); |
| 184 | |
| 185 | _.each( attributes, function(v, k){ |
| 186 | container.append( |
| 187 | $( thisView.selectorTemplate( { selector: '<strong>' + k + '</strong>: ' + v } ) ) |
| 188 | .data( 'property', k + ': ' + v ) |
| 189 | ); |
| 190 | } ); |
| 191 | |
| 192 | container.find('> div') |
| 193 | .click( function(e){ |
| 194 | e.preventDefault(); |
| 195 | e.stopPropagation(); |
| 196 | |
| 197 | thisView.trigger( 'click_property', $(this).data('property') ); |
| 198 | }); |
| 199 | |
| 200 | // Display the link |
| 201 | var link = el.closest('a[href]'); |
| 202 | var linkContainer = this.$('.socss-link'); |
| 203 | if( link.length ) { |
| 204 | linkContainer.show().find('a') |
| 205 | .html( link.attr('href').replace(/[\?&]*so_css_preview=1/, '') ) |
| 206 | .attr('href', link.attr('href') ); |
| 207 | } |
| 208 | else { |
| 209 | linkContainer.hide(); |
| 210 | } |
| 211 | |
| 212 | this.trigger('set_active_element', el, selectors); |
| 213 | } |
| 214 | |
| 215 | } ); |
| 216 | |
| 217 | socss.view.highlighter = Backbone.View.extend( { |
| 218 | template: _.template( $('#socss-template-hover').html().trim() ), |
| 219 | highlighted: [ ], |
| 220 | |
| 221 | highlight: function( els ){ |
| 222 | this.clear(); |
| 223 | var thisView = this; |
| 224 | |
| 225 | $(els).each(function(i, el){ |
| 226 | el = $(el); |
| 227 | |
| 228 | if( !el.is(':visible') ) { |
| 229 | // Skip over invisible elements |
| 230 | return true; |
| 231 | } |
| 232 | |
| 233 | var hl = $( thisView.template() ); |
| 234 | hl.css({ |
| 235 | 'top' : el.offset().top, |
| 236 | 'left' : el.offset().left, |
| 237 | 'width' : el.outerWidth(), |
| 238 | 'height' : el.outerHeight() |
| 239 | }).appendTo( 'body' ); |
| 240 | |
| 241 | var g; |
| 242 | |
| 243 | var padding = el.padding(); |
| 244 | for( var k in padding ) { |
| 245 | if( parseInt( padding[k] ) > 0 ) { |
| 246 | g = hl.find( '.socss-guide-padding.socss-guide-' + k ).show(); |
| 247 | if( k === 'top' || k === 'bottom' ) { |
| 248 | g.css('height', padding[k]); |
| 249 | } |
| 250 | else { |
| 251 | g.css('width', padding[k]); |
| 252 | g.css({ |
| 253 | 'width': padding[k], |
| 254 | 'top' : padding.top, |
| 255 | 'bottom' : padding.bottom |
| 256 | }); |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | var margin = el.margin(); |
| 262 | for( var k in margin ) { |
| 263 | if( parseInt( margin[k] ) > 0 ) { |
| 264 | g = hl.find( '.socss-guide-margin.socss-guide-' + k ).show(); |
| 265 | if( k === 'top' || k === 'bottom' ) { |
| 266 | g.css('height', margin[k]); |
| 267 | } |
| 268 | else { |
| 269 | g.css('width', margin[k]); |
| 270 | } |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | thisView.highlighted.push( hl ); |
| 275 | } ); |
| 276 | }, |
| 277 | |
| 278 | clear: function(){ |
| 279 | while( this.highlighted.length ) { |
| 280 | this.highlighted.pop().remove(); |
| 281 | } |
| 282 | } |
| 283 | } ); |
| 284 | |
| 285 | socss.parsedCss = {}; |
| 286 | socss.fn.getParsedCss = function(){ |
| 287 | // Load all the parsed CSS |
| 288 | if( Object.keys(socss.parsedCss).length === 0 ) { |
| 289 | var parser = new cssjs(); |
| 290 | $('.socss-theme-styles').each(function(){ |
| 291 | var $$ = $(this); |
| 292 | var p = parser.parseCSS( $$.html() ); |
| 293 | socss.parsedCss[ $$.attr('id') ] = p; |
| 294 | }); |
| 295 | } |
| 296 | return socss.parsedCss; |
| 297 | }; |
| 298 | |
| 299 | /** |
| 300 | * Function to get all the available page selectors |
| 301 | */ |
| 302 | socss.fn.pageSelectors = function(){ |
| 303 | var selectors = []; |
| 304 | var parsedCss = socss.fn.getParsedCss(); |
| 305 | |
| 306 | for( var k in parsedCss ) { |
| 307 | for( var i = 0; i < parsedCss[k].length; i++ ) { |
| 308 | if (typeof parsedCss[k][i].selector === 'undefined') { |
| 309 | continue; |
| 310 | } |
| 311 | |
| 312 | var ruleSpecificity = SPECIFICITY.calculate( parsedCss[k][i].selector ); |
| 313 | for (var j = 0; j < ruleSpecificity.length; j++) { |
| 314 | selectors.push({ |
| 315 | 'selector': ruleSpecificity[j].selector.trim(), |
| 316 | 'specificity': parseInt(ruleSpecificity[j].specificity.replace(/,/g, '')) |
| 317 | }); |
| 318 | } |
| 319 | |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | // Also add selectors for all the elements in the |
| 324 | $('body *').each(function(){ |
| 325 | var $$ = $(this); |
| 326 | var elName = socss.fn.elSelector( $$ ); |
| 327 | var ruleSpecificity = SPECIFICITY.calculate( elName ); |
| 328 | for (var k = 0; k < ruleSpecificity.length; k++) { |
| 329 | selectors.push({ |
| 330 | 'selector': ruleSpecificity[k].selector.trim(), |
| 331 | 'specificity': parseInt(ruleSpecificity[k].specificity.replace(/,/g, '')) |
| 332 | }); |
| 333 | } |
| 334 | }); |
| 335 | |
| 336 | selectors = _.uniq( selectors, false, function( a ){ |
| 337 | return a.selector; |
| 338 | } ); |
| 339 | |
| 340 | selectors.sort(function(a, b){ |
| 341 | return a.specificity > b.specificity ? -1 : 1; |
| 342 | }); |
| 343 | |
| 344 | return selectors; |
| 345 | }; |
| 346 | |
| 347 | socss.fn.elementAttributes = function( el ) { |
| 348 | if( !document.styleSheets ) { |
| 349 | return []; |
| 350 | } |
| 351 | |
| 352 | var elProperties = []; |
| 353 | |
| 354 | var trimFunc = function(e) { |
| 355 | return e.trim(); |
| 356 | }; |
| 357 | |
| 358 | var filterFunc = function(e){ |
| 359 | return e !== ''; |
| 360 | }; |
| 361 | |
| 362 | var splitFunc = function(e) { |
| 363 | return e.split(':').map( trimFunc ); |
| 364 | }; |
| 365 | |
| 366 | |
| 367 | var parsedCss = socss.fn.getParsedCss(); |
| 368 | |
| 369 | for( var k in parsedCss ) { |
| 370 | for( var i = 0; i < parsedCss[k].length; i++ ) { |
| 371 | if ( |
| 372 | typeof parsedCss[k][i].selector === 'undefined' || |
| 373 | typeof parsedCss[k][i].type !== 'undefined' || |
| 374 | parsedCss[k][i].selector[0] === '@' |
| 375 | ) { |
| 376 | continue; |
| 377 | } |
| 378 | |
| 379 | var ruleSpecificity = SPECIFICITY.calculate( parsedCss[k][i].selector ); |
| 380 | for (var j = 0; j < ruleSpecificity.length; j++) { |
| 381 | try { |
| 382 | if( el.is( ruleSpecificity[j].selector ) ) { |
| 383 | for( var l = 0; l < parsedCss[k][i].rules.length; l++ ) { |
| 384 | elProperties.push({ |
| 385 | 'name' : parsedCss[k][i].rules[l].directive, |
| 386 | 'value' : parsedCss[k][i].rules[l].value, |
| 387 | 'specificity' : parseInt(ruleSpecificity[j].specificity.replace(/,/g, '')) |
| 388 | }); |
| 389 | } |
| 390 | } |
| 391 | } |
| 392 | catch( e ) { |
| 393 | // For now, we're just going to ignore rules that trigger jQuery errors |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | elProperties.sort( function(a,b) { |
| 401 | return a.specificity > b.specificity ? 1 : -1; |
| 402 | }).reverse(); |
| 403 | |
| 404 | var returnProperties = {}; |
| 405 | for( var pi = 0; pi < elProperties.length; pi++ ) { |
| 406 | if( typeof returnProperties[elProperties[pi].name] === 'undefined' ) { |
| 407 | returnProperties[elProperties[pi].name] = elProperties[pi].value; |
| 408 | } |
| 409 | } |
| 410 | |
| 411 | return returnProperties; |
| 412 | }; |
| 413 | |
| 414 | socss.fn.elSelector = function( el ){ |
| 415 | var elName = ''; |
| 416 | if( el.attr('id') !== undefined ) { |
| 417 | elName += '#' + el.attr('id'); |
| 418 | } |
| 419 | if( el.attr('class') !== undefined ) { |
| 420 | elName += '.' + el.attr('class').replace(/\s+/, '.'); |
| 421 | } |
| 422 | |
| 423 | if( elName === '' ) { |
| 424 | elName = el.prop('tagName').toLowerCase(); |
| 425 | } |
| 426 | |
| 427 | return elName; |
| 428 | }; |
| 429 | |
| 430 | window.socssInspector = socss; |
| 431 | |
| 432 | } ) ( jQuery, _, socssOptions ); |
| 433 | |
| 434 | jQuery( function($){ |
| 435 | var socss = window.socssInspector; |
| 436 | |
| 437 | // Setup the editor |
| 438 | var inspector = new socss.view.inspector( { |
| 439 | el : $('#socss-inspector-interface').get(0) |
| 440 | } ); |
| 441 | inspector.activate(); |
| 442 | |
| 443 | window.socssInspector.mainInspector = inspector; |
| 444 | } ); |