css.js
10 years ago
css.min.js
10 years ago
csslint.js
11 years ago
csslint.min.js
10 years ago
editor.js
11 years ago
editor.min.js
10 years ago
inspector.js
11 years ago
inspector.min.js
10 years ago
jquery.sizes.js
11 years ago
jquery.sizes.min.js
10 years ago
specificity.js
11 years ago
specificity.min.js
10 years ago
editor.js
1527 lines
| 1 | /* globals jQuery, _, socssOptions, Backbone, CodeMirror, console, cssjs, wp */ |
| 2 | |
| 3 | (function ($, _, socssOptions) { |
| 4 | |
| 5 | var socss = { |
| 6 | model: {}, |
| 7 | collection: {}, |
| 8 | view: {}, |
| 9 | fn: {} |
| 10 | }; |
| 11 | |
| 12 | window.socss = socss; |
| 13 | |
| 14 | /** |
| 15 | * The toolbar view |
| 16 | */ |
| 17 | socss.view.toolbar = Backbone.View.extend({ |
| 18 | |
| 19 | button: _.template('<li><a href="#" class="toolbar-button socss-button"><%= text %></a></li>'), |
| 20 | |
| 21 | editor: null, |
| 22 | |
| 23 | initialize: function ( attr ) { |
| 24 | this.editor = attr.editor; |
| 25 | |
| 26 | var thisView = this; |
| 27 | this.$('.editor-expand').click(function (e) { |
| 28 | e.preventDefault(); |
| 29 | $(this).blur(); |
| 30 | thisView.trigger('click_expand'); |
| 31 | }); |
| 32 | |
| 33 | this.$('.editor-visual').click(function (e) { |
| 34 | e.preventDefault(); |
| 35 | $(this).blur(); |
| 36 | thisView.trigger('click_visual'); |
| 37 | }); |
| 38 | }, |
| 39 | |
| 40 | addButton: function (text, action) { |
| 41 | var thisView = this; |
| 42 | var button = $(this.button({text: text})) |
| 43 | .appendTo(this.$('.toolbar-function-buttons .toolbar-buttons')) |
| 44 | .click(function (e) { |
| 45 | e.preventDefault(); |
| 46 | $(this).blur(); |
| 47 | thisView.trigger('click_' + action); |
| 48 | }); |
| 49 | |
| 50 | return button; |
| 51 | } |
| 52 | }); |
| 53 | |
| 54 | /** |
| 55 | * The editor view, which handles codemirror stuff |
| 56 | */ |
| 57 | socss.view.editor = Backbone.View.extend({ |
| 58 | |
| 59 | codeMirror: null, |
| 60 | snippets: null, |
| 61 | toolbar: null, |
| 62 | visualProperties: null, |
| 63 | |
| 64 | inspector: null, |
| 65 | |
| 66 | cssSelectors: [], |
| 67 | |
| 68 | initialize: function (args) { |
| 69 | this.setupEditor(); |
| 70 | }, |
| 71 | |
| 72 | render: function () { |
| 73 | var thisView = this; |
| 74 | |
| 75 | // Setup the toolbar |
| 76 | this.toolbar = new socss.view.toolbar({ |
| 77 | editor: this, |
| 78 | el: this.$('.custom-css-toolbar') |
| 79 | }); |
| 80 | this.toolbar.editor = this; |
| 81 | this.toolbar.render(); |
| 82 | |
| 83 | // Create the visual properties view |
| 84 | this.visualProperties = new socss.view.properties({ |
| 85 | editor: this, |
| 86 | el: $('#so-custom-css-properties') |
| 87 | }); |
| 88 | this.visualProperties.render(); |
| 89 | |
| 90 | this.toolbar.on('click_expand', function () { |
| 91 | thisView.toggleExpand(); |
| 92 | }); |
| 93 | |
| 94 | this.toolbar.on('click_visual', function () { |
| 95 | thisView.visualProperties.loadCSS( thisView.codeMirror.getValue() ); |
| 96 | thisView.visualProperties.show(); |
| 97 | }); |
| 98 | |
| 99 | this.preview = new socss.view.preview({ |
| 100 | editor: this, |
| 101 | el: this.$('.custom-css-preview') |
| 102 | }); |
| 103 | this.preview.render(); |
| 104 | }, |
| 105 | |
| 106 | /** |
| 107 | * Do the initial setup of the CodeMirror editor |
| 108 | */ |
| 109 | setupEditor: function () { |
| 110 | var thisView = this; |
| 111 | this.registerCodeMirrorAutocomplete(); |
| 112 | |
| 113 | // Setup the Codemirror instance |
| 114 | this.codeMirror = CodeMirror.fromTextArea(this.$('textarea.css-editor').get(0), { |
| 115 | tabSize: 2, |
| 116 | mode: 'css', |
| 117 | theme: 'neat', |
| 118 | gutters: [ |
| 119 | "CodeMirror-lint-markers" |
| 120 | ], |
| 121 | lint: true |
| 122 | }); |
| 123 | |
| 124 | // Make sure the user doesn't leave without saving |
| 125 | var startCss = this.$('textarea.css-editor').val(); |
| 126 | this.$el.on('submit', function(){ |
| 127 | startCss = thisView.codeMirror.getValue(); |
| 128 | }); |
| 129 | $(window).bind('beforeunload', function(){ |
| 130 | if( thisView.codeMirror.getValue() !== startCss ) { |
| 131 | return socssOptions.loc.leave; |
| 132 | } |
| 133 | }); |
| 134 | |
| 135 | |
| 136 | // Set the container to visible overflow once the editor is setup |
| 137 | this.$el.find('.custom-css-container').css('overflow', 'visible'); |
| 138 | this.scaleEditor(); |
| 139 | |
| 140 | // Scale the editor whenever the window is resized |
| 141 | $(window).resize(function () { |
| 142 | thisView.scaleEditor(); |
| 143 | }); |
| 144 | |
| 145 | // Setup the extensions |
| 146 | this.setupCodeMirrorExtensions(); |
| 147 | }, |
| 148 | |
| 149 | /** |
| 150 | * Register the autocomplete helper. Based on css-hint.js in the codemirror addon folder. |
| 151 | */ |
| 152 | registerCodeMirrorAutocomplete: function () { |
| 153 | var thisView = this; |
| 154 | |
| 155 | var pseudoClasses = { |
| 156 | link: 1, visited: 1, active: 1, hover: 1, focus: 1, |
| 157 | "first-letter": 1, "first-line": 1, "first-child": 1, |
| 158 | before: 1, after: 1, lang: 1 |
| 159 | }; |
| 160 | |
| 161 | CodeMirror.registerHelper("hint", "css", function (cm) { |
| 162 | var cur = cm.getCursor(), token = cm.getTokenAt(cur); |
| 163 | var inner = CodeMirror.innerMode(cm.getMode(), token.state); |
| 164 | if (inner.mode.name !== "css") { |
| 165 | return; |
| 166 | } |
| 167 | |
| 168 | if (token.type === "keyword" && "!important".indexOf(token.string) === 0) { |
| 169 | return { |
| 170 | list: ["!important"], from: CodeMirror.Pos(cur.line, token.start), |
| 171 | to: CodeMirror.Pos(cur.line, token.end) |
| 172 | }; |
| 173 | } |
| 174 | |
| 175 | var start = token.start, end = cur.ch, word = token.string.slice(0, end - start); |
| 176 | if (/[^\w$_-]/.test(word)) { |
| 177 | word = ""; |
| 178 | start = end = cur.ch; |
| 179 | } |
| 180 | |
| 181 | var spec = CodeMirror.resolveMode("text/css"); |
| 182 | |
| 183 | var result = []; |
| 184 | |
| 185 | function add(keywords) { |
| 186 | for (var name in keywords) { |
| 187 | if (!word || name.lastIndexOf(word, 0) === 0) { |
| 188 | result.push(name); |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | var st = inner.state.state; |
| 194 | |
| 195 | if (st === 'top') { |
| 196 | // We're going to autocomplete the selector using our own set of rules |
| 197 | var line = cm.getLine(cur.line).trim(); |
| 198 | |
| 199 | var selectors = thisView.cssSelectors; |
| 200 | for (var i = 0; i < selectors.length; i++) { |
| 201 | if (selectors[i].selector.indexOf(line) !== -1) { |
| 202 | result.push(selectors[i].selector); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | if (result.length) { |
| 207 | return { |
| 208 | list: result, |
| 209 | from: CodeMirror.Pos(cur.line, 0), |
| 210 | to: CodeMirror.Pos(cur.line, end) |
| 211 | }; |
| 212 | } |
| 213 | } |
| 214 | else { |
| 215 | |
| 216 | if (st === "pseudo" || token.type === "variable-3") { |
| 217 | add(pseudoClasses); |
| 218 | } |
| 219 | else if (st === "block" || st === "maybeprop") { |
| 220 | add(spec.propertyKeywords); |
| 221 | } |
| 222 | else if (st === "prop" || st === "parens" || st === "at" || st === "params") { |
| 223 | add(spec.valueKeywords); |
| 224 | add(spec.colorKeywords); |
| 225 | } |
| 226 | else if (st === "media" || st === "media_parens") { |
| 227 | add(spec.mediaTypes); |
| 228 | add(spec.mediaFeatures); |
| 229 | } |
| 230 | |
| 231 | if (result.length) { |
| 232 | return { |
| 233 | list: result, |
| 234 | from: CodeMirror.Pos(cur.line, start), |
| 235 | to: CodeMirror.Pos(cur.line, end) |
| 236 | }; |
| 237 | } |
| 238 | |
| 239 | } |
| 240 | |
| 241 | }); |
| 242 | }, |
| 243 | |
| 244 | setupCodeMirrorExtensions: function () { |
| 245 | var thisView = this; |
| 246 | |
| 247 | this.codeMirror.on('cursorActivity', function (cm) { |
| 248 | var cur = cm.getCursor(), token = cm.getTokenAt(cur); |
| 249 | var inner = CodeMirror.innerMode(cm.getMode(), token.state); |
| 250 | |
| 251 | // If we have a qualifier selected, then highlight that in the preview |
| 252 | if (token.type === 'qualifier' || token.type === 'tag' || token.type === 'builtin') { |
| 253 | var line = cm.getLine(cur.line); |
| 254 | var selector = line.substring(0, token.end); |
| 255 | |
| 256 | thisView.preview.highlight(selector); |
| 257 | } |
| 258 | else { |
| 259 | thisView.preview.clearHighlight(); |
| 260 | } |
| 261 | }); |
| 262 | |
| 263 | // This sets up automatic autocompletion at all times |
| 264 | this.codeMirror.on('keyup', function (cm, e) { |
| 265 | if ( |
| 266 | ( e.keyCode >= 65 && e.keyCode <= 90 ) || |
| 267 | ( e.keyCode === 189 && !e.shiftKey ) || |
| 268 | ( e.keyCode === 190 && !e.shiftKey ) || |
| 269 | ( e.keyCode === 51 && e.shiftKey ) || |
| 270 | ( e.keyCode === 189 && e.shiftKey ) |
| 271 | ) { |
| 272 | cm.showHint(e); |
| 273 | } |
| 274 | }); |
| 275 | }, |
| 276 | |
| 277 | /** |
| 278 | * Scale the size of the editor depending on whether it's expanded or not |
| 279 | */ |
| 280 | scaleEditor: function () { |
| 281 | if (this.$el.hasClass('expanded')) { |
| 282 | // If we're in the expanded view, then resize the editor |
| 283 | this.codeMirror.setSize('100%', $(window).outerHeight() - this.$('.custom-css-toolbar').outerHeight()); |
| 284 | } |
| 285 | else { |
| 286 | this.codeMirror.setSize('100%', 'auto'); |
| 287 | } |
| 288 | }, |
| 289 | |
| 290 | /** |
| 291 | * Check if the editor is in expanded mode |
| 292 | * @returns bool |
| 293 | */ |
| 294 | isExpanded: function () { |
| 295 | return this.$el.hasClass('expanded'); |
| 296 | }, |
| 297 | |
| 298 | /** |
| 299 | * Toggle if this is expanded or not |
| 300 | */ |
| 301 | toggleExpand: function () { |
| 302 | this.$el.toggleClass('expanded'); |
| 303 | this.scaleEditor(); |
| 304 | }, |
| 305 | |
| 306 | /** |
| 307 | * Set the expanded state of the editor |
| 308 | * @param expanded |
| 309 | */ |
| 310 | setExpand: function (expanded) { |
| 311 | if (expanded) { |
| 312 | this.$el.addClass('expanded'); |
| 313 | } |
| 314 | else { |
| 315 | this.$el.removeClass('expanded'); |
| 316 | } |
| 317 | this.scaleEditor(); |
| 318 | }, |
| 319 | |
| 320 | /** |
| 321 | * Set the snippets available to this editor |
| 322 | */ |
| 323 | setSnippets: function (snippets) { |
| 324 | if (!_.isEmpty(snippets)) { |
| 325 | var thisView = this; |
| 326 | |
| 327 | this.snippets = new socss.view.snippets({ |
| 328 | snippets: snippets |
| 329 | }); |
| 330 | this.snippets.editor = this; |
| 331 | |
| 332 | this.snippets.render(); |
| 333 | this.toolbar.addButton('Snippets', 'snippets'); |
| 334 | this.toolbar.on('click_snippets', function () { |
| 335 | thisView.snippets.show(); |
| 336 | }); |
| 337 | } |
| 338 | }, |
| 339 | |
| 340 | /** |
| 341 | * Add some CSS to the editor. |
| 342 | * @param css |
| 343 | */ |
| 344 | addCode: function (css) { |
| 345 | var editor = this.codeMirror; |
| 346 | |
| 347 | var before_css = ''; |
| 348 | if (editor.doc.lineCount() === 1 && editor.doc.getLine(editor.doc.lastLine()).length === 0) { |
| 349 | before_css = ""; |
| 350 | } |
| 351 | else if (editor.doc.getLine(editor.doc.lastLine()).length === 0) { |
| 352 | before_css = "\n"; |
| 353 | } |
| 354 | else { |
| 355 | before_css = "\n\n"; |
| 356 | } |
| 357 | |
| 358 | // Now insert the code in the editor |
| 359 | editor.doc.setCursor( |
| 360 | editor.doc.lastLine(), |
| 361 | editor.doc.getLine(editor.doc.lastLine()).length |
| 362 | ); |
| 363 | editor.doc.replaceSelection(before_css + css); |
| 364 | }, |
| 365 | |
| 366 | addEmptySelector: function (selector) { |
| 367 | this.addCode(selector + " {\n \n}"); |
| 368 | }, |
| 369 | |
| 370 | /** |
| 371 | * Sets the inspector view that's being used by the editor |
| 372 | */ |
| 373 | setInspector: function (inspector) { |
| 374 | var thisView = this; |
| 375 | this.inspector = inspector; |
| 376 | this.cssSelectors = inspector.pageSelectors; |
| 377 | |
| 378 | // A selector is clicked in the inspector |
| 379 | inspector.on('click_selector', function (selector) { |
| 380 | if ( thisView.visualProperties.isVisible() ) { |
| 381 | thisView.visualProperties.addSelector(selector); |
| 382 | } |
| 383 | else { |
| 384 | thisView.addEmptySelector(selector); |
| 385 | } |
| 386 | }); |
| 387 | |
| 388 | // A property is clicked in the inspector |
| 389 | inspector.on('click_property', function (property) { |
| 390 | if ( ! thisView.visualProperties.isVisible() ) { |
| 391 | thisView.codeMirror.replaceSelection(property + ";\n "); |
| 392 | } |
| 393 | }); |
| 394 | |
| 395 | inspector.on('set_active_element', function(el, selectors){ |
| 396 | if ( thisView.visualProperties.isVisible() && selectors.length ) { |
| 397 | thisView.visualProperties.addSelector( selectors[0].selector ); |
| 398 | } |
| 399 | }); |
| 400 | } |
| 401 | |
| 402 | }); |
| 403 | |
| 404 | /** |
| 405 | * The preview. |
| 406 | */ |
| 407 | socss.view.preview = Backbone.View.extend({ |
| 408 | |
| 409 | template: _.template('<iframe class="preview-iframe" seamless="seamless"></iframe>'), |
| 410 | editor: null, |
| 411 | |
| 412 | initialize: function (attr) { |
| 413 | this.editor = attr.editor; |
| 414 | |
| 415 | var thisView = this; |
| 416 | this.editor.codeMirror.on('change', function (cm, c) { |
| 417 | thisView.updatePreviewCss(); |
| 418 | }); |
| 419 | }, |
| 420 | |
| 421 | render: function () { |
| 422 | var thisView = this; |
| 423 | |
| 424 | this.$el.html(this.template()); |
| 425 | |
| 426 | this.$('.preview-iframe') |
| 427 | .attr('src', socssOptions.homeURL) |
| 428 | .load(function () { |
| 429 | var $$ = $(this); |
| 430 | $$.contents().find('a').each(function () { |
| 431 | var href = $(this).attr('href'); |
| 432 | if (href === undefined) { |
| 433 | return true; |
| 434 | } |
| 435 | |
| 436 | var firstSeperator = (href.indexOf('?') === -1 ? '?' : '&'); |
| 437 | $(this).attr('href', href + firstSeperator + 'so_css_preview=1'); |
| 438 | }); |
| 439 | |
| 440 | thisView.updatePreviewCss(); |
| 441 | }) |
| 442 | .mouseleave(function () { |
| 443 | thisView.clearHighlight(); |
| 444 | }); |
| 445 | }, |
| 446 | |
| 447 | /** |
| 448 | * Update the preview CSS from the CodeMirror value in the editor |
| 449 | */ |
| 450 | updatePreviewCss: function () { |
| 451 | var preview = this.$('.preview-iframe'); |
| 452 | if (preview.length === 0) { |
| 453 | return; |
| 454 | } |
| 455 | |
| 456 | var head = preview.contents().find('head'); |
| 457 | if (head.find('style.siteorigin-custom-css').length === 0) { |
| 458 | head.append('<style class="siteorigin-custom-css" type="text/css"></style>'); |
| 459 | } |
| 460 | var style = head.find('style.siteorigin-custom-css'); |
| 461 | |
| 462 | // Update the CSS after a short delay |
| 463 | var css = this.editor.codeMirror.getValue(); |
| 464 | style.html(css); |
| 465 | }, |
| 466 | |
| 467 | /** |
| 468 | * Highlight all elements with a given selector |
| 469 | */ |
| 470 | highlight: function (selector) { |
| 471 | try { |
| 472 | this.editor.inspector.hl.highlight(selector); |
| 473 | } |
| 474 | catch (err) { |
| 475 | console.log('No inspector to highlight with'); |
| 476 | } |
| 477 | }, |
| 478 | |
| 479 | /** |
| 480 | * Clear the currently highlighted elements in preview |
| 481 | */ |
| 482 | clearHighlight: function () { |
| 483 | try { |
| 484 | this.editor.inspector.hl.clear(); |
| 485 | } |
| 486 | catch (err) { |
| 487 | console.log('No inspector to highlight with'); |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | }); |
| 492 | |
| 493 | /** |
| 494 | * The dialog for the snippets browser |
| 495 | */ |
| 496 | socss.view.snippets = Backbone.View.extend({ |
| 497 | template: _.template($('#template-snippet-browser').html()), |
| 498 | snippet: _.template('<li class="snippet"><%- name %></li>'), |
| 499 | className: 'css-editor-snippet-browser', |
| 500 | snippets: null, |
| 501 | editor: null, |
| 502 | |
| 503 | events: { |
| 504 | 'click .close': 'hide', |
| 505 | 'click .buttons .insert-snippet': 'insertSnippet' |
| 506 | }, |
| 507 | |
| 508 | currentSnippet: null, |
| 509 | |
| 510 | initialize: function (args) { |
| 511 | this.snippets = args.snippets; |
| 512 | }, |
| 513 | |
| 514 | render: function () { |
| 515 | var thisView = this; |
| 516 | |
| 517 | |
| 518 | var clickSnippet = function (e) { |
| 519 | e.preventDefault(); |
| 520 | var $$ = $(this); |
| 521 | |
| 522 | thisView.$('.snippets li.snippet').removeClass('active'); |
| 523 | $(this).addClass('active'); |
| 524 | thisView.viewSnippet({ |
| 525 | name: $$.html(), |
| 526 | description: $$.data('description'), |
| 527 | css: $$.data('css') |
| 528 | }); |
| 529 | }; |
| 530 | |
| 531 | this.$el.html(this.template()); |
| 532 | for (var i = 0; i < this.snippets.length; i++) { |
| 533 | $(this.snippet({name: this.snippets[i].Name})) |
| 534 | .data({ |
| 535 | 'description': this.snippets[i].Description, |
| 536 | 'css': this.snippets[i].css |
| 537 | }) |
| 538 | .appendTo(this.$('ul.snippets')) |
| 539 | .click(clickSnippet); |
| 540 | } |
| 541 | |
| 542 | // Click on the first one |
| 543 | thisView.$('.snippets li.snippet').eq(0).click(); |
| 544 | |
| 545 | this.attach(); |
| 546 | return this; |
| 547 | }, |
| 548 | |
| 549 | viewSnippet: function (args) { |
| 550 | var w = this.$('.main .snippet-view'); |
| 551 | |
| 552 | w.find('.snippet-title').html(args.name); |
| 553 | w.find('.snippet-description').html(args.description); |
| 554 | w.find('.snippet-code').html(args.css); |
| 555 | |
| 556 | this.currentSnippet = args; |
| 557 | }, |
| 558 | |
| 559 | insertSnippet: function () { |
| 560 | var editor = this.editor.codeMirror; |
| 561 | var css = this.currentSnippet.css; |
| 562 | |
| 563 | var before_css = ''; |
| 564 | if (editor.doc.lineCount() === 1 && editor.doc.getLine(editor.doc.lastLine()).length === 0) { |
| 565 | before_css = ""; |
| 566 | } |
| 567 | else if (editor.doc.getLine(editor.doc.lastLine()).length === 0) { |
| 568 | before_css = "\n"; |
| 569 | } |
| 570 | else { |
| 571 | before_css = "\n\n"; |
| 572 | } |
| 573 | |
| 574 | // Now insert the code in the editor |
| 575 | editor.doc.setCursor( |
| 576 | editor.doc.lastLine(), |
| 577 | editor.doc.getLine(editor.doc.lastLine()).length |
| 578 | ); |
| 579 | editor.doc.replaceSelection(before_css + css); |
| 580 | |
| 581 | this.hide(); |
| 582 | }, |
| 583 | |
| 584 | attach: function () { |
| 585 | this.$el.appendTo('body'); |
| 586 | }, |
| 587 | |
| 588 | show: function () { |
| 589 | this.$el.show(); |
| 590 | }, |
| 591 | |
| 592 | hide: function () { |
| 593 | this.$el.hide(); |
| 594 | } |
| 595 | }); |
| 596 | |
| 597 | |
| 598 | /** |
| 599 | * The visual properties editor |
| 600 | */ |
| 601 | socss.view.properties = Backbone.View.extend({ |
| 602 | |
| 603 | model: socss.model.cssRules, |
| 604 | |
| 605 | tabTemplate: _.template('<li data-section="<%- id %>"><span class="fa fa-<%- icon %>"></span> <%- title %></li>'), |
| 606 | sectionTemplate: _.template('<div class="section" data-section="<%- id %>"><table class="fields-table"><tbody></tbody></table></div>'), |
| 607 | controllerTemplate: _.template('<tr><th scope="row"><%- title %></th><td></td></tr>'), |
| 608 | |
| 609 | /** |
| 610 | * The controllers for each of the properties |
| 611 | */ |
| 612 | propertyControllers: [], |
| 613 | |
| 614 | /** |
| 615 | * The editor view |
| 616 | */ |
| 617 | editor: null, |
| 618 | |
| 619 | /** |
| 620 | * The current, raw CSS |
| 621 | */ |
| 622 | css: '', |
| 623 | |
| 624 | /** |
| 625 | * Parsed CSS |
| 626 | */ |
| 627 | parsed: {}, |
| 628 | |
| 629 | /** |
| 630 | * The current active selector |
| 631 | */ |
| 632 | activeSelector: '', |
| 633 | |
| 634 | /** |
| 635 | * Was the editor expanded before we went into the property editor |
| 636 | */ |
| 637 | editorExpandedBefore: false, |
| 638 | |
| 639 | events: { |
| 640 | 'click .close': 'hide' |
| 641 | }, |
| 642 | |
| 643 | /** |
| 644 | * Initialize the properties editor with a new model |
| 645 | */ |
| 646 | initialize: function ( attr ) { |
| 647 | this.parser = new cssjs(); |
| 648 | this.editor = attr.editor; |
| 649 | }, |
| 650 | |
| 651 | /** |
| 652 | * Render the property editor |
| 653 | */ |
| 654 | render: function () { |
| 655 | var thisView = this; |
| 656 | |
| 657 | var controllers = socssOptions.propertyControllers; |
| 658 | |
| 659 | for (var id in controllers) { |
| 660 | // Create the tabs |
| 661 | var $t = $(this.tabTemplate({ |
| 662 | id: id, |
| 663 | icon: controllers[id].icon, |
| 664 | title: controllers[id].title |
| 665 | })).appendTo(this.$('.section-tabs')); |
| 666 | |
| 667 | // Create the section wrapper |
| 668 | var $s = $(this.sectionTemplate({ |
| 669 | id: id |
| 670 | })).appendTo(this.$('.sections')); |
| 671 | |
| 672 | // Now lets add the controllers |
| 673 | if (!_.isEmpty(controllers[id].controllers)) { |
| 674 | |
| 675 | for (var i = 0; i < controllers[id].controllers.length; i++) { |
| 676 | |
| 677 | var $c = $(thisView.controllerTemplate({ |
| 678 | title: controllers[id].controllers[i].title |
| 679 | })).appendTo($s.find('tbody')); |
| 680 | |
| 681 | var controllerAtts = controllers[id].controllers[i]; |
| 682 | var controller; |
| 683 | |
| 684 | if (typeof socss.view.properties.controllers[controllerAtts.type] === 'undefined') { |
| 685 | // Setup a default controller |
| 686 | controller = new socss.view.propertyController({ |
| 687 | el: $c.find('td'), |
| 688 | propertiesView: thisView, |
| 689 | args: ( typeof controllerAtts.args === 'undefined' ? {} : controllerAtts.args ) |
| 690 | }); |
| 691 | } |
| 692 | else { |
| 693 | // Setup a specific controller |
| 694 | controller = new socss.view.properties.controllers[controllerAtts.type]({ |
| 695 | el: $c.find('td'), |
| 696 | propertiesView: thisView, |
| 697 | args: ( typeof controllerAtts.args === 'undefined' ? {} : controllerAtts.args ) |
| 698 | }); |
| 699 | } |
| 700 | |
| 701 | thisView.propertyControllers.push(controller); |
| 702 | |
| 703 | // Setup and render the controller |
| 704 | controller.render(); |
| 705 | controller.initChangeEvents(); |
| 706 | } |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | // Setup the tab switching for the property sections |
| 711 | this.$('.section-tabs li').click(function () { |
| 712 | var $$ = $(this); |
| 713 | var show = thisView.$('.sections .section[data-section="' + $$.data('section') + '"]'); |
| 714 | |
| 715 | thisView.$('.sections .section').not(show).hide().removeClass('active'); |
| 716 | show.show().addClass('active'); |
| 717 | |
| 718 | thisView.$('.section-tabs li').not($$).removeClass('active'); |
| 719 | $$.addClass('active'); |
| 720 | }).eq(0).click(); |
| 721 | |
| 722 | this.$('.toolbar select').change(function () { |
| 723 | thisView.setActivateSelector($(this).find(':selected').data('selector')); |
| 724 | }); |
| 725 | }, |
| 726 | |
| 727 | /** |
| 728 | * Sets the rule value for the active selector |
| 729 | * @param rule |
| 730 | * @param value |
| 731 | */ |
| 732 | setRuleValue: function (rule, value) { |
| 733 | if (typeof this.activeSelector === 'undefined' || typeof this.activeSelector.rules === 'undefined') { |
| 734 | return; |
| 735 | } |
| 736 | |
| 737 | var newRule = true; |
| 738 | for (var i = 0; i < this.activeSelector.rules.length; i++) { |
| 739 | if (this.activeSelector.rules[i].directive === rule) { |
| 740 | this.activeSelector.rules[i].value = value; |
| 741 | newRule = false; |
| 742 | break; |
| 743 | } |
| 744 | } |
| 745 | |
| 746 | if (newRule) { |
| 747 | this.activeSelector.rules.push({ |
| 748 | directive: rule, |
| 749 | value: value |
| 750 | }); |
| 751 | } |
| 752 | |
| 753 | this.updateMainEditor( false ); |
| 754 | }, |
| 755 | |
| 756 | /** |
| 757 | * Get the rule value for the active selector |
| 758 | * @param rule |
| 759 | */ |
| 760 | getRuleValue: function (rule) { |
| 761 | if (typeof this.activeSelector === 'undefined' || typeof this.activeSelector.rules === 'undefined') { |
| 762 | return ''; |
| 763 | } |
| 764 | |
| 765 | for (var i = 0; i < this.activeSelector.rules.length; i++) { |
| 766 | if (this.activeSelector.rules[i].directive === rule) { |
| 767 | return this.activeSelector.rules[i].value; |
| 768 | } |
| 769 | } |
| 770 | return ''; |
| 771 | }, |
| 772 | |
| 773 | /** |
| 774 | * Update the main editor with the value of the parsed CSS |
| 775 | */ |
| 776 | updateMainEditor: function ( compress ) { |
| 777 | var css; |
| 778 | if( typeof compress === 'undefined' || compress === true ) { |
| 779 | css = this.parser.compressCSS( this.parsed ); |
| 780 | // Also remove any empty selectors |
| 781 | css = css.filter( function(v){ |
| 782 | return ( |
| 783 | typeof v.type !== 'undefined' || |
| 784 | v.rules.length > 0 |
| 785 | ); |
| 786 | } ); |
| 787 | } |
| 788 | else { |
| 789 | css = this.parsed; |
| 790 | } |
| 791 | |
| 792 | this.editor.codeMirror.setValue( this.parser.getCSSForEditor( css ).trim() ); |
| 793 | }, |
| 794 | |
| 795 | /** |
| 796 | * Show the properties editor |
| 797 | */ |
| 798 | show: function () { |
| 799 | this.editorExpandedBefore = this.editor.isExpanded(); |
| 800 | this.editor.setExpand(true); |
| 801 | |
| 802 | this.$el.show().animate({'left': 0}, 'fast'); |
| 803 | }, |
| 804 | |
| 805 | /** |
| 806 | * Hide the properties editor |
| 807 | */ |
| 808 | hide: function () { |
| 809 | this.editor.setExpand(this.editorExpandedBefore); |
| 810 | this.$el.animate( {'left': -338}, 'fast', function(){ |
| 811 | $(this).hide(); |
| 812 | } ); |
| 813 | |
| 814 | // Update the main editor with compressed CSS when we close the properties editor |
| 815 | this.updateMainEditor( true ); |
| 816 | }, |
| 817 | |
| 818 | /** |
| 819 | * @returns boolean |
| 820 | */ |
| 821 | isVisible: function () { |
| 822 | return this.$el.is(':visible'); |
| 823 | }, |
| 824 | |
| 825 | /** |
| 826 | * Loads a single CSS selector and associated properties into the model |
| 827 | * @param css |
| 828 | */ |
| 829 | loadCSS: function (css, activeSelector) { |
| 830 | this.css = css; |
| 831 | |
| 832 | // Load the CSS and combine rules |
| 833 | this.parsed = this.parser.compressCSS( this.parser.parseCSS(css) ); |
| 834 | |
| 835 | // Add the dropdown menu items |
| 836 | var dropdown = this.$('.toolbar select').empty(); |
| 837 | for (var i = 0; i < this.parsed.length; i++) { |
| 838 | var rule = this.parsed[i]; |
| 839 | |
| 840 | if( typeof rule.subStyles !== 'undefined' ) { |
| 841 | |
| 842 | for (var j = 0; j < rule.subStyles.length; j++) { |
| 843 | var subRule = rule.subStyles[j]; |
| 844 | dropdown.append( |
| 845 | $('<option>') |
| 846 | .html( rule.selector + ': ' + subRule.selector ) |
| 847 | .attr( 'val', rule.selector + ': ' + subRule.selector ) |
| 848 | .data( 'selector', subRule ) |
| 849 | ); |
| 850 | } |
| 851 | |
| 852 | } |
| 853 | else { |
| 854 | dropdown.append( |
| 855 | $('<option>') |
| 856 | .html( rule.selector ) |
| 857 | .attr( 'val', rule.selector ) |
| 858 | .data( 'selector', rule ) |
| 859 | ); |
| 860 | } |
| 861 | } |
| 862 | |
| 863 | if (typeof activeSelector === 'undefined') { |
| 864 | activeSelector = dropdown.find('option').eq(0).attr('val'); |
| 865 | } |
| 866 | dropdown.val(activeSelector).change(); |
| 867 | }, |
| 868 | |
| 869 | /** |
| 870 | * Set the selector that we're currently dealing with |
| 871 | * @param selector |
| 872 | */ |
| 873 | setActivateSelector: function (selector) { |
| 874 | this.activeSelector = selector; |
| 875 | for (var i = 0; i < this.propertyControllers.length; i++) { |
| 876 | this.propertyControllers[i].refreshFromRule(); |
| 877 | } |
| 878 | }, |
| 879 | |
| 880 | /** |
| 881 | * Add or select a selector. |
| 882 | * |
| 883 | * @param selector |
| 884 | */ |
| 885 | addSelector: function(selector) { |
| 886 | // Check if this selector already exists |
| 887 | var dropdown = this.$('.toolbar select'); |
| 888 | dropdown.val( selector ); |
| 889 | |
| 890 | if( dropdown.val() === selector ) { |
| 891 | // Trigger a change event to load the existing selector |
| 892 | dropdown.change(); |
| 893 | } |
| 894 | else { |
| 895 | // The selector doesn't exist, so add it to the CSS, then reload |
| 896 | this.editor.addEmptySelector(selector); |
| 897 | this.loadCSS( this.editor.codeMirror.getValue(), selector ); |
| 898 | } |
| 899 | |
| 900 | dropdown.addClass('highlighted'); |
| 901 | setTimeout(function(){ |
| 902 | dropdown.removeClass('highlighted'); |
| 903 | }, 2000); |
| 904 | } |
| 905 | |
| 906 | }); |
| 907 | |
| 908 | // The basic property controller |
| 909 | socss.view.propertyController = Backbone.View.extend({ |
| 910 | |
| 911 | template: _.template('<input type="text" value="" />'), |
| 912 | activeRule: null, |
| 913 | args: null, |
| 914 | propertiesView: null, |
| 915 | |
| 916 | initialize: function (args) { |
| 917 | |
| 918 | this.args = args.args; |
| 919 | this.propertiesView = args.propertiesView; |
| 920 | |
| 921 | // By default, update the active rule whenever things change |
| 922 | this.on('set_value', this.updateRule, this); |
| 923 | this.on('change', this.updateRule, this); |
| 924 | }, |
| 925 | |
| 926 | /** |
| 927 | * Render the property field controller |
| 928 | */ |
| 929 | render: function () { |
| 930 | this.$el.append( $(this.template( {} )) ); |
| 931 | this.field = this.$('input'); |
| 932 | }, |
| 933 | |
| 934 | /** |
| 935 | * Initialize the events that constitute a change |
| 936 | */ |
| 937 | initChangeEvents: function () { |
| 938 | var thisView = this; |
| 939 | this.field.on( 'change keyup', function () { |
| 940 | thisView.trigger('change', $(this).val()); |
| 941 | } ); |
| 942 | }, |
| 943 | |
| 944 | |
| 945 | /** |
| 946 | * Update the value of an active rule |
| 947 | */ |
| 948 | updateRule: function () { |
| 949 | this.propertiesView.setRuleValue( |
| 950 | this.args.property, |
| 951 | this.getValue() |
| 952 | ); |
| 953 | }, |
| 954 | |
| 955 | /** |
| 956 | * This is called when the selector changes |
| 957 | */ |
| 958 | refreshFromRule: function () { |
| 959 | var value = this.propertiesView.getRuleValue(this.args.property); |
| 960 | this.setValue(value, {silent: true}); |
| 961 | }, |
| 962 | |
| 963 | /** |
| 964 | * Get the current value |
| 965 | * @return string |
| 966 | */ |
| 967 | getValue: function () { |
| 968 | return this.field.val(); |
| 969 | }, |
| 970 | |
| 971 | /** |
| 972 | * Set the current value |
| 973 | * @param socss.view.properties val |
| 974 | */ |
| 975 | setValue: function (val, options) { |
| 976 | options = _.extend({silent: false}, options); |
| 977 | |
| 978 | this.field.val(val); |
| 979 | |
| 980 | if (!options.silent) { |
| 981 | this.trigger('set_value', val); |
| 982 | } |
| 983 | }, |
| 984 | |
| 985 | /** |
| 986 | * Reset the current value |
| 987 | */ |
| 988 | reset: function (options) { |
| 989 | options = _.extend({silent: false}, options); |
| 990 | |
| 991 | this.setValue('', options); |
| 992 | } |
| 993 | |
| 994 | }); |
| 995 | |
| 996 | // All the value controllers |
| 997 | socss.view.properties.controllers = {}; |
| 998 | |
| 999 | // The color controller |
| 1000 | socss.view.properties.controllers.color = socss.view.propertyController.extend({ |
| 1001 | |
| 1002 | template: _.template('<input type="text" value="" />'), |
| 1003 | |
| 1004 | render: function () { |
| 1005 | var thisView = this; |
| 1006 | |
| 1007 | this.$el.append($(this.template({}))); |
| 1008 | |
| 1009 | // Set this up as a color picker |
| 1010 | this.field = this.$el.find('input'); |
| 1011 | this.field.minicolors({}); |
| 1012 | |
| 1013 | }, |
| 1014 | |
| 1015 | initChangeEvents: function () { |
| 1016 | var thisView = this; |
| 1017 | this.field.on('change keyup', function () { |
| 1018 | thisView.trigger('change', thisView.field.minicolors('value')); |
| 1019 | }); |
| 1020 | }, |
| 1021 | |
| 1022 | getValue: function () { |
| 1023 | return this.field.minicolors('value'); |
| 1024 | }, |
| 1025 | |
| 1026 | setValue: function (val, options) { |
| 1027 | options = _.extend({silent: false}, options); |
| 1028 | |
| 1029 | this.field.minicolors('value', val); |
| 1030 | |
| 1031 | if (!options.silent) { |
| 1032 | this.trigger('set_value', val); |
| 1033 | } |
| 1034 | } |
| 1035 | |
| 1036 | }); |
| 1037 | |
| 1038 | // The dropdown select box controller |
| 1039 | socss.view.properties.controllers.select = socss.view.propertyController.extend( { |
| 1040 | template: _.template('<select></select>'), |
| 1041 | |
| 1042 | render: function(){ |
| 1043 | var thisView = this; |
| 1044 | |
| 1045 | this.$el.append($(this.template({}))); |
| 1046 | this.field = this.$el.find('select'); |
| 1047 | |
| 1048 | // Add the unchanged option |
| 1049 | this.field.append( $('<option value=""></option>').html('') ); |
| 1050 | |
| 1051 | // Add all the options to the dropdown |
| 1052 | for( var k in this.args.options ) { |
| 1053 | this.field.append( $('<option></option>').attr('value', k).html( this.args.options[k] ) ); |
| 1054 | } |
| 1055 | |
| 1056 | if( typeof this.args.option_icons !== 'undefined' ) { |
| 1057 | this.setupVisualSelect(); |
| 1058 | } |
| 1059 | }, |
| 1060 | |
| 1061 | setupVisualSelect: function(){ |
| 1062 | var thisView = this; |
| 1063 | this.field.hide(); |
| 1064 | |
| 1065 | var $tc = $('<div class="select-tabs"></div>').appendTo( this.$el ); |
| 1066 | |
| 1067 | // Add the none value |
| 1068 | $('<div class="select-tab" data-value=""><span class="fa fa-circle-o"></span></div>').appendTo($tc); |
| 1069 | |
| 1070 | // Now add one for each of the option icons |
| 1071 | for ( var k in this.args.option_icons ) { |
| 1072 | $('<div class="select-tab"></div>') |
| 1073 | .appendTo($tc) |
| 1074 | .append( |
| 1075 | $('<span class="fa"></span>') |
| 1076 | .addClass('fa-' + this.args.option_icons[k]) |
| 1077 | ) |
| 1078 | .attr('data-value', k) |
| 1079 | ; |
| 1080 | } |
| 1081 | |
| 1082 | $tc.find('.select-tab') |
| 1083 | .css('width', 100/( $tc.find('>div').length ) + "%" ) |
| 1084 | .click( function(){ |
| 1085 | var $t = $(this); |
| 1086 | $tc.find('.select-tab').removeClass('active'); |
| 1087 | $t.addClass('active'); |
| 1088 | thisView.field.val( $t.data('value')).change(); |
| 1089 | } ); |
| 1090 | }, |
| 1091 | |
| 1092 | /** |
| 1093 | * Set the current value |
| 1094 | * @param socss.view.properties val |
| 1095 | */ |
| 1096 | setValue: function (val, options) { |
| 1097 | options = _.extend({silent: false}, options); |
| 1098 | |
| 1099 | this.field.val(val); |
| 1100 | |
| 1101 | this.$('.select-tabs .select-tab').removeClass('active').filter('[data-value="' + val + '"]').addClass('active'); |
| 1102 | |
| 1103 | if (!options.silent) { |
| 1104 | this.trigger('set_value', val); |
| 1105 | } |
| 1106 | } |
| 1107 | |
| 1108 | } ); |
| 1109 | |
| 1110 | // A field that lets a user upload an image |
| 1111 | socss.view.properties.controllers.image = socss.view.propertyController.extend( { |
| 1112 | template: _.template('<input type="text" value="" /> <span class="select socss-button"><span class="fa fa-upload"></span></span>'), |
| 1113 | |
| 1114 | render: function(){ |
| 1115 | var thisView = this; |
| 1116 | |
| 1117 | this.media = wp.media({ |
| 1118 | // Set the title of the modal. |
| 1119 | title: socssOptions.loc.select_image, |
| 1120 | |
| 1121 | // Tell the modal to show only images. |
| 1122 | library: { |
| 1123 | type: 'image' |
| 1124 | }, |
| 1125 | |
| 1126 | // Customize the submit button. |
| 1127 | button: { |
| 1128 | // Set the text of the button. |
| 1129 | text: socssOptions.loc.select, |
| 1130 | // Tell the button not to close the modal, since we're |
| 1131 | // going to refresh the page when the image is selected. |
| 1132 | close: false |
| 1133 | } |
| 1134 | }); |
| 1135 | |
| 1136 | this.$el.append( $(this.template({ |
| 1137 | select: socssOptions.loc.select |
| 1138 | })) ); |
| 1139 | |
| 1140 | this.field = this.$el.find('input'); |
| 1141 | |
| 1142 | this.$('.select').click(function(){ |
| 1143 | thisView.media.open(); |
| 1144 | }); |
| 1145 | |
| 1146 | this.media.on('select', function(){ |
| 1147 | // Grab the selected attachment. |
| 1148 | var attachment = this.state().get('selection').first().attributes; |
| 1149 | var val = thisView.args.value.replace('{{url}}', attachment.url); |
| 1150 | |
| 1151 | // Change the field value and trigger a change event |
| 1152 | thisView.field.val( val ).change(); |
| 1153 | |
| 1154 | // Close the image selector |
| 1155 | thisView.media.close(); |
| 1156 | |
| 1157 | }, this.media); |
| 1158 | } |
| 1159 | |
| 1160 | } ); |
| 1161 | |
| 1162 | // A simple measurement field |
| 1163 | socss.view.properties.controllers.measurement = socss.view.propertyController.extend( { |
| 1164 | |
| 1165 | wrapperClass: 'socss-field-measurement', |
| 1166 | |
| 1167 | render: function(){ |
| 1168 | this.$el.append($(this.template({}))); |
| 1169 | this.field = this.$('input'); |
| 1170 | this.setupMeasurementField( this.field, {} ); |
| 1171 | }, |
| 1172 | |
| 1173 | setValue: function (val, options) { |
| 1174 | options = _.extend({silent: false}, options); |
| 1175 | this.field.val(val).trigger('measurement_refresh'); |
| 1176 | if (!options.silent) { |
| 1177 | this.trigger('set_value', val); |
| 1178 | } |
| 1179 | }, |
| 1180 | |
| 1181 | units : [ |
| 1182 | 'px', |
| 1183 | '%', |
| 1184 | 'em', |
| 1185 | 'cm', |
| 1186 | 'mm', |
| 1187 | 'in', |
| 1188 | 'pt', |
| 1189 | 'pc', |
| 1190 | 'ex', |
| 1191 | 'ch', |
| 1192 | 'rem', |
| 1193 | 'vw', |
| 1194 | 'vh', |
| 1195 | 'vmin', |
| 1196 | 'vmax' |
| 1197 | ], |
| 1198 | |
| 1199 | parseUnits: function( value ){ |
| 1200 | var escapeRegExp = function(str) { |
| 1201 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); |
| 1202 | }; |
| 1203 | |
| 1204 | var regexUnits = this.units.map(escapeRegExp); |
| 1205 | var regex = new RegExp('([0-9\\.\\-]+)(' + regexUnits.join('|') + ')?', 'i'); |
| 1206 | var result = regex.exec( value ); |
| 1207 | |
| 1208 | if( result === null ) { |
| 1209 | return { |
| 1210 | value: '', |
| 1211 | unit: '' |
| 1212 | }; |
| 1213 | } |
| 1214 | else { |
| 1215 | return { |
| 1216 | value: result[1], |
| 1217 | unit: result[2] === undefined ? '' : result[2] |
| 1218 | }; |
| 1219 | } |
| 1220 | }, |
| 1221 | |
| 1222 | setupMeasurementField: function( $el, options ){ |
| 1223 | var thisView = this; |
| 1224 | var $p = $el.parent(); |
| 1225 | |
| 1226 | options = _.extend( { |
| 1227 | defaultUnit: 'px' |
| 1228 | }, options ); |
| 1229 | |
| 1230 | $el.hide(); |
| 1231 | $p.addClass( this.wrapperClass ).data('unit', options.defaultUnit); |
| 1232 | |
| 1233 | // Create the fake input field |
| 1234 | var $fi = $('<input type="text" class="socss-field-input"/>').appendTo($p); |
| 1235 | var $da = $('<span class="dashicons dashicons-arrow-down"></span>').appendTo($p); |
| 1236 | var $dd = $('<ul class="dropdown"></ul>').appendTo($p); |
| 1237 | var $u = $('<span class="units"></span>').html( options.defaultUnit ).appendTo( $p ); |
| 1238 | |
| 1239 | for( var i = 0; i < thisView.units.length; i++ ) { |
| 1240 | var $o = $('<li></li>').html( thisView.units[i] ).data('unit', thisView.units[i]); |
| 1241 | if( thisView.units[i] === options.defaultUnit ) { |
| 1242 | $o.addClass('active'); |
| 1243 | } |
| 1244 | $dd.append( $o ); |
| 1245 | } |
| 1246 | |
| 1247 | var updateValue = function(){ |
| 1248 | var value = thisView.parseUnits( $fi.val() ); |
| 1249 | |
| 1250 | if( value.unit !== '' && value.unit !== $p.data( 'unit' ) ) { |
| 1251 | $fi.val( value.value ); |
| 1252 | setUnit( value.unit ); |
| 1253 | } |
| 1254 | |
| 1255 | if( value.value === '' ) { |
| 1256 | $el.val( '' ); |
| 1257 | } |
| 1258 | else { |
| 1259 | $el.val( value.value + $p.data( 'unit' ) ); |
| 1260 | } |
| 1261 | }; |
| 1262 | |
| 1263 | var setUnit = function( unit ){ |
| 1264 | $u.html( unit ); |
| 1265 | $p.data( 'unit', unit ); |
| 1266 | $fi.trigger('keydown'); |
| 1267 | }; |
| 1268 | |
| 1269 | $da.click( function(){ |
| 1270 | $dd.toggle(); |
| 1271 | } ); |
| 1272 | |
| 1273 | $dd.find('li').click( function(){ |
| 1274 | $dd.toggle(); |
| 1275 | setUnit( $(this).data('unit') ); |
| 1276 | updateValue(); |
| 1277 | $el.trigger('change'); |
| 1278 | } ); |
| 1279 | |
| 1280 | $fi.on( 'keyup keydown', function(e){ |
| 1281 | var $$ = $(this); |
| 1282 | |
| 1283 | var char = ''; |
| 1284 | if( e.type === 'keydown' ) { |
| 1285 | if(e.keyCode >= 48 && e.keyCode <= 57 ) { |
| 1286 | char = String.fromCharCode(e.keyCode); |
| 1287 | } |
| 1288 | else if( e.keyCode === 189 ) { |
| 1289 | char = '-'; |
| 1290 | } |
| 1291 | else if( e.keyCode === 190 ) { |
| 1292 | char = '.'; |
| 1293 | } |
| 1294 | } |
| 1295 | |
| 1296 | var $pl = $('<span class="socss-hidden-placeholder"></span>') |
| 1297 | .css( { |
| 1298 | 'font-size' : '14px' |
| 1299 | } ) |
| 1300 | .html( $fi.val() + char ) |
| 1301 | .appendTo( 'body' ); |
| 1302 | var width = $pl.width(); |
| 1303 | width = Math.min(width, 63); |
| 1304 | $pl.remove(); |
| 1305 | |
| 1306 | $u.css('left', width + 12); |
| 1307 | } ); |
| 1308 | |
| 1309 | $fi.on('keyup', function(e){ |
| 1310 | updateValue(); |
| 1311 | $el.trigger('change'); |
| 1312 | } ); |
| 1313 | |
| 1314 | $el.on('measurement_refresh', function(){ |
| 1315 | var value = thisView.parseUnits( $el.val() ); |
| 1316 | $fi.val( value.value ); |
| 1317 | |
| 1318 | var unit = value.unit === '' ? options.defaultUnit : value.unit; |
| 1319 | $p.data( 'unit', unit ); |
| 1320 | $u.html( unit ); |
| 1321 | |
| 1322 | var $pl = $('<span class="socss-hidden-placeholder"></span>') |
| 1323 | .css({ |
| 1324 | 'font-size' : '14px' |
| 1325 | }) |
| 1326 | .html( value.value ) |
| 1327 | .appendTo( 'body' ); |
| 1328 | var width = $pl.width(); |
| 1329 | width = Math.min(width, 63); |
| 1330 | $pl.remove(); |
| 1331 | |
| 1332 | $u.css('left', width + 12); |
| 1333 | } ); |
| 1334 | |
| 1335 | // Now add the increment/decrement buttons |
| 1336 | var $diw = $('<div class="socss-diw"></div>').appendTo($p); |
| 1337 | var $dec = $('<div class="dec-button socss-button"><span class="fa fa-minus"></span></div>').appendTo($diw); |
| 1338 | var $inc = $('<div class="inc-button socss-button"><span class="fa fa-plus"></span></div>').appendTo($diw); |
| 1339 | |
| 1340 | // Increment is clicked |
| 1341 | $inc.click( function(){ |
| 1342 | var value = thisView.parseUnits( $el.val() ); |
| 1343 | if( value.value === '' ) { |
| 1344 | return true; |
| 1345 | } |
| 1346 | |
| 1347 | var newVal = Math.ceil( value.value * 1.05 ); |
| 1348 | |
| 1349 | $fi.val( newVal ); |
| 1350 | updateValue(); |
| 1351 | $el.trigger('change').trigger('measurement_refresh'); |
| 1352 | } ); |
| 1353 | |
| 1354 | $dec.click( function(){ |
| 1355 | var value = thisView.parseUnits( $el.val() ); |
| 1356 | if( value.value === '' ) { |
| 1357 | return true; |
| 1358 | } |
| 1359 | |
| 1360 | var newVal = Math.floor( value.value / 1.05 ); |
| 1361 | |
| 1362 | $fi.val( newVal ); |
| 1363 | updateValue(); |
| 1364 | $el.trigger('change').trigger('measurement_refresh'); |
| 1365 | } ); |
| 1366 | } |
| 1367 | |
| 1368 | } ); |
| 1369 | |
| 1370 | // A simple measurement field |
| 1371 | socss.view.properties.controllers.number = socss.view.propertyController.extend( { |
| 1372 | |
| 1373 | render: function(){ |
| 1374 | this.$el.append($(this.template({}))); |
| 1375 | this.field = this.$('input'); |
| 1376 | |
| 1377 | // Setup the measurement field |
| 1378 | this.setupNumberField(this.field, this.args); |
| 1379 | }, |
| 1380 | |
| 1381 | /** |
| 1382 | * Setup the number field |
| 1383 | * @param el |
| 1384 | * @param options |
| 1385 | */ |
| 1386 | setupNumberField: function($el, options){ |
| 1387 | options = _.extend({ |
| 1388 | change: null, |
| 1389 | default: 0, |
| 1390 | increment: 1, |
| 1391 | decrement: -1, |
| 1392 | max: null, |
| 1393 | min: null |
| 1394 | }, options); |
| 1395 | |
| 1396 | var $p = $el.parent(); |
| 1397 | $p.addClass('socss-field-number'); |
| 1398 | |
| 1399 | // Now add the increment/decrement buttons |
| 1400 | var $diw = $('<div class="socss-diw"></div>').appendTo($p); |
| 1401 | var $dec = $('<div class="dec-button socss-button">-</div>').appendTo($diw); |
| 1402 | var $inc = $('<div class="inc-button socss-button">+</div>').appendTo($diw); |
| 1403 | |
| 1404 | // Increment is clicked |
| 1405 | $diw.find('> div').click( function(e){ |
| 1406 | e.preventDefault(); |
| 1407 | |
| 1408 | var val = options.default; |
| 1409 | if( $el.val() !== '' ) { |
| 1410 | val = Number($el.val()); |
| 1411 | } |
| 1412 | val = val + ( $(this).is( $dec ) ? options.decrement : options.increment ); |
| 1413 | |
| 1414 | val = Math.round(val*100)/100; |
| 1415 | |
| 1416 | if( options.max !== null ) { |
| 1417 | val = Math.min( options.max, val); |
| 1418 | } |
| 1419 | |
| 1420 | if( options.min !== null ) { |
| 1421 | val = Math.max( options.min, val); |
| 1422 | } |
| 1423 | |
| 1424 | $el.val( val ); |
| 1425 | $el.trigger('change'); |
| 1426 | } ); |
| 1427 | |
| 1428 | return this; |
| 1429 | } |
| 1430 | |
| 1431 | } ); |
| 1432 | |
| 1433 | |
| 1434 | socss.view.properties.controllers.sides = socss.view.propertyController.extend( { |
| 1435 | |
| 1436 | template: _.template( $('#template-sides-field').html().trim() ), |
| 1437 | |
| 1438 | controllers: [], |
| 1439 | |
| 1440 | render: function(){ |
| 1441 | var thisView = this; |
| 1442 | |
| 1443 | this.$el.append( $(this.template({})) ); |
| 1444 | this.field = this.$el.find('input'); |
| 1445 | |
| 1446 | if( !thisView.args.hasAll ) { |
| 1447 | this.$('.select-tab').eq(0).remove(); |
| 1448 | this.$('.select-tab').css('width', '25%'); |
| 1449 | } |
| 1450 | |
| 1451 | this.$('.select-tab').each( function(){ |
| 1452 | var dir = $(this).data('direction'); |
| 1453 | |
| 1454 | var container = $('<li class="side">') |
| 1455 | .appendTo( thisView.$('.sides') ) |
| 1456 | .hide(); |
| 1457 | |
| 1458 | for( var i = 0; i < thisView.args.controllers.length; i++ ) { |
| 1459 | |
| 1460 | var controllerArgs = thisView.args.controllers[i]; |
| 1461 | |
| 1462 | if( typeof socss.view.properties.controllers[ controllerArgs.type ] ) { |
| 1463 | |
| 1464 | // Create the measurement view |
| 1465 | var property = ''; |
| 1466 | if( dir === 'all' ) { |
| 1467 | property = controllerArgs.args.propertyAll; |
| 1468 | } |
| 1469 | else { |
| 1470 | property = controllerArgs.args.property.replace('{dir}', dir); |
| 1471 | } |
| 1472 | |
| 1473 | var theseControllerArgs = _.extend({}, controllerArgs.args, {property: property}); |
| 1474 | |
| 1475 | var controller = new socss.view.properties.controllers[ controllerArgs.type ]( { |
| 1476 | el: $('<div>').appendTo( container ), |
| 1477 | propertiesView: thisView.propertiesView, |
| 1478 | args: theseControllerArgs |
| 1479 | } ); |
| 1480 | |
| 1481 | // Setup and render the measurement controller and register it with the properties view |
| 1482 | controller.render(); |
| 1483 | controller.initChangeEvents(); |
| 1484 | thisView.propertiesView.propertyControllers.push(controller); |
| 1485 | |
| 1486 | } |
| 1487 | |
| 1488 | } |
| 1489 | |
| 1490 | $(this).on( 'click', function(){ |
| 1491 | thisView.$('.select-tab').removeClass('active'); |
| 1492 | $(this).addClass('active'); |
| 1493 | |
| 1494 | thisView.$('.sides .side').hide(); |
| 1495 | container.show(); |
| 1496 | } ); |
| 1497 | |
| 1498 | } ); |
| 1499 | |
| 1500 | // Select the first tab by default |
| 1501 | this.$('.select-tab').eq(0).click(); |
| 1502 | } |
| 1503 | |
| 1504 | } ); |
| 1505 | |
| 1506 | })(jQuery, _, socssOptions); |
| 1507 | |
| 1508 | // Setup the main editor |
| 1509 | jQuery(function ($) { |
| 1510 | var socss = window.socss; |
| 1511 | |
| 1512 | // Setup the editor |
| 1513 | var editor = new socss.view.editor({ |
| 1514 | el: $('#so-custom-css-form').get(0) |
| 1515 | }); |
| 1516 | editor.render(); |
| 1517 | editor.setSnippets(socssOptions.snippets); |
| 1518 | |
| 1519 | window.socss.mainEditor = editor; |
| 1520 | |
| 1521 | // This is for hiding the getting started video |
| 1522 | $('#so-custom-css-getting-started a.hide').click( function(e){ |
| 1523 | e.preventDefault(); |
| 1524 | $('#so-custom-css-getting-started').slideUp(); |
| 1525 | $.get( $(this).attr('href') ); |
| 1526 | } ); |
| 1527 | }); |