OLD | NEW |
(Empty) | |
| 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 consu
me 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 presente
d 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 edg
e) |
| 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 s
creen. |
| 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 pane
l 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#lan
gs">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_)?iter
ator|(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|ca
se|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|includ
e|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 14
4. |
| 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 tha
t |
| 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 + '|' + htmlDocCom
mentClause, '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 pragm
a 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 as
ynchronously. 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 cr
edentials 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) : n
ull; |
| 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 `par
ams` 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,'<').re
place(/>/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=radi
o]"></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</lab
el> <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 |
OLD | NEW |