1if (typeof(PhpDebugBar) == 'undefined') { 2 // namespace 3 var PhpDebugBar = {}; 4 PhpDebugBar.$ = jQuery; 5} 6 7(function($) { 8 9 /** 10 * @namespace 11 */ 12 PhpDebugBar.Widgets = {}; 13 14 var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-'); 15 16 /** 17 * Replaces spaces with and line breaks with <br> 18 * 19 * @param {String} text 20 * @return {String} 21 */ 22 var htmlize = PhpDebugBar.Widgets.htmlize = function(text) { 23 return text.replace(/\n/g, '<br>').replace(/\s/g, " ") 24 }; 25 26 /** 27 * Returns a string representation of value, using JSON.stringify 28 * if it's an object. 29 * 30 * @param {Object} value 31 * @param {Boolean} prettify Uses htmlize() if true 32 * @return {String} 33 */ 34 var renderValue = PhpDebugBar.Widgets.renderValue = function(value, prettify) { 35 if (typeof(value) !== 'string') { 36 if (prettify) { 37 return htmlize(JSON.stringify(value, undefined, 2)); 38 } 39 return JSON.stringify(value); 40 } 41 return value; 42 }; 43 44 /** 45 * Highlights a block of code 46 * 47 * @param {String} code 48 * @param {String} lang 49 * @return {String} 50 */ 51 var highlight = PhpDebugBar.Widgets.highlight = function(code, lang) { 52 if (typeof(code) === 'string') { 53 if (typeof(hljs) === 'undefined') { 54 return htmlize(code); 55 } 56 if (lang && hljs.getLanguage(lang)) { 57 return hljs.highlight(code, {language: lang}).value; 58 } 59 return hljs.highlightAuto(code).value; 60 } 61 62 if (typeof(hljs) === 'object') { 63 code.each(function(i, e) { hljs.highlightElement(e); }); 64 } 65 return code; 66 }; 67 68 /** 69 * Creates a <pre> element with a block of code 70 * 71 * @param {String} code 72 * @param {String} lang 73 * @param {Number} [firstLineNumber] If provided, shows line numbers beginning with the given value. 74 * @param {Number} [highlightedLine] If provided, the given line number will be highlighted. 75 * @return {String} 76 */ 77 var createCodeBlock = PhpDebugBar.Widgets.createCodeBlock = function(code, lang, firstLineNumber, highlightedLine) { 78 var pre = $('<pre />').addClass(csscls('code-block')); 79 // Add a newline to prevent <code> element from vertically collapsing too far if the last 80 // code line was empty: that creates problems with the horizontal scrollbar being 81 // incorrectly positioned - most noticeable when line numbers are shown. 82 var codeElement = $('<code />').text(code + '\n').appendTo(pre); 83 84 // Format the code 85 if (lang) { 86 codeElement.addClass("language-" + lang); 87 } 88 highlight(codeElement).removeClass('hljs'); 89 90 // Show line numbers in a list 91 if (!isNaN(parseFloat(firstLineNumber))) { 92 var lineCount = code.split('\n').length; 93 var $lineNumbers = $('<ul />').prependTo(pre); 94 pre.children().addClass(csscls('numbered-code')); 95 for (var i = firstLineNumber; i < firstLineNumber + lineCount; i++) { 96 var li = $('<li />').text(i).appendTo($lineNumbers); 97 98 // Add a span with a special class if we are supposed to highlight a line. 99 if (highlightedLine === i) { 100 li.addClass(csscls('highlighted-line')).append('<span> </span>'); 101 } 102 } 103 } 104 105 return pre; 106 }; 107 108 var getDictValue = PhpDebugBar.utils.getDictValue = function(dict, key, default_value) { 109 var d = dict, parts = key.split('.'); 110 for (var i = 0; i < parts.length; i++) { 111 if (!d[parts[i]]) { 112 return default_value; 113 } 114 d = d[parts[i]]; 115 } 116 return d; 117 } 118 119 // ------------------------------------------------------------------ 120 // Generic widgets 121 // ------------------------------------------------------------------ 122 123 /** 124 * Displays array element in a <ul> list 125 * 126 * Options: 127 * - data 128 * - itemRenderer: a function used to render list items (optional) 129 */ 130 var ListWidget = PhpDebugBar.Widgets.ListWidget = PhpDebugBar.Widget.extend({ 131 132 tagName: 'ul', 133 134 className: csscls('list'), 135 136 initialize: function(options) { 137 if (!options['itemRenderer']) { 138 options['itemRenderer'] = this.itemRenderer; 139 } 140 this.set(options); 141 }, 142 143 render: function() { 144 this.bindAttr(['itemRenderer', 'data'], function() { 145 this.$el.empty(); 146 if (!this.has('data')) { 147 return; 148 } 149 150 var data = this.get('data'); 151 for (var i = 0; i < data.length; i++) { 152 var li = $('<li />').addClass(csscls('list-item')).appendTo(this.$el); 153 this.get('itemRenderer')(li, data[i]); 154 } 155 }); 156 }, 157 158 /**
159 * Renders the content of a <li> element 160 * 161 * @param {jQuery} li The <li> element as a jQuery Object 162 * @param {Object} value An item from the data array 163 */ 164 itemRenderer: function(li, value) { 165 li.html(renderValue(value)); 166 } 167 168 }); 169 170 // ------------------------------------------------------------------ 171 172 /** 173 * Displays object property/value paris in a <dl> list 174 * 175 * Options: 176 * - data 177 * - itemRenderer: a function used to render list items (optional) 178 */ 179 var KVListWidget = PhpDebugBar.Widgets.KVListWidget = ListWidget.extend({ 180 181 tagName: 'dl', 182 183 className: csscls('kvlist'), 184 185 render: function() { 186 this.bindAttr(['itemRenderer', 'data'], function() { 187 this.$el.empty(); 188 if (!this.has('data')) { 189 return; 190 } 191 192 var self = this; 193 $.each(this.get('data'), function(key, value) { 194 var dt = $('<dt />').addClass(csscls('key')).appendTo(self.$el); 195 var dd = $('<dd />').addClass(csscls('value')).appendTo(self.$el); 196 self.get('itemRenderer')(dt, dd, key, value); 197 }); 198 }); 199 }, 200 201 /** 202 * Renders the content of the <dt> and <dd> elements 203 * 204 * @param {jQuery} dt The <dt> element as a jQuery Object 205 * @param {jQuery} dd The <dd> element as a jQuery Object 206 * @param {String} key Property name 207 * @param {Object} value Property value 208 */ 209 itemRenderer: function(dt, dd, key, value) { 210 dt.text(key); 211 dd.html(htmlize(value)); 212 } 213 214 }); 215 216 // ------------------------------------------------------------------ 217 218 /** 219 * An extension of KVListWidget where the data represents a list 220 * of variables 221 * 222 * Options: 223 * - data 224 */ 225 var VariableListWidget = PhpDebugBar.Widgets.VariableListWidget = KVListWidget.extend({ 226 227 className: csscls('kvlist varlist'), 228 229 itemRenderer: function(dt, dd, key, value) { 230 $('<span />').attr('title', key).text(key).appendTo(dt); 231 232 var v = value && value.value || value; 233 if (v && v.length > 100) { 234 v = v.substr(0, 100) + "..."; 235 } 236 var prettyVal = null; 237 dd.text(v).click(function() { 238 if (dd.hasClass(csscls('pretty'))) { 239 dd.text(v).removeClass(csscls('pretty')); 240 } else { 241 prettyVal = prettyVal || createCodeBlock(value); 242 dd.addClass(csscls('pretty')).empty().append(prettyVal); 243 } 244 }); 245 } 246 247 }); 248 249 // ------------------------------------------------------------------ 250 251 /** 252 * An extension of KVListWidget where the data represents a list 253 * of variables whose contents are HTML; this is useful for showing 254 * variable output from VarDumper's HtmlDumper. 255 * 256 * Options: 257 * - data 258 */ 259 var HtmlVariableListWidget = PhpDebugBar.Widgets.HtmlVariableListWidget = KVListWidget.extend({ 260 261 className: csscls('kvlist htmlvarlist'), 262 263 itemRenderer: function(dt, dd, key, value) { 264 $('<span />').attr('title', $('<i />').html(key ?? '').text()).html(key ?? '').appendTo(dt); 265 dd.html(value && value.value || value); 266 267 if (value && value.xdebug_link) { 268 var header = $('<span />').addClass(csscls('filename')).text(value.xdebug_link.filename + ( value.xdebug_link.line ? "#" + value.xdebug_link.line : '')); 269 if (value.xdebug_link) { 270 if (value.xdebug_link.ajax) { 271 $('<a title="' + value.xdebug_link.url + '"></a>').on('click', function () { 272 $.ajax(value.xdebug_link.url); 273 }).addClass(csscls('editor-link')).appendTo(header); 274 } else { 275 $('<a href="' + value.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(header); 276 } 277 } 278 header.appendTo(dd); 279 } 280 } 281 282 }); 283 284 // ------------------------------------------------------------------ 285 286 /**
287 * Displays array element in a <table> list, columns keys map 288 * useful for showing a multiple values table 289 * 290 * Options: 291 * - data 292 * - key_map: list of keys to be displayed with an optional label 293 * example: {key1: label1, key2: label2} or [key1, key2] 294 */ 295 var TableVariableListWidget = PhpDebugBar.Widgets.TableVariableListWidget = PhpDebugBar.Widget.extend({ 296 297 tagName: 'div', 298 299 className: csscls('tablevarlist'), 300 301 render: function() { 302 this.bindAttr('data', function(data) { 303 this.$el.empty(); 304 305 if (!this.has('data')) { 306 return; 307 } 308 309 this.$table = $('<table />').addClass(csscls('tablevar')).appendTo(this.$el); 310 var $header = $('<tr />').addClass(csscls('header')).append('<td />').appendTo(this.$table); 311 var key_map = data.key_map || {value: 'Value'}; 312 313 if (Array.isArray(key_map)) { 314 key_map = Object.fromEntries(key_map.map(k => [k, null])); 315 } 316 317 $.each(key_map, function(key, label) { 318 var colTitle = $('<td />').text(label ?? key).appendTo($header); 319 320 if (data.badges && data.badges[key]) { 321 $('<span />').text(data.badges[key]).addClass(csscls('badge')).appendTo(colTitle); 322 } 323 }); 324 325 var self = this; 326 $.each(data.data, function(key, values) { 327 var $tr = $('<tr />').addClass(csscls('item')).appendTo(self.$table); 328 $('<td />').addClass(csscls('key')).text(key).appendTo($tr); 329 330 if (typeof values !== 'object' || values === null) { 331 $('<td />').addClass(csscls('value')).text(values ?? '').appendTo($tr); 332 return; 333 } 334 335 $.each(key_map, function(key) { 336 $('<td />').addClass(csscls('value')).text(values[key] ?? '').appendTo($tr); 337 }); 338 339 if (values.xdebug_link) { 340 var filename = $('<span />').addClass(csscls('filename')) 341 .text(values.xdebug_link.filename + ( values.xdebug_link.line ? "#" + values.xdebug_link.line : '')) 342 .appendTo($('<td />').addClass(csscls('editor')).appendTo($tr)); 343 if (values.xdebug_link.ajax) { 344 $('<a title="' + values.xdebug_link.url + '"></a>').on('click', function () { 345 $.ajax(values.xdebug_link.url); 346 }).addClass(csscls('editor-link')).appendTo(filename); 347 } else { 348 $('<a href="' + values.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(filename); 349 } 350 351 if (!data.xdebug_link) { 352 data.xdebug_link = true; 353 $header.append($('<td />')); 354 } 355 } 356 }); 357 358 if (!data.summary) return; 359 360 var $tr = $('<tr />').addClass(csscls('summary')).appendTo(self.$table); 361 $('<td />').addClass(csscls('key')).appendTo($tr); 362 363 if (typeof data.summary !== 'object' || data.summary === null) { 364 $('<td />').addClass(csscls('value')).text(data.summary ?? '').appendTo($tr); 365 } else { 366 $.each(key_map, function(key) { 367 $('<td />').addClass(csscls('value')).text(data.summary[key] ?? '').appendTo($tr); 368 }); 369 } 370 371 if (data.xdebug_link) { 372 $('<td />').appendTo($tr); 373 } 374 }); 375 } 376 }); 377 378 // ------------------------------------------------------------------ 379 380 /** 381 * Iframe widget 382 * 383 * Options: 384 * - data 385 */ 386 var IFrameWidget = PhpDebugBar.Widgets.IFrameWidget = PhpDebugBar.Widget.extend({ 387 388 tagName: 'iframe', 389 390 className: csscls('iframe'), 391 392 render: function() { 393 this.$el.attr({ 394 seamless: "seamless", 395 border: "0", 396 width: "100%", 397 height: "100%" 398 }); 399 this.bindAttr('data', function(url) { this.$el.attr('src', url); }); 400 } 401 402 }); 403 404 405 // ------------------------------------------------------------------ 406 // Collector specific widgets 407 // ------------------------------------------------------------------ 408 409 /** 410 * Widget for the MessagesCollector 411 * 412 * Uses ListWidget under the hood 413 * 414 * Options: 415 * - data 416 */ 417 var MessagesWidget = PhpDebugBar.Widgets.MessagesWidget = PhpDebugBar.Widget.extend({ 418 419 className: csscls('messages'), 420 421 render: function() { 422 var self = this; 423 424 this.$list = new ListWidget({ itemRenderer: function(li, value) { 425 if (value.message_html) { 426 var val = $('<span />').addClass(csscls('value')).html(value.message_html).appendTo(l
426i); 427 } else { 428 var m = value.message; 429 if (m.length > 100) { 430 m = m.substr(0, 100) + "..."; 431 } 432 433 var val = $('<span />').addClass(csscls('value')).text(m).appendTo(li); 434 if (!value.is_string || value.message.length > 100) { 435 var prettyVal = value.message; 436 if (!value.is_string) { 437 prettyVal = null; 438 } 439 li.css('cursor', 'pointer').click(function () { 440 if (window.getSelection().type == "Range") { 441 return'' 442 } 443 if (val.hasClass(csscls('pretty'))) { 444 val.text(m).removeClass(csscls('pretty')); 445 } else { 446 prettyVal = prettyVal || createCodeBlock(value.message, 'php'); 447 val.addClass(csscls('pretty')).empty().append(prettyVal); 448 } 449 }); 450 } 451 } 452 if (value.xdebug_link) { 453 var header = $('<span />').addClass(csscls('filename')).text(value.xdebug_link.filename + ( value.xdebug_link.line ? "#" + value.xdebug_link.line : '')); 454 if (value.xdebug_link) { 455 if (value.xdebug_link.ajax) { 456 $('<a title="' + value.xdebug_link.url + '"></a>').on('click', function () { 457 $.ajax(value.xdebug_link.url); 458 }).addClass(csscls('editor-link')).appendTo(header); 459 } else { 460 $('<a href="' + value.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(header); 461 } 462 } 463 header.prependTo(li); 464 } 465 if (value.collector) { 466 $('<span />').addClass(csscls('collector')).text(value.collector).prependTo(li); 467 } 468 if (value.label) { 469 val.addClass(csscls(value.label)); 470 $('<span />').addClass(csscls('label')).text(value.label).prependTo(li); 471 } 472 }}); 473 474 this.$list.$el.appendTo(this.$el); 475 this.$toolbar = $('<div><i class="phpdebugbar-fa phpdebugbar-fa-search"></i></div>').addClass(csscls('toolbar')).appendTo(this.$el); 476 477 $('<input type="text" name="search" aria-label="Search" placeholder="Search" />') 478 .on('change', function() { self.set('search', this.value); }) 479 .appendTo(this.$toolbar); 480 481 this.bindAttr('data', function(data) { 482 this.set({excludelabel: [], excludecollector: [], search: ''}); 483 this.$toolbar.find(csscls('.filter')).remove(); 484 485 var labels = [], collectors = [], self = this, 486 createFilterItem = function (type, value) { 487 $('<a />') 488 .addClass(csscls('filter')).addClass(csscls(type)) 489 .text(value).attr('rel', value) 490 .on('click', function() { self.onFilterClick(this, type); }) 491 .appendTo(self.$toolbar) 492 }; 493 494 data.forEach(function (item) { 495 if (!labels.includes(item.label || 'none')) { 496 labels.push(item.label || 'none'); 497 } 498 499 if (!collectors.includes(item.collector || 'none')) { 500 collectors.push(item.collector || 'none'); 501 } 502 }); 503 504 if (labels.length > 1) { 505 labels.forEach(label => createFilterItem('label', label)); 506 } 507 508 if (collectors.length === 1) { 509 return; 510 } 511 512 $('<a />').addClass(csscls('filter')).css('visibility', 'hidden').appendTo(self.$toolbar);
513 collectors.forEach(collector => createFilterItem('collector', collector)); 514 }); 515 516 this.bindAttr(['excludelabel', 'excludecollector', 'search'], function() { 517 var excludelabel = this.get('excludelabel') || [], 518 excludecollector = this.get('excludecollector') || [], 519 search = this.get('search'), 520 caseless = false, 521 fdata = []; 522 523 if (search && search === search.toLowerCase()) { 524 caseless = true; 525 } 526 527 this.get('data').forEach(function (item) { 528 var message = caseless ? item.message.toLowerCase() : item.message; 529 530 if ( 531 !excludelabel.includes(item.label || undefined) && 532 !excludecollector.includes(item.collector || undefined) && 533 (!search || message.indexOf(search) > -1) 534 ) { 535 fdata.push(item); 536 } 537 }); 538 539 this.$list.set('data', fdata); 540 }); 541 }, 542 543 onFilterClick: function(el, type) { 544 $(el).toggleClass(csscls('excluded')); 545 546 var excluded = []; 547 this.$toolbar.find(csscls('.filter') + csscls('.excluded') + csscls('.' + type)).each(function() { 548 excluded.push(this.rel === 'none' || !this.rel ? undefined : this.rel); 549 }); 550 551 this.set('exclude' + type, excluded); 552 } 553 554 }); 555 556 // ------------------------------------------------------------------ 557 558 /** 559 * Widget for the TimeDataCollector 560 * 561 * Options: 562 * - data 563 */ 564 var TimelineWidget = PhpDebugBar.Widgets.TimelineWidget = PhpDebugBar.Widget.extend({ 565 566 tagName: 'ul', 567 568 className: csscls('timeline'), 569 570 render: function() { 571 this.bindAttr('data', function(data) { 572 573 // ported from php DataFormatter 574 var formatDuration = function(seconds) { 575 if (seconds < 0.001) 576 return (seconds * 1000000).toFixed() + 'μs'; 577 else if (seconds < 0.1) 578 return (seconds * 1000).toFixed(2) + 'ms'; 579 else if (seconds < 1) 580 return (seconds * 1000).toFixed() + 'ms'; 581 return (seconds).toFixed(2) + 's'; 582 }; 583 584 // ported from php DataFormatter 585 var formatBytes = function formatBytes(size) { 586 if (size === 0 || size === null) { 587 return '0B'; 588 } 589 590 var sign = size < 0 ? '-' : '', 591 size = Math.abs(size), 592 base = Math.log(size) / Math.log(1024), 593 suffixes = ['B', 'KB', 'MB', 'GB', 'TB']; 594 return sign + (Math.round(Math.pow(1024, base - Math.floor(base)) * 100) / 100) + suffixes[Math.floor(base)]; 595 } 596 597 this.$el.empty(); 598 if (data.measures) { 599 var aggregate = {}; 600 601 for (var i = 0; i < data.measures.length; i++) { 602 var measure = data.measures[i]; 603 var group = measure.group || measure.label; 604 605 if(!aggregate[group]) { 606 aggregate[group] = { count: 0, duration: 0, memory : 0 }; 607 } 608 609 aggregate[group]['count'] += 1; 610 aggregate[group]['duration'] += measure.duration; 611 aggregate[group]['memory'] += (measure.memory || 0); 612 613 var m = $('<div />').addClass(csscls('measure')), 614 li = $('<li />'), 615 left = (measure.relative_start * 100 / data.duration).toFixed(2), 616 width = Math.min((measure.duration * 100 / data.duration).toFixed(2), 100 - left); 617 618 m.append($('<span />').addClass(csscls('value')).css({ 619 left: left + "%", 620 width: width + "%" 621 })); 622 m.append($('<span />').addClass(csscls('label')) 623 .text(measure.label.replace(/\s+/g, ' ') + ( measure.duration ? " (" + measure.duration_str +(measure.memory ? '/' + measure.memory_str: '') + ")" : ""))); 624 625 if (measure.collector) { 626 $('<span />').addClass(csscls('collector')).text(measure.collector).appendTo(m); 627 } 628
629 m.appendTo(li); 630 this.$el.append(li); 631 632 if (measure.params && !$.isEmptyObject(measure.params)) { 633 var table = $('<table><tr><th colspan="2">Params</th></tr></table>').hide().addClass(csscls('params')).appendTo(li); 634 for (var key in measure.params) { 635 if (typeof measure.params[key] !== 'function') { 636 table.append('<tr><td class="' + csscls('name') + '">' + key + '</td><td class="' + csscls('value') + 637 '"><pre><code>' + measure.params[key] + '</code></pre></td></tr>'); 638 } 639 } 640 li.css('cursor', 'pointer').click(function() { 641 if (window.getSelection().type == "Range") { 642 return ''; 643 } 644 var table = $(this).find('table'); 645 if (table.is(':visible')) { 646 table.hide(); 647 } else { 648 table.show(); 649 } 650 }).on('click', '.sf-dump', function(event) { 651 event.stopPropagation(); 652 }); 653 } 654 } 655 656 // convert to array and sort by duration 657 aggregate = $.map(aggregate, function(data, label) { 658 return { 659 label: label, 660 data: data 661 } 662 }).sort(function(a, b) { 663 return b.data.duration - a.data.duration 664 }); 665 666 // build table and add 667 var aggregateTable = $('<table></table>').addClass(csscls('params')); 668 $.each(aggregate, function(i, aggregate) { 669 width = Math.min((aggregate.data.duration * 100 / data.duration).toFixed(2), 100); 670 671 aggregateTable.append('<tr><td class="' + csscls('name') + '">' + 672 aggregate.data.count + ' x ' + $('<i />').text(aggregate.label.replace(/\s+/g, ' ')).html() + ' (' + width + '%)</td><td class="' + csscls('value') + '">' + 673 '<div class="' + csscls('measure') +'">' + 674 '<span class="' + csscls('value') + '"></span>' + 675 '<span class="' + csscls('label') + '">' + formatDuration(aggregate.data.duration) + (aggregate.data.memory ? '/' + formatBytes(aggregate.data.memory) : '') + '</span>' + 676 '</div></td></tr>'); 677 aggregateTable.find('span.' + csscls('value') + ':last').css({width: width + "%" }); 678 }); 679 680 this.$el.append('<li/>').find('li:last').append(aggregateTable); 681 } 682 }); 683 } 684 685 }); 686 687 // ------------------------------------------------------------------ 688 689 /** 690 * Widget for the displaying exceptions 691 * 692 * Options: 693 * - data 694 */ 695 var ExceptionsWidget = PhpDebugBar.Widgets.ExceptionsWidget = PhpDebugBar.Widget.extend({ 696 697 className: csscls('exceptions'), 698 699 render: function() { 700 this.$list = new ListWidget({ itemRenderer: function(li, e) { 701 $('<span />').addClass(csscls('message')).text(e.message).appendTo(li); 702 if (e.file) { 703 var header = $('<span />').addClass(csscls('filename')).text(e.file + "#" + e.line); 704 if (e.xdebug_link) { 705 if (e.xdebug_link.ajax) { 706 $('<a title="' + e.xdebug_link.url + '"></a>').on('click', function () { 707 fetch(e.xdebug_link.url); 708 }).addClass(csscls('editor-link')).appendTo(header); 709 } else { 710 $('<a href="' + e.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(header); 711 } 712 }
713 header.appendTo(li); 714 } 715 if (e.type) { 716 $('<span />').addClass(csscls('type')).text(e.type).appendTo(li); 717 } 718 if (e.surrounding_lines) { 719 var startLine = (e.line - 3) <= 0 ? 1 : e.line - 3; 720 var pre = createCodeBlock(e.surrounding_lines.join(""), 'php', startLine, e.line).addClass(csscls('file')).appendTo(li); 721 if (!e.stack_trace_html) { 722 // This click event makes the var-dumper hard to use. 723 li.click(function () { 724 if (pre.is(':visible')) { 725 pre.hide(); 726 } else { 727 pre.show(); 728 } 729 }); 730 } 731 } 732 if (e.stack_trace_html) { 733 var $trace = $('<span />').addClass(csscls('filename')).html(e.stack_trace_html); 734 $trace.find('samp[data-depth="1"]').removeClass('sf-dump-expanded').addClass('sf-dump-compact').parent() 735 .find('>.sf-dump-note').html((_, t) => t.replace(/^array:/, '<span class="sf-dump-key">Stack Trace:</span> ') + ' files'); 736 $trace.appendTo(li); 737 } else if (e.stack_trace) { 738 e.stack_trace.split("\n").forEach(function (trace) { 739 var $traceLine = $('<div />'); 740 $('<span />').addClass(csscls('filename')).text(trace).appendTo($traceLine); 741 $traceLine.appendTo(li); 742 }); 743 } 744 }}); 745 this.$list.$el.appendTo(this.$el); 746 747 this.bindAttr('data', function(data) { 748 this.$list.set('data', data); 749 if (data.length == 1) { 750 this.$list.$el.children().first().find(csscls('.file')).show(); 751 } 752 }); 753 754 } 755 756 }); 757 758 /** 759 * Displays datasets in a table 760 * 761 */ 762 var DatasetWidget = PhpDebugBar.Widgets.DatasetWidget = PhpDebugBar.Widget.extend({ 763 764 initialize: function(options) { 765 if (!options['itemRenderer']) { 766 options['itemRenderer'] = this.itemRenderer; 767 } 768 this.set(options); 769 this.set('autoshow', null); 770 this.set('id', null); 771 this.set('sort', localStorage.getItem('debugbar-history-sort') || 'asc'); 772 this.$el.addClass(csscls('dataset-history')) 773 774 this.renderHead(); 775 }, 776 777 renderHead: function() { 778 this.$el.empty(); 779 this.$actions = $('<div />').addClass(csscls('dataset-actions')).appendTo(this.$el); 780 781 var self = this; 782 var debugbar = self.get('debugbar'); 783 784 this.$autoshow = $('<input type=checkbox>') 785 .on('click', function() { 786 if (debugbar.ajaxHandler) { 787 debugbar.ajaxHandler.setAutoShow($(this).is(':checked')); 788 } 789 if (debugbar.controls['__settings']) { 790 debugbar.controls['__settings'].get('widget').set('autoshow', this.autoShow); 791 } 792 }); 793 794 $('<label>Autoshow</label>') 795 .append(this.$autoshow) 796 .appendTo(this.$actions) 797 798 799 this.$clearbtn = $('<a>Clear</a>') 800 .appendTo(this.$actions) 801 .on('click', function() { 802 self.$table.empty(); 803 }); 804 805 this.$showBtn = $('<a>Show all</a>') 806 .appendTo(this.$actions) 807 .on('click', function() { 808 self.searchInput.val(null); 809 self.methodInput.val(null); 810 self.set('search', null); 811 self.set('method', null); 812 }); 813 814 this.methodInput = $('<select name="method" style="width:100px"><option>
814(method)</option><option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option></select>') 815 .on('change', function() { self.set('method', this.value)}) 816 .appendTo(this.$actions) 817 818 this.searchInput = $('<input type="text" name="search" aria-label="Search" placeholder="Search" />') 819 .on('input', function() { self.set('search', this.value); }) 820 .appendTo(this.$actions); 821 822 823 this.$table = $('<tbody />'); 824 825 $('<table/>') 826 .append($('<thead/>') 827 .append($('<tr/>') 828 .append($('<th></th>').css('width', '30px')) 829 .append($('<th>Date â</th>').css('width', '175px').click(function() { 830 self.set('sort', self.get('sort') === 'asc' ? 'desc' : 'asc') 831 localStorage.setItem('debugbar-history-sort', self.get('sort')) 832 })) 833 .append($('<th>Method</th>').css('width', '80px')) 834 .append($('<th>URL</th>')) 835 .append($('<th width="40%">Data</th>'))) 836 ) 837 .append(this.$table) 838 .appendTo(this.$el); 839 840 841 }, 842 843 renderDatasets: function() { 844 this.$table.empty(); 845 var self = this; 846 $.each(this.get('data'), function(key, data) { 847 if (!data.__meta) { 848 return; 849 } 850 851 self.get('itemRenderer')(self, data); 852 }); 853 }, 854 855 render: function() { 856 this.bindAttr('data', function() { 857 if (this.get('autoshow') === null && this.get('debugbar').ajaxHandler) { 858 this.set('autoshow', this.get('debugbar').ajaxHandler.autoShow); 859 } 860 861 if (!this.has('data')) { 862 return; 863 } 864 865 // Render the latest item 866 var datasets = this.get('data'); 867 var data = datasets[Object.keys(datasets)[Object.keys(datasets).length - 1]] 868 if (!data.__meta) { 869 return; 870 } 871 872 this.get('itemRenderer')(this, data); 873 }); 874 this.bindAttr(['itemRenderer', 'search', 'method', 'sort'], function() { 875 this.renderDatasets(); 876 }) 877 this.bindAttr('autoshow', function() { 878 var autoshow = this.get('autoshow'); 879 this.$autoshow.prop('checked', autoshow); 880 }) 881 this.bindAttr('id', function() { 882 var id = this.get('id'); 883 this.$table.find('.' + csscls('active')).removeClass(csscls('active')); 884 this.$table.find('tr[data-id=' + id+']').addClass(csscls('active')); 885 }) 886 }, 887 888 /** 889 * Renders the content of a dataset item 890 * 891 * @param {Object} value An item from the data array 892 */ 893 itemRenderer: function(widget, data) { 894 var meta = data.__meta; 895 896 var $badges = $('<td />'); 897 var tr = $('<tr />'); 898 if (widget.get('sort') === 'asc') { 899 tr.appendTo(widget.$table); 900 } else { 901 tr.prependTo(widget.$table); 902 } 903 904 var clickHandler = function() { 905 var debugbar = widget.get('debugbar'); 906 debugbar.showDataSet(meta.id, debugbar.datesetTitleFormater.format('', data, meta.suffix, meta.nb)); 907 widget.$table.find('.' + csscls('active')).removeClass(csscls('active')); 908 tr.addClass(csscls('active')); 909 910 if ($(this).data('tab')) { 911 debugbar.showTab($(this).data('tab')); 912 } 913 } 914 915 tr.attr('data-id', meta['id']) 916 .append($('<td>#' + meta['nb'] + '</td>').click(clickHandler)) 917 .append($('<td>' + meta['datetime'] + '</td>').click(clickHandler)) 918 .append($('<td>' + meta['method'] + '</td>').click(clickHandler)) 919 .append($('<td />').append(meta['uri'] + (meta['suffix'] ? ' ' + meta['suffix'] : '')).click(clickHandler)) 920 .css('cursor', 'pointer') 921 .addClass(csscls('table-row')) 922 923 var debugbar = widget.get('debugbar'); 924 $.each(debugbar.dataMap, function(key, def) { 925 var d = getDictValue(data, def[0], def[1]); 926 if (key.indexOf(':') != -1) { 927 key = key.split(':'); 928 if (key[1] === 'badge' && d > 0) { 929 var control = debugbar.getControl(key[0]); 930 var $a = $('<a>').attr('title', control.get('title')).data('tab', key[0]); 931 if (control.$icon) { 932 $a.append(debugbar.getControl(key[0]).$icon.clone()); 933 } 934 if (control.$badge) { 935 $a.append(debugbar.getControl(key[0]).$badge.clone().css('display', 'inline-block').text(d)); 936 } 937 $a.appendTo($badges).click(clickHandler); 938 } else if (key[1] === 'tooltip') { 939 debugbar.getControl(key[0]).set('tooltip', d); 940 } 941 } 942 }); 943 tr.append($badges); 944 945 if (debugbar.activeDatasetId === meta['id']) { 946 tr.addClass(csscls('active')); 947 } 948 949 var search = widget.get('search'); 950 var method = widget.get('method'); 951 if ((search && meta['uri'].indexOf(search) == -1) || (method && meta['method'] !== method)) { 952 tr.hide(); 953 } 954 } 955 956 }); 957 958 959 960})(PhpDebugBar.$);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.