1 2 3 (function() { 4 5 Polymer('core-layout', { 6 7 isContainer: false, 8 /** 9 * Controls if the element lays out vertically or not. 10 * 11 * @attribute vertical 12 * @type boolean 13 * @default false 14 */ 15 vertical: false, 16 /** 17 * Controls how the items are aligned in the main-axis direction. For 18 * example for a horizontal layout, this controls how each item is aligned 19 * horizontally. 20 * 21 * @attribute justify 22 * @type string start|center|end|between 23 * @default '' 24 */ 25 justify: '', 26 /** 27 * Controls how the items are aligned in cross-axis direction. For 28 * example for a horizontal layout, this controls how each item is aligned 29 * vertically. 30 * 31 * @attribute align 32 * @type string start|center|end 33 * @default '' 34 */ 35 align: '', 36 /** 37 * Controls whether or not the items layout in reverse order. 38 * 39 * @attribute reverse 40 * @type boolean 41 * @default false 42 */ 43 reverse: false, 44 layoutPrefix: 'core-', 45 46 // NOTE: include template so that styles are loaded, but remove 47 // so that we can decide dynamically what part to include 48 registerCallback: function(polymerElement) { 49 var template = polymerElement.querySelector('template'); 50 this.styles = template.content.querySelectorAll('style').array(); 51 this.styles.forEach(function(s) { 52 s.removeAttribute('no-shim'); 53 }) 54 }, 55 56 fetchTemplate: function() { 57 return null; 58 }, 59 60 attached: function() { 61 this.installScopeStyle(this.styles[0]); 62 if (this.children.length) { 63 this.isContainer = true; 64 } 65 var container = this.isContainer ? this : this.parentNode; 66 // detect if laying out a shadowRoot host. 67 var forHost = container instanceof ShadowRoot; 68 if (forHost) { 69 this.installScopeStyle(this.styles[1], 'host'); 70 container = container.host || document.body; 71 } 72 this.layoutContainer = container; 73 }, 74 75 detached: function() { 76 this.layoutContainer = null; 77 }, 78 79 layoutContainerChanged: function(old) { 80 this.style.display = this.layoutContainer === this ? null : 'none'; 81 this.verticalChanged(); 82 this.alignChanged(); 83 this.justifyChanged(); 84 }, 85 86 setLayoutClass: function(prefix, old, newValue) { 87 if (this.layoutContainer) { 88 prefix = this.layoutPrefix + prefix; 89 if (old) { 90 this.layoutContainer.classList.remove(prefix + old); 91 } 92 if (newValue) { 93 this.layoutContainer.classList.add(prefix + newValue); 94 } 95 } 96 }, 97 98 verticalChanged: function(old) { 99 old = old ? 'v' : 'h'; 100 var vertical = this.vertical ? 'v' : 'h'; 101 this.setLayoutClass('', old, vertical); 102 }, 103 104 alignChanged: function(old) { 105 this.setLayoutClass('align-', old, this.align); 106 }, 107 108 justifyChanged: function(old) { 109 this.setLayoutClass('justify-', old, this.justify); 110 }, 111 112 reverseChanged: function(old) { 113 old = old ? 'reverse' : ''; 114 var newValue = this.reverse ? 'reverse' : ''; 115 this.setLayoutClass('', old, newValue); 116 } 117 118 }); 119 120 })(); 121 ; 122 123 124 (function() { 125 126 var SKIP_ID = 'meta'; 127 var metaData = {}, metaArray = {}; 128 129 Polymer('core-meta', { 130 131 /** 132 * The type of meta-data. All meta-data with the same type with be 133 * stored together. 134 * 135 * @attribute type 136 * @type string 137 * @default 'default' 138 */ 139 type: 'default', 140 141 alwaysPrepare: true, 142 143 ready: function() { 144 this.register(this.id); 145 }, 146 147 get metaArray() { 148 var t = this.type; 149 if (!metaArray[t]) { 150 metaArray[t] = []; 151 } 152 return metaArray[t]; 153 }, 154 155 get metaData() { 156 var t = this.type; 157 if (!metaData[t]) { 158 metaData[t] = {}; 159 } 160 return metaData[t]; 161 }, 162 163 register: function(id, old) { 164 if (id && id !== SKIP_ID) { 165 this.unregister(this, old); 166 this.metaData[id] = this; 167 this.metaArray.push(this); 168 } 169 }, 170 171 unregister: function(meta, id) { 172 delete this.metaData[id || meta.id]; 173 var i = this.metaArray.indexOf(meta); 174 if (i >= 0) { 175 this.metaArray.splice(i, 1); 176 } 177 }, 178 179 /** 180 * Returns a list of all meta-data elements with the same type. 181 * 182 * @attribute list 183 * @type array 184 * @default [] 185 */ 186 get list() { 187 return this.metaArray; 188 }, 189 190 /** 191 * Retrieves meta-data by ID. 192 * 193 * @method byId 194 * @param {String} id The ID of the meta-data to be returned. 195 * @returns Returns meta-data. 196 */ 197 byId: function(id) { 198 return this.metaData[id]; 199 } 200 201 }); 202 203 })(); 204 205 ; 206 207 208 Polymer('core-iconset', { 209 210 /** 211 * The URL of the iconset image. 212 * 213 * @attribute src 214 * @type string 215 * @default '' 216 */ 217 src: '', 218 219 /** 220 * The width of the iconset image. This must only be specified if the 221 * icons are arranged into separate rows inside the image. 222 * 223 * @attribute width 224 * @type number 225 * @default 0 226 */ 227 width: 0, 228 229 /** 230 * A space separated list of names corresponding to icons in the iconset 231 * image file. This list must be ordered the same as the icon images 232 * in the image file. 233 * 234 * @attribute icons 235 * @type string 236 * @default '' 237 */ 238 icons: '', 239 240 /** 241 * The size of an individual icon. Note that icons must be square. 242 * 243 * @attribute iconSize 244 * @type number 245 * @default 24 246 */ 247 iconSize: 24, 248 249 /** 250 * The horizontal offset of the icon images in the inconset src image. 251 * This is typically used if the image resource contains additional images 252 * beside those intended for the iconset. 253 * 254 * @attribute offsetX 255 * @type number 256 * @default 0 257 */ 258 offsetX: 0, 259 /** 260 * The vertical offset of the icon images in the inconset src image. 261 * This is typically used if the image resource contains additional images 262 * beside those intended for the iconset. 263 * 264 * @attribute offsetY 265 * @type number 266 * @default 0 267 */ 268 offsetY: 0, 269 type: 'iconset', 270 271 created: function() { 272 this.iconMap = {}; 273 this.iconNames = []; 274 this.themes = {}; 275 }, 276 277 ready: function() { 278 // TODO(sorvell): ensure iconset's src is always relative to the main 279 // document 280 if (this.src && (this.ownerDocument !== document)) { 281 this.src = this.resolvePath(this.src, this.ownerDocument.baseURI); 282 } 283 this.super(); 284 this.updateThemes(); 285 }, 286 287 iconsChanged: function() { 288 var ox = this.offsetX; 289 var oy = this.offsetY; 290 this.icons && this.icons.split(/\s+/g).forEach(function(name, i) { 291 this.iconNames.push(name); 292 this.iconMap[name] = { 293 offsetX: ox, 294 offsetY: oy 295 } 296 if (ox + this.iconSize < this.width) { 297 ox += this.iconSize; 298 } else { 299 ox = this.offsetX; 300 oy += this.iconSize; 301 } 302 }, this); 303 }, 304 305 updateThemes: function() { 306 var ts = this.querySelectorAll('property[theme]'); 307 ts && ts.array().forEach(function(t) { 308 this.themes[t.getAttribute('theme')] = { 309 offsetX: parseInt(t.getAttribute('offsetX')) || 0, 310 offsetY: parseInt(t.getAttribute('offsetY')) || 0 311 }; 312 }, this); 313 }, 314 315 // TODO(ffu): support retrived by index e.g. getOffset(10); 316 /** 317 * Returns an object containing `offsetX` and `offsetY` properties which 318 * specify the pixel locaion in the iconset's src file for the given 319 * `icon` and `theme`. It's uncommon to call this method. It is useful, 320 * for example, to manually position a css backgroundImage to the proper 321 * offset. It's more common to use the `applyIcon` method. 322 * 323 * @method getOffset 324 * @param {String|Number} icon The name of the icon or the index of the 325 * icon within in the icon image. 326 * @param {String} theme The name of the theme. 327 * @returns {Object} An object specifying the offset of the given icon 328 * within the icon resource file; `offsetX` is the horizontal offset and 329 * `offsetY` is the vertical offset. Both values are in pixel units. 330 */ 331 getOffset: function(icon, theme) { 332 var i = this.iconMap[icon]; 333 if (!i) { 334 var n = this.iconNames[Number(icon)]; 335 i = this.iconMap[n]; 336 } 337 var t = this.themes[theme]; 338 if (i && t) { 339 return { 340 offsetX: i.offsetX + t.offsetX, 341 offsetY: i.offsetY + t.offsetY 342 } 343 } 344 return i; 345 }, 346 347 /** 348 * Applies an icon to the given element as a css background image. This 349 * method does not size the element, and it's often necessary to set 350 * the element's height and width so that the background image is visible. 351 * 352 * @method applyIcon 353 * @param {Element} element The element to which the background is 354 * applied. 355 * @param {String|Number} icon The name or index of the icon to apply. 356 * @param {String} theme (optional) The name of the theme for the icon. 357 * @param {Number} scale (optional, defaults to 1) A scaling factor 358 * with which the icon can be magnified. 359 */ 360 applyIcon: function(element, icon, scale) { 361 var offset = this.getOffset(icon); 362 scale = scale || 1; 363 if (element && offset) { 364 var style = element.style; 365 style.backgroundImage = 'url(' + this.src + ')'; 366 style.backgroundPosition = (-offset.offsetX * scale + 'px') + 367 ' ' + (-offset.offsetY * scale + 'px'); 368 style.backgroundSize = scale === 1 ? 'auto' : 369 this.width * scale + 'px'; 370 } 371 } 372 373 }); 374 375 ; 376 377 378 Polymer('core-iconset-svg', { 379 380 381 /** 382 * The size of an individual icon. Note that icons must be square. 383 * 384 * @attribute iconSize 385 * @type number 386 * @default 24 387 */ 388 iconSize: 24, 389 type: 'iconset', 390 391 created: function() { 392 this._icons = {}; 393 }, 394 395 ready: function() { 396 this.super(); 397 this.updateIcons(); 398 }, 399 400 iconById: function(id) { 401 return this._icons[id] || (this._icons[id] = this.querySelector('#' + id)); 402 }, 403 404 cloneIcon: function(id) { 405 var icon = this.iconById(id); 406 if (icon) { 407 var content = icon.cloneNode(true); 408 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 409 svg.setAttribute('viewBox', '0 0 ' + this.iconSize + ' ' + 410 this.iconSize); 411 // NOTE(dfreedm): work around https://crbug.com/370136 412 svg.style.pointerEvents = 'none'; 413 svg.appendChild(content); 414 return svg; 415 } 416 }, 417 418 get iconNames() { 419 if (!this._iconNames) { 420 this._iconNames = this.findIconNames(); 421 } 422 return this._iconNames; 423 }, 424 425 findIconNames: function() { 426 var icons = this.querySelectorAll('[id]').array(); 427 if (icons.length) { 428 return icons.map(function(n){ return n.id }); 429 } 430 }, 431 432 /** 433 * Applies an icon to the given element. The svg icon is added to the 434 * element's shadowRoot if one exists or directly to itself. 435 * 436 * @method applyIcon 437 * @param {Element} element The element to which the icon is 438 * applied. 439 * @param {String|Number} icon The name the icon to apply. 440 */ 441 applyIcon: function(element, icon, scale) { 442 var root = element.shadowRoot || element; 443 // remove old 444 var old = root.querySelector('svg'); 445 if (old) { 446 old.remove(); 447 } 448 // install new 449 var svg = this.cloneIcon(icon); 450 if (!svg) { 451 return; 452 } 453 var size = scale * this.iconSize; 454 if (size) { 455 svg.style.height = svg.style.width = size + 'px'; 456 } else { 457 svg.setAttribute('height', '100%'); 458 svg.setAttribute('width', '100%'); 459 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); 460 } 461 svg.style.display = 'block'; 462 root.insertBefore(svg, root.firstElementChild); 463 }, 464 465 /** 466 * Tell users of the iconset, that the set has loaded. 467 * This finds all elements matching the selector argument and calls 468 * the method argument on them. 469 * @method updateIcons 470 * @param selector {string} css selector to identify iconset users, 471 * defaults to '[icon]' 472 * @param method {string} method to call on found elements, 473 * defaults to 'updateIcon' 474 */ 475 updateIcons: function(selector, method) { 476 selector = selector || '[icon]'; 477 method = method || 'updateIcon'; 478 var deep = window.ShadowDOMPolyfill ? '' : 'html /deep/ '; 479 var i$ = document.querySelectorAll(deep + selector); 480 for (var i=0, e; e=i$[i]; i++) { 481 if (e[method]) { 482 e[method].call(e); 483 } 484 } 485 } 486 487 488 }); 489 490 ; 491 492 (function() { 493 494 // mono-state 495 var meta; 496 497 Polymer('core-icon', { 498 499 /** 500 * The URL of an image for the icon. If the src property is specified, 501 * the icon property should not be. 502 * 503 * @attribute src 504 * @type string 505 * @default '' 506 */ 507 src: '', 508 509 /** 510 * Specifies the size of the icon in pixel units. 511 * 512 * @attribute size 513 * @type string 514 * @default 24 515 */ 516 size: 24, 517 518 /** 519 * Specifies the icon name or index in the set of icons available in 520 * the icon's icon set. If the icon property is specified, 521 * the src property should not be. 522 * 523 * @attribute icon 524 * @type string 525 * @default '' 526 */ 527 icon: '', 528 529 observe: { 530 'size icon': 'updateIcon' 531 }, 532 533 defaultIconset: 'icons', 534 535 ready: function() { 536 if (!meta) { 537 meta = document.createElement('core-iconset'); 538 } 539 this.updateIcon(); 540 }, 541 542 srcChanged: function() { 543 this.style.backgroundImage = 'url(' + this.src + ')'; 544 this.style.backgroundPosition = 'center'; 545 this.style.backgroundSize = this.size + 'px ' + this.size + 'px'; 546 }, 547 548 getIconset: function(name) { 549 return meta.byId(name || this.defaultIconset); 550 }, 551 552 updateIcon: function() { 553 if (this.size) { 554 this.style.width = this.style.height = this.size + 'px'; 555 } 556 if (this.icon) { 557 var parts = String(this.icon).split(':'); 558 var icon = parts.pop(); 559 if (icon) { 560 var set = this.getIconset(parts.pop()); 561 if (set) { 562 set.applyIcon(this, icon, this.size / set.iconSize); 563 } 564 } 565 } 566 } 567 568 }); 569 570 })(); 571 ; 572 573 574 Polymer('core-icon-button', { 575 576 /** 577 * The URL of an image for the icon. Should not use `icon` property 578 * if you are using this property. 579 * 580 * @attribute src 581 * @type string 582 * @default '' 583 */ 584 src: '', 585 586 /** 587 * If true, border is placed around the button to indicate it's 588 * active state. 589 * 590 * @attribute active 591 * @type boolean 592 * @default false 593 */ 594 active: false, 595 596 /** 597 * Specifies the icon name or index in the set of icons available in 598 * the icon set. Should not use `src` property if you are using this 599 * property. 600 * 601 * @attribute icon 602 * @type string 603 * @default '' 604 */ 605 icon: '', 606 607 activeChanged: function() { 608 this.classList.toggle('selected', this.active); 609 } 610 611 }); 612 613 ; 614 Polymer('core-toolbar');; 615 616 617 Polymer('core-header-panel', { 618 619 publish: { 620 /** 621 * Controls header and scrolling behavior. Options are 622 * `standard`, `seamed`, `waterfall`, `waterfall-tall`, 623 * `waterfall-medium-tall`, `scroll` and `cover`. 624 * Default is `standard`. 625 * 626 * `standard`: The header is a step above the panel. The header will consume the 627 * panel at the point of entry, preventing it from passing through to the 628 * opposite side. 629 * 630 * `seamed`: The header is presented as seamed with the panel. 631 * 632 * `waterfall`: Similar to standard mode, but header is initially presented as 633 * seamed with panel, but then separates to form the step. 634 * 635 * `waterfall-tall`: The header is initially taller (`tall` class is added to 636 * the header). As the user scrolls, the header separates (forming an edge) 637 * while condensing (`tall` class is removed from the header). 638 * 639 * `scroll`: The header keeps its seam with the panel, and is pushed off screen. 640 * 641 * `cover`: The panel covers the whole `core-header-panel` including the 642 * header. This allows user to style the panel in such a way that the panel is 643 * partially covering the header. 644 * 645 * <style> 646 * core-header-panel[mode=cover]::shadow #mainContainer { 647 * left: 80px; 648 * } 649 * .content { 650 * margin: 60px 60px 60px 0; 651 * } 652 * </style> 653 * 654 * <core-header-panel mode="cover"> 655 * <core-appbar class="tall"> 656 * <core-icon-button icon="menu"></core-icon-button> 657 * </core-appbar> 658 * <div class="content"></div> 659 * </core-header-panel> 660 * 661 * @attribute mode 662 * @type string 663 * @default '' 664 */ 665 mode: {value: '', reflect: true}, 666 667 /** 668 * The class used in waterfall-tall mode. Change this if the header 669 * accepts a different class for toggling height, e.g. "medium-tall" 670 * 671 * @attribute tallClass 672 * @type string 673 * @default 'tall' 674 */ 675 tallClass: 'tall', 676 677 /** 678 * If true, the drop-shadow is always shown no matter what mode is set to. 679 * 680 * @attribute shadow 681 * @type boolean 682 * @default false 683 */ 684 shadow: false, 685 }, 686 687 domReady: function() { 688 this.async('scroll'); 689 }, 690 691 modeChanged: function() { 692 this.scroll(); 693 }, 694 695 get header() { 696 return this.$.headerContent.getDistributedNodes()[0]; 697 }, 698 699 scroll: function() { 700 var shadowMode = {'waterfall': 1, 'waterfall-tall': 1}; 701 var noShadow = {'seamed': 1, 'cover': 1, 'scroll': 1}; 702 var tallMode = {'waterfall-tall': 1}; 703 704 var main = this.$.mainContainer; 705 var header = this.header; 706 707 var sTop = main.scrollTop; 708 var atTop = sTop === 0; 709 710 if (header) { 711 this.$.dropShadow.classList.toggle('hidden', !this.shadow && 712 (atTop && shadowMode[this.mode] || noShadow[this.mode])); 713 714 if (tallMode[this.mode]) { 715 header.classList.toggle(this.tallClass, atTop); 716 } 717 718 header.classList.toggle('animate', tallMode[this.mode]); 719 } 720 } 721 722 }); 723 724 ; 725 /** 726 * marked - a markdown parser 727 * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 728 * https://github.com/chjj/marked 729 */ 730 731 ;(function() { 732 733 /** 734 * Block-Level Grammar 735 */ 736 737 var block = { 738 newline: /^\n+/, 739 code: /^( {4}[^\n]+\n*)+/, 740 fences: noop, 741 hr: /^( *[-*_]){3,} *(?:\n+|$)/, 742 heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, 743 nptable: noop, 744 lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 745 blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, 746 list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 747 html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, 748 def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, 749 table: noop, 750 paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, 751 text: /^[^\n]+/ 752 }; 753 754 block.bullet = /(?:[*+-]|\d+\.)/; 755 block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; 756 block.item = replace(block.item, 'gm') 757 (/bull/g, block.bullet) 758 (); 759 760 block.list = replace(block.list) 761 (/bull/g, block.bullet) 762 ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') 763 ('def', '\\n+(?=' + block.def.source + ')') 764 (); 765 766 block.blockquote = replace(block.blockquote) 767 ('def', block.def) 768 (); 769 770 block._tag = '(?!(?:' 771 + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' 772 + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' 773 + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; 774 775 block.html = replace(block.html) 776 ('comment', /<!--[\s\S]*?-->/) 777 ('closed', /<(tag)[\s\S]+?<\/\1>/) 778 ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/) 779 (/tag/g, block._tag) 780 (); 781 782 block.paragraph = replace(block.paragraph) 783 ('hr', block.hr) 784 ('heading', block.heading) 785 ('lheading', block.lheading) 786 ('blockquote', block.blockquote) 787 ('tag', '<' + block._tag) 788 ('def', block.def) 789 (); 790 791 /** 792 * Normal Block Grammar 793 */ 794 795 block.normal = merge({}, block); 796 797 /** 798 * GFM Block Grammar 799 */ 800 801 block.gfm = merge({}, block.normal, { 802 fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/, 803 paragraph: /^/ 804 }); 805 806 block.gfm.paragraph = replace(block.paragraph) 807 ('(?!', '(?!' 808 + block.gfm.fences.source.replace('\\1', '\\2') + '|' 809 + block.list.source.replace('\\1', '\\3') + '|') 810 (); 811 812 /** 813 * GFM + Tables Block Grammar 814 */ 815 816 block.tables = merge({}, block.gfm, { 817 nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, 818 table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ 819 }); 820 821 /** 822 * Block Lexer 823 */ 824 825 function Lexer(options) { 826 this.tokens = []; 827 this.tokens.links = {}; 828 this.options = options || marked.defaults; 829 this.rules = block.normal; 830 831 if (this.options.gfm) { 832 if (this.options.tables) { 833 this.rules = block.tables; 834 } else { 835 this.rules = block.gfm; 836 } 837 } 838 } 839 840 /** 841 * Expose Block Rules 842 */ 843 844 Lexer.rules = block; 845 846 /** 847 * Static Lex Method 848 */ 849 850 Lexer.lex = function(src, options) { 851 var lexer = new Lexer(options); 852 return lexer.lex(src); 853 }; 854 855 /** 856 * Preprocessing 857 */ 858 859 Lexer.prototype.lex = function(src) { 860 src = src 861 .replace(/\r\n|\r/g, '\n') 862 .replace(/\t/g, ' ') 863 .replace(/\u00a0/g, ' ') 864 .replace(/\u2424/g, '\n'); 865 866 return this.token(src, true); 867 }; 868 869 /** 870 * Lexing 871 */ 872 873 Lexer.prototype.token = function(src, top, bq) { 874 var src = src.replace(/^ +$/gm, '') 875 , next 876 , loose 877 , cap 878 , bull 879 , b 880 , item 881 , space 882 , i 883 , l; 884 885 while (src) { 886 // newline 887 if (cap = this.rules.newline.exec(src)) { 888 src = src.substring(cap[0].length); 889 if (cap[0].length > 1) { 890 this.tokens.push({ 891 type: 'space' 892 }); 893 } 894 } 895 896 // code 897 if (cap = this.rules.code.exec(src)) { 898 src = src.substring(cap[0].length); 899 cap = cap[0].replace(/^ {4}/gm, ''); 900 this.tokens.push({ 901 type: 'code', 902 text: !this.options.pedantic 903 ? cap.replace(/\n+$/, '') 904 : cap 905 }); 906 continue; 907 } 908 909 // fences (gfm) 910 if (cap = this.rules.fences.exec(src)) { 911 src = src.substring(cap[0].length); 912 this.tokens.push({ 913 type: 'code', 914 lang: cap[2], 915 text: cap[3] 916 }); 917 continue; 918 } 919 920 // heading 921 if (cap = this.rules.heading.exec(src)) { 922 src = src.substring(cap[0].length); 923 this.tokens.push({ 924 type: 'heading', 925 depth: cap[1].length, 926 text: cap[2] 927 }); 928 continue; 929 } 930 931 // table no leading pipe (gfm) 932 if (top && (cap = this.rules.nptable.exec(src))) { 933 src = src.substring(cap[0].length); 934 935 item = { 936 type: 'table', 937 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 938 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 939 cells: cap[3].replace(/\n$/, '').split('\n') 940 }; 941 942 for (i = 0; i < item.align.length; i++) { 943 if (/^ *-+: *$/.test(item.align[i])) { 944 item.align[i] = 'right'; 945 } else if (/^ *:-+: *$/.test(item.align[i])) { 946 item.align[i] = 'center'; 947 } else if (/^ *:-+ *$/.test(item.align[i])) { 948 item.align[i] = 'left'; 949 } else { 950 item.align[i] = null; 951 } 952 } 953 954 for (i = 0; i < item.cells.length; i++) { 955 item.cells[i] = item.cells[i].split(/ *\| */); 956 } 957 958 this.tokens.push(item); 959 960 continue; 961 } 962 963 // lheading 964 if (cap = this.rules.lheading.exec(src)) { 965 src = src.substring(cap[0].length); 966 this.tokens.push({ 967 type: 'heading', 968 depth: cap[2] === '=' ? 1 : 2, 969 text: cap[1] 970 }); 971 continue; 972 } 973 974 // hr 975 if (cap = this.rules.hr.exec(src)) { 976 src = src.substring(cap[0].length); 977 this.tokens.push({ 978 type: 'hr' 979 }); 980 continue; 981 } 982 983 // blockquote 984 if (cap = this.rules.blockquote.exec(src)) { 985 src = src.substring(cap[0].length); 986 987 this.tokens.push({ 988 type: 'blockquote_start' 989 }); 990 991 cap = cap[0].replace(/^ *> ?/gm, ''); 992 993 // Pass `top` to keep the current 994 // "toplevel" state. This is exactly 995 // how markdown.pl works. 996 this.token(cap, top, true); 997 998 this.tokens.push({ 999 type: 'blockquote_end' 1000 }); 1001 1002 continue; 1003 } 1004 1005 // list 1006 if (cap = this.rules.list.exec(src)) { 1007 src = src.substring(cap[0].length); 1008 bull = cap[2]; 1009 1010 this.tokens.push({ 1011 type: 'list_start', 1012 ordered: bull.length > 1 1013 }); 1014 1015 // Get each top-level item. 1016 cap = cap[0].match(this.rules.item); 1017 1018 next = false; 1019 l = cap.length; 1020 i = 0; 1021 1022 for (; i < l; i++) { 1023 item = cap[i]; 1024 1025 // Remove the list item's bullet 1026 // so it is seen as the next token. 1027 space = item.length; 1028 item = item.replace(/^ *([*+-]|\d+\.) +/, ''); 1029 1030 // Outdent whatever the 1031 // list item contains. Hacky. 1032 if (~item.indexOf('\n ')) { 1033 space -= item.length; 1034 item = !this.options.pedantic 1035 ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') 1036 : item.replace(/^ {1,4}/gm, ''); 1037 } 1038 1039 // Determine whether the next list item belongs here. 1040 // Backpedal if it does not belong in this list. 1041 if (this.options.smartLists && i !== l - 1) { 1042 b = block.bullet.exec(cap[i + 1])[0]; 1043 if (bull !== b && !(bull.length > 1 && b.length > 1)) { 1044 src = cap.slice(i + 1).join('\n') + src; 1045 i = l - 1; 1046 } 1047 } 1048 1049 // Determine whether item is loose or not. 1050 // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 1051 // for discount behavior. 1052 loose = next || /\n\n(?!\s*$)/.test(item); 1053 if (i !== l - 1) { 1054 next = item.charAt(item.length - 1) === '\n'; 1055 if (!loose) loose = next; 1056 } 1057 1058 this.tokens.push({ 1059 type: loose 1060 ? 'loose_item_start' 1061 : 'list_item_start' 1062 }); 1063 1064 // Recurse. 1065 this.token(item, false, bq); 1066 1067 this.tokens.push({ 1068 type: 'list_item_end' 1069 }); 1070 } 1071 1072 this.tokens.push({ 1073 type: 'list_end' 1074 }); 1075 1076 continue; 1077 } 1078 1079 // html 1080 if (cap = this.rules.html.exec(src)) { 1081 src = src.substring(cap[0].length); 1082 this.tokens.push({ 1083 type: this.options.sanitize 1084 ? 'paragraph' 1085 : 'html', 1086 pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', 1087 text: cap[0] 1088 }); 1089 continue; 1090 } 1091 1092 // def 1093 if ((!bq && top) && (cap = this.rules.def.exec(src))) { 1094 src = src.substring(cap[0].length); 1095 this.tokens.links[cap[1].toLowerCase()] = { 1096 href: cap[2], 1097 title: cap[3] 1098 }; 1099 continue; 1100 } 1101 1102 // table (gfm) 1103 if (top && (cap = this.rules.table.exec(src))) { 1104 src = src.substring(cap[0].length); 1105 1106 item = { 1107 type: 'table', 1108 header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 1109 align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 1110 cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') 1111 }; 1112 1113 for (i = 0; i < item.align.length; i++) { 1114 if (/^ *-+: *$/.test(item.align[i])) { 1115 item.align[i] = 'right'; 1116 } else if (/^ *:-+: *$/.test(item.align[i])) { 1117 item.align[i] = 'center'; 1118 } else if (/^ *:-+ *$/.test(item.align[i])) { 1119 item.align[i] = 'left'; 1120 } else { 1121 item.align[i] = null; 1122 } 1123 } 1124 1125 for (i = 0; i < item.cells.length; i++) { 1126 item.cells[i] = item.cells[i] 1127 .replace(/^ *\| *| *\| *$/g, '') 1128 .split(/ *\| */); 1129 } 1130 1131 this.tokens.push(item); 1132 1133 continue; 1134 } 1135 1136 // top-level paragraph 1137 if (top && (cap = this.rules.paragraph.exec(src))) { 1138 src = src.substring(cap[0].length); 1139 this.tokens.push({ 1140 type: 'paragraph', 1141 text: cap[1].charAt(cap[1].length - 1) === '\n' 1142 ? cap[1].slice(0, -1) 1143 : cap[1] 1144 }); 1145 continue; 1146 } 1147 1148 // text 1149 if (cap = this.rules.text.exec(src)) { 1150 // Top-level should never reach here. 1151 src = src.substring(cap[0].length); 1152 this.tokens.push({ 1153 type: 'text', 1154 text: cap[0] 1155 }); 1156 continue; 1157 } 1158 1159 if (src) { 1160 throw new 1161 Error('Infinite loop on byte: ' + src.charCodeAt(0)); 1162 } 1163 } 1164 1165 return this.tokens; 1166 }; 1167 1168 /** 1169 * Inline-Level Grammar 1170 */ 1171 1172 var inline = { 1173 escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 1174 autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, 1175 url: noop, 1176 tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, 1177 link: /^!?\[(inside)\]\(href\)/, 1178 reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 1179 nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, 1180 strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 1181 em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, 1182 code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, 1183 br: /^ {2,}\n(?!\s*$)/, 1184 del: noop, 1185 text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/ 1186 }; 1187 1188 inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/; 1189 inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/; 1190 1191 inline.link = replace(inline.link) 1192 ('inside', inline._inside) 1193 ('href', inline._href) 1194 (); 1195 1196 inline.reflink = replace(inline.reflink) 1197 ('inside', inline._inside) 1198 (); 1199 1200 /** 1201 * Normal Inline Grammar 1202 */ 1203 1204 inline.normal = merge({}, inline); 1205 1206 /** 1207 * Pedantic Inline Grammar 1208 */ 1209 1210 inline.pedantic = merge({}, inline.normal, { 1211 strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 1212 em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ 1213 }); 1214 1215 /** 1216 * GFM Inline Grammar 1217 */ 1218 1219 inline.gfm = merge({}, inline.normal, { 1220 escape: replace(inline.escape)('])', '~|])')(), 1221 url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, 1222 del: /^~~(?=\S)([\s\S]*?\S)~~/, 1223 text: replace(inline.text) 1224 (']|', '~]|') 1225 ('|', '|https?://|') 1226 () 1227 }); 1228 1229 /** 1230 * GFM + Line Breaks Inline Grammar 1231 */ 1232 1233 inline.breaks = merge({}, inline.gfm, { 1234 br: replace(inline.br)('{2,}', '*')(), 1235 text: replace(inline.gfm.text)('{2,}', '*')() 1236 }); 1237 1238 /** 1239 * Inline Lexer & Compiler 1240 */ 1241 1242 function InlineLexer(links, options) { 1243 this.options = options || marked.defaults; 1244 this.links = links; 1245 this.rules = inline.normal; 1246 this.renderer = this.options.renderer || new Renderer; 1247 this.renderer.options = this.options; 1248 1249 if (!this.links) { 1250 throw new 1251 Error('Tokens array requires a `links` property.'); 1252 } 1253 1254 if (this.options.gfm) { 1255 if (this.options.breaks) { 1256 this.rules = inline.breaks; 1257 } else { 1258 this.rules = inline.gfm; 1259 } 1260 } else if (this.options.pedantic) { 1261 this.rules = inline.pedantic; 1262 } 1263 } 1264 1265 /** 1266 * Expose Inline Rules 1267 */ 1268 1269 InlineLexer.rules = inline; 1270 1271 /** 1272 * Static Lexing/Compiling Method 1273 */ 1274 1275 InlineLexer.output = function(src, links, options) { 1276 var inline = new InlineLexer(links, options); 1277 return inline.output(src); 1278 }; 1279 1280 /** 1281 * Lexing/Compiling 1282 */ 1283 1284 InlineLexer.prototype.output = function(src) { 1285 var out = '' 1286 , link 1287 , text 1288 , href 1289 , cap; 1290 1291 while (src) { 1292 // escape 1293 if (cap = this.rules.escape.exec(src)) { 1294 src = src.substring(cap[0].length); 1295 out += cap[1]; 1296 continue; 1297 } 1298 1299 // autolink 1300 if (cap = this.rules.autolink.exec(src)) { 1301 src = src.substring(cap[0].length); 1302 if (cap[2] === '@') { 1303 text = cap[1].charAt(6) === ':' 1304 ? this.mangle(cap[1].substring(7)) 1305 : this.mangle(cap[1]); 1306 href = this.mangle('mailto:') + text; 1307 } else { 1308 text = escape(cap[1]); 1309 href = text; 1310 } 1311 out += this.renderer.link(href, null, text); 1312 continue; 1313 } 1314 1315 // url (gfm) 1316 if (!this.inLink && (cap = this.rules.url.exec(src))) { 1317 src = src.substring(cap[0].length); 1318 text = escape(cap[1]); 1319 href = text; 1320 out += this.renderer.link(href, null, text); 1321 continue; 1322 } 1323 1324 // tag 1325 if (cap = this.rules.tag.exec(src)) { 1326 if (!this.inLink && /^<a /i.test(cap[0])) { 1327 this.inLink = true; 1328 } else if (this.inLink && /^<\/a>/i.test(cap[0])) { 1329 this.inLink = false; 1330 } 1331 src = src.substring(cap[0].length); 1332 out += this.options.sanitize 1333 ? escape(cap[0]) 1334 : cap[0]; 1335 continue; 1336 } 1337 1338 // link 1339 if (cap = this.rules.link.exec(src)) { 1340 src = src.substring(cap[0].length); 1341 this.inLink = true; 1342 out += this.outputLink(cap, { 1343 href: cap[2], 1344 title: cap[3] 1345 }); 1346 this.inLink = false; 1347 continue; 1348 } 1349 1350 // reflink, nolink 1351 if ((cap = this.rules.reflink.exec(src)) 1352 || (cap = this.rules.nolink.exec(src))) { 1353 src = src.substring(cap[0].length); 1354 link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 1355 link = this.links[link.toLowerCase()]; 1356 if (!link || !link.href) { 1357 out += cap[0].charAt(0); 1358 src = cap[0].substring(1) + src; 1359 continue; 1360 } 1361 this.inLink = true; 1362 out += this.outputLink(cap, link); 1363 this.inLink = false; 1364 continue; 1365 } 1366 1367 // strong 1368 if (cap = this.rules.strong.exec(src)) { 1369 src = src.substring(cap[0].length); 1370 out += this.renderer.strong(this.output(cap[2] || cap[1])); 1371 continue; 1372 } 1373 1374 // em 1375 if (cap = this.rules.em.exec(src)) { 1376 src = src.substring(cap[0].length); 1377 out += this.renderer.em(this.output(cap[2] || cap[1])); 1378 continue; 1379 } 1380 1381 // code 1382 if (cap = this.rules.code.exec(src)) { 1383 src = src.substring(cap[0].length); 1384 out += this.renderer.codespan(escape(cap[2], true)); 1385 continue; 1386 } 1387 1388 // br 1389 if (cap = this.rules.br.exec(src)) { 1390 src = src.substring(cap[0].length); 1391 out += this.renderer.br(); 1392 continue; 1393 } 1394 1395 // del (gfm) 1396 if (cap = this.rules.del.exec(src)) { 1397 src = src.substring(cap[0].length); 1398 out += this.renderer.del(this.output(cap[1])); 1399 continue; 1400 } 1401 1402 // text 1403 if (cap = this.rules.text.exec(src)) { 1404 src = src.substring(cap[0].length); 1405 out += escape(this.smartypants(cap[0])); 1406 continue; 1407 } 1408 1409 if (src) { 1410 throw new 1411 Error('Infinite loop on byte: ' + src.charCodeAt(0)); 1412 } 1413 } 1414 1415 return out; 1416 }; 1417 1418 /** 1419 * Compile Link 1420 */ 1421 1422 InlineLexer.prototype.outputLink = function(cap, link) { 1423 var href = escape(link.href) 1424 , title = link.title ? escape(link.title) : null; 1425 1426 return cap[0].charAt(0) !== '!' 1427 ? this.renderer.link(href, title, this.output(cap[1])) 1428 : this.renderer.image(href, title, escape(cap[1])); 1429 }; 1430 1431 /** 1432 * Smartypants Transformations 1433 */ 1434 1435 InlineLexer.prototype.smartypants = function(text) { 1436 if (!this.options.smartypants) return text; 1437 return text 1438 // em-dashes 1439 .replace(/--/g, '\u2014') 1440 // opening singles 1441 .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 1442 // closing singles & apostrophes 1443 .replace(/'/g, '\u2019') 1444 // opening doubles 1445 .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 1446 // closing doubles 1447 .replace(/"/g, '\u201d') 1448 // ellipses 1449 .replace(/\.{3}/g, '\u2026'); 1450 }; 1451 1452 /** 1453 * Mangle Links 1454 */ 1455 1456 InlineLexer.prototype.mangle = function(text) { 1457 var out = '' 1458 , l = text.length 1459 , i = 0 1460 , ch; 1461 1462 for (; i < l; i++) { 1463 ch = text.charCodeAt(i); 1464 if (Math.random() > 0.5) { 1465 ch = 'x' + ch.toString(16); 1466 } 1467 out += '&#' + ch + ';'; 1468 } 1469 1470 return out; 1471 }; 1472 1473 /** 1474 * Renderer 1475 */ 1476 1477 function Renderer(options) { 1478 this.options = options || {}; 1479 } 1480 1481 Renderer.prototype.code = function(code, lang, escaped) { 1482 if (this.options.highlight) { 1483 var out = this.options.highlight(code, lang); 1484 if (out != null && out !== code) { 1485 escaped = true; 1486 code = out; 1487 } 1488 } 1489 1490 if (!lang) { 1491 return '<pre><code>' 1492 + (escaped ? code : escape(code, true)) 1493 + '\n</code></pre>'; 1494 } 1495 1496 return '<pre><code class="' 1497 + this.options.langPrefix 1498 + escape(lang, true) 1499 + '">' 1500 + (escaped ? code : escape(code, true)) 1501 + '\n</code></pre>\n'; 1502 }; 1503 1504 Renderer.prototype.blockquote = function(quote) { 1505 return '<blockquote>\n' + quote + '</blockquote>\n'; 1506 }; 1507 1508 Renderer.prototype.html = function(html) { 1509 return html; 1510 }; 1511 1512 Renderer.prototype.heading = function(text, level, raw) { 1513 return '<h' 1514 + level 1515 + ' id="' 1516 + this.options.headerPrefix 1517 + raw.toLowerCase().replace(/[^\w]+/g, '-') 1518 + '">' 1519 + text 1520 + '</h' 1521 + level 1522 + '>\n'; 1523 }; 1524 1525 Renderer.prototype.hr = function() { 1526 return this.options.xhtml ? '<hr/>\n' : '<hr>\n'; 1527 }; 1528 1529 Renderer.prototype.list = function(body, ordered) { 1530 var type = ordered ? 'ol' : 'ul'; 1531 return '<' + type + '>\n' + body + '</' + type + '>\n'; 1532 }; 1533 1534 Renderer.prototype.listitem = function(text) { 1535 return '<li>' + text + '</li>\n'; 1536 }; 1537 1538 Renderer.prototype.paragraph = function(text) { 1539 return '<p>' + text + '</p>\n'; 1540 }; 1541 1542 Renderer.prototype.table = function(header, body) { 1543 return '<table>\n' 1544 + '<thead>\n' 1545 + header 1546 + '</thead>\n' 1547 + '<tbody>\n' 1548 + body 1549 + '</tbody>\n' 1550 + '</table>\n'; 1551 }; 1552 1553 Renderer.prototype.tablerow = function(content) { 1554 return '<tr>\n' + content + '</tr>\n'; 1555 }; 1556 1557 Renderer.prototype.tablecell = function(content, flags) { 1558 var type = flags.header ? 'th' : 'td'; 1559 var tag = flags.align 1560 ? '<' + type + ' style="text-align:' + flags.align + '">' 1561 : '<' + type + '>'; 1562 return tag + content + '</' + type + '>\n'; 1563 }; 1564 1565 // span level renderer 1566 Renderer.prototype.strong = function(text) { 1567 return '<strong>' + text + '</strong>'; 1568 }; 1569 1570 Renderer.prototype.em = function(text) { 1571 return '<em>' + text + '</em>'; 1572 }; 1573 1574 Renderer.prototype.codespan = function(text) { 1575 return '<code>' + text + '</code>'; 1576 }; 1577 1578 Renderer.prototype.br = function() { 1579 return this.options.xhtml ? '<br/>' : '<br>'; 1580 }; 1581 1582 Renderer.prototype.del = function(text) { 1583 return '<del>' + text + '</del>'; 1584 }; 1585 1586 Renderer.prototype.link = function(href, title, text) { 1587 if (this.options.sanitize) { 1588 try { 1589 var prot = decodeURIComponent(unescape(href)) 1590 .replace(/[^\w:]/g, '') 1591 .toLowerCase(); 1592 } catch (e) { 1593 return ''; 1594 } 1595 if (prot.indexOf('javascript:') === 0) { 1596 return ''; 1597 } 1598 } 1599 var out = '<a href="' + href + '"'; 1600 if (title) { 1601 out += ' title="' + title + '"'; 1602 } 1603 out += '>' + text + '</a>'; 1604 return out; 1605 }; 1606 1607 Renderer.prototype.image = function(href, title, text) { 1608 var out = '<img src="' + href + '" alt="' + text + '"'; 1609 if (title) { 1610 out += ' title="' + title + '"'; 1611 } 1612 out += this.options.xhtml ? '/>' : '>'; 1613 return out; 1614 }; 1615 1616 /** 1617 * Parsing & Compiling 1618 */ 1619 1620 function Parser(options) { 1621 this.tokens = []; 1622 this.token = null; 1623 this.options = options || marked.defaults; 1624 this.options.renderer = this.options.renderer || new Renderer; 1625 this.renderer = this.options.renderer; 1626 this.renderer.options = this.options; 1627 } 1628 1629 /** 1630 * Static Parse Method 1631 */ 1632 1633 Parser.parse = function(src, options, renderer) { 1634 var parser = new Parser(options, renderer); 1635 return parser.parse(src); 1636 }; 1637 1638 /** 1639 * Parse Loop 1640 */ 1641 1642 Parser.prototype.parse = function(src) { 1643 this.inline = new InlineLexer(src.links, this.options, this.renderer); 1644 this.tokens = src.reverse(); 1645 1646 var out = ''; 1647 while (this.next()) { 1648 out += this.tok(); 1649 } 1650 1651 return out; 1652 }; 1653 1654 /** 1655 * Next Token 1656 */ 1657 1658 Parser.prototype.next = function() { 1659 return this.token = this.tokens.pop(); 1660 }; 1661 1662 /** 1663 * Preview Next Token 1664 */ 1665 1666 Parser.prototype.peek = function() { 1667 return this.tokens[this.tokens.length - 1] || 0; 1668 }; 1669 1670 /** 1671 * Parse Text Tokens 1672 */ 1673 1674 Parser.prototype.parseText = function() { 1675 var body = this.token.text; 1676 1677 while (this.peek().type === 'text') { 1678 body += '\n' + this.next().text; 1679 } 1680 1681 return this.inline.output(body); 1682 }; 1683 1684 /** 1685 * Parse Current Token 1686 */ 1687 1688 Parser.prototype.tok = function() { 1689 switch (this.token.type) { 1690 case 'space': { 1691 return ''; 1692 } 1693 case 'hr': { 1694 return this.renderer.hr(); 1695 } 1696 case 'heading': { 1697 return this.renderer.heading( 1698 this.inline.output(this.token.text), 1699 this.token.depth, 1700 this.token.text); 1701 } 1702 case 'code': { 1703 return this.renderer.code(this.token.text, 1704 this.token.lang, 1705 this.token.escaped); 1706 } 1707 case 'table': { 1708 var header = '' 1709 , body = '' 1710 , i 1711 , row 1712 , cell 1713 , flags 1714 , j; 1715 1716 // header 1717 cell = ''; 1718 for (i = 0; i < this.token.header.length; i++) { 1719 flags = { header: true, align: this.token.align[i] }; 1720 cell += this.renderer.tablecell( 1721 this.inline.output(this.token.header[i]), 1722 { header: true, align: this.token.align[i] } 1723 ); 1724 } 1725 header += this.renderer.tablerow(cell); 1726 1727 for (i = 0; i < this.token.cells.length; i++) { 1728 row = this.token.cells[i]; 1729 1730 cell = ''; 1731 for (j = 0; j < row.length; j++) { 1732 cell += this.renderer.tablecell( 1733 this.inline.output(row[j]), 1734 { header: false, align: this.token.align[j] } 1735 ); 1736 } 1737 1738 body += this.renderer.tablerow(cell); 1739 } 1740 return this.renderer.table(header, body); 1741 } 1742 case 'blockquote_start': { 1743 var body = ''; 1744 1745 while (this.next().type !== 'blockquote_end') { 1746 body += this.tok(); 1747 } 1748 1749 return this.renderer.blockquote(body); 1750 } 1751 case 'list_start': { 1752 var body = '' 1753 , ordered = this.token.ordered; 1754 1755 while (this.next().type !== 'list_end') { 1756 body += this.tok(); 1757 } 1758 1759 return this.renderer.list(body, ordered); 1760 } 1761 case 'list_item_start': { 1762 var body = ''; 1763 1764 while (this.next().type !== 'list_item_end') { 1765 body += this.token.type === 'text' 1766 ? this.parseText() 1767 : this.tok(); 1768 } 1769 1770 return this.renderer.listitem(body); 1771 } 1772 case 'loose_item_start': { 1773 var body = ''; 1774 1775 while (this.next().type !== 'list_item_end') { 1776 body += this.tok(); 1777 } 1778 1779 return this.renderer.listitem(body); 1780 } 1781 case 'html': { 1782 var html = !this.token.pre && !this.options.pedantic 1783 ? this.inline.output(this.token.text) 1784 : this.token.text; 1785 return this.renderer.html(html); 1786 } 1787 case 'paragraph': { 1788 return this.renderer.paragraph(this.inline.output(this.token.text)); 1789 } 1790 case 'text': { 1791 return this.renderer.paragraph(this.parseText()); 1792 } 1793 } 1794 }; 1795 1796 /** 1797 * Helpers 1798 */ 1799 1800 function escape(html, encode) { 1801 return html 1802 .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 1803 .replace(/</g, '<') 1804 .replace(/>/g, '>') 1805 .replace(/"/g, '"') 1806 .replace(/'/g, '''); 1807 } 1808 1809 function unescape(html) { 1810 return html.replace(/&([#\w]+);/g, function(_, n) { 1811 n = n.toLowerCase(); 1812 if (n === 'colon') return ':'; 1813 if (n.charAt(0) === '#') { 1814 return n.charAt(1) === 'x' 1815 ? String.fromCharCode(parseInt(n.substring(2), 16)) 1816 : String.fromCharCode(+n.substring(1)); 1817 } 1818 return ''; 1819 }); 1820 } 1821 1822 function replace(regex, opt) { 1823 regex = regex.source; 1824 opt = opt || ''; 1825 return function self(name, val) { 1826 if (!name) return new RegExp(regex, opt); 1827 val = val.source || val; 1828 val = val.replace(/(^|[^\[])\^/g, '$1'); 1829 regex = regex.replace(name, val); 1830 return self; 1831 }; 1832 } 1833 1834 function noop() {} 1835 noop.exec = noop; 1836 1837 function merge(obj) { 1838 var i = 1 1839 , target 1840 , key; 1841 1842 for (; i < arguments.length; i++) { 1843 target = arguments[i]; 1844 for (key in target) { 1845 if (Object.prototype.hasOwnProperty.call(target, key)) { 1846 obj[key] = target[key]; 1847 } 1848 } 1849 } 1850 1851 return obj; 1852 } 1853 1854 1855 /** 1856 * Marked 1857 */ 1858 1859 function marked(src, opt, callback) { 1860 if (callback || typeof opt === 'function') { 1861 if (!callback) { 1862 callback = opt; 1863 opt = null; 1864 } 1865 1866 opt = merge({}, marked.defaults, opt || {}); 1867 1868 var highlight = opt.highlight 1869 , tokens 1870 , pending 1871 , i = 0; 1872 1873 try { 1874 tokens = Lexer.lex(src, opt) 1875 } catch (e) { 1876 return callback(e); 1877 } 1878 1879 pending = tokens.length; 1880 1881 var done = function() { 1882 var out, err; 1883 1884 try { 1885 out = Parser.parse(tokens, opt); 1886 } catch (e) { 1887 err = e; 1888 } 1889 1890 opt.highlight = highlight; 1891 1892 return err 1893 ? callback(err) 1894 : callback(null, out); 1895 }; 1896 1897 if (!highlight || highlight.length < 3) { 1898 return done(); 1899 } 1900 1901 delete opt.highlight; 1902 1903 if (!pending) return done(); 1904 1905 for (; i < tokens.length; i++) { 1906 (function(token) { 1907 if (token.type !== 'code') { 1908 return --pending || done(); 1909 } 1910 return highlight(token.text, token.lang, function(err, code) { 1911 if (code == null || code === token.text) { 1912 return --pending || done(); 1913 } 1914 token.text = code; 1915 token.escaped = true; 1916 --pending || done(); 1917 }); 1918 })(tokens[i]); 1919 } 1920 1921 return; 1922 } 1923 try { 1924 if (opt) opt = merge({}, marked.defaults, opt); 1925 return Parser.parse(Lexer.lex(src, opt), opt); 1926 } catch (e) { 1927 e.message += '\nPlease report this to https://github.com/chjj/marked.'; 1928 if ((opt || marked.defaults).silent) { 1929 return '<p>An error occured:</p><pre>' 1930 + escape(e.message + '', true) 1931 + '</pre>'; 1932 } 1933 throw e; 1934 } 1935 } 1936 1937 /** 1938 * Options 1939 */ 1940 1941 marked.options = 1942 marked.setOptions = function(opt) { 1943 merge(marked.defaults, opt); 1944 return marked; 1945 }; 1946 1947 marked.defaults = { 1948 gfm: true, 1949 tables: true, 1950 breaks: false, 1951 pedantic: false, 1952 sanitize: false, 1953 smartLists: false, 1954 silent: false, 1955 highlight: null, 1956 langPrefix: 'lang-', 1957 smartypants: false, 1958 headerPrefix: '', 1959 renderer: new Renderer, 1960 xhtml: false 1961 }; 1962 1963 /** 1964 * Expose 1965 */ 1966 1967 marked.Parser = Parser; 1968 marked.parser = Parser.parse; 1969 1970 marked.Renderer = Renderer; 1971 1972 marked.Lexer = Lexer; 1973 marked.lexer = Lexer.lex; 1974 1975 marked.InlineLexer = InlineLexer; 1976 marked.inlineLexer = InlineLexer.output; 1977 1978 marked.parse = marked; 1979 1980 if (typeof exports === 'object') { 1981 module.exports = marked; 1982 } else if (typeof define === 'function' && define.amd) { 1983 define(function() { return marked; }); 1984 } else { 1985 this.marked = marked; 1986 } 1987 1988 }).call(function() { 1989 return this || (typeof window !== 'undefined' ? window : global); 1990 }()); 1991 ; 1992 1993 1994 Polymer('marked-element', { 1995 1996 text: '', 1997 1998 attached: function() { 1999 marked.setOptions({ 2000 highlight: this.highlight.bind(this) 2001 }); 2002 if (!this.text) { 2003 this.text = this.innerHTML; 2004 } 2005 }, 2006 2007 textChanged: function () { 2008 this.innerHTML = marked(this.text); 2009 }, 2010 2011 highlight: function(code, lang) { 2012 var event = this.fire('marked-js-highlight', {code: code, lang: lang}); 2013 return event.detail.code || code; 2014 } 2015 2016 }); 2017 2018 ; 2019 // Copyright (C) 2006 Google Inc. 2020 // 2021 // Licensed under the Apache License, Version 2.0 (the "License"); 2022 // you may not use this file except in compliance with the License. 2023 // You may obtain a copy of the License at 2024 // 2025 // http://www.apache.org/licenses/LICENSE-2.0 2026 // 2027 // Unless required by applicable law or agreed to in writing, software 2028 // distributed under the License is distributed on an "AS IS" BASIS, 2029 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2030 // See the License for the specific language governing permissions and 2031 // limitations under the License. 2032 2033 2034 /** 2035 * @fileoverview 2036 * some functions for browser-side pretty printing of code contained in html. 2037 * 2038 * <p> 2039 * For a fairly comprehensive set of languages see the 2040 * <a href="http://google-code-prettify.googlecode.com/svn/trunk/README.html#langs">README</a> 2041 * file that came with this source. At a minimum, the lexer should work on a 2042 * number of languages including C and friends, Java, Python, Bash, SQL, HTML, 2043 * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk 2044 * and a subset of Perl, but, because of commenting conventions, doesn't work on 2045 * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. 2046 * <p> 2047 * Usage: <ol> 2048 * <li> include this source file in an html page via 2049 * {@code <script type="text/javascript" src="/path/to/prettify.js"><\/script>} 2050 * <li> define style rules. See the example page for examples. 2051 * <li> mark the {@code <pre>} and {@code <code>} tags in your source with 2052 * {@code class=prettyprint.} 2053 * You can also use the (html deprecated) {@code <xmp>} tag, but the pretty 2054 * printer needs to do more substantial DOM manipulations to support that, so 2055 * some css styles may not be preserved. 2056 * </ol> 2057 * That's it. I wanted to keep the API as simple as possible, so there's no 2058 * need to specify which language the code is in, but if you wish, you can add 2059 * another class to the {@code <pre>} or {@code <code>} element to specify the 2060 * language, as in {@code <pre class="prettyprint lang-java">}. Any class that 2061 * starts with "lang-" followed by a file extension, specifies the file type. 2062 * See the "lang-*.js" files in this directory for code that implements 2063 * per-language file handlers. 2064 * <p> 2065 * Change log:<br> 2066 * cbeust, 2006/08/22 2067 * <blockquote> 2068 * Java annotations (start with "@") are now captured as literals ("lit") 2069 * </blockquote> 2070 * @requires console 2071 */ 2072 2073 // JSLint declarations 2074 /*global console, document, navigator, setTimeout, window, define */ 2075 2076 /** 2077 * Split {@code prettyPrint} into multiple timeouts so as not to interfere with 2078 * UI events. 2079 * If set to {@code false}, {@code prettyPrint()} is synchronous. 2080 */ 2081 window['PR_SHOULD_USE_CONTINUATION'] = true; 2082 2083 /** 2084 * Find all the {@code <pre>} and {@code <code>} tags in the DOM with 2085 * {@code class=prettyprint} and prettify them. 2086 * 2087 * @param {Function?} opt_whenDone if specified, called when the last entry 2088 * has been finished. 2089 */ 2090 var prettyPrintOne; 2091 /** 2092 * Pretty print a chunk of code. 2093 * 2094 * @param {string} sourceCodeHtml code as html 2095 * @return {string} code as html, but prettier 2096 */ 2097 var prettyPrint; 2098 2099 2100 (function () { 2101 var win = window; 2102 // Keyword lists for various languages. 2103 // We use things that coerce to strings to make them compact when minified 2104 // and to defeat aggressive optimizers that fold large string constants. 2105 var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"]; 2106 var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," + 2107 "double,enum,extern,float,goto,int,long,register,short,signed,sizeof," + 2108 "static,struct,switch,typedef,union,unsigned,void,volatile"]; 2109 var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," + 2110 "new,operator,private,protected,public,this,throw,true,try,typeof"]; 2111 var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," + 2112 "concept,concept_map,const_cast,constexpr,decltype," + 2113 "dynamic_cast,explicit,export,friend,inline,late_check," + 2114 "mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast," + 2115 "template,typeid,typename,using,virtual,where"]; 2116 var JAVA_KEYWORDS = [COMMON_KEYWORDS, 2117 "abstract,boolean,byte,extends,final,finally,implements,import," + 2118 "instanceof,null,native,package,strictfp,super,synchronized,throws," + 2119 "transient"]; 2120 var CSHARP_KEYWORDS = [JAVA_KEYWORDS, 2121 "as,base,by,checked,decimal,delegate,descending,dynamic,event," + 2122 "fixed,foreach,from,group,implicit,in,interface,internal,into,is,let," + 2123 "lock,object,out,override,orderby,params,partial,readonly,ref,sbyte," + 2124 "sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort," + 2125 "var,virtual,where"]; 2126 var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," + 2127 "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," + 2128 "throw,true,try,unless,until,when,while,yes"; 2129 var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS, 2130 "debugger,eval,export,function,get,null,set,undefined,var,with," + 2131 "Infinity,NaN"]; 2132 var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," + 2133 "goto,if,import,last,local,my,next,no,our,print,package,redo,require," + 2134 "sub,undef,unless,until,use,wantarray,while,BEGIN,END"; 2135 var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," + 2136 "elif,except,exec,finally,from,global,import,in,is,lambda," + 2137 "nonlocal,not,or,pass,print,raise,try,with,yield," + 2138 "False,True,None"]; 2139 var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," + 2140 "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," + 2141 "rescue,retry,self,super,then,true,undef,unless,until,when,yield," + 2142 "BEGIN,END"]; 2143 var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," + 2144 "function,in,local,set,then,until"]; 2145 var ALL_KEYWORDS = [ 2146 CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS + 2147 PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS]; 2148 var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/; 2149 2150 // token style names. correspond to css classes 2151 /** 2152 * token style for a string literal 2153 * @const 2154 */ 2155 var PR_STRING = 'str'; 2156 /** 2157 * token style for a keyword 2158 * @const 2159 */ 2160 var PR_KEYWORD = 'kwd'; 2161 /** 2162 * token style for a comment 2163 * @const 2164 */ 2165 var PR_COMMENT = 'com'; 2166 /** 2167 * token style for a type 2168 * @const 2169 */ 2170 var PR_TYPE = 'typ'; 2171 /** 2172 * token style for a literal value. e.g. 1, null, true. 2173 * @const 2174 */ 2175 var PR_LITERAL = 'lit'; 2176 /** 2177 * token style for a punctuation string. 2178 * @const 2179 */ 2180 var PR_PUNCTUATION = 'pun'; 2181 /** 2182 * token style for plain text. 2183 * @const 2184 */ 2185 var PR_PLAIN = 'pln'; 2186 2187 /** 2188 * token style for an sgml tag. 2189 * @const 2190 */ 2191 var PR_TAG = 'tag'; 2192 /** 2193 * token style for a markup declaration such as a DOCTYPE. 2194 * @const 2195 */ 2196 var PR_DECLARATION = 'dec'; 2197 /** 2198 * token style for embedded source. 2199 * @const 2200 */ 2201 var PR_SOURCE = 'src'; 2202 /** 2203 * token style for an sgml attribute name. 2204 * @const 2205 */ 2206 var PR_ATTRIB_NAME = 'atn'; 2207 /** 2208 * token style for an sgml attribute value. 2209 * @const 2210 */ 2211 var PR_ATTRIB_VALUE = 'atv'; 2212 2213 /** 2214 * A class that indicates a section of markup that is not code, e.g. to allow 2215 * embedding of line numbers within code listings. 2216 * @const 2217 */ 2218 var PR_NOCODE = 'nocode'; 2219 2220 2221 2222 /** 2223 * A set of tokens that can precede a regular expression literal in 2224 * javascript 2225 * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html 2226 * has the full list, but I've removed ones that might be problematic when 2227 * seen in languages that don't support regular expression literals. 2228 * 2229 * <p>Specifically, I've removed any keywords that can't precede a regexp 2230 * literal in a syntactically legal javascript program, and I've removed the 2231 * "in" keyword since it's not a keyword in many languages, and might be used 2232 * as a count of inches. 2233 * 2234 * <p>The link above does not accurately describe EcmaScript rules since 2235 * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works 2236 * very well in practice. 2237 * 2238 * @private 2239 * @const 2240 */ 2241 var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*'; 2242 2243 // CAVEAT: this does not properly handle the case where a regular 2244 // expression immediately follows another since a regular expression may 2245 // have flags for case-sensitivity and the like. Having regexp tokens 2246 // adjacent is not valid in any language I'm aware of, so I'm punting. 2247 // TODO: maybe style special characters inside a regexp as punctuation. 2248 2249 2250 /** 2251 * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally 2252 * matches the union of the sets of strings matched by the input RegExp. 2253 * Since it matches globally, if the input strings have a start-of-input 2254 * anchor (/^.../), it is ignored for the purposes of unioning. 2255 * @param {Array.<RegExp>} regexs non multiline, non-global regexs. 2256 * @return {RegExp} a global regex. 2257 */ 2258 function combinePrefixPatterns(regexs) { 2259 var capturedGroupIndex = 0; 2260 2261 var needToFoldCase = false; 2262 var ignoreCase = false; 2263 for (var i = 0, n = regexs.length; i < n; ++i) { 2264 var regex = regexs[i]; 2265 if (regex.ignoreCase) { 2266 ignoreCase = true; 2267 } else if (/[a-z]/i.test(regex.source.replace( 2268 /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) { 2269 needToFoldCase = true; 2270 ignoreCase = false; 2271 break; 2272 } 2273 } 2274 2275 var escapeCharToCodeUnit = { 2276 'b': 8, 2277 't': 9, 2278 'n': 0xa, 2279 'v': 0xb, 2280 'f': 0xc, 2281 'r': 0xd 2282 }; 2283 2284 function decodeEscape(charsetPart) { 2285 var cc0 = charsetPart.charCodeAt(0); 2286 if (cc0 !== 92 /* \\ */) { 2287 return cc0; 2288 } 2289 var c1 = charsetPart.charAt(1); 2290 cc0 = escapeCharToCodeUnit[c1]; 2291 if (cc0) { 2292 return cc0; 2293 } else if ('0' <= c1 && c1 <= '7') { 2294 return parseInt(charsetPart.substring(1), 8); 2295 } else if (c1 === 'u' || c1 === 'x') { 2296 return parseInt(charsetPart.substring(2), 16); 2297 } else { 2298 return charsetPart.charCodeAt(1); 2299 } 2300 } 2301 2302 function encodeEscape(charCode) { 2303 if (charCode < 0x20) { 2304 return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16); 2305 } 2306 var ch = String.fromCharCode(charCode); 2307 return (ch === '\\' || ch === '-' || ch === ']' || ch === '^') 2308 ? "\\" + ch : ch; 2309 } 2310 2311 function caseFoldCharset(charSet) { 2312 var charsetParts = charSet.substring(1, charSet.length - 1).match( 2313 new RegExp( 2314 '\\\\u[0-9A-Fa-f]{4}' 2315 + '|\\\\x[0-9A-Fa-f]{2}' 2316 + '|\\\\[0-3][0-7]{0,2}' 2317 + '|\\\\[0-7]{1,2}' 2318 + '|\\\\[\\s\\S]' 2319 + '|-' 2320 + '|[^-\\\\]', 2321 'g')); 2322 var ranges = []; 2323 var inverse = charsetParts[0] === '^'; 2324 2325 var out = ['[']; 2326 if (inverse) { out.push('^'); } 2327 2328 for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) { 2329 var p = charsetParts[i]; 2330 if (/\\[bdsw]/i.test(p)) { // Don't muck with named groups. 2331 out.push(p); 2332 } else { 2333 var start = decodeEscape(p); 2334 var end; 2335 if (i + 2 < n && '-' === charsetParts[i + 1]) { 2336 end = decodeEscape(charsetParts[i + 2]); 2337 i += 2; 2338 } else { 2339 end = start; 2340 } 2341 ranges.push([start, end]); 2342 // If the range might intersect letters, then expand it. 2343 // This case handling is too simplistic. 2344 // It does not deal with non-latin case folding. 2345 // It works for latin source code identifiers though. 2346 if (!(end < 65 || start > 122)) { 2347 if (!(end < 65 || start > 90)) { 2348 ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]); 2349 } 2350 if (!(end < 97 || start > 122)) { 2351 ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]); 2352 } 2353 } 2354 } 2355 } 2356 2357 // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]] 2358 // -> [[1, 12], [14, 14], [16, 17]] 2359 ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); }); 2360 var consolidatedRanges = []; 2361 var lastRange = []; 2362 for (var i = 0; i < ranges.length; ++i) { 2363 var range = ranges[i]; 2364 if (range[0] <= lastRange[1] + 1) { 2365 lastRange[1] = Math.max(lastRange[1], range[1]); 2366 } else { 2367 consolidatedRanges.push(lastRange = range); 2368 } 2369 } 2370 2371 for (var i = 0; i < consolidatedRanges.length; ++i) { 2372 var range = consolidatedRanges[i]; 2373 out.push(encodeEscape(range[0])); 2374 if (range[1] > range[0]) { 2375 if (range[1] + 1 > range[0]) { out.push('-'); } 2376 out.push(encodeEscape(range[1])); 2377 } 2378 } 2379 out.push(']'); 2380 return out.join(''); 2381 } 2382 2383 function allowAnywhereFoldCaseAndRenumberGroups(regex) { 2384 // Split into character sets, escape sequences, punctuation strings 2385 // like ('(', '(?:', ')', '^'), and runs of characters that do not 2386 // include any of the above. 2387 var parts = regex.source.match( 2388 new RegExp( 2389 '(?:' 2390 + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]' // a character set 2391 + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape 2392 + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape 2393 + '|\\\\[0-9]+' // a back-reference or octal escape 2394 + '|\\\\[^ux0-9]' // other escape sequence 2395 + '|\\(\\?[:!=]' // start of a non-capturing group 2396 + '|[\\(\\)\\^]' // start/end of a group, or line start 2397 + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters 2398 + ')', 2399 'g')); 2400 var n = parts.length; 2401 2402 // Maps captured group numbers to the number they will occupy in 2403 // the output or to -1 if that has not been determined, or to 2404 // undefined if they need not be capturing in the output. 2405 var capturedGroups = []; 2406 2407 // Walk over and identify back references to build the capturedGroups 2408 // mapping. 2409 for (var i = 0, groupIndex = 0; i < n; ++i) { 2410 var p = parts[i]; 2411 if (p === '(') { 2412 // groups are 1-indexed, so max group index is count of '(' 2413 ++groupIndex; 2414 } else if ('\\' === p.charAt(0)) { 2415 var decimalValue = +p.substring(1); 2416 if (decimalValue) { 2417 if (decimalValue <= groupIndex) { 2418 capturedGroups[decimalValue] = -1; 2419 } else { 2420 // Replace with an unambiguous escape sequence so that 2421 // an octal escape sequence does not turn into a backreference 2422 // to a capturing group from an earlier regex. 2423 parts[i] = encodeEscape(decimalValue); 2424 } 2425 } 2426 } 2427 } 2428 2429 // Renumber groups and reduce capturing groups to non-capturing groups 2430 // where possible. 2431 for (var i = 1; i < capturedGroups.length; ++i) { 2432 if (-1 === capturedGroups[i]) { 2433 capturedGroups[i] = ++capturedGroupIndex; 2434 } 2435 } 2436 for (var i = 0, groupIndex = 0; i < n; ++i) { 2437 var p = parts[i]; 2438 if (p === '(') { 2439 ++groupIndex; 2440 if (!capturedGroups[groupIndex]) { 2441 parts[i] = '(?:'; 2442 } 2443 } else if ('\\' === p.charAt(0)) { 2444 var decimalValue = +p.substring(1); 2445 if (decimalValue && decimalValue <= groupIndex) { 2446 parts[i] = '\\' + capturedGroups[decimalValue]; 2447 } 2448 } 2449 } 2450 2451 // Remove any prefix anchors so that the output will match anywhere. 2452 // ^^ really does mean an anchored match though. 2453 for (var i = 0; i < n; ++i) { 2454 if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; } 2455 } 2456 2457 // Expand letters to groups to handle mixing of case-sensitive and 2458 // case-insensitive patterns if necessary. 2459 if (regex.ignoreCase && needToFoldCase) { 2460 for (var i = 0; i < n; ++i) { 2461 var p = parts[i]; 2462 var ch0 = p.charAt(0); 2463 if (p.length >= 2 && ch0 === '[') { 2464 parts[i] = caseFoldCharset(p); 2465 } else if (ch0 !== '\\') { 2466 // TODO: handle letters in numeric escapes. 2467 parts[i] = p.replace( 2468 /[a-zA-Z]/g, 2469 function (ch) { 2470 var cc = ch.charCodeAt(0); 2471 return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']'; 2472 }); 2473 } 2474 } 2475 } 2476 2477 return parts.join(''); 2478 } 2479 2480 var rewritten = []; 2481 for (var i = 0, n = regexs.length; i < n; ++i) { 2482 var regex = regexs[i]; 2483 if (regex.global || regex.multiline) { throw new Error('' + regex); } 2484 rewritten.push( 2485 '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')'); 2486 } 2487 2488 return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g'); 2489 } 2490 2491 2492 /** 2493 * Split markup into a string of source code and an array mapping ranges in 2494 * that string to the text nodes in which they appear. 2495 * 2496 * <p> 2497 * The HTML DOM structure:</p> 2498 * <pre> 2499 * (Element "p" 2500 * (Element "b" 2501 * (Text "print ")) ; #1 2502 * (Text "'Hello '") ; #2 2503 * (Element "br") ; #3 2504 * (Text " + 'World';")) ; #4 2505 * </pre> 2506 * <p> 2507 * corresponds to the HTML 2508 * {@code <p><b>print </b>'Hello '<br> + 'World';</p>}.</p> 2509 * 2510 * <p> 2511 * It will produce the output:</p> 2512 * <pre> 2513 * { 2514 * sourceCode: "print 'Hello '\n + 'World';", 2515 * // 1 2 2516 * // 012345678901234 5678901234567 2517 * spans: [0, #1, 6, #2, 14, #3, 15, #4] 2518 * } 2519 * </pre> 2520 * <p> 2521 * where #1 is a reference to the {@code "print "} text node above, and so 2522 * on for the other text nodes. 2523 * </p> 2524 * 2525 * <p> 2526 * The {@code} spans array is an array of pairs. Even elements are the start 2527 * indices of substrings, and odd elements are the text nodes (or BR elements) 2528 * that contain the text for those substrings. 2529 * Substrings continue until the next index or the end of the source. 2530 * </p> 2531 * 2532 * @param {Node} node an HTML DOM subtree containing source-code. 2533 * @param {boolean} isPreformatted true if white-space in text nodes should 2534 * be considered significant. 2535 * @return {Object} source code and the text nodes in which they occur. 2536 */ 2537 function extractSourceSpans(node, isPreformatted) { 2538 var nocode = /(?:^|\s)nocode(?:\s|$)/; 2539 2540 var chunks = []; 2541 var length = 0; 2542 var spans = []; 2543 var k = 0; 2544 2545 function walk(node) { 2546 switch (node.nodeType) { 2547 case 1: // Element 2548 if (nocode.test(node.className)) { return; } 2549 for (var child = node.firstChild; child; child = child.nextSibling) { 2550 walk(child); 2551 } 2552 var nodeName = node.nodeName.toLowerCase(); 2553 if ('br' === nodeName || 'li' === nodeName) { 2554 chunks[k] = '\n'; 2555 spans[k << 1] = length++; 2556 spans[(k++ << 1) | 1] = node; 2557 } 2558 break; 2559 case 3: case 4: // Text 2560 var text = node.nodeValue; 2561 if (text.length) { 2562 if (!isPreformatted) { 2563 text = text.replace(/[ \t\r\n]+/g, ' '); 2564 } else { 2565 text = text.replace(/\r\n?/g, '\n'); // Normalize newlines. 2566 } 2567 // TODO: handle tabs here? 2568 chunks[k] = text; 2569 spans[k << 1] = length; 2570 length += text.length; 2571 spans[(k++ << 1) | 1] = node; 2572 } 2573 break; 2574 } 2575 } 2576 2577 walk(node); 2578 2579 return { 2580 sourceCode: chunks.join('').replace(/\n$/, ''), 2581 spans: spans 2582 }; 2583 } 2584 2585 2586 /** 2587 * Apply the given language handler to sourceCode and add the resulting 2588 * decorations to out. 2589 * @param {number} basePos the index of sourceCode within the chunk of source 2590 * whose decorations are already present on out. 2591 */ 2592 function appendDecorations(basePos, sourceCode, langHandler, out) { 2593 if (!sourceCode) { return; } 2594 var job = { 2595 sourceCode: sourceCode, 2596 basePos: basePos 2597 }; 2598 langHandler(job); 2599 out.push.apply(out, job.decorations); 2600 } 2601 2602 var notWs = /\S/; 2603 2604 /** 2605 * Given an element, if it contains only one child element and any text nodes 2606 * it contains contain only space characters, return the sole child element. 2607 * Otherwise returns undefined. 2608 * <p> 2609 * This is meant to return the CODE element in {@code <pre><code ...>} when 2610 * there is a single child element that contains all the non-space textual 2611 * content, but not to return anything where there are multiple child elements 2612 * as in {@code <pre><code>...</code><code>...</code></pre>} or when there 2613 * is textual content. 2614 */ 2615 function childContentWrapper(element) { 2616 var wrapper = undefined; 2617 for (var c = element.firstChild; c; c = c.nextSibling) { 2618 var type = c.nodeType; 2619 wrapper = (type === 1) // Element Node 2620 ? (wrapper ? element : c) 2621 : (type === 3) // Text Node 2622 ? (notWs.test(c.nodeValue) ? element : wrapper) 2623 : wrapper; 2624 } 2625 return wrapper === element ? undefined : wrapper; 2626 } 2627 2628 /** Given triples of [style, pattern, context] returns a lexing function, 2629 * The lexing function interprets the patterns to find token boundaries and 2630 * returns a decoration list of the form 2631 * [index_0, style_0, index_1, style_1, ..., index_n, style_n] 2632 * where index_n is an index into the sourceCode, and style_n is a style 2633 * constant like PR_PLAIN. index_n-1 <= index_n, and style_n-1 applies to 2634 * all characters in sourceCode[index_n-1:index_n]. 2635 * 2636 * The stylePatterns is a list whose elements have the form 2637 * [style : string, pattern : RegExp, DEPRECATED, shortcut : string]. 2638 * 2639 * Style is a style constant like PR_PLAIN, or can be a string of the 2640 * form 'lang-FOO', where FOO is a language extension describing the 2641 * language of the portion of the token in $1 after pattern executes. 2642 * E.g., if style is 'lang-lisp', and group 1 contains the text 2643 * '(hello (world))', then that portion of the token will be passed to the 2644 * registered lisp handler for formatting. 2645 * The text before and after group 1 will be restyled using this decorator 2646 * so decorators should take care that this doesn't result in infinite 2647 * recursion. For example, the HTML lexer rule for SCRIPT elements looks 2648 * something like ['lang-js', /<[s]cript>(.+?)<\/script>/]. This may match 2649 * '<script>foo()<\/script>', which would cause the current decorator to 2650 * be called with '<script>' which would not match the same rule since 2651 * group 1 must not be empty, so it would be instead styled as PR_TAG by 2652 * the generic tag rule. The handler registered for the 'js' extension would 2653 * then be called with 'foo()', and finally, the current decorator would 2654 * be called with '<\/script>' which would not match the original rule and 2655 * so the generic tag rule would identify it as a tag. 2656 * 2657 * Pattern must only match prefixes, and if it matches a prefix, then that 2658 * match is considered a token with the same style. 2659 * 2660 * Context is applied to the last non-whitespace, non-comment token 2661 * recognized. 2662 * 2663 * Shortcut is an optional string of characters, any of which, if the first 2664 * character, gurantee that this pattern and only this pattern matches. 2665 * 2666 * @param {Array} shortcutStylePatterns patterns that always start with 2667 * a known character. Must have a shortcut string. 2668 * @param {Array} fallthroughStylePatterns patterns that will be tried in 2669 * order if the shortcut ones fail. May have shortcuts. 2670 * 2671 * @return {function (Object)} a 2672 * function that takes source code and returns a list of decorations. 2673 */ 2674 function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { 2675 var shortcuts = {}; 2676 var tokenizer; 2677 (function () { 2678 var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); 2679 var allRegexs = []; 2680 var regexKeys = {}; 2681 for (var i = 0, n = allPatterns.length; i < n; ++i) { 2682 var patternParts = allPatterns[i]; 2683 var shortcutChars = patternParts[3]; 2684 if (shortcutChars) { 2685 for (var c = shortcutChars.length; --c >= 0;) { 2686 shortcuts[shortcutChars.charAt(c)] = patternParts; 2687 } 2688 } 2689 var regex = patternParts[1]; 2690 var k = '' + regex; 2691 if (!regexKeys.hasOwnProperty(k)) { 2692 allRegexs.push(regex); 2693 regexKeys[k] = null; 2694 } 2695 } 2696 allRegexs.push(/[\0-\uffff]/); 2697 tokenizer = combinePrefixPatterns(allRegexs); 2698 })(); 2699 2700 var nPatterns = fallthroughStylePatterns.length; 2701 2702 /** 2703 * Lexes job.sourceCode and produces an output array job.decorations of 2704 * style classes preceded by the position at which they start in 2705 * job.sourceCode in order. 2706 * 2707 * @param {Object} job an object like <pre>{ 2708 * sourceCode: {string} sourceText plain text, 2709 * basePos: {int} position of job.sourceCode in the larger chunk of 2710 * sourceCode. 2711 * }</pre> 2712 */ 2713 var decorate = function (job) { 2714 var sourceCode = job.sourceCode, basePos = job.basePos; 2715 /** Even entries are positions in source in ascending order. Odd enties 2716 * are style markers (e.g., PR_COMMENT) that run from that position until 2717 * the end. 2718 * @type {Array.<number|string>} 2719 */ 2720 var decorations = [basePos, PR_PLAIN]; 2721 var pos = 0; // index into sourceCode 2722 var tokens = sourceCode.match(tokenizer) || []; 2723 var styleCache = {}; 2724 2725 for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) { 2726 var token = tokens[ti]; 2727 var style = styleCache[token]; 2728 var match = void 0; 2729 2730 var isEmbedded; 2731 if (typeof style === 'string') { 2732 isEmbedded = false; 2733 } else { 2734 var patternParts = shortcuts[token.charAt(0)]; 2735 if (patternParts) { 2736 match = token.match(patternParts[1]); 2737 style = patternParts[0]; 2738 } else { 2739 for (var i = 0; i < nPatterns; ++i) { 2740 patternParts = fallthroughStylePatterns[i]; 2741 match = token.match(patternParts[1]); 2742 if (match) { 2743 style = patternParts[0]; 2744 break; 2745 } 2746 } 2747 2748 if (!match) { // make sure that we make progress 2749 style = PR_PLAIN; 2750 } 2751 } 2752 2753 isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5); 2754 if (isEmbedded && !(match && typeof match[1] === 'string')) { 2755 isEmbedded = false; 2756 style = PR_SOURCE; 2757 } 2758 2759 if (!isEmbedded) { styleCache[token] = style; } 2760 } 2761 2762 var tokenStart = pos; 2763 pos += token.length; 2764 2765 if (!isEmbedded) { 2766 decorations.push(basePos + tokenStart, style); 2767 } else { // Treat group 1 as an embedded block of source code. 2768 var embeddedSource = match[1]; 2769 var embeddedSourceStart = token.indexOf(embeddedSource); 2770 var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length; 2771 if (match[2]) { 2772 // If embeddedSource can be blank, then it would match at the 2773 // beginning which would cause us to infinitely recurse on the 2774 // entire token, so we catch the right context in match[2]. 2775 embeddedSourceEnd = token.length - match[2].length; 2776 embeddedSourceStart = embeddedSourceEnd - embeddedSource.length; 2777 } 2778 var lang = style.substring(5); 2779 // Decorate the left of the embedded source 2780 appendDecorations( 2781 basePos + tokenStart, 2782 token.substring(0, embeddedSourceStart), 2783 decorate, decorations); 2784 // Decorate the embedded source 2785 appendDecorations( 2786 basePos + tokenStart + embeddedSourceStart, 2787 embeddedSource, 2788 langHandlerForExtension(lang, embeddedSource), 2789 decorations); 2790 // Decorate the right of the embedded section 2791 appendDecorations( 2792 basePos + tokenStart + embeddedSourceEnd, 2793 token.substring(embeddedSourceEnd), 2794 decorate, decorations); 2795 } 2796 } 2797 job.decorations = decorations; 2798 }; 2799 return decorate; 2800 } 2801 2802 /** returns a function that produces a list of decorations from source text. 2803 * 2804 * This code treats ", ', and ` as string delimiters, and \ as a string 2805 * escape. It does not recognize perl's qq() style strings. 2806 * It has no special handling for double delimiter escapes as in basic, or 2807 * the tripled delimiters used in python, but should work on those regardless 2808 * although in those cases a single string literal may be broken up into 2809 * multiple adjacent string literals. 2810 * 2811 * It recognizes C, C++, and shell style comments. 2812 * 2813 * @param {Object} options a set of optional parameters. 2814 * @return {function (Object)} a function that examines the source code 2815 * in the input job and builds the decoration list. 2816 */ 2817 function sourceDecorator(options) { 2818 var shortcutStylePatterns = [], fallthroughStylePatterns = []; 2819 if (options['tripleQuotedStrings']) { 2820 // '''multi-line-string''', 'single-line-string', and double-quoted 2821 shortcutStylePatterns.push( 2822 [PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, 2823 null, '\'"']); 2824 } else if (options['multiLineStrings']) { 2825 // 'multi-line-string', "multi-line-string" 2826 shortcutStylePatterns.push( 2827 [PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, 2828 null, '\'"`']); 2829 } else { 2830 // 'single-line-string', "single-line-string" 2831 shortcutStylePatterns.push( 2832 [PR_STRING, 2833 /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, 2834 null, '"\'']); 2835 } 2836 if (options['verbatimStrings']) { 2837 // verbatim-string-literal production from the C# grammar. See issue 93. 2838 fallthroughStylePatterns.push( 2839 [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]); 2840 } 2841 var hc = options['hashComments']; 2842 if (hc) { 2843 if (options['cStyleComments']) { 2844 if (hc > 1) { // multiline hash comments 2845 shortcutStylePatterns.push( 2846 [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']); 2847 } else { 2848 // Stop C preprocessor declarations at an unclosed open comment 2849 shortcutStylePatterns.push( 2850 [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/, 2851 null, '#']); 2852 } 2853 // #include <stdio.h> 2854 fallthroughStylePatterns.push( 2855 [PR_STRING, 2856 /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/, 2857 null]); 2858 } else { 2859 shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); 2860 } 2861 } 2862 if (options['cStyleComments']) { 2863 fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); 2864 fallthroughStylePatterns.push( 2865 [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); 2866 } 2867 if (options['regexLiterals']) { 2868 /** 2869 * @const 2870 */ 2871 var REGEX_LITERAL = ( 2872 // A regular expression literal starts with a slash that is 2873 // not followed by * or / so that it is not confused with 2874 // comments. 2875 '/(?=[^/*])' 2876 // and then contains any number of raw characters, 2877 + '(?:[^/\\x5B\\x5C]' 2878 // escape sequences (\x5C), 2879 + '|\\x5C[\\s\\S]' 2880 // or non-nesting character sets (\x5B\x5D); 2881 + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' 2882 // finally closed by a /. 2883 + '/'); 2884 fallthroughStylePatterns.push( 2885 ['lang-regex', 2886 new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')') 2887 ]); 2888 } 2889 2890 var types = options['types']; 2891 if (types) { 2892 fallthroughStylePatterns.push([PR_TYPE, types]); 2893 } 2894 2895 var keywords = ("" + options['keywords']).replace(/^ | $/g, ''); 2896 if (keywords.length) { 2897 fallthroughStylePatterns.push( 2898 [PR_KEYWORD, 2899 new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'), 2900 null]); 2901 } 2902 2903 shortcutStylePatterns.push([PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0']); 2904 2905 var punctuation = 2906 // The Bash man page says 2907 2908 // A word is a sequence of characters considered as a single 2909 // unit by GRUB. Words are separated by metacharacters, 2910 // which are the following plus space, tab, and newline: { } 2911 // | & $ ; < > 2912 // ... 2913 2914 // A word beginning with # causes that word and all remaining 2915 // characters on that line to be ignored. 2916 2917 // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a 2918 // comment but empirically 2919 // $ echo {#} 2920 // {#} 2921 // $ echo \$# 2922 // $# 2923 // $ echo }# 2924 // }# 2925 2926 // so /(?:^|[|&;<>\s])/ is more appropriate. 2927 2928 // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3 2929 // suggests that this definition is compatible with a 2930 // default mode that tries to use a single token definition 2931 // to recognize both bash/python style comments and C 2932 // preprocessor directives. 2933 2934 // This definition of punctuation does not include # in the list of 2935 // follow-on exclusions, so # will not be broken before if preceeded 2936 // by a punctuation character. We could try to exclude # after 2937 // [|&;<>] but that doesn't seem to cause many major problems. 2938 // If that does turn out to be a problem, we should change the below 2939 // when hc is truthy to include # in the run of punctuation characters 2940 // only when not followint [|&;<>]. 2941 /^.[^\s\w\.$@\'\"\`\/\\]*/; 2942 2943 fallthroughStylePatterns.push( 2944 // TODO(mikesamuel): recognize non-latin letters and numerals in idents 2945 [PR_LITERAL, /^@[a-z_$][a-z_$@0-9]*/i, null], 2946 [PR_TYPE, /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null], 2947 [PR_PLAIN, /^[a-z_$][a-z_$@0-9]*/i, null], 2948 [PR_LITERAL, 2949 new RegExp( 2950 '^(?:' 2951 // A hex number 2952 + '0x[a-f0-9]+' 2953 // or an octal or decimal number, 2954 + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)' 2955 // possibly in scientific notation 2956 + '(?:e[+\\-]?\\d+)?' 2957 + ')' 2958 // with an optional modifier like UL for unsigned long 2959 + '[a-z]*', 'i'), 2960 null, '0123456789'], 2961 // Don't treat escaped quotes in bash as starting strings. See issue 144. 2962 [PR_PLAIN, /^\\[\s\S]?/, null], 2963 [PR_PUNCTUATION, punctuation, null]); 2964 2965 return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); 2966 } 2967 2968 var decorateSource = sourceDecorator({ 2969 'keywords': ALL_KEYWORDS, 2970 'hashComments': true, 2971 'cStyleComments': true, 2972 'multiLineStrings': true, 2973 'regexLiterals': true 2974 }); 2975 2976 /** 2977 * Given a DOM subtree, wraps it in a list, and puts each line into its own 2978 * list item. 2979 * 2980 * @param {Node} node modified in place. Its content is pulled into an 2981 * HTMLOListElement, and each line is moved into a separate list item. 2982 * This requires cloning elements, so the input might not have unique 2983 * IDs after numbering. 2984 * @param {boolean} isPreformatted true iff white-space in text nodes should 2985 * be treated as significant. 2986 */ 2987 function numberLines(node, opt_startLineNum, isPreformatted) { 2988 var nocode = /(?:^|\s)nocode(?:\s|$)/; 2989 var lineBreak = /\r\n?|\n/; 2990 2991 var document = node.ownerDocument; 2992 2993 var li = document.createElement('li'); 2994 while (node.firstChild) { 2995 li.appendChild(node.firstChild); 2996 } 2997 // An array of lines. We split below, so this is initialized to one 2998 // un-split line. 2999 var listItems = [li]; 3000 3001 function walk(node) { 3002 switch (node.nodeType) { 3003 case 1: // Element 3004 if (nocode.test(node.className)) { break; } 3005 if ('br' === node.nodeName) { 3006 breakAfter(node); 3007 // Discard the <BR> since it is now flush against a </LI>. 3008 if (node.parentNode) { 3009 node.parentNode.removeChild(node); 3010 } 3011 } else { 3012 for (var child = node.firstChild; child; child = child.nextSibling) { 3013 walk(child); 3014 } 3015 } 3016 break; 3017 case 3: case 4: // Text 3018 if (isPreformatted) { 3019 var text = node.nodeValue; 3020 var match = text.match(lineBreak); 3021 if (match) { 3022 var firstLine = text.substring(0, match.index); 3023 node.nodeValue = firstLine; 3024 var tail = text.substring(match.index + match[0].length); 3025 if (tail) { 3026 var parent = node.parentNode; 3027 parent.insertBefore( 3028 document.createTextNode(tail), node.nextSibling); 3029 } 3030 breakAfter(node); 3031 if (!firstLine) { 3032 // Don't leave blank text nodes in the DOM. 3033 node.parentNode.removeChild(node); 3034 } 3035 } 3036 } 3037 break; 3038 } 3039 } 3040 3041 // Split a line after the given node. 3042 function breakAfter(lineEndNode) { 3043 // If there's nothing to the right, then we can skip ending the line 3044 // here, and move root-wards since splitting just before an end-tag 3045 // would require us to create a bunch of empty copies. 3046 while (!lineEndNode.nextSibling) { 3047 lineEndNode = lineEndNode.parentNode; 3048 if (!lineEndNode) { return; } 3049 } 3050 3051 function breakLeftOf(limit, copy) { 3052 // Clone shallowly if this node needs to be on both sides of the break. 3053 var rightSide = copy ? limit.cloneNode(false) : limit; 3054 var parent = limit.parentNode; 3055 if (parent) { 3056 // We clone the parent chain. 3057 // This helps us resurrect important styling elements that cross lines. 3058 // E.g. in <i>Foo<br>Bar</i> 3059 // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>. 3060 var parentClone = breakLeftOf(parent, 1); 3061 // Move the clone and everything to the right of the original 3062 // onto the cloned parent. 3063 var next = limit.nextSibling; 3064 parentClone.appendChild(rightSide); 3065 for (var sibling = next; sibling; sibling = next) { 3066 next = sibling.nextSibling; 3067 parentClone.appendChild(sibling); 3068 } 3069 } 3070 return rightSide; 3071 } 3072 3073 var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0); 3074 3075 // Walk the parent chain until we reach an unattached LI. 3076 for (var parent; 3077 // Check nodeType since IE invents document fragments. 3078 (parent = copiedListItem.parentNode) && parent.nodeType === 1;) { 3079 copiedListItem = parent; 3080 } 3081 // Put it on the list of lines for later processing. 3082 listItems.push(copiedListItem); 3083 } 3084 3085 // Split lines while there are lines left to split. 3086 for (var i = 0; // Number of lines that have been split so far. 3087 i < listItems.length; // length updated by breakAfter calls. 3088 ++i) { 3089 walk(listItems[i]); 3090 } 3091 3092 // Make sure numeric indices show correctly. 3093 if (opt_startLineNum === (opt_startLineNum|0)) { 3094 listItems[0].setAttribute('value', opt_startLineNum); 3095 } 3096 3097 var ol = document.createElement('ol'); 3098 ol.className = 'linenums'; 3099 var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0; 3100 for (var i = 0, n = listItems.length; i < n; ++i) { 3101 li = listItems[i]; 3102 // Stick a class on the LIs so that stylesheets can 3103 // color odd/even rows, or any other row pattern that 3104 // is co-prime with 10. 3105 li.className = 'L' + ((i + offset) % 10); 3106 if (!li.firstChild) { 3107 li.appendChild(document.createTextNode('\xA0')); 3108 } 3109 ol.appendChild(li); 3110 } 3111 3112 node.appendChild(ol); 3113 } 3114 3115 /** 3116 * Breaks {@code job.sourceCode} around style boundaries in 3117 * {@code job.decorations} and modifies {@code job.sourceNode} in place. 3118 * @param {Object} job like <pre>{ 3119 * sourceCode: {string} source as plain text, 3120 * spans: {Array.<number|Node>} alternating span start indices into source 3121 * and the text node or element (e.g. {@code <BR>}) corresponding to that 3122 * span. 3123 * decorations: {Array.<number|string} an array of style classes preceded 3124 * by the position at which they start in job.sourceCode in order 3125 * }</pre> 3126 * @private 3127 */ 3128 function recombineTagsAndDecorations(job) { 3129 var isIE8OrEarlier = /\bMSIE\s(\d+)/.exec(navigator.userAgent); 3130 isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8; 3131 var newlineRe = /\n/g; 3132 3133 var source = job.sourceCode; 3134 var sourceLength = source.length; 3135 // Index into source after the last code-unit recombined. 3136 var sourceIndex = 0; 3137 3138 var spans = job.spans; 3139 var nSpans = spans.length; 3140 // Index into spans after the last span which ends at or before sourceIndex. 3141 var spanIndex = 0; 3142 3143 var decorations = job.decorations; 3144 var nDecorations = decorations.length; 3145 // Index into decorations after the last decoration which ends at or before 3146 // sourceIndex. 3147 var decorationIndex = 0; 3148 3149 // Remove all zero-length decorations. 3150 decorations[nDecorations] = sourceLength; 3151 var decPos, i; 3152 for (i = decPos = 0; i < nDecorations;) { 3153 if (decorations[i] !== decorations[i + 2]) { 3154 decorations[decPos++] = decorations[i++]; 3155 decorations[decPos++] = decorations[i++]; 3156 } else { 3157 i += 2; 3158 } 3159 } 3160 nDecorations = decPos; 3161 3162 // Simplify decorations. 3163 for (i = decPos = 0; i < nDecorations;) { 3164 var startPos = decorations[i]; 3165 // Conflate all adjacent decorations that use the same style. 3166 var startDec = decorations[i + 1]; 3167 var end = i + 2; 3168 while (end + 2 <= nDecorations && decorations[end + 1] === startDec) { 3169 end += 2; 3170 } 3171 decorations[decPos++] = startPos; 3172 decorations[decPos++] = startDec; 3173 i = end; 3174 } 3175 3176 nDecorations = decorations.length = decPos; 3177 3178 var sourceNode = job.sourceNode; 3179 var oldDisplay; 3180 if (sourceNode) { 3181 oldDisplay = sourceNode.style.display; 3182 sourceNode.style.display = 'none'; 3183 } 3184 try { 3185 var decoration = null; 3186 while (spanIndex < nSpans) { 3187 var spanStart = spans[spanIndex]; 3188 var spanEnd = spans[spanIndex + 2] || sourceLength; 3189 3190 var decEnd = decorations[decorationIndex + 2] || sourceLength; 3191 3192 var end = Math.min(spanEnd, decEnd); 3193 3194 var textNode = spans[spanIndex + 1]; 3195 var styledText; 3196 if (textNode.nodeType !== 1 // Don't muck with <BR>s or <LI>s 3197 // Don't introduce spans around empty text nodes. 3198 && (styledText = source.substring(sourceIndex, end))) { 3199 // This may seem bizarre, and it is. Emitting LF on IE causes the 3200 // code to display with spaces instead of line breaks. 3201 // Emitting Windows standard issue linebreaks (CRLF) causes a blank 3202 // space to appear at the beginning of every line but the first. 3203 // Emitting an old Mac OS 9 line separator makes everything spiffy. 3204 if (isIE8OrEarlier) { 3205 styledText = styledText.replace(newlineRe, '\r'); 3206 } 3207 textNode.nodeValue = styledText; 3208 var document = textNode.ownerDocument; 3209 var span = document.createElement('span'); 3210 span.className = decorations[decorationIndex + 1]; 3211 var parentNode = textNode.parentNode; 3212 parentNode.replaceChild(span, textNode); 3213 span.appendChild(textNode); 3214 if (sourceIndex < spanEnd) { // Split off a text node. 3215 spans[spanIndex + 1] = textNode 3216 // TODO: Possibly optimize by using '' if there's no flicker. 3217 = document.createTextNode(source.substring(end, spanEnd)); 3218 parentNode.insertBefore(textNode, span.nextSibling); 3219 } 3220 } 3221 3222 sourceIndex = end; 3223 3224 if (sourceIndex >= spanEnd) { 3225 spanIndex += 2; 3226 } 3227 if (sourceIndex >= decEnd) { 3228 decorationIndex += 2; 3229 } 3230 } 3231 } finally { 3232 if (sourceNode) { 3233 sourceNode.style.display = oldDisplay; 3234 } 3235 } 3236 } 3237 3238 3239 /** Maps language-specific file extensions to handlers. */ 3240 var langHandlerRegistry = {}; 3241 /** Register a language handler for the given file extensions. 3242 * @param {function (Object)} handler a function from source code to a list 3243 * of decorations. Takes a single argument job which describes the 3244 * state of the computation. The single parameter has the form 3245 * {@code { 3246 * sourceCode: {string} as plain text. 3247 * decorations: {Array.<number|string>} an array of style classes 3248 * preceded by the position at which they start in 3249 * job.sourceCode in order. 3250 * The language handler should assigned this field. 3251 * basePos: {int} the position of source in the larger source chunk. 3252 * All positions in the output decorations array are relative 3253 * to the larger source chunk. 3254 * } } 3255 * @param {Array.<string>} fileExtensions 3256 */ 3257 function registerLangHandler(handler, fileExtensions) { 3258 for (var i = fileExtensions.length; --i >= 0;) { 3259 var ext = fileExtensions[i]; 3260 if (!langHandlerRegistry.hasOwnProperty(ext)) { 3261 langHandlerRegistry[ext] = handler; 3262 } else if (win['console']) { 3263 console['warn']('cannot override language handler %s', ext); 3264 } 3265 } 3266 } 3267 function langHandlerForExtension(extension, source) { 3268 if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) { 3269 // Treat it as markup if the first non whitespace character is a < and 3270 // the last non-whitespace character is a >. 3271 extension = /^\s*</.test(source) 3272 ? 'default-markup' 3273 : 'default-code'; 3274 } 3275 return langHandlerRegistry[extension]; 3276 } 3277 registerLangHandler(decorateSource, ['default-code']); 3278 registerLangHandler( 3279 createSimpleLexer( 3280 [], 3281 [ 3282 [PR_PLAIN, /^[^<?]+/], 3283 [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/], 3284 [PR_COMMENT, /^<\!--[\s\S]*?(?:-\->|$)/], 3285 // Unescaped content in an unknown language 3286 ['lang-', /^<\?([\s\S]+?)(?:\?>|$)/], 3287 ['lang-', /^<%([\s\S]+?)(?:%>|$)/], 3288 [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/], 3289 ['lang-', /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i], 3290 // Unescaped content in javascript. (Or possibly vbscript). 3291 ['lang-js', /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i], 3292 // Contains unescaped stylesheet content 3293 ['lang-css', /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i], 3294 ['lang-in.tag', /^(<\/?[a-z][^<>]*>)/i] 3295 ]), 3296 ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']); 3297 registerLangHandler( 3298 createSimpleLexer( 3299 [ 3300 [PR_PLAIN, /^[\s]+/, null, ' \t\r\n'], 3301 [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\''] 3302 ], 3303 [ 3304 [PR_TAG, /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i], 3305 [PR_ATTRIB_NAME, /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i], 3306 ['lang-uq.val', /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], 3307 [PR_PUNCTUATION, /^[=<>\/]+/], 3308 ['lang-js', /^on\w+\s*=\s*\"([^\"]+)\"/i], 3309 ['lang-js', /^on\w+\s*=\s*\'([^\']+)\'/i], 3310 ['lang-js', /^on\w+\s*=\s*([^\"\'>\s]+)/i], 3311 ['lang-css', /^style\s*=\s*\"([^\"]+)\"/i], 3312 ['lang-css', /^style\s*=\s*\'([^\']+)\'/i], 3313 ['lang-css', /^style\s*=\s*([^\"\'>\s]+)/i] 3314 ]), 3315 ['in.tag']); 3316 registerLangHandler( 3317 createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']); 3318 registerLangHandler(sourceDecorator({ 3319 'keywords': CPP_KEYWORDS, 3320 'hashComments': true, 3321 'cStyleComments': true, 3322 'types': C_TYPES 3323 }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']); 3324 registerLangHandler(sourceDecorator({ 3325 'keywords': 'null,true,false' 3326 }), ['json']); 3327 registerLangHandler(sourceDecorator({ 3328 'keywords': CSHARP_KEYWORDS, 3329 'hashComments': true, 3330 'cStyleComments': true, 3331 'verbatimStrings': true, 3332 'types': C_TYPES 3333 }), ['cs']); 3334 registerLangHandler(sourceDecorator({ 3335 'keywords': JAVA_KEYWORDS, 3336 'cStyleComments': true 3337 }), ['java']); 3338 registerLangHandler(sourceDecorator({ 3339 'keywords': SH_KEYWORDS, 3340 'hashComments': true, 3341 'multiLineStrings': true 3342 }), ['bsh', 'csh', 'sh']); 3343 registerLangHandler(sourceDecorator({ 3344 'keywords': PYTHON_KEYWORDS, 3345 'hashComments': true, 3346 'multiLineStrings': true, 3347 'tripleQuotedStrings': true 3348 }), ['cv', 'py']); 3349 registerLangHandler(sourceDecorator({ 3350 'keywords': PERL_KEYWORDS, 3351 'hashComments': true, 3352 'multiLineStrings': true, 3353 'regexLiterals': true 3354 }), ['perl', 'pl', 'pm']); 3355 registerLangHandler(sourceDecorator({ 3356 'keywords': RUBY_KEYWORDS, 3357 'hashComments': true, 3358 'multiLineStrings': true, 3359 'regexLiterals': true 3360 }), ['rb']); 3361 registerLangHandler(sourceDecorator({ 3362 'keywords': JSCRIPT_KEYWORDS, 3363 'cStyleComments': true, 3364 'regexLiterals': true 3365 }), ['js']); 3366 registerLangHandler(sourceDecorator({ 3367 'keywords': COFFEE_KEYWORDS, 3368 'hashComments': 3, // ### style block comments 3369 'cStyleComments': true, 3370 'multilineStrings': true, 3371 'tripleQuotedStrings': true, 3372 'regexLiterals': true 3373 }), ['coffee']); 3374 registerLangHandler( 3375 createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']); 3376 3377 function applyDecorator(job) { 3378 var opt_langExtension = job.langExtension; 3379 3380 try { 3381 // Extract tags, and convert the source code to plain text. 3382 var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre); 3383 /** Plain text. @type {string} */ 3384 var source = sourceAndSpans.sourceCode; 3385 job.sourceCode = source; 3386 job.spans = sourceAndSpans.spans; 3387 job.basePos = 0; 3388 3389 // Apply the appropriate language handler 3390 langHandlerForExtension(opt_langExtension, source)(job); 3391 3392 // Integrate the decorations and tags back into the source code, 3393 // modifying the sourceNode in place. 3394 recombineTagsAndDecorations(job); 3395 } catch (e) { 3396 if (win['console']) { 3397 console['log'](e && e['stack'] ? e['stack'] : e); 3398 } 3399 } 3400 } 3401 3402 /** 3403 * @param sourceCodeHtml {string} The HTML to pretty print. 3404 * @param opt_langExtension {string} The language name to use. 3405 * Typically, a filename extension like 'cpp' or 'java'. 3406 * @param opt_numberLines {number|boolean} True to number lines, 3407 * or the 1-indexed number of the first line in sourceCodeHtml. 3408 */ 3409 function prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) { 3410 var container = document.createElement('pre'); 3411 // This could cause images to load and onload listeners to fire. 3412 // E.g. <img onerror="alert(1337)" src="nosuchimage.png">. 3413 // We assume that the inner HTML is from a trusted source. 3414 container.innerHTML = sourceCodeHtml; 3415 if (opt_numberLines) { 3416 numberLines(container, opt_numberLines, true); 3417 } 3418 3419 var job = { 3420 langExtension: opt_langExtension, 3421 numberLines: opt_numberLines, 3422 sourceNode: container, 3423 pre: 1 3424 }; 3425 applyDecorator(job); 3426 return container.innerHTML; 3427 } 3428 3429 function prettyPrint(opt_whenDone) { 3430 function byTagName(tn) { return document.getElementsByTagName(tn); } 3431 // fetch a list of nodes to rewrite 3432 var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')]; 3433 var elements = []; 3434 for (var i = 0; i < codeSegments.length; ++i) { 3435 for (var j = 0, n = codeSegments[i].length; j < n; ++j) { 3436 elements.push(codeSegments[i][j]); 3437 } 3438 } 3439 codeSegments = null; 3440 3441 var clock = Date; 3442 if (!clock['now']) { 3443 clock = { 'now': function () { return +(new Date); } }; 3444 } 3445 3446 // The loop is broken into a series of continuations to make sure that we 3447 // don't make the browser unresponsive when rewriting a large page. 3448 var k = 0; 3449 var prettyPrintingJob; 3450 3451 var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/; 3452 var prettyPrintRe = /\bprettyprint\b/; 3453 var prettyPrintedRe = /\bprettyprinted\b/; 3454 var preformattedTagNameRe = /pre|xmp/i; 3455 var codeRe = /^code$/i; 3456 var preCodeXmpRe = /^(?:pre|code|xmp)$/i; 3457 3458 function doWork() { 3459 var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ? 3460 clock['now']() + 250 /* ms */ : 3461 Infinity); 3462 for (; k < elements.length && clock['now']() < endTime; k++) { 3463 var cs = elements[k]; 3464 var className = cs.className; 3465 if (prettyPrintRe.test(className) 3466 // Don't redo this if we've already done it. 3467 // This allows recalling pretty print to just prettyprint elements 3468 // that have been added to the page since last call. 3469 && !prettyPrintedRe.test(className)) { 3470 3471 // make sure this is not nested in an already prettified element 3472 var nested = false; 3473 for (var p = cs.parentNode; p; p = p.parentNode) { 3474 var tn = p.tagName; 3475 if (preCodeXmpRe.test(tn) 3476 && p.className && prettyPrintRe.test(p.className)) { 3477 nested = true; 3478 break; 3479 } 3480 } 3481 if (!nested) { 3482 // Mark done. If we fail to prettyprint for whatever reason, 3483 // we shouldn't try again. 3484 cs.className += ' prettyprinted'; 3485 3486 // If the classes includes a language extensions, use it. 3487 // Language extensions can be specified like 3488 // <pre class="prettyprint lang-cpp"> 3489 // the language extension "cpp" is used to find a language handler 3490 // as passed to PR.registerLangHandler. 3491 // HTML5 recommends that a language be specified using "language-" 3492 // as the prefix instead. Google Code Prettify supports both. 3493 // http://dev.w3.org/html5/spec-author-view/the-code-element.html 3494 var langExtension = className.match(langExtensionRe); 3495 // Support <pre class="prettyprint"><code class="language-c"> 3496 var wrapper; 3497 if (!langExtension && (wrapper = childContentWrapper(cs)) 3498 && codeRe.test(wrapper.tagName)) { 3499 langExtension = wrapper.className.match(langExtensionRe); 3500 } 3501 3502 if (langExtension) { langExtension = langExtension[1]; } 3503 3504 var preformatted; 3505 if (preformattedTagNameRe.test(cs.tagName)) { 3506 preformatted = 1; 3507 } else { 3508 var currentStyle = cs['currentStyle']; 3509 var whitespace = ( 3510 currentStyle 3511 ? currentStyle['whiteSpace'] 3512 : (document.defaultView 3513 && document.defaultView.getComputedStyle) 3514 ? document.defaultView.getComputedStyle(cs, null) 3515 .getPropertyValue('white-space') 3516 : 0); 3517 preformatted = whitespace 3518 && 'pre' === whitespace.substring(0, 3); 3519 } 3520 3521 // Look for a class like linenums or linenums:<n> where <n> is the 3522 // 1-indexed number of the first line. 3523 var lineNums = cs.className.match(/\blinenums\b(?::(\d+))?/); 3524 lineNums = lineNums 3525 ? lineNums[1] && lineNums[1].length ? +lineNums[1] : true 3526 : false; 3527 if (lineNums) { numberLines(cs, lineNums, preformatted); } 3528 3529 // do the pretty printing 3530 prettyPrintingJob = { 3531 langExtension: langExtension, 3532 sourceNode: cs, 3533 numberLines: lineNums, 3534 pre: preformatted 3535 }; 3536 applyDecorator(prettyPrintingJob); 3537 } 3538 } 3539 } 3540 if (k < elements.length) { 3541 // finish up in a continuation 3542 setTimeout(doWork, 250); 3543 } else if (opt_whenDone) { 3544 opt_whenDone(); 3545 } 3546 } 3547 3548 doWork(); 3549 } 3550 3551 /** 3552 * Contains functions for creating and registering new language handlers. 3553 * @type {Object} 3554 */ 3555 var PR = win['PR'] = { 3556 'createSimpleLexer': createSimpleLexer, 3557 'registerLangHandler': registerLangHandler, 3558 'sourceDecorator': sourceDecorator, 3559 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 3560 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 3561 'PR_COMMENT': PR_COMMENT, 3562 'PR_DECLARATION': PR_DECLARATION, 3563 'PR_KEYWORD': PR_KEYWORD, 3564 'PR_LITERAL': PR_LITERAL, 3565 'PR_NOCODE': PR_NOCODE, 3566 'PR_PLAIN': PR_PLAIN, 3567 'PR_PUNCTUATION': PR_PUNCTUATION, 3568 'PR_SOURCE': PR_SOURCE, 3569 'PR_STRING': PR_STRING, 3570 'PR_TAG': PR_TAG, 3571 'PR_TYPE': PR_TYPE, 3572 'prettyPrintOne': win['prettyPrintOne'] = prettyPrintOne, 3573 'prettyPrint': win['prettyPrint'] = prettyPrint 3574 }; 3575 3576 // Make PR available via the Asynchronous Module Definition (AMD) API. 3577 // Per https://github.com/amdjs/amdjs-api/wiki/AMD: 3578 // The Asynchronous Module Definition (AMD) API specifies a 3579 // mechanism for defining modules such that the module and its 3580 // dependencies can be asynchronously loaded. 3581 // ... 3582 // To allow a clear indicator that a global define function (as 3583 // needed for script src browser loading) conforms to the AMD API, 3584 // any global define function SHOULD have a property called "amd" 3585 // whose value is an object. This helps avoid conflict with any 3586 // other existing JavaScript code that could have defined a define() 3587 // function that does not conform to the AMD API. 3588 if (typeof define === "function" && define['amd']) { 3589 define(function () { 3590 return PR; 3591 }); 3592 } 3593 })(); 3594 ; 3595 (function(scope) { 3596 3597 var ContextFreeParser = { 3598 parse: function(text) { 3599 var top = {}; 3600 var entities = []; 3601 var current = top; 3602 var subCurrent = {}; 3603 3604 var scriptDocCommentClause = '\\/\\*\\*([\\s\\S]*?)\\*\\/'; 3605 var htmlDocCommentClause = '<!--([\\s\\S]*?)-->'; 3606 3607 // matches text between /** and */ inclusive and <!-- and --> inclusive 3608 var docCommentRegex = new RegExp(scriptDocCommentClause + '|' + htmlDocCommentClause, 'g'); 3609 3610 // acquire all script doc comments 3611 var docComments = text.match(docCommentRegex) || []; 3612 3613 // each match represents a single block of doc comments 3614 docComments.forEach(function(m) { 3615 // unify line ends, remove all comment characters, split into individual lines 3616 var lines = m.replace(/\r\n/g, '\n').replace(/^\s*\/\*\*|^\s*\*\/|^\s*\* ?|^\s*\<\!-\-|^s*\-\-\>/gm, '').split('\n'); 3617 3618 // pragmas (@-rules) must occur on a line by themselves 3619 var pragmas = []; 3620 // filter lines whose first non-whitespace character is @ into the pragma list 3621 // (and out of the `lines` array) 3622 lines = lines.filter(function(l) { 3623 var m = l.match(/\s*@([\w-]*) (.*)/); 3624 if (!m) { 3625 return true; 3626 } 3627 pragmas.push(m); 3628 }); 3629 3630 // collect all other text into a single block 3631 var code = lines.join('\n'); 3632 3633 // process pragmas 3634 pragmas.forEach(function(m) { 3635 var pragma = m[1], content = m[2]; 3636 switch (pragma) { 3637 3638 // currently all entities are either @class or @element 3639 case 'class': 3640 case 'element': 3641 current = { 3642 name: content, 3643 description: code 3644 }; 3645 entities.push(current); 3646 break; 3647 3648 // an entity may have these describable sub-features 3649 case 'attribute': 3650 case 'property': 3651 case 'method': 3652 case 'event': 3653 subCurrent = { 3654 name: content, 3655 description: code 3656 }; 3657 var label = pragma == 'property' ? 'properties' : pragma + 's'; 3658 makePragma(current, label, subCurrent); 3659 break; 3660 3661 // sub-feature pragmas 3662 case 'default': 3663 case 'type': 3664 subCurrent[pragma] = content; 3665 break; 3666 3667 // everything else 3668 default: 3669 current[pragma] = content; 3670 break; 3671 } 3672 }); 3673 3674 // utility function, yay hoisting 3675 function makePragma(object, pragma, content) { 3676 var p$ = object; 3677 var p = p$[pragma]; 3678 if (!p) { 3679 p$[pragma] = p = []; 3680 } 3681 p.push(content); 3682 } 3683 3684 }); 3685 3686 if (entities.length === 0) { 3687 entities.push({name: 'Entity', description: '**Undocumented**'}); 3688 } 3689 return entities; 3690 } 3691 }; 3692 3693 if (typeof module !== 'undefined' && module.exports) { 3694 module.exports = ContextFreeParser; 3695 } else { 3696 scope.ContextFreeParser = ContextFreeParser; 3697 } 3698 3699 })(this);; 3700 3701 3702 Polymer('core-xhr', { 3703 3704 /** 3705 * Sends a HTTP request to the server and returns the XHR object. 3706 * 3707 * @method request 3708 * @param {Object} inOptions 3709 * @param {String} inOptions.url The url to which the request is sent. 3710 * @param {String} inOptions.method The HTTP method to use, default is GET. 3711 * @param {boolean} inOptions.sync By default, all requests are sent asynchronously. To send synchronous requests, set to true. 3712 * @param {Object} inOptions.params Data to be sent to the server. 3713 * @param {Object} inOptions.body The content for the request body for POST method. 3714 * @param {Object} inOptions.headers HTTP request headers. 3715 * @param {String} inOptions.responseType The response type. Default is 'text'. 3716 * @param {boolean} inOptions.withCredentials Whether or not to send credentials on the request. Default is false. 3717 * @param {Object} inOptions.callback Called when request is completed. 3718 * @returns {Object} XHR object. 3719 */ 3720 request: function(options) { 3721 var xhr = new XMLHttpRequest(); 3722 var url = options.url; 3723 var method = options.method || 'GET'; 3724 var async = !options.sync; 3725 // 3726 var params = this.toQueryString(options.params); 3727 if (params && method == 'GET') { 3728 url += (url.indexOf('?') > 0 ? '&' : '?') + params; 3729 } 3730 var xhrParams = this.isBodyMethod(method) ? (options.body || params) : null; 3731 // 3732 xhr.open(method, url, async); 3733 if (options.responseType) { 3734 xhr.responseType = options.responseType; 3735 } 3736 if (options.withCredentials) { 3737 xhr.withCredentials = true; 3738 } 3739 this.makeReadyStateHandler(xhr, options.callback); 3740 this.setRequestHeaders(xhr, options.headers); 3741 xhr.send(xhrParams); 3742 if (!async) { 3743 xhr.onreadystatechange(xhr); 3744 } 3745 return xhr; 3746 }, 3747 3748 toQueryString: function(params) { 3749 var r = []; 3750 for (var n in params) { 3751 var v = params[n]; 3752 n = encodeURIComponent(n); 3753 r.push(v == null ? n : (n + '=' + encodeURIComponent(v))); 3754 } 3755 return r.join('&'); 3756 }, 3757 3758 isBodyMethod: function(method) { 3759 return this.bodyMethods[(method || '').toUpperCase()]; 3760 }, 3761 3762 bodyMethods: { 3763 POST: 1, 3764 PUT: 1, 3765 DELETE: 1 3766 }, 3767 3768 makeReadyStateHandler: function(xhr, callback) { 3769 xhr.onreadystatechange = function() { 3770 if (xhr.readyState == 4) { 3771 callback && callback.call(null, xhr.response, xhr); 3772 } 3773 }; 3774 }, 3775 3776 setRequestHeaders: function(xhr, headers) { 3777 if (headers) { 3778 for (var name in headers) { 3779 xhr.setRequestHeader(name, headers[name]); 3780 } 3781 } 3782 } 3783 3784 }); 3785 3786 ; 3787 3788 3789 Polymer('core-ajax', { 3790 /** 3791 * Fired when a response is received. 3792 * 3793 * @event core-response 3794 */ 3795 3796 /** 3797 * Fired when an error is received. 3798 * 3799 * @event core-error 3800 */ 3801 3802 /** 3803 * Fired whenever a response or an error is received. 3804 * 3805 * @event core-complete 3806 */ 3807 3808 /** 3809 * The URL target of the request. 3810 * 3811 * @attribute url 3812 * @type string 3813 * @default '' 3814 */ 3815 url: '', 3816 3817 /** 3818 * Specifies what data to store in the `response` property, and 3819 * to deliver as `event.response` in `response` events. 3820 * 3821 * One of: 3822 * 3823 * `text`: uses `XHR.responseText`. 3824 * 3825 * `xml`: uses `XHR.responseXML`. 3826 * 3827 * `json`: uses `XHR.responseText` parsed as JSON. 3828 * 3829 * `arraybuffer`: uses `XHR.response`. 3830 * 3831 * `blob`: uses `XHR.response`. 3832 * 3833 * `document`: uses `XHR.response`. 3834 * 3835 * @attribute handleAs 3836 * @type string 3837 * @default 'text' 3838 */ 3839 handleAs: '', 3840 3841 /** 3842 * If true, automatically performs an Ajax request when either `url` or `params` changes. 3843 * 3844 * @attribute auto 3845 * @type boolean 3846 * @default false 3847 */ 3848 auto: false, 3849 3850 /** 3851 * Parameters to send to the specified URL, as JSON. 3852 * 3853 * @attribute params 3854 * @type string (JSON) 3855 * @default '' 3856 */ 3857 params: '', 3858 3859 /** 3860 * Returns the response object. 3861 * 3862 * @attribute response 3863 * @type Object 3864 * @default null 3865 */ 3866 response: null, 3867 3868 /** 3869 * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. 3870 * Default is 'GET'. 3871 * 3872 * @attribute method 3873 * @type string 3874 * @default '' 3875 */ 3876 method: '', 3877 3878 /** 3879 * HTTP request headers to send. 3880 * 3881 * Example: 3882 * 3883 * <core-ajax 3884 * auto 3885 * url="http://somesite.com" 3886 * headers='{"X-Requested-With": "XMLHttpRequest"}' 3887 * handleAs="json" 3888 * on-core-response="{{handleResponse}}"></core-ajax> 3889 * 3890 * @attribute headers 3891 * @type Object 3892 * @default null 3893 */ 3894 headers: null, 3895 3896 /** 3897 * Optional raw body content to send when method === "POST". 3898 * 3899 * Example: 3900 * 3901 * <core-ajax method="POST" auto url="http://somesite.com" 3902 * body='{"foo":1, "bar":2}'> 3903 * </core-ajax> 3904 * 3905 * @attribute body 3906 * @type Object 3907 * @default null 3908 */ 3909 body: null, 3910 3911 /** 3912 * Content type to use when sending data. 3913 * 3914 * @attribute contentType 3915 * @type string 3916 * @default 'application/x-www-form-urlencoded' 3917 */ 3918 contentType: 'application/x-www-form-urlencoded', 3919 3920 /** 3921 * Set the withCredentials flag on the request. 3922 * 3923 * @attribute withCredentials 3924 * @type boolean 3925 * @default false 3926 */ 3927 withCredentials: false, 3928 3929 /** 3930 * Additional properties to send to core-xhr. 3931 * 3932 * Can be set to an object containing default properties 3933 * to send as arguments to the `core-xhr.request()` method 3934 * which implements the low-level communication. 3935 * 3936 * @property xhrArgs 3937 * @type Object 3938 * @default null 3939 */ 3940 xhrArgs: null, 3941 3942 ready: function() { 3943 this.xhr = document.createElement('core-xhr'); 3944 }, 3945 3946 receive: function(response, xhr) { 3947 if (this.isSuccess(xhr)) { 3948 this.processResponse(xhr); 3949 } else { 3950 this.error(xhr); 3951 } 3952 this.complete(xhr); 3953 }, 3954 3955 isSuccess: function(xhr) { 3956 var status = xhr.status || 0; 3957 return !status || (status >= 200 && status < 300); 3958 }, 3959 3960 processResponse: function(xhr) { 3961 var response = this.evalResponse(xhr); 3962 this.response = response; 3963 this.fire('core-response', {response: response, xhr: xhr}); 3964 }, 3965 3966 error: function(xhr) { 3967 var response = xhr.status + ': ' + xhr.responseText; 3968 this.fire('core-error', {response: response, xhr: xhr}); 3969 }, 3970 3971 complete: function(xhr) { 3972 this.fire('core-complete', {response: xhr.status, xhr: xhr}); 3973 }, 3974 3975 evalResponse: function(xhr) { 3976 return this[(this.handleAs || 'text') + 'Handler'](xhr); 3977 }, 3978 3979 xmlHandler: function(xhr) { 3980 return xhr.responseXML; 3981 }, 3982 3983 textHandler: function(xhr) { 3984 return xhr.responseText; 3985 }, 3986 3987 jsonHandler: function(xhr) { 3988 var r = xhr.responseText; 3989 try { 3990 return JSON.parse(r); 3991 } catch (x) { 3992 return r; 3993 } 3994 }, 3995 3996 documentHandler: function(xhr) { 3997 return xhr.response; 3998 }, 3999 4000 blobHandler: function(xhr) { 4001 return xhr.response; 4002 }, 4003 4004 arraybufferHandler: function(xhr) { 4005 return xhr.response; 4006 }, 4007 4008 urlChanged: function() { 4009 if (!this.handleAs) { 4010 var ext = String(this.url).split('.').pop(); 4011 switch (ext) { 4012 case 'json': 4013 this.handleAs = 'json'; 4014 break; 4015 } 4016 } 4017 this.autoGo(); 4018 }, 4019 4020 paramsChanged: function() { 4021 this.autoGo(); 4022 }, 4023 4024 autoChanged: function() { 4025 this.autoGo(); 4026 }, 4027 4028 // TODO(sorvell): multiple side-effects could call autoGo 4029 // during one micro-task, use a job to have only one action 4030 // occur 4031 autoGo: function() { 4032 if (this.auto) { 4033 this.goJob = this.job(this.goJob, this.go, 0); 4034 } 4035 }, 4036 4037 /** 4038 * Performs an Ajax request to the specified URL. 4039 * 4040 * @method go 4041 */ 4042 go: function() { 4043 var args = this.xhrArgs || {}; 4044 // TODO(sjmiles): we may want XHR to default to POST if body is set 4045 args.body = this.body || args.body; 4046 args.params = this.params || args.params; 4047 if (args.params && typeof(args.params) == 'string') { 4048 args.params = JSON.parse(args.params); 4049 } 4050 args.headers = this.headers || args.headers || {}; 4051 if (args.headers && typeof(args.headers) == 'string') { 4052 args.headers = JSON.parse(args.headers); 4053 } 4054 if (this.contentType) { 4055 args.headers['content-type'] = this.contentType; 4056 } 4057 if (this.handleAs === 'arraybuffer' || this.handleAs === 'blob' || 4058 this.handleAs === 'document') { 4059 args.responseType = this.handleAs; 4060 } 4061 args.withCredentials = this.withCredentials; 4062 args.callback = this.receive.bind(this); 4063 args.url = this.url; 4064 args.method = this.method; 4065 return args.url && this.xhr.request(args); 4066 } 4067 4068 }); 4069 4070 ; 4071 4072 4073 Polymer('context-free-parser', { 4074 4075 text: null, 4076 4077 textChanged: function() { 4078 if (this.text) { 4079 var entities = ContextFreeParser.parse(this.text); 4080 if (!entities || entities.length === 0) { 4081 entities = [ 4082 {name: this.url.split('/').pop(), description: '**Undocumented**'} 4083 ]; 4084 } 4085 this.data = { classes: entities }; 4086 } 4087 }, 4088 4089 dataChanged: function() { 4090 this.fire('data-ready'); 4091 } 4092 4093 }); 4094 4095 ; 4096 4097 4098 Polymer('core-doc-page', { 4099 4100 hilight: function(event, detail, sender) { 4101 detail.code = prettyPrintOne((detail.code || '').replace(/</g,'<').replace(/>/g,'>')); 4102 }, 4103 4104 homepageFilter: function(data) { 4105 if (!data) { 4106 return ''; 4107 } 4108 if (!data.homepage || data.homepage === 'github.io') { 4109 return '//polymer.github.io/' + data.name; 4110 } else { 4111 return data.homepage; 4112 } 4113 } 4114 4115 }); 4116 4117 ; 4118 4119 Polymer('core-selection', { 4120 /** 4121 * If true, multiple selections are allowed. 4122 * 4123 * @attribute multi 4124 * @type boolean 4125 * @default false 4126 */ 4127 multi: false, 4128 ready: function() { 4129 this.clear(); 4130 }, 4131 clear: function() { 4132 this.selection = []; 4133 }, 4134 /** 4135 * Retrieves the selected item(s). 4136 * @method getSelection 4137 * @returns Returns the selected item(s). If the multi property is true, 4138 * getSelection will return an array, otherwise it will return 4139 * the selected item or undefined if there is no selection. 4140 */ 4141 getSelection: function() { 4142 return this.multi ? this.selection : this.selection[0]; 4143 }, 4144 /** 4145 * Indicates if a given item is selected. 4146 * @method isSelected 4147 * @param {any} item The item whose selection state should be checked. 4148 * @returns Returns true if `item` is selected. 4149 */ 4150 isSelected: function(item) { 4151 return this.selection.indexOf(item) >= 0; 4152 }, 4153 setItemSelected: function(item, isSelected) { 4154 if (item !== undefined && item !== null) { 4155 if (isSelected) { 4156 this.selection.push(item); 4157 } else { 4158 var i = this.selection.indexOf(item); 4159 if (i >= 0) { 4160 this.selection.splice(i, 1); 4161 } 4162 } 4163 this.fire("core-select", {isSelected: isSelected, item: item}); 4164 } 4165 }, 4166 /** 4167 * Set the selection state for a given `item`. If the multi property 4168 * is true, then the selected state of `item` will be toggled; otherwise 4169 * the `item` will be selected. 4170 * @method select 4171 * @param {any} item: The item to select. 4172 */ 4173 select: function(item) { 4174 if (this.multi) { 4175 this.toggle(item); 4176 } else if (this.getSelection() !== item) { 4177 this.setItemSelected(this.getSelection(), false); 4178 this.setItemSelected(item, true); 4179 } 4180 }, 4181 /** 4182 * Toggles the selection state for `item`. 4183 * @method toggle 4184 * @param {any} item: The item to toggle. 4185 */ 4186 toggle: function(item) { 4187 this.setItemSelected(item, !this.isSelected(item)); 4188 } 4189 }); 4190 ; 4191 4192 4193 Polymer('core-selector', { 4194 4195 /** 4196 * Gets or sets the selected element. Default to use the index 4197 * of the item element. 4198 * 4199 * If you want a specific attribute value of the element to be 4200 * used instead of index, set "valueattr" to that attribute name. 4201 * 4202 * Example: 4203 * 4204 * <core-selector valueattr="label" selected="foo"> 4205 * <div label="foo"></div> 4206 * <div label="bar"></div> 4207 * <div label="zot"></div> 4208 * </core-selector> 4209 * 4210 * In multi-selection this should be an array of values. 4211 * 4212 * Example: 4213 * 4214 * <core-selector id="selector" valueattr="label" multi> 4215 * <div label="foo"></div> 4216 * <div label="bar"></div> 4217 * <div label="zot"></div> 4218 * </core-selector> 4219 * 4220 * this.$.selector.selected = ['foo', 'zot']; 4221 * 4222 * @attribute selected 4223 * @type Object 4224 * @default null 4225 */ 4226 selected: null, 4227 4228 /** 4229 * If true, multiple selections are allowed. 4230 * 4231 * @attribute multi 4232 * @type boolean 4233 * @default false 4234 */ 4235 multi: false, 4236 4237 /** 4238 * Specifies the attribute to be used for "selected" attribute. 4239 * 4240 * @attribute valueattr 4241 * @type string 4242 * @default 'name' 4243 */ 4244 valueattr: 'name', 4245 4246 /** 4247 * Specifies the CSS class to be used to add to the selected element. 4248 * 4249 * @attribute selectedClass 4250 * @type string 4251 * @default 'core-selected' 4252 */ 4253 selectedClass: 'core-selected', 4254 4255 /** 4256 * Specifies the property to be used to set on the selected element 4257 * to indicate its active state. 4258 * 4259 * @attribute selectedProperty 4260 * @type string 4261 * @default '' 4262 */ 4263 selectedProperty: '', 4264 4265 /** 4266 * Specifies the attribute to set on the selected element to indicate 4267 * its active state. 4268 * 4269 * @attribute selectedAttribute 4270 * @type string 4271 * @default 'active' 4272 */ 4273 selectedAttribute: 'active', 4274 4275 /** 4276 * Returns the currently selected element. In multi-selection this returns 4277 * an array of selected elements. 4278 * 4279 * @attribute selectedItem 4280 * @type Object 4281 * @default null 4282 */ 4283 selectedItem: null, 4284 4285 /** 4286 * In single selection, this returns the model associated with the 4287 * selected element. 4288 * 4289 * @attribute selectedModel 4290 * @type Object 4291 * @default null 4292 */ 4293 selectedModel: null, 4294 4295 /** 4296 * In single selection, this returns the selected index. 4297 * 4298 * @attribute selectedIndex 4299 * @type number 4300 * @default -1 4301 */ 4302 selectedIndex: -1, 4303 4304 /** 4305 * The target element that contains items. If this is not set 4306 * core-selector is the container. 4307 * 4308 * @attribute target 4309 * @type Object 4310 * @default null 4311 */ 4312 target: null, 4313 4314 /** 4315 * This can be used to query nodes from the target node to be used for 4316 * selection items. Note this only works if the 'target' property is set. 4317 * 4318 * Example: 4319 * 4320 * <core-selector target="{{$.myForm}}" itemsSelector="input[type=radio]"></core-selector> 4321 * <form id="myForm"> 4322 * <label><input type="radio" name="color" value="red"> Red</label> <br> 4323 * <label><input type="radio" name="color" value="green"> Green</label> <br> 4324 * <label><input type="radio" name="color" value="blue"> Blue</label> <br> 4325 * <p>color = {{color}}</p> 4326 * </form> 4327 * 4328 * @attribute itemSelector 4329 * @type string 4330 * @default '' 4331 */ 4332 itemsSelector: '', 4333 4334 /** 4335 * The event that would be fired from the item element to indicate 4336 * it is being selected. 4337 * 4338 * @attribute activateEvent 4339 * @type string 4340 * @default 'tap' 4341 */ 4342 activateEvent: 'tap', 4343 4344 /** 4345 * Set this to true to disallow changing the selection via the 4346 * `activateEvent`. 4347 * 4348 * @attribute notap 4349 * @type boolean 4350 * @default false 4351 */ 4352 notap: false, 4353 4354 ready: function() { 4355 this.activateListener = this.activateHandler.bind(this); 4356 this.observer = new MutationObserver(this.updateSelected.bind(this)); 4357 if (!this.target) { 4358 this.target = this; 4359 } 4360 }, 4361 4362 get items() { 4363 if (!this.target) { 4364 return []; 4365 } 4366 var nodes = this.target !== this ? (this.itemsSelector ? 4367 this.target.querySelectorAll(this.itemsSelector) : 4368 this.target.children) : this.$.items.getDistributedNodes(); 4369 return Array.prototype.filter.call(nodes || [], function(n) { 4370 return n && n.localName !== 'template'; 4371 }); 4372 }, 4373 4374 targetChanged: function(old) { 4375 if (old) { 4376 this.removeListener(old); 4377 this.observer.disconnect(); 4378 this.clearSelection(); 4379 } 4380 if (this.target) { 4381 this.addListener(this.target); 4382 this.observer.observe(this.target, {childList: true}); 4383 this.updateSelected(); 4384 } 4385 }, 4386 4387 addListener: function(node) { 4388 node.addEventListener(this.activateEvent, this.activateListener); 4389 }, 4390 4391 removeListener: function(node) { 4392 node.removeEventListener(this.activateEvent, this.activateListener); 4393 }, 4394 4395 get selection() { 4396 return this.$.selection.getSelection(); 4397 }, 4398 4399 selectedChanged: function() { 4400 this.updateSelected(); 4401 }, 4402 4403 updateSelected: function() { 4404 this.validateSelected(); 4405 if (this.multi) { 4406 this.clearSelection(); 4407 this.selected && this.selected.forEach(function(s) { 4408 this.valueToSelection(s); 4409 }, this); 4410 } else { 4411 this.valueToSelection(this.selected); 4412 } 4413 }, 4414 4415 validateSelected: function() { 4416 // convert to an array for multi-selection 4417 if (this.multi && !Array.isArray(this.selected) && 4418 this.selected !== null && this.selected !== undefined) { 4419 this.selected = [this.selected]; 4420 } 4421 }, 4422 4423 clearSelection: function() { 4424 if (this.multi) { 4425 this.selection.slice().forEach(function(s) { 4426 this.$.selection.setItemSelected(s, false); 4427 }, this); 4428 } else { 4429 this.$.selection.setItemSelected(this.selection, false); 4430 } 4431 this.selectedItem = null; 4432 this.$.selection.clear(); 4433 }, 4434 4435 valueToSelection: function(value) { 4436 var item = (value === null || value === undefined) ? 4437 null : this.items[this.valueToIndex(value)]; 4438 this.$.selection.select(item); 4439 }, 4440 4441 updateSelectedItem: function() { 4442 this.selectedItem = this.selection; 4443 }, 4444 4445 selectedItemChanged: function() { 4446 if (this.selectedItem) { 4447 var t = this.selectedItem.templateInstance; 4448 this.selectedModel = t ? t.model : undefined; 4449 } else { 4450 this.selectedModel = null; 4451 } 4452 this.selectedIndex = this.selectedItem ? 4453 parseInt(this.valueToIndex(this.selected)) : -1; 4454 }, 4455 4456 valueToIndex: function(value) { 4457 // find an item with value == value and return it's index 4458 for (var i=0, items=this.items, c; (c=items[i]); i++) { 4459 if (this.valueForNode(c) == value) { 4460 return i; 4461 } 4462 } 4463 // if no item found, the value itself is probably the index 4464 return value; 4465 }, 4466 4467 valueForNode: function(node) { 4468 return node[this.valueattr] || node.getAttribute(this.valueattr); 4469 }, 4470 4471 // events fired from <core-selection> object 4472 selectionSelect: function(e, detail) { 4473 this.updateSelectedItem(); 4474 if (detail.item) { 4475 this.applySelection(detail.item, detail.isSelected); 4476 } 4477 }, 4478 4479 applySelection: function(item, isSelected) { 4480 if (this.selectedClass) { 4481 item.classList.toggle(this.selectedClass, isSelected); 4482 } 4483 if (this.selectedProperty) { 4484 item[this.selectedProperty] = isSelected; 4485 } 4486 if (this.selectedAttribute && item.setAttribute) { 4487 if (isSelected) { 4488 item.setAttribute(this.selectedAttribute, ''); 4489 } else { 4490 item.removeAttribute(this.selectedAttribute); 4491 } 4492 } 4493 }, 4494 4495 // event fired from host 4496 activateHandler: function(e) { 4497 if (!this.notap) { 4498 var i = this.findDistributedTarget(e.target, this.items); 4499 if (i >= 0) { 4500 var item = this.items[i]; 4501 var s = this.valueForNode(item) || i; 4502 if (this.multi) { 4503 if (this.selected) { 4504 this.addRemoveSelected(s); 4505 } else { 4506 this.selected = [s]; 4507 } 4508 } else { 4509 this.selected = s; 4510 } 4511 this.asyncFire('core-activate', {item: item}); 4512 } 4513 } 4514 }, 4515 4516 addRemoveSelected: function(value) { 4517 var i = this.selected.indexOf(value); 4518 if (i >= 0) { 4519 this.selected.splice(i, 1); 4520 } else { 4521 this.selected.push(value); 4522 } 4523 this.valueToSelection(value); 4524 }, 4525 4526 findDistributedTarget: function(target, nodes) { 4527 // find first ancestor of target (including itself) that 4528 // is in nodes, if any 4529 while (target && target != this) { 4530 var i = Array.prototype.indexOf.call(nodes, target); 4531 if (i >= 0) { 4532 return i; 4533 } 4534 target = target.parentNode; 4535 } 4536 } 4537 }); 4538 ; 4539 4540 Polymer('core-menu',{}); 4541 ; 4542 4543 4544 Polymer('core-item', { 4545 4546 /** 4547 * The URL of an image for the icon. 4548 * 4549 * @attribute src 4550 * @type string 4551 * @default '' 4552 */ 4553 4554 /** 4555 * Specifies the icon from the Polymer icon set. 4556 * 4557 * @attribute icon 4558 * @type string 4559 * @default '' 4560 */ 4561 4562 /** 4563 * Specifies the label for the menu item. 4564 * 4565 * @attribute label 4566 * @type string 4567 * @default '' 4568 */ 4569 4570 }); 4571 4572 ; 4573 4574 4575 Polymer('core-doc-toc', { 4576 4577 searchAction: function() { 4578 this.$.searchBar.style.opacity = 1; 4579 this.$.searchBar.style.display = ''; 4580 }, 4581 4582 closeSearchAction: function() { 4583 this.$.searchBar.style.opacity = 0; 4584 this.$.searchBar.style.display = 'none'; 4585 } 4586 4587 }); 4588 4589 ; 4590 4591 4592 Polymer('core-doc-viewer', { 4593 /** 4594 * A single file to parse for docs 4595 * 4596 * @attribute url 4597 * @type String 4598 * @default '' 4599 */ 4600 4601 /** 4602 * Class documentation extracted from the parser 4603 * 4604 * @property classes 4605 * @type Array 4606 * @default [] 4607 */ 4608 classes: [], 4609 4610 /** 4611 * Files to parse for docs 4612 * 4613 * @attribute sources 4614 * @type Array 4615 * @default [] 4616 */ 4617 sources: [], 4618 4619 ready: function() { 4620 window.addEventListener('hashchange', this.parseLocationHash.bind(this)); 4621 this.parseLocationHash(); 4622 }, 4623 4624 parseLocationHash: function() { 4625 this.route = window.location.hash.slice(1); 4626 }, 4627 4628 routeChanged: function() { 4629 this.validateRoute(); 4630 }, 4631 4632 validateRoute: function() { 4633 if (this.route) { 4634 this.classes.some(function(c) { 4635 if (c.name === this.route) { 4636 this.data = c; 4637 this.route = ''; 4638 return; 4639 } 4640 }, this); 4641 } 4642 }, 4643 4644 selectedChanged: function() { 4645 this.data = this.classes[this.selected]; 4646 }, 4647 4648 parserDataReady: function(event) { 4649 this.assimilateData(event.target.data); 4650 }, 4651 4652 assimilateData: function(data) { 4653 this.classes = this.classes.concat(data.classes); 4654 this.classes.sort(function(a, b) { 4655 var na = a && a.name.toLowerCase(), nb = b && b.name.toLowerCase(); 4656 return (na < nb) ? -1 : (na == nb) ? 0 : 1; 4657 }); 4658 if (!this.data && !this.route && this.classes.length) { 4659 this.data = this.classes[0]; 4660 } 4661 if (this.classes.length > 1) { 4662 this.$.toc.style.display = 'block'; 4663 } 4664 this.validateRoute(); 4665 } 4666 4667 }); 4668 4669 ; 4670 4671 4672 Polymer('core-component-page', { 4673 4674 moduleName: '', 4675 // TODO(sjmiles): needed this to force Object type for deserialization 4676 sources: [], 4677 4678 ready: function() { 4679 this.moduleName = this.moduleName || this.findModuleName(); 4680 }, 4681 4682 moduleNameChanged: function() { 4683 document.title = this.moduleName; 4684 this.url = !this.sources.length && this.moduleName ? this.moduleName + '.html' : ''; 4685 }, 4686 4687 findModuleName: function() { 4688 var path = location.pathname.split('/'); 4689 var name = path.pop() || path.pop(); 4690 if (name.indexOf('.html') >= 0) { 4691 name = path.pop(); 4692 } 4693 return name || ''; 4694 } 4695 4696 }); 4697 4698