Index: third_party/polymer/components-chromium/core-component-page/core-component-page-extracted.js |
diff --git a/third_party/polymer/components-chromium/core-component-page/core-component-page-extracted.js b/third_party/polymer/components-chromium/core-component-page/core-component-page-extracted.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fe36fb744a631d657baefb8657f8b6e4d4ef4c8d |
--- /dev/null |
+++ b/third_party/polymer/components-chromium/core-component-page/core-component-page-extracted.js |
@@ -0,0 +1,4698 @@ |
+ |
+ |
+ (function() { |
+ |
+ Polymer('core-layout', { |
+ |
+ isContainer: false, |
+ /** |
+ * Controls if the element lays out vertically or not. |
+ * |
+ * @attribute vertical |
+ * @type boolean |
+ * @default false |
+ */ |
+ vertical: false, |
+ /** |
+ * Controls how the items are aligned in the main-axis direction. For |
+ * example for a horizontal layout, this controls how each item is aligned |
+ * horizontally. |
+ * |
+ * @attribute justify |
+ * @type string start|center|end|between |
+ * @default '' |
+ */ |
+ justify: '', |
+ /** |
+ * Controls how the items are aligned in cross-axis direction. For |
+ * example for a horizontal layout, this controls how each item is aligned |
+ * vertically. |
+ * |
+ * @attribute align |
+ * @type string start|center|end |
+ * @default '' |
+ */ |
+ align: '', |
+ /** |
+ * Controls whether or not the items layout in reverse order. |
+ * |
+ * @attribute reverse |
+ * @type boolean |
+ * @default false |
+ */ |
+ reverse: false, |
+ layoutPrefix: 'core-', |
+ |
+ // NOTE: include template so that styles are loaded, but remove |
+ // so that we can decide dynamically what part to include |
+ registerCallback: function(polymerElement) { |
+ var template = polymerElement.querySelector('template'); |
+ this.styles = template.content.querySelectorAll('style').array(); |
+ this.styles.forEach(function(s) { |
+ s.removeAttribute('no-shim'); |
+ }) |
+ }, |
+ |
+ fetchTemplate: function() { |
+ return null; |
+ }, |
+ |
+ attached: function() { |
+ this.installScopeStyle(this.styles[0]); |
+ if (this.children.length) { |
+ this.isContainer = true; |
+ } |
+ var container = this.isContainer ? this : this.parentNode; |
+ // detect if laying out a shadowRoot host. |
+ var forHost = container instanceof ShadowRoot; |
+ if (forHost) { |
+ this.installScopeStyle(this.styles[1], 'host'); |
+ container = container.host || document.body; |
+ } |
+ this.layoutContainer = container; |
+ }, |
+ |
+ detached: function() { |
+ this.layoutContainer = null; |
+ }, |
+ |
+ layoutContainerChanged: function(old) { |
+ this.style.display = this.layoutContainer === this ? null : 'none'; |
+ this.verticalChanged(); |
+ this.alignChanged(); |
+ this.justifyChanged(); |
+ }, |
+ |
+ setLayoutClass: function(prefix, old, newValue) { |
+ if (this.layoutContainer) { |
+ prefix = this.layoutPrefix + prefix; |
+ if (old) { |
+ this.layoutContainer.classList.remove(prefix + old); |
+ } |
+ if (newValue) { |
+ this.layoutContainer.classList.add(prefix + newValue); |
+ } |
+ } |
+ }, |
+ |
+ verticalChanged: function(old) { |
+ old = old ? 'v' : 'h'; |
+ var vertical = this.vertical ? 'v' : 'h'; |
+ this.setLayoutClass('', old, vertical); |
+ }, |
+ |
+ alignChanged: function(old) { |
+ this.setLayoutClass('align-', old, this.align); |
+ }, |
+ |
+ justifyChanged: function(old) { |
+ this.setLayoutClass('justify-', old, this.justify); |
+ }, |
+ |
+ reverseChanged: function(old) { |
+ old = old ? 'reverse' : ''; |
+ var newValue = this.reverse ? 'reverse' : ''; |
+ this.setLayoutClass('', old, newValue); |
+ } |
+ |
+ }); |
+ |
+ })(); |
+ ; |
+ |
+ |
+ (function() { |
+ |
+ var SKIP_ID = 'meta'; |
+ var metaData = {}, metaArray = {}; |
+ |
+ Polymer('core-meta', { |
+ |
+ /** |
+ * The type of meta-data. All meta-data with the same type with be |
+ * stored together. |
+ * |
+ * @attribute type |
+ * @type string |
+ * @default 'default' |
+ */ |
+ type: 'default', |
+ |
+ alwaysPrepare: true, |
+ |
+ ready: function() { |
+ this.register(this.id); |
+ }, |
+ |
+ get metaArray() { |
+ var t = this.type; |
+ if (!metaArray[t]) { |
+ metaArray[t] = []; |
+ } |
+ return metaArray[t]; |
+ }, |
+ |
+ get metaData() { |
+ var t = this.type; |
+ if (!metaData[t]) { |
+ metaData[t] = {}; |
+ } |
+ return metaData[t]; |
+ }, |
+ |
+ register: function(id, old) { |
+ if (id && id !== SKIP_ID) { |
+ this.unregister(this, old); |
+ this.metaData[id] = this; |
+ this.metaArray.push(this); |
+ } |
+ }, |
+ |
+ unregister: function(meta, id) { |
+ delete this.metaData[id || meta.id]; |
+ var i = this.metaArray.indexOf(meta); |
+ if (i >= 0) { |
+ this.metaArray.splice(i, 1); |
+ } |
+ }, |
+ |
+ /** |
+ * Returns a list of all meta-data elements with the same type. |
+ * |
+ * @attribute list |
+ * @type array |
+ * @default [] |
+ */ |
+ get list() { |
+ return this.metaArray; |
+ }, |
+ |
+ /** |
+ * Retrieves meta-data by ID. |
+ * |
+ * @method byId |
+ * @param {String} id The ID of the meta-data to be returned. |
+ * @returns Returns meta-data. |
+ */ |
+ byId: function(id) { |
+ return this.metaData[id]; |
+ } |
+ |
+ }); |
+ |
+ })(); |
+ |
+; |
+ |
+ |
+ Polymer('core-iconset', { |
+ |
+ /** |
+ * The URL of the iconset image. |
+ * |
+ * @attribute src |
+ * @type string |
+ * @default '' |
+ */ |
+ src: '', |
+ |
+ /** |
+ * The width of the iconset image. This must only be specified if the |
+ * icons are arranged into separate rows inside the image. |
+ * |
+ * @attribute width |
+ * @type number |
+ * @default 0 |
+ */ |
+ width: 0, |
+ |
+ /** |
+ * A space separated list of names corresponding to icons in the iconset |
+ * image file. This list must be ordered the same as the icon images |
+ * in the image file. |
+ * |
+ * @attribute icons |
+ * @type string |
+ * @default '' |
+ */ |
+ icons: '', |
+ |
+ /** |
+ * The size of an individual icon. Note that icons must be square. |
+ * |
+ * @attribute iconSize |
+ * @type number |
+ * @default 24 |
+ */ |
+ iconSize: 24, |
+ |
+ /** |
+ * The horizontal offset of the icon images in the inconset src image. |
+ * This is typically used if the image resource contains additional images |
+ * beside those intended for the iconset. |
+ * |
+ * @attribute offsetX |
+ * @type number |
+ * @default 0 |
+ */ |
+ offsetX: 0, |
+ /** |
+ * The vertical offset of the icon images in the inconset src image. |
+ * This is typically used if the image resource contains additional images |
+ * beside those intended for the iconset. |
+ * |
+ * @attribute offsetY |
+ * @type number |
+ * @default 0 |
+ */ |
+ offsetY: 0, |
+ type: 'iconset', |
+ |
+ created: function() { |
+ this.iconMap = {}; |
+ this.iconNames = []; |
+ this.themes = {}; |
+ }, |
+ |
+ ready: function() { |
+ // TODO(sorvell): ensure iconset's src is always relative to the main |
+ // document |
+ if (this.src && (this.ownerDocument !== document)) { |
+ this.src = this.resolvePath(this.src, this.ownerDocument.baseURI); |
+ } |
+ this.super(); |
+ this.updateThemes(); |
+ }, |
+ |
+ iconsChanged: function() { |
+ var ox = this.offsetX; |
+ var oy = this.offsetY; |
+ this.icons && this.icons.split(/\s+/g).forEach(function(name, i) { |
+ this.iconNames.push(name); |
+ this.iconMap[name] = { |
+ offsetX: ox, |
+ offsetY: oy |
+ } |
+ if (ox + this.iconSize < this.width) { |
+ ox += this.iconSize; |
+ } else { |
+ ox = this.offsetX; |
+ oy += this.iconSize; |
+ } |
+ }, this); |
+ }, |
+ |
+ updateThemes: function() { |
+ var ts = this.querySelectorAll('property[theme]'); |
+ ts && ts.array().forEach(function(t) { |
+ this.themes[t.getAttribute('theme')] = { |
+ offsetX: parseInt(t.getAttribute('offsetX')) || 0, |
+ offsetY: parseInt(t.getAttribute('offsetY')) || 0 |
+ }; |
+ }, this); |
+ }, |
+ |
+ // TODO(ffu): support retrived by index e.g. getOffset(10); |
+ /** |
+ * Returns an object containing `offsetX` and `offsetY` properties which |
+ * specify the pixel locaion in the iconset's src file for the given |
+ * `icon` and `theme`. It's uncommon to call this method. It is useful, |
+ * for example, to manually position a css backgroundImage to the proper |
+ * offset. It's more common to use the `applyIcon` method. |
+ * |
+ * @method getOffset |
+ * @param {String|Number} icon The name of the icon or the index of the |
+ * icon within in the icon image. |
+ * @param {String} theme The name of the theme. |
+ * @returns {Object} An object specifying the offset of the given icon |
+ * within the icon resource file; `offsetX` is the horizontal offset and |
+ * `offsetY` is the vertical offset. Both values are in pixel units. |
+ */ |
+ getOffset: function(icon, theme) { |
+ var i = this.iconMap[icon]; |
+ if (!i) { |
+ var n = this.iconNames[Number(icon)]; |
+ i = this.iconMap[n]; |
+ } |
+ var t = this.themes[theme]; |
+ if (i && t) { |
+ return { |
+ offsetX: i.offsetX + t.offsetX, |
+ offsetY: i.offsetY + t.offsetY |
+ } |
+ } |
+ return i; |
+ }, |
+ |
+ /** |
+ * Applies an icon to the given element as a css background image. This |
+ * method does not size the element, and it's often necessary to set |
+ * the element's height and width so that the background image is visible. |
+ * |
+ * @method applyIcon |
+ * @param {Element} element The element to which the background is |
+ * applied. |
+ * @param {String|Number} icon The name or index of the icon to apply. |
+ * @param {String} theme (optional) The name of the theme for the icon. |
+ * @param {Number} scale (optional, defaults to 1) A scaling factor |
+ * with which the icon can be magnified. |
+ */ |
+ applyIcon: function(element, icon, scale) { |
+ var offset = this.getOffset(icon); |
+ scale = scale || 1; |
+ if (element && offset) { |
+ var style = element.style; |
+ style.backgroundImage = 'url(' + this.src + ')'; |
+ style.backgroundPosition = (-offset.offsetX * scale + 'px') + |
+ ' ' + (-offset.offsetY * scale + 'px'); |
+ style.backgroundSize = scale === 1 ? 'auto' : |
+ this.width * scale + 'px'; |
+ } |
+ } |
+ |
+ }); |
+ |
+ ; |
+ |
+ |
+ Polymer('core-iconset-svg', { |
+ |
+ |
+ /** |
+ * The size of an individual icon. Note that icons must be square. |
+ * |
+ * @attribute iconSize |
+ * @type number |
+ * @default 24 |
+ */ |
+ iconSize: 24, |
+ type: 'iconset', |
+ |
+ created: function() { |
+ this._icons = {}; |
+ }, |
+ |
+ ready: function() { |
+ this.super(); |
+ this.updateIcons(); |
+ }, |
+ |
+ iconById: function(id) { |
+ return this._icons[id] || (this._icons[id] = this.querySelector('#' + id)); |
+ }, |
+ |
+ cloneIcon: function(id) { |
+ var icon = this.iconById(id); |
+ if (icon) { |
+ var content = icon.cloneNode(true); |
+ var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); |
+ svg.setAttribute('viewBox', '0 0 ' + this.iconSize + ' ' + |
+ this.iconSize); |
+ // NOTE(dfreedm): work around https://crbug.com/370136 |
+ svg.style.pointerEvents = 'none'; |
+ svg.appendChild(content); |
+ return svg; |
+ } |
+ }, |
+ |
+ get iconNames() { |
+ if (!this._iconNames) { |
+ this._iconNames = this.findIconNames(); |
+ } |
+ return this._iconNames; |
+ }, |
+ |
+ findIconNames: function() { |
+ var icons = this.querySelectorAll('[id]').array(); |
+ if (icons.length) { |
+ return icons.map(function(n){ return n.id }); |
+ } |
+ }, |
+ |
+ /** |
+ * Applies an icon to the given element. The svg icon is added to the |
+ * element's shadowRoot if one exists or directly to itself. |
+ * |
+ * @method applyIcon |
+ * @param {Element} element The element to which the icon is |
+ * applied. |
+ * @param {String|Number} icon The name the icon to apply. |
+ */ |
+ applyIcon: function(element, icon, scale) { |
+ var root = element.shadowRoot || element; |
+ // remove old |
+ var old = root.querySelector('svg'); |
+ if (old) { |
+ old.remove(); |
+ } |
+ // install new |
+ var svg = this.cloneIcon(icon); |
+ if (!svg) { |
+ return; |
+ } |
+ var size = scale * this.iconSize; |
+ if (size) { |
+ svg.style.height = svg.style.width = size + 'px'; |
+ } else { |
+ svg.setAttribute('height', '100%'); |
+ svg.setAttribute('width', '100%'); |
+ svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); |
+ } |
+ svg.style.display = 'block'; |
+ root.insertBefore(svg, root.firstElementChild); |
+ }, |
+ |
+ /** |
+ * Tell users of the iconset, that the set has loaded. |
+ * This finds all elements matching the selector argument and calls |
+ * the method argument on them. |
+ * @method updateIcons |
+ * @param selector {string} css selector to identify iconset users, |
+ * defaults to '[icon]' |
+ * @param method {string} method to call on found elements, |
+ * defaults to 'updateIcon' |
+ */ |
+ updateIcons: function(selector, method) { |
+ selector = selector || '[icon]'; |
+ method = method || 'updateIcon'; |
+ var deep = window.ShadowDOMPolyfill ? '' : 'html /deep/ '; |
+ var i$ = document.querySelectorAll(deep + selector); |
+ for (var i=0, e; e=i$[i]; i++) { |
+ if (e[method]) { |
+ e[method].call(e); |
+ } |
+ } |
+ } |
+ |
+ |
+ }); |
+ |
+ ; |
+ |
+(function() { |
+ |
+ // mono-state |
+ var meta; |
+ |
+ Polymer('core-icon', { |
+ |
+ /** |
+ * The URL of an image for the icon. If the src property is specified, |
+ * the icon property should not be. |
+ * |
+ * @attribute src |
+ * @type string |
+ * @default '' |
+ */ |
+ src: '', |
+ |
+ /** |
+ * Specifies the size of the icon in pixel units. |
+ * |
+ * @attribute size |
+ * @type string |
+ * @default 24 |
+ */ |
+ size: 24, |
+ |
+ /** |
+ * Specifies the icon name or index in the set of icons available in |
+ * the icon's icon set. If the icon property is specified, |
+ * the src property should not be. |
+ * |
+ * @attribute icon |
+ * @type string |
+ * @default '' |
+ */ |
+ icon: '', |
+ |
+ observe: { |
+ 'size icon': 'updateIcon' |
+ }, |
+ |
+ defaultIconset: 'icons', |
+ |
+ ready: function() { |
+ if (!meta) { |
+ meta = document.createElement('core-iconset'); |
+ } |
+ this.updateIcon(); |
+ }, |
+ |
+ srcChanged: function() { |
+ this.style.backgroundImage = 'url(' + this.src + ')'; |
+ this.style.backgroundPosition = 'center'; |
+ this.style.backgroundSize = this.size + 'px ' + this.size + 'px'; |
+ }, |
+ |
+ getIconset: function(name) { |
+ return meta.byId(name || this.defaultIconset); |
+ }, |
+ |
+ updateIcon: function() { |
+ if (this.size) { |
+ this.style.width = this.style.height = this.size + 'px'; |
+ } |
+ if (this.icon) { |
+ var parts = String(this.icon).split(':'); |
+ var icon = parts.pop(); |
+ if (icon) { |
+ var set = this.getIconset(parts.pop()); |
+ if (set) { |
+ set.applyIcon(this, icon, this.size / set.iconSize); |
+ } |
+ } |
+ } |
+ } |
+ |
+ }); |
+ |
+})(); |
+; |
+ |
+ |
+ Polymer('core-icon-button', { |
+ |
+ /** |
+ * The URL of an image for the icon. Should not use `icon` property |
+ * if you are using this property. |
+ * |
+ * @attribute src |
+ * @type string |
+ * @default '' |
+ */ |
+ src: '', |
+ |
+ /** |
+ * If true, border is placed around the button to indicate it's |
+ * active state. |
+ * |
+ * @attribute active |
+ * @type boolean |
+ * @default false |
+ */ |
+ active: false, |
+ |
+ /** |
+ * Specifies the icon name or index in the set of icons available in |
+ * the icon set. Should not use `src` property if you are using this |
+ * property. |
+ * |
+ * @attribute icon |
+ * @type string |
+ * @default '' |
+ */ |
+ icon: '', |
+ |
+ activeChanged: function() { |
+ this.classList.toggle('selected', this.active); |
+ } |
+ |
+ }); |
+ |
+ ; |
+Polymer('core-toolbar');; |
+ |
+ |
+ Polymer('core-header-panel', { |
+ |
+ publish: { |
+ /** |
+ * Controls header and scrolling behavior. Options are |
+ * `standard`, `seamed`, `waterfall`, `waterfall-tall`, |
+ * `waterfall-medium-tall`, `scroll` and `cover`. |
+ * Default is `standard`. |
+ * |
+ * `standard`: The header is a step above the panel. The header will consume the |
+ * panel at the point of entry, preventing it from passing through to the |
+ * opposite side. |
+ * |
+ * `seamed`: The header is presented as seamed with the panel. |
+ * |
+ * `waterfall`: Similar to standard mode, but header is initially presented as |
+ * seamed with panel, but then separates to form the step. |
+ * |
+ * `waterfall-tall`: The header is initially taller (`tall` class is added to |
+ * the header). As the user scrolls, the header separates (forming an edge) |
+ * while condensing (`tall` class is removed from the header). |
+ * |
+ * `scroll`: The header keeps its seam with the panel, and is pushed off screen. |
+ * |
+ * `cover`: The panel covers the whole `core-header-panel` including the |
+ * header. This allows user to style the panel in such a way that the panel is |
+ * partially covering the header. |
+ * |
+ * <style> |
+ * core-header-panel[mode=cover]::shadow #mainContainer { |
+ * left: 80px; |
+ * } |
+ * .content { |
+ * margin: 60px 60px 60px 0; |
+ * } |
+ * </style> |
+ * |
+ * <core-header-panel mode="cover"> |
+ * <core-appbar class="tall"> |
+ * <core-icon-button icon="menu"></core-icon-button> |
+ * </core-appbar> |
+ * <div class="content"></div> |
+ * </core-header-panel> |
+ * |
+ * @attribute mode |
+ * @type string |
+ * @default '' |
+ */ |
+ mode: {value: '', reflect: true}, |
+ |
+ /** |
+ * The class used in waterfall-tall mode. Change this if the header |
+ * accepts a different class for toggling height, e.g. "medium-tall" |
+ * |
+ * @attribute tallClass |
+ * @type string |
+ * @default 'tall' |
+ */ |
+ tallClass: 'tall', |
+ |
+ /** |
+ * If true, the drop-shadow is always shown no matter what mode is set to. |
+ * |
+ * @attribute shadow |
+ * @type boolean |
+ * @default false |
+ */ |
+ shadow: false, |
+ }, |
+ |
+ domReady: function() { |
+ this.async('scroll'); |
+ }, |
+ |
+ modeChanged: function() { |
+ this.scroll(); |
+ }, |
+ |
+ get header() { |
+ return this.$.headerContent.getDistributedNodes()[0]; |
+ }, |
+ |
+ scroll: function() { |
+ var shadowMode = {'waterfall': 1, 'waterfall-tall': 1}; |
+ var noShadow = {'seamed': 1, 'cover': 1, 'scroll': 1}; |
+ var tallMode = {'waterfall-tall': 1}; |
+ |
+ var main = this.$.mainContainer; |
+ var header = this.header; |
+ |
+ var sTop = main.scrollTop; |
+ var atTop = sTop === 0; |
+ |
+ if (header) { |
+ this.$.dropShadow.classList.toggle('hidden', !this.shadow && |
+ (atTop && shadowMode[this.mode] || noShadow[this.mode])); |
+ |
+ if (tallMode[this.mode]) { |
+ header.classList.toggle(this.tallClass, atTop); |
+ } |
+ |
+ header.classList.toggle('animate', tallMode[this.mode]); |
+ } |
+ } |
+ |
+ }); |
+ |
+; |
+/** |
+ * marked - a markdown parser |
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) |
+ * https://github.com/chjj/marked |
+ */ |
+ |
+;(function() { |
+ |
+/** |
+ * Block-Level Grammar |
+ */ |
+ |
+var block = { |
+ newline: /^\n+/, |
+ code: /^( {4}[^\n]+\n*)+/, |
+ fences: noop, |
+ hr: /^( *[-*_]){3,} *(?:\n+|$)/, |
+ heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, |
+ nptable: noop, |
+ lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, |
+ blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, |
+ list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, |
+ html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, |
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, |
+ table: noop, |
+ paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, |
+ text: /^[^\n]+/ |
+}; |
+ |
+block.bullet = /(?:[*+-]|\d+\.)/; |
+block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; |
+block.item = replace(block.item, 'gm') |
+ (/bull/g, block.bullet) |
+ (); |
+ |
+block.list = replace(block.list) |
+ (/bull/g, block.bullet) |
+ ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') |
+ ('def', '\\n+(?=' + block.def.source + ')') |
+ (); |
+ |
+block.blockquote = replace(block.blockquote) |
+ ('def', block.def) |
+ (); |
+ |
+block._tag = '(?!(?:' |
+ + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' |
+ + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' |
+ + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; |
+ |
+block.html = replace(block.html) |
+ ('comment', /<!--[\s\S]*?-->/) |
+ ('closed', /<(tag)[\s\S]+?<\/\1>/) |
+ ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/) |
+ (/tag/g, block._tag) |
+ (); |
+ |
+block.paragraph = replace(block.paragraph) |
+ ('hr', block.hr) |
+ ('heading', block.heading) |
+ ('lheading', block.lheading) |
+ ('blockquote', block.blockquote) |
+ ('tag', '<' + block._tag) |
+ ('def', block.def) |
+ (); |
+ |
+/** |
+ * Normal Block Grammar |
+ */ |
+ |
+block.normal = merge({}, block); |
+ |
+/** |
+ * GFM Block Grammar |
+ */ |
+ |
+block.gfm = merge({}, block.normal, { |
+ fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/, |
+ paragraph: /^/ |
+}); |
+ |
+block.gfm.paragraph = replace(block.paragraph) |
+ ('(?!', '(?!' |
+ + block.gfm.fences.source.replace('\\1', '\\2') + '|' |
+ + block.list.source.replace('\\1', '\\3') + '|') |
+ (); |
+ |
+/** |
+ * GFM + Tables Block Grammar |
+ */ |
+ |
+block.tables = merge({}, block.gfm, { |
+ nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, |
+ table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ |
+}); |
+ |
+/** |
+ * Block Lexer |
+ */ |
+ |
+function Lexer(options) { |
+ this.tokens = []; |
+ this.tokens.links = {}; |
+ this.options = options || marked.defaults; |
+ this.rules = block.normal; |
+ |
+ if (this.options.gfm) { |
+ if (this.options.tables) { |
+ this.rules = block.tables; |
+ } else { |
+ this.rules = block.gfm; |
+ } |
+ } |
+} |
+ |
+/** |
+ * Expose Block Rules |
+ */ |
+ |
+Lexer.rules = block; |
+ |
+/** |
+ * Static Lex Method |
+ */ |
+ |
+Lexer.lex = function(src, options) { |
+ var lexer = new Lexer(options); |
+ return lexer.lex(src); |
+}; |
+ |
+/** |
+ * Preprocessing |
+ */ |
+ |
+Lexer.prototype.lex = function(src) { |
+ src = src |
+ .replace(/\r\n|\r/g, '\n') |
+ .replace(/\t/g, ' ') |
+ .replace(/\u00a0/g, ' ') |
+ .replace(/\u2424/g, '\n'); |
+ |
+ return this.token(src, true); |
+}; |
+ |
+/** |
+ * Lexing |
+ */ |
+ |
+Lexer.prototype.token = function(src, top, bq) { |
+ var src = src.replace(/^ +$/gm, '') |
+ , next |
+ , loose |
+ , cap |
+ , bull |
+ , b |
+ , item |
+ , space |
+ , i |
+ , l; |
+ |
+ while (src) { |
+ // newline |
+ if (cap = this.rules.newline.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ if (cap[0].length > 1) { |
+ this.tokens.push({ |
+ type: 'space' |
+ }); |
+ } |
+ } |
+ |
+ // code |
+ if (cap = this.rules.code.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ cap = cap[0].replace(/^ {4}/gm, ''); |
+ this.tokens.push({ |
+ type: 'code', |
+ text: !this.options.pedantic |
+ ? cap.replace(/\n+$/, '') |
+ : cap |
+ }); |
+ continue; |
+ } |
+ |
+ // fences (gfm) |
+ if (cap = this.rules.fences.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ this.tokens.push({ |
+ type: 'code', |
+ lang: cap[2], |
+ text: cap[3] |
+ }); |
+ continue; |
+ } |
+ |
+ // heading |
+ if (cap = this.rules.heading.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ this.tokens.push({ |
+ type: 'heading', |
+ depth: cap[1].length, |
+ text: cap[2] |
+ }); |
+ continue; |
+ } |
+ |
+ // table no leading pipe (gfm) |
+ if (top && (cap = this.rules.nptable.exec(src))) { |
+ src = src.substring(cap[0].length); |
+ |
+ item = { |
+ type: 'table', |
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), |
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), |
+ cells: cap[3].replace(/\n$/, '').split('\n') |
+ }; |
+ |
+ for (i = 0; i < item.align.length; i++) { |
+ if (/^ *-+: *$/.test(item.align[i])) { |
+ item.align[i] = 'right'; |
+ } else if (/^ *:-+: *$/.test(item.align[i])) { |
+ item.align[i] = 'center'; |
+ } else if (/^ *:-+ *$/.test(item.align[i])) { |
+ item.align[i] = 'left'; |
+ } else { |
+ item.align[i] = null; |
+ } |
+ } |
+ |
+ for (i = 0; i < item.cells.length; i++) { |
+ item.cells[i] = item.cells[i].split(/ *\| */); |
+ } |
+ |
+ this.tokens.push(item); |
+ |
+ continue; |
+ } |
+ |
+ // lheading |
+ if (cap = this.rules.lheading.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ this.tokens.push({ |
+ type: 'heading', |
+ depth: cap[2] === '=' ? 1 : 2, |
+ text: cap[1] |
+ }); |
+ continue; |
+ } |
+ |
+ // hr |
+ if (cap = this.rules.hr.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ this.tokens.push({ |
+ type: 'hr' |
+ }); |
+ continue; |
+ } |
+ |
+ // blockquote |
+ if (cap = this.rules.blockquote.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ |
+ this.tokens.push({ |
+ type: 'blockquote_start' |
+ }); |
+ |
+ cap = cap[0].replace(/^ *> ?/gm, ''); |
+ |
+ // Pass `top` to keep the current |
+ // "toplevel" state. This is exactly |
+ // how markdown.pl works. |
+ this.token(cap, top, true); |
+ |
+ this.tokens.push({ |
+ type: 'blockquote_end' |
+ }); |
+ |
+ continue; |
+ } |
+ |
+ // list |
+ if (cap = this.rules.list.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ bull = cap[2]; |
+ |
+ this.tokens.push({ |
+ type: 'list_start', |
+ ordered: bull.length > 1 |
+ }); |
+ |
+ // Get each top-level item. |
+ cap = cap[0].match(this.rules.item); |
+ |
+ next = false; |
+ l = cap.length; |
+ i = 0; |
+ |
+ for (; i < l; i++) { |
+ item = cap[i]; |
+ |
+ // Remove the list item's bullet |
+ // so it is seen as the next token. |
+ space = item.length; |
+ item = item.replace(/^ *([*+-]|\d+\.) +/, ''); |
+ |
+ // Outdent whatever the |
+ // list item contains. Hacky. |
+ if (~item.indexOf('\n ')) { |
+ space -= item.length; |
+ item = !this.options.pedantic |
+ ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') |
+ : item.replace(/^ {1,4}/gm, ''); |
+ } |
+ |
+ // Determine whether the next list item belongs here. |
+ // Backpedal if it does not belong in this list. |
+ if (this.options.smartLists && i !== l - 1) { |
+ b = block.bullet.exec(cap[i + 1])[0]; |
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) { |
+ src = cap.slice(i + 1).join('\n') + src; |
+ i = l - 1; |
+ } |
+ } |
+ |
+ // Determine whether item is loose or not. |
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ |
+ // for discount behavior. |
+ loose = next || /\n\n(?!\s*$)/.test(item); |
+ if (i !== l - 1) { |
+ next = item.charAt(item.length - 1) === '\n'; |
+ if (!loose) loose = next; |
+ } |
+ |
+ this.tokens.push({ |
+ type: loose |
+ ? 'loose_item_start' |
+ : 'list_item_start' |
+ }); |
+ |
+ // Recurse. |
+ this.token(item, false, bq); |
+ |
+ this.tokens.push({ |
+ type: 'list_item_end' |
+ }); |
+ } |
+ |
+ this.tokens.push({ |
+ type: 'list_end' |
+ }); |
+ |
+ continue; |
+ } |
+ |
+ // html |
+ if (cap = this.rules.html.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ this.tokens.push({ |
+ type: this.options.sanitize |
+ ? 'paragraph' |
+ : 'html', |
+ pre: cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style', |
+ text: cap[0] |
+ }); |
+ continue; |
+ } |
+ |
+ // def |
+ if ((!bq && top) && (cap = this.rules.def.exec(src))) { |
+ src = src.substring(cap[0].length); |
+ this.tokens.links[cap[1].toLowerCase()] = { |
+ href: cap[2], |
+ title: cap[3] |
+ }; |
+ continue; |
+ } |
+ |
+ // table (gfm) |
+ if (top && (cap = this.rules.table.exec(src))) { |
+ src = src.substring(cap[0].length); |
+ |
+ item = { |
+ type: 'table', |
+ header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), |
+ align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), |
+ cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') |
+ }; |
+ |
+ for (i = 0; i < item.align.length; i++) { |
+ if (/^ *-+: *$/.test(item.align[i])) { |
+ item.align[i] = 'right'; |
+ } else if (/^ *:-+: *$/.test(item.align[i])) { |
+ item.align[i] = 'center'; |
+ } else if (/^ *:-+ *$/.test(item.align[i])) { |
+ item.align[i] = 'left'; |
+ } else { |
+ item.align[i] = null; |
+ } |
+ } |
+ |
+ for (i = 0; i < item.cells.length; i++) { |
+ item.cells[i] = item.cells[i] |
+ .replace(/^ *\| *| *\| *$/g, '') |
+ .split(/ *\| */); |
+ } |
+ |
+ this.tokens.push(item); |
+ |
+ continue; |
+ } |
+ |
+ // top-level paragraph |
+ if (top && (cap = this.rules.paragraph.exec(src))) { |
+ src = src.substring(cap[0].length); |
+ this.tokens.push({ |
+ type: 'paragraph', |
+ text: cap[1].charAt(cap[1].length - 1) === '\n' |
+ ? cap[1].slice(0, -1) |
+ : cap[1] |
+ }); |
+ continue; |
+ } |
+ |
+ // text |
+ if (cap = this.rules.text.exec(src)) { |
+ // Top-level should never reach here. |
+ src = src.substring(cap[0].length); |
+ this.tokens.push({ |
+ type: 'text', |
+ text: cap[0] |
+ }); |
+ continue; |
+ } |
+ |
+ if (src) { |
+ throw new |
+ Error('Infinite loop on byte: ' + src.charCodeAt(0)); |
+ } |
+ } |
+ |
+ return this.tokens; |
+}; |
+ |
+/** |
+ * Inline-Level Grammar |
+ */ |
+ |
+var inline = { |
+ escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, |
+ autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, |
+ url: noop, |
+ tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, |
+ link: /^!?\[(inside)\]\(href\)/, |
+ reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, |
+ nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, |
+ strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, |
+ em: /^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, |
+ code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, |
+ br: /^ {2,}\n(?!\s*$)/, |
+ del: noop, |
+ text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/ |
+}; |
+ |
+inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/; |
+inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/; |
+ |
+inline.link = replace(inline.link) |
+ ('inside', inline._inside) |
+ ('href', inline._href) |
+ (); |
+ |
+inline.reflink = replace(inline.reflink) |
+ ('inside', inline._inside) |
+ (); |
+ |
+/** |
+ * Normal Inline Grammar |
+ */ |
+ |
+inline.normal = merge({}, inline); |
+ |
+/** |
+ * Pedantic Inline Grammar |
+ */ |
+ |
+inline.pedantic = merge({}, inline.normal, { |
+ strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, |
+ em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ |
+}); |
+ |
+/** |
+ * GFM Inline Grammar |
+ */ |
+ |
+inline.gfm = merge({}, inline.normal, { |
+ escape: replace(inline.escape)('])', '~|])')(), |
+ url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, |
+ del: /^~~(?=\S)([\s\S]*?\S)~~/, |
+ text: replace(inline.text) |
+ (']|', '~]|') |
+ ('|', '|https?://|') |
+ () |
+}); |
+ |
+/** |
+ * GFM + Line Breaks Inline Grammar |
+ */ |
+ |
+inline.breaks = merge({}, inline.gfm, { |
+ br: replace(inline.br)('{2,}', '*')(), |
+ text: replace(inline.gfm.text)('{2,}', '*')() |
+}); |
+ |
+/** |
+ * Inline Lexer & Compiler |
+ */ |
+ |
+function InlineLexer(links, options) { |
+ this.options = options || marked.defaults; |
+ this.links = links; |
+ this.rules = inline.normal; |
+ this.renderer = this.options.renderer || new Renderer; |
+ this.renderer.options = this.options; |
+ |
+ if (!this.links) { |
+ throw new |
+ Error('Tokens array requires a `links` property.'); |
+ } |
+ |
+ if (this.options.gfm) { |
+ if (this.options.breaks) { |
+ this.rules = inline.breaks; |
+ } else { |
+ this.rules = inline.gfm; |
+ } |
+ } else if (this.options.pedantic) { |
+ this.rules = inline.pedantic; |
+ } |
+} |
+ |
+/** |
+ * Expose Inline Rules |
+ */ |
+ |
+InlineLexer.rules = inline; |
+ |
+/** |
+ * Static Lexing/Compiling Method |
+ */ |
+ |
+InlineLexer.output = function(src, links, options) { |
+ var inline = new InlineLexer(links, options); |
+ return inline.output(src); |
+}; |
+ |
+/** |
+ * Lexing/Compiling |
+ */ |
+ |
+InlineLexer.prototype.output = function(src) { |
+ var out = '' |
+ , link |
+ , text |
+ , href |
+ , cap; |
+ |
+ while (src) { |
+ // escape |
+ if (cap = this.rules.escape.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ out += cap[1]; |
+ continue; |
+ } |
+ |
+ // autolink |
+ if (cap = this.rules.autolink.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ if (cap[2] === '@') { |
+ text = cap[1].charAt(6) === ':' |
+ ? this.mangle(cap[1].substring(7)) |
+ : this.mangle(cap[1]); |
+ href = this.mangle('mailto:') + text; |
+ } else { |
+ text = escape(cap[1]); |
+ href = text; |
+ } |
+ out += this.renderer.link(href, null, text); |
+ continue; |
+ } |
+ |
+ // url (gfm) |
+ if (!this.inLink && (cap = this.rules.url.exec(src))) { |
+ src = src.substring(cap[0].length); |
+ text = escape(cap[1]); |
+ href = text; |
+ out += this.renderer.link(href, null, text); |
+ continue; |
+ } |
+ |
+ // tag |
+ if (cap = this.rules.tag.exec(src)) { |
+ if (!this.inLink && /^<a /i.test(cap[0])) { |
+ this.inLink = true; |
+ } else if (this.inLink && /^<\/a>/i.test(cap[0])) { |
+ this.inLink = false; |
+ } |
+ src = src.substring(cap[0].length); |
+ out += this.options.sanitize |
+ ? escape(cap[0]) |
+ : cap[0]; |
+ continue; |
+ } |
+ |
+ // link |
+ if (cap = this.rules.link.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ this.inLink = true; |
+ out += this.outputLink(cap, { |
+ href: cap[2], |
+ title: cap[3] |
+ }); |
+ this.inLink = false; |
+ continue; |
+ } |
+ |
+ // reflink, nolink |
+ if ((cap = this.rules.reflink.exec(src)) |
+ || (cap = this.rules.nolink.exec(src))) { |
+ src = src.substring(cap[0].length); |
+ link = (cap[2] || cap[1]).replace(/\s+/g, ' '); |
+ link = this.links[link.toLowerCase()]; |
+ if (!link || !link.href) { |
+ out += cap[0].charAt(0); |
+ src = cap[0].substring(1) + src; |
+ continue; |
+ } |
+ this.inLink = true; |
+ out += this.outputLink(cap, link); |
+ this.inLink = false; |
+ continue; |
+ } |
+ |
+ // strong |
+ if (cap = this.rules.strong.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ out += this.renderer.strong(this.output(cap[2] || cap[1])); |
+ continue; |
+ } |
+ |
+ // em |
+ if (cap = this.rules.em.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ out += this.renderer.em(this.output(cap[2] || cap[1])); |
+ continue; |
+ } |
+ |
+ // code |
+ if (cap = this.rules.code.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ out += this.renderer.codespan(escape(cap[2], true)); |
+ continue; |
+ } |
+ |
+ // br |
+ if (cap = this.rules.br.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ out += this.renderer.br(); |
+ continue; |
+ } |
+ |
+ // del (gfm) |
+ if (cap = this.rules.del.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ out += this.renderer.del(this.output(cap[1])); |
+ continue; |
+ } |
+ |
+ // text |
+ if (cap = this.rules.text.exec(src)) { |
+ src = src.substring(cap[0].length); |
+ out += escape(this.smartypants(cap[0])); |
+ continue; |
+ } |
+ |
+ if (src) { |
+ throw new |
+ Error('Infinite loop on byte: ' + src.charCodeAt(0)); |
+ } |
+ } |
+ |
+ return out; |
+}; |
+ |
+/** |
+ * Compile Link |
+ */ |
+ |
+InlineLexer.prototype.outputLink = function(cap, link) { |
+ var href = escape(link.href) |
+ , title = link.title ? escape(link.title) : null; |
+ |
+ return cap[0].charAt(0) !== '!' |
+ ? this.renderer.link(href, title, this.output(cap[1])) |
+ : this.renderer.image(href, title, escape(cap[1])); |
+}; |
+ |
+/** |
+ * Smartypants Transformations |
+ */ |
+ |
+InlineLexer.prototype.smartypants = function(text) { |
+ if (!this.options.smartypants) return text; |
+ return text |
+ // em-dashes |
+ .replace(/--/g, '\u2014') |
+ // opening singles |
+ .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') |
+ // closing singles & apostrophes |
+ .replace(/'/g, '\u2019') |
+ // opening doubles |
+ .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') |
+ // closing doubles |
+ .replace(/"/g, '\u201d') |
+ // ellipses |
+ .replace(/\.{3}/g, '\u2026'); |
+}; |
+ |
+/** |
+ * Mangle Links |
+ */ |
+ |
+InlineLexer.prototype.mangle = function(text) { |
+ var out = '' |
+ , l = text.length |
+ , i = 0 |
+ , ch; |
+ |
+ for (; i < l; i++) { |
+ ch = text.charCodeAt(i); |
+ if (Math.random() > 0.5) { |
+ ch = 'x' + ch.toString(16); |
+ } |
+ out += '&#' + ch + ';'; |
+ } |
+ |
+ return out; |
+}; |
+ |
+/** |
+ * Renderer |
+ */ |
+ |
+function Renderer(options) { |
+ this.options = options || {}; |
+} |
+ |
+Renderer.prototype.code = function(code, lang, escaped) { |
+ if (this.options.highlight) { |
+ var out = this.options.highlight(code, lang); |
+ if (out != null && out !== code) { |
+ escaped = true; |
+ code = out; |
+ } |
+ } |
+ |
+ if (!lang) { |
+ return '<pre><code>' |
+ + (escaped ? code : escape(code, true)) |
+ + '\n</code></pre>'; |
+ } |
+ |
+ return '<pre><code class="' |
+ + this.options.langPrefix |
+ + escape(lang, true) |
+ + '">' |
+ + (escaped ? code : escape(code, true)) |
+ + '\n</code></pre>\n'; |
+}; |
+ |
+Renderer.prototype.blockquote = function(quote) { |
+ return '<blockquote>\n' + quote + '</blockquote>\n'; |
+}; |
+ |
+Renderer.prototype.html = function(html) { |
+ return html; |
+}; |
+ |
+Renderer.prototype.heading = function(text, level, raw) { |
+ return '<h' |
+ + level |
+ + ' id="' |
+ + this.options.headerPrefix |
+ + raw.toLowerCase().replace(/[^\w]+/g, '-') |
+ + '">' |
+ + text |
+ + '</h' |
+ + level |
+ + '>\n'; |
+}; |
+ |
+Renderer.prototype.hr = function() { |
+ return this.options.xhtml ? '<hr/>\n' : '<hr>\n'; |
+}; |
+ |
+Renderer.prototype.list = function(body, ordered) { |
+ var type = ordered ? 'ol' : 'ul'; |
+ return '<' + type + '>\n' + body + '</' + type + '>\n'; |
+}; |
+ |
+Renderer.prototype.listitem = function(text) { |
+ return '<li>' + text + '</li>\n'; |
+}; |
+ |
+Renderer.prototype.paragraph = function(text) { |
+ return '<p>' + text + '</p>\n'; |
+}; |
+ |
+Renderer.prototype.table = function(header, body) { |
+ return '<table>\n' |
+ + '<thead>\n' |
+ + header |
+ + '</thead>\n' |
+ + '<tbody>\n' |
+ + body |
+ + '</tbody>\n' |
+ + '</table>\n'; |
+}; |
+ |
+Renderer.prototype.tablerow = function(content) { |
+ return '<tr>\n' + content + '</tr>\n'; |
+}; |
+ |
+Renderer.prototype.tablecell = function(content, flags) { |
+ var type = flags.header ? 'th' : 'td'; |
+ var tag = flags.align |
+ ? '<' + type + ' style="text-align:' + flags.align + '">' |
+ : '<' + type + '>'; |
+ return tag + content + '</' + type + '>\n'; |
+}; |
+ |
+// span level renderer |
+Renderer.prototype.strong = function(text) { |
+ return '<strong>' + text + '</strong>'; |
+}; |
+ |
+Renderer.prototype.em = function(text) { |
+ return '<em>' + text + '</em>'; |
+}; |
+ |
+Renderer.prototype.codespan = function(text) { |
+ return '<code>' + text + '</code>'; |
+}; |
+ |
+Renderer.prototype.br = function() { |
+ return this.options.xhtml ? '<br/>' : '<br>'; |
+}; |
+ |
+Renderer.prototype.del = function(text) { |
+ return '<del>' + text + '</del>'; |
+}; |
+ |
+Renderer.prototype.link = function(href, title, text) { |
+ if (this.options.sanitize) { |
+ try { |
+ var prot = decodeURIComponent(unescape(href)) |
+ .replace(/[^\w:]/g, '') |
+ .toLowerCase(); |
+ } catch (e) { |
+ return ''; |
+ } |
+ if (prot.indexOf('javascript:') === 0) { |
+ return ''; |
+ } |
+ } |
+ var out = '<a href="' + href + '"'; |
+ if (title) { |
+ out += ' title="' + title + '"'; |
+ } |
+ out += '>' + text + '</a>'; |
+ return out; |
+}; |
+ |
+Renderer.prototype.image = function(href, title, text) { |
+ var out = '<img src="' + href + '" alt="' + text + '"'; |
+ if (title) { |
+ out += ' title="' + title + '"'; |
+ } |
+ out += this.options.xhtml ? '/>' : '>'; |
+ return out; |
+}; |
+ |
+/** |
+ * Parsing & Compiling |
+ */ |
+ |
+function Parser(options) { |
+ this.tokens = []; |
+ this.token = null; |
+ this.options = options || marked.defaults; |
+ this.options.renderer = this.options.renderer || new Renderer; |
+ this.renderer = this.options.renderer; |
+ this.renderer.options = this.options; |
+} |
+ |
+/** |
+ * Static Parse Method |
+ */ |
+ |
+Parser.parse = function(src, options, renderer) { |
+ var parser = new Parser(options, renderer); |
+ return parser.parse(src); |
+}; |
+ |
+/** |
+ * Parse Loop |
+ */ |
+ |
+Parser.prototype.parse = function(src) { |
+ this.inline = new InlineLexer(src.links, this.options, this.renderer); |
+ this.tokens = src.reverse(); |
+ |
+ var out = ''; |
+ while (this.next()) { |
+ out += this.tok(); |
+ } |
+ |
+ return out; |
+}; |
+ |
+/** |
+ * Next Token |
+ */ |
+ |
+Parser.prototype.next = function() { |
+ return this.token = this.tokens.pop(); |
+}; |
+ |
+/** |
+ * Preview Next Token |
+ */ |
+ |
+Parser.prototype.peek = function() { |
+ return this.tokens[this.tokens.length - 1] || 0; |
+}; |
+ |
+/** |
+ * Parse Text Tokens |
+ */ |
+ |
+Parser.prototype.parseText = function() { |
+ var body = this.token.text; |
+ |
+ while (this.peek().type === 'text') { |
+ body += '\n' + this.next().text; |
+ } |
+ |
+ return this.inline.output(body); |
+}; |
+ |
+/** |
+ * Parse Current Token |
+ */ |
+ |
+Parser.prototype.tok = function() { |
+ switch (this.token.type) { |
+ case 'space': { |
+ return ''; |
+ } |
+ case 'hr': { |
+ return this.renderer.hr(); |
+ } |
+ case 'heading': { |
+ return this.renderer.heading( |
+ this.inline.output(this.token.text), |
+ this.token.depth, |
+ this.token.text); |
+ } |
+ case 'code': { |
+ return this.renderer.code(this.token.text, |
+ this.token.lang, |
+ this.token.escaped); |
+ } |
+ case 'table': { |
+ var header = '' |
+ , body = '' |
+ , i |
+ , row |
+ , cell |
+ , flags |
+ , j; |
+ |
+ // header |
+ cell = ''; |
+ for (i = 0; i < this.token.header.length; i++) { |
+ flags = { header: true, align: this.token.align[i] }; |
+ cell += this.renderer.tablecell( |
+ this.inline.output(this.token.header[i]), |
+ { header: true, align: this.token.align[i] } |
+ ); |
+ } |
+ header += this.renderer.tablerow(cell); |
+ |
+ for (i = 0; i < this.token.cells.length; i++) { |
+ row = this.token.cells[i]; |
+ |
+ cell = ''; |
+ for (j = 0; j < row.length; j++) { |
+ cell += this.renderer.tablecell( |
+ this.inline.output(row[j]), |
+ { header: false, align: this.token.align[j] } |
+ ); |
+ } |
+ |
+ body += this.renderer.tablerow(cell); |
+ } |
+ return this.renderer.table(header, body); |
+ } |
+ case 'blockquote_start': { |
+ var body = ''; |
+ |
+ while (this.next().type !== 'blockquote_end') { |
+ body += this.tok(); |
+ } |
+ |
+ return this.renderer.blockquote(body); |
+ } |
+ case 'list_start': { |
+ var body = '' |
+ , ordered = this.token.ordered; |
+ |
+ while (this.next().type !== 'list_end') { |
+ body += this.tok(); |
+ } |
+ |
+ return this.renderer.list(body, ordered); |
+ } |
+ case 'list_item_start': { |
+ var body = ''; |
+ |
+ while (this.next().type !== 'list_item_end') { |
+ body += this.token.type === 'text' |
+ ? this.parseText() |
+ : this.tok(); |
+ } |
+ |
+ return this.renderer.listitem(body); |
+ } |
+ case 'loose_item_start': { |
+ var body = ''; |
+ |
+ while (this.next().type !== 'list_item_end') { |
+ body += this.tok(); |
+ } |
+ |
+ return this.renderer.listitem(body); |
+ } |
+ case 'html': { |
+ var html = !this.token.pre && !this.options.pedantic |
+ ? this.inline.output(this.token.text) |
+ : this.token.text; |
+ return this.renderer.html(html); |
+ } |
+ case 'paragraph': { |
+ return this.renderer.paragraph(this.inline.output(this.token.text)); |
+ } |
+ case 'text': { |
+ return this.renderer.paragraph(this.parseText()); |
+ } |
+ } |
+}; |
+ |
+/** |
+ * Helpers |
+ */ |
+ |
+function escape(html, encode) { |
+ return html |
+ .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') |
+ .replace(/</g, '<') |
+ .replace(/>/g, '>') |
+ .replace(/"/g, '"') |
+ .replace(/'/g, '''); |
+} |
+ |
+function unescape(html) { |
+ return html.replace(/&([#\w]+);/g, function(_, n) { |
+ n = n.toLowerCase(); |
+ if (n === 'colon') return ':'; |
+ if (n.charAt(0) === '#') { |
+ return n.charAt(1) === 'x' |
+ ? String.fromCharCode(parseInt(n.substring(2), 16)) |
+ : String.fromCharCode(+n.substring(1)); |
+ } |
+ return ''; |
+ }); |
+} |
+ |
+function replace(regex, opt) { |
+ regex = regex.source; |
+ opt = opt || ''; |
+ return function self(name, val) { |
+ if (!name) return new RegExp(regex, opt); |
+ val = val.source || val; |
+ val = val.replace(/(^|[^\[])\^/g, '$1'); |
+ regex = regex.replace(name, val); |
+ return self; |
+ }; |
+} |
+ |
+function noop() {} |
+noop.exec = noop; |
+ |
+function merge(obj) { |
+ var i = 1 |
+ , target |
+ , key; |
+ |
+ for (; i < arguments.length; i++) { |
+ target = arguments[i]; |
+ for (key in target) { |
+ if (Object.prototype.hasOwnProperty.call(target, key)) { |
+ obj[key] = target[key]; |
+ } |
+ } |
+ } |
+ |
+ return obj; |
+} |
+ |
+ |
+/** |
+ * Marked |
+ */ |
+ |
+function marked(src, opt, callback) { |
+ if (callback || typeof opt === 'function') { |
+ if (!callback) { |
+ callback = opt; |
+ opt = null; |
+ } |
+ |
+ opt = merge({}, marked.defaults, opt || {}); |
+ |
+ var highlight = opt.highlight |
+ , tokens |
+ , pending |
+ , i = 0; |
+ |
+ try { |
+ tokens = Lexer.lex(src, opt) |
+ } catch (e) { |
+ return callback(e); |
+ } |
+ |
+ pending = tokens.length; |
+ |
+ var done = function() { |
+ var out, err; |
+ |
+ try { |
+ out = Parser.parse(tokens, opt); |
+ } catch (e) { |
+ err = e; |
+ } |
+ |
+ opt.highlight = highlight; |
+ |
+ return err |
+ ? callback(err) |
+ : callback(null, out); |
+ }; |
+ |
+ if (!highlight || highlight.length < 3) { |
+ return done(); |
+ } |
+ |
+ delete opt.highlight; |
+ |
+ if (!pending) return done(); |
+ |
+ for (; i < tokens.length; i++) { |
+ (function(token) { |
+ if (token.type !== 'code') { |
+ return --pending || done(); |
+ } |
+ return highlight(token.text, token.lang, function(err, code) { |
+ if (code == null || code === token.text) { |
+ return --pending || done(); |
+ } |
+ token.text = code; |
+ token.escaped = true; |
+ --pending || done(); |
+ }); |
+ })(tokens[i]); |
+ } |
+ |
+ return; |
+ } |
+ try { |
+ if (opt) opt = merge({}, marked.defaults, opt); |
+ return Parser.parse(Lexer.lex(src, opt), opt); |
+ } catch (e) { |
+ e.message += '\nPlease report this to https://github.com/chjj/marked.'; |
+ if ((opt || marked.defaults).silent) { |
+ return '<p>An error occured:</p><pre>' |
+ + escape(e.message + '', true) |
+ + '</pre>'; |
+ } |
+ throw e; |
+ } |
+} |
+ |
+/** |
+ * Options |
+ */ |
+ |
+marked.options = |
+marked.setOptions = function(opt) { |
+ merge(marked.defaults, opt); |
+ return marked; |
+}; |
+ |
+marked.defaults = { |
+ gfm: true, |
+ tables: true, |
+ breaks: false, |
+ pedantic: false, |
+ sanitize: false, |
+ smartLists: false, |
+ silent: false, |
+ highlight: null, |
+ langPrefix: 'lang-', |
+ smartypants: false, |
+ headerPrefix: '', |
+ renderer: new Renderer, |
+ xhtml: false |
+}; |
+ |
+/** |
+ * Expose |
+ */ |
+ |
+marked.Parser = Parser; |
+marked.parser = Parser.parse; |
+ |
+marked.Renderer = Renderer; |
+ |
+marked.Lexer = Lexer; |
+marked.lexer = Lexer.lex; |
+ |
+marked.InlineLexer = InlineLexer; |
+marked.inlineLexer = InlineLexer.output; |
+ |
+marked.parse = marked; |
+ |
+if (typeof exports === 'object') { |
+ module.exports = marked; |
+} else if (typeof define === 'function' && define.amd) { |
+ define(function() { return marked; }); |
+} else { |
+ this.marked = marked; |
+} |
+ |
+}).call(function() { |
+ return this || (typeof window !== 'undefined' ? window : global); |
+}()); |
+; |
+ |
+ |
+ Polymer('marked-element', { |
+ |
+ text: '', |
+ |
+ attached: function() { |
+ marked.setOptions({ |
+ highlight: this.highlight.bind(this) |
+ }); |
+ if (!this.text) { |
+ this.text = this.innerHTML; |
+ } |
+ }, |
+ |
+ textChanged: function () { |
+ this.innerHTML = marked(this.text); |
+ }, |
+ |
+ highlight: function(code, lang) { |
+ var event = this.fire('marked-js-highlight', {code: code, lang: lang}); |
+ return event.detail.code || code; |
+ } |
+ |
+ }); |
+ |
+; |
+// Copyright (C) 2006 Google Inc. |
+// |
+// Licensed under the Apache License, Version 2.0 (the "License"); |
+// you may not use this file except in compliance with the License. |
+// You may obtain a copy of the License at |
+// |
+// http://www.apache.org/licenses/LICENSE-2.0 |
+// |
+// Unless required by applicable law or agreed to in writing, software |
+// distributed under the License is distributed on an "AS IS" BASIS, |
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+// See the License for the specific language governing permissions and |
+// limitations under the License. |
+ |
+ |
+/** |
+ * @fileoverview |
+ * some functions for browser-side pretty printing of code contained in html. |
+ * |
+ * <p> |
+ * For a fairly comprehensive set of languages see the |
+ * <a href="http://google-code-prettify.googlecode.com/svn/trunk/README.html#langs">README</a> |
+ * file that came with this source. At a minimum, the lexer should work on a |
+ * number of languages including C and friends, Java, Python, Bash, SQL, HTML, |
+ * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk |
+ * and a subset of Perl, but, because of commenting conventions, doesn't work on |
+ * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. |
+ * <p> |
+ * Usage: <ol> |
+ * <li> include this source file in an html page via |
+ * {@code <script type="text/javascript" src="/path/to/prettify.js"><\/script>} |
+ * <li> define style rules. See the example page for examples. |
+ * <li> mark the {@code <pre>} and {@code <code>} tags in your source with |
+ * {@code class=prettyprint.} |
+ * You can also use the (html deprecated) {@code <xmp>} tag, but the pretty |
+ * printer needs to do more substantial DOM manipulations to support that, so |
+ * some css styles may not be preserved. |
+ * </ol> |
+ * That's it. I wanted to keep the API as simple as possible, so there's no |
+ * need to specify which language the code is in, but if you wish, you can add |
+ * another class to the {@code <pre>} or {@code <code>} element to specify the |
+ * language, as in {@code <pre class="prettyprint lang-java">}. Any class that |
+ * starts with "lang-" followed by a file extension, specifies the file type. |
+ * See the "lang-*.js" files in this directory for code that implements |
+ * per-language file handlers. |
+ * <p> |
+ * Change log:<br> |
+ * cbeust, 2006/08/22 |
+ * <blockquote> |
+ * Java annotations (start with "@") are now captured as literals ("lit") |
+ * </blockquote> |
+ * @requires console |
+ */ |
+ |
+// JSLint declarations |
+/*global console, document, navigator, setTimeout, window, define */ |
+ |
+/** |
+ * Split {@code prettyPrint} into multiple timeouts so as not to interfere with |
+ * UI events. |
+ * If set to {@code false}, {@code prettyPrint()} is synchronous. |
+ */ |
+window['PR_SHOULD_USE_CONTINUATION'] = true; |
+ |
+/** |
+ * Find all the {@code <pre>} and {@code <code>} tags in the DOM with |
+ * {@code class=prettyprint} and prettify them. |
+ * |
+ * @param {Function?} opt_whenDone if specified, called when the last entry |
+ * has been finished. |
+ */ |
+var prettyPrintOne; |
+/** |
+ * Pretty print a chunk of code. |
+ * |
+ * @param {string} sourceCodeHtml code as html |
+ * @return {string} code as html, but prettier |
+ */ |
+var prettyPrint; |
+ |
+ |
+(function () { |
+ var win = window; |
+ // Keyword lists for various languages. |
+ // We use things that coerce to strings to make them compact when minified |
+ // and to defeat aggressive optimizers that fold large string constants. |
+ var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"]; |
+ var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," + |
+ "double,enum,extern,float,goto,int,long,register,short,signed,sizeof," + |
+ "static,struct,switch,typedef,union,unsigned,void,volatile"]; |
+ var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," + |
+ "new,operator,private,protected,public,this,throw,true,try,typeof"]; |
+ var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," + |
+ "concept,concept_map,const_cast,constexpr,decltype," + |
+ "dynamic_cast,explicit,export,friend,inline,late_check," + |
+ "mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast," + |
+ "template,typeid,typename,using,virtual,where"]; |
+ var JAVA_KEYWORDS = [COMMON_KEYWORDS, |
+ "abstract,boolean,byte,extends,final,finally,implements,import," + |
+ "instanceof,null,native,package,strictfp,super,synchronized,throws," + |
+ "transient"]; |
+ var CSHARP_KEYWORDS = [JAVA_KEYWORDS, |
+ "as,base,by,checked,decimal,delegate,descending,dynamic,event," + |
+ "fixed,foreach,from,group,implicit,in,interface,internal,into,is,let," + |
+ "lock,object,out,override,orderby,params,partial,readonly,ref,sbyte," + |
+ "sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort," + |
+ "var,virtual,where"]; |
+ var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," + |
+ "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," + |
+ "throw,true,try,unless,until,when,while,yes"; |
+ var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS, |
+ "debugger,eval,export,function,get,null,set,undefined,var,with," + |
+ "Infinity,NaN"]; |
+ var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," + |
+ "goto,if,import,last,local,my,next,no,our,print,package,redo,require," + |
+ "sub,undef,unless,until,use,wantarray,while,BEGIN,END"; |
+ var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," + |
+ "elif,except,exec,finally,from,global,import,in,is,lambda," + |
+ "nonlocal,not,or,pass,print,raise,try,with,yield," + |
+ "False,True,None"]; |
+ var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," + |
+ "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," + |
+ "rescue,retry,self,super,then,true,undef,unless,until,when,yield," + |
+ "BEGIN,END"]; |
+ var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," + |
+ "function,in,local,set,then,until"]; |
+ var ALL_KEYWORDS = [ |
+ CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS + |
+ PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS]; |
+ var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/; |
+ |
+ // token style names. correspond to css classes |
+ /** |
+ * token style for a string literal |
+ * @const |
+ */ |
+ var PR_STRING = 'str'; |
+ /** |
+ * token style for a keyword |
+ * @const |
+ */ |
+ var PR_KEYWORD = 'kwd'; |
+ /** |
+ * token style for a comment |
+ * @const |
+ */ |
+ var PR_COMMENT = 'com'; |
+ /** |
+ * token style for a type |
+ * @const |
+ */ |
+ var PR_TYPE = 'typ'; |
+ /** |
+ * token style for a literal value. e.g. 1, null, true. |
+ * @const |
+ */ |
+ var PR_LITERAL = 'lit'; |
+ /** |
+ * token style for a punctuation string. |
+ * @const |
+ */ |
+ var PR_PUNCTUATION = 'pun'; |
+ /** |
+ * token style for plain text. |
+ * @const |
+ */ |
+ var PR_PLAIN = 'pln'; |
+ |
+ /** |
+ * token style for an sgml tag. |
+ * @const |
+ */ |
+ var PR_TAG = 'tag'; |
+ /** |
+ * token style for a markup declaration such as a DOCTYPE. |
+ * @const |
+ */ |
+ var PR_DECLARATION = 'dec'; |
+ /** |
+ * token style for embedded source. |
+ * @const |
+ */ |
+ var PR_SOURCE = 'src'; |
+ /** |
+ * token style for an sgml attribute name. |
+ * @const |
+ */ |
+ var PR_ATTRIB_NAME = 'atn'; |
+ /** |
+ * token style for an sgml attribute value. |
+ * @const |
+ */ |
+ var PR_ATTRIB_VALUE = 'atv'; |
+ |
+ /** |
+ * A class that indicates a section of markup that is not code, e.g. to allow |
+ * embedding of line numbers within code listings. |
+ * @const |
+ */ |
+ var PR_NOCODE = 'nocode'; |
+ |
+ |
+ |
+/** |
+ * A set of tokens that can precede a regular expression literal in |
+ * javascript |
+ * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html |
+ * has the full list, but I've removed ones that might be problematic when |
+ * seen in languages that don't support regular expression literals. |
+ * |
+ * <p>Specifically, I've removed any keywords that can't precede a regexp |
+ * literal in a syntactically legal javascript program, and I've removed the |
+ * "in" keyword since it's not a keyword in many languages, and might be used |
+ * as a count of inches. |
+ * |
+ * <p>The link above does not accurately describe EcmaScript rules since |
+ * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works |
+ * very well in practice. |
+ * |
+ * @private |
+ * @const |
+ */ |
+var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*'; |
+ |
+// CAVEAT: this does not properly handle the case where a regular |
+// expression immediately follows another since a regular expression may |
+// have flags for case-sensitivity and the like. Having regexp tokens |
+// adjacent is not valid in any language I'm aware of, so I'm punting. |
+// TODO: maybe style special characters inside a regexp as punctuation. |
+ |
+ |
+ /** |
+ * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally |
+ * matches the union of the sets of strings matched by the input RegExp. |
+ * Since it matches globally, if the input strings have a start-of-input |
+ * anchor (/^.../), it is ignored for the purposes of unioning. |
+ * @param {Array.<RegExp>} regexs non multiline, non-global regexs. |
+ * @return {RegExp} a global regex. |
+ */ |
+ function combinePrefixPatterns(regexs) { |
+ var capturedGroupIndex = 0; |
+ |
+ var needToFoldCase = false; |
+ var ignoreCase = false; |
+ for (var i = 0, n = regexs.length; i < n; ++i) { |
+ var regex = regexs[i]; |
+ if (regex.ignoreCase) { |
+ ignoreCase = true; |
+ } else if (/[a-z]/i.test(regex.source.replace( |
+ /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) { |
+ needToFoldCase = true; |
+ ignoreCase = false; |
+ break; |
+ } |
+ } |
+ |
+ var escapeCharToCodeUnit = { |
+ 'b': 8, |
+ 't': 9, |
+ 'n': 0xa, |
+ 'v': 0xb, |
+ 'f': 0xc, |
+ 'r': 0xd |
+ }; |
+ |
+ function decodeEscape(charsetPart) { |
+ var cc0 = charsetPart.charCodeAt(0); |
+ if (cc0 !== 92 /* \\ */) { |
+ return cc0; |
+ } |
+ var c1 = charsetPart.charAt(1); |
+ cc0 = escapeCharToCodeUnit[c1]; |
+ if (cc0) { |
+ return cc0; |
+ } else if ('0' <= c1 && c1 <= '7') { |
+ return parseInt(charsetPart.substring(1), 8); |
+ } else if (c1 === 'u' || c1 === 'x') { |
+ return parseInt(charsetPart.substring(2), 16); |
+ } else { |
+ return charsetPart.charCodeAt(1); |
+ } |
+ } |
+ |
+ function encodeEscape(charCode) { |
+ if (charCode < 0x20) { |
+ return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16); |
+ } |
+ var ch = String.fromCharCode(charCode); |
+ return (ch === '\\' || ch === '-' || ch === ']' || ch === '^') |
+ ? "\\" + ch : ch; |
+ } |
+ |
+ function caseFoldCharset(charSet) { |
+ var charsetParts = charSet.substring(1, charSet.length - 1).match( |
+ new RegExp( |
+ '\\\\u[0-9A-Fa-f]{4}' |
+ + '|\\\\x[0-9A-Fa-f]{2}' |
+ + '|\\\\[0-3][0-7]{0,2}' |
+ + '|\\\\[0-7]{1,2}' |
+ + '|\\\\[\\s\\S]' |
+ + '|-' |
+ + '|[^-\\\\]', |
+ 'g')); |
+ var ranges = []; |
+ var inverse = charsetParts[0] === '^'; |
+ |
+ var out = ['[']; |
+ if (inverse) { out.push('^'); } |
+ |
+ for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) { |
+ var p = charsetParts[i]; |
+ if (/\\[bdsw]/i.test(p)) { // Don't muck with named groups. |
+ out.push(p); |
+ } else { |
+ var start = decodeEscape(p); |
+ var end; |
+ if (i + 2 < n && '-' === charsetParts[i + 1]) { |
+ end = decodeEscape(charsetParts[i + 2]); |
+ i += 2; |
+ } else { |
+ end = start; |
+ } |
+ ranges.push([start, end]); |
+ // If the range might intersect letters, then expand it. |
+ // This case handling is too simplistic. |
+ // It does not deal with non-latin case folding. |
+ // It works for latin source code identifiers though. |
+ if (!(end < 65 || start > 122)) { |
+ if (!(end < 65 || start > 90)) { |
+ ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]); |
+ } |
+ if (!(end < 97 || start > 122)) { |
+ ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]); |
+ } |
+ } |
+ } |
+ } |
+ |
+ // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]] |
+ // -> [[1, 12], [14, 14], [16, 17]] |
+ ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); }); |
+ var consolidatedRanges = []; |
+ var lastRange = []; |
+ for (var i = 0; i < ranges.length; ++i) { |
+ var range = ranges[i]; |
+ if (range[0] <= lastRange[1] + 1) { |
+ lastRange[1] = Math.max(lastRange[1], range[1]); |
+ } else { |
+ consolidatedRanges.push(lastRange = range); |
+ } |
+ } |
+ |
+ for (var i = 0; i < consolidatedRanges.length; ++i) { |
+ var range = consolidatedRanges[i]; |
+ out.push(encodeEscape(range[0])); |
+ if (range[1] > range[0]) { |
+ if (range[1] + 1 > range[0]) { out.push('-'); } |
+ out.push(encodeEscape(range[1])); |
+ } |
+ } |
+ out.push(']'); |
+ return out.join(''); |
+ } |
+ |
+ function allowAnywhereFoldCaseAndRenumberGroups(regex) { |
+ // Split into character sets, escape sequences, punctuation strings |
+ // like ('(', '(?:', ')', '^'), and runs of characters that do not |
+ // include any of the above. |
+ var parts = regex.source.match( |
+ new RegExp( |
+ '(?:' |
+ + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]' // a character set |
+ + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape |
+ + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape |
+ + '|\\\\[0-9]+' // a back-reference or octal escape |
+ + '|\\\\[^ux0-9]' // other escape sequence |
+ + '|\\(\\?[:!=]' // start of a non-capturing group |
+ + '|[\\(\\)\\^]' // start/end of a group, or line start |
+ + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters |
+ + ')', |
+ 'g')); |
+ var n = parts.length; |
+ |
+ // Maps captured group numbers to the number they will occupy in |
+ // the output or to -1 if that has not been determined, or to |
+ // undefined if they need not be capturing in the output. |
+ var capturedGroups = []; |
+ |
+ // Walk over and identify back references to build the capturedGroups |
+ // mapping. |
+ for (var i = 0, groupIndex = 0; i < n; ++i) { |
+ var p = parts[i]; |
+ if (p === '(') { |
+ // groups are 1-indexed, so max group index is count of '(' |
+ ++groupIndex; |
+ } else if ('\\' === p.charAt(0)) { |
+ var decimalValue = +p.substring(1); |
+ if (decimalValue) { |
+ if (decimalValue <= groupIndex) { |
+ capturedGroups[decimalValue] = -1; |
+ } else { |
+ // Replace with an unambiguous escape sequence so that |
+ // an octal escape sequence does not turn into a backreference |
+ // to a capturing group from an earlier regex. |
+ parts[i] = encodeEscape(decimalValue); |
+ } |
+ } |
+ } |
+ } |
+ |
+ // Renumber groups and reduce capturing groups to non-capturing groups |
+ // where possible. |
+ for (var i = 1; i < capturedGroups.length; ++i) { |
+ if (-1 === capturedGroups[i]) { |
+ capturedGroups[i] = ++capturedGroupIndex; |
+ } |
+ } |
+ for (var i = 0, groupIndex = 0; i < n; ++i) { |
+ var p = parts[i]; |
+ if (p === '(') { |
+ ++groupIndex; |
+ if (!capturedGroups[groupIndex]) { |
+ parts[i] = '(?:'; |
+ } |
+ } else if ('\\' === p.charAt(0)) { |
+ var decimalValue = +p.substring(1); |
+ if (decimalValue && decimalValue <= groupIndex) { |
+ parts[i] = '\\' + capturedGroups[decimalValue]; |
+ } |
+ } |
+ } |
+ |
+ // Remove any prefix anchors so that the output will match anywhere. |
+ // ^^ really does mean an anchored match though. |
+ for (var i = 0; i < n; ++i) { |
+ if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; } |
+ } |
+ |
+ // Expand letters to groups to handle mixing of case-sensitive and |
+ // case-insensitive patterns if necessary. |
+ if (regex.ignoreCase && needToFoldCase) { |
+ for (var i = 0; i < n; ++i) { |
+ var p = parts[i]; |
+ var ch0 = p.charAt(0); |
+ if (p.length >= 2 && ch0 === '[') { |
+ parts[i] = caseFoldCharset(p); |
+ } else if (ch0 !== '\\') { |
+ // TODO: handle letters in numeric escapes. |
+ parts[i] = p.replace( |
+ /[a-zA-Z]/g, |
+ function (ch) { |
+ var cc = ch.charCodeAt(0); |
+ return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']'; |
+ }); |
+ } |
+ } |
+ } |
+ |
+ return parts.join(''); |
+ } |
+ |
+ var rewritten = []; |
+ for (var i = 0, n = regexs.length; i < n; ++i) { |
+ var regex = regexs[i]; |
+ if (regex.global || regex.multiline) { throw new Error('' + regex); } |
+ rewritten.push( |
+ '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')'); |
+ } |
+ |
+ return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g'); |
+ } |
+ |
+ |
+ /** |
+ * Split markup into a string of source code and an array mapping ranges in |
+ * that string to the text nodes in which they appear. |
+ * |
+ * <p> |
+ * The HTML DOM structure:</p> |
+ * <pre> |
+ * (Element "p" |
+ * (Element "b" |
+ * (Text "print ")) ; #1 |
+ * (Text "'Hello '") ; #2 |
+ * (Element "br") ; #3 |
+ * (Text " + 'World';")) ; #4 |
+ * </pre> |
+ * <p> |
+ * corresponds to the HTML |
+ * {@code <p><b>print </b>'Hello '<br> + 'World';</p>}.</p> |
+ * |
+ * <p> |
+ * It will produce the output:</p> |
+ * <pre> |
+ * { |
+ * sourceCode: "print 'Hello '\n + 'World';", |
+ * // 1 2 |
+ * // 012345678901234 5678901234567 |
+ * spans: [0, #1, 6, #2, 14, #3, 15, #4] |
+ * } |
+ * </pre> |
+ * <p> |
+ * where #1 is a reference to the {@code "print "} text node above, and so |
+ * on for the other text nodes. |
+ * </p> |
+ * |
+ * <p> |
+ * The {@code} spans array is an array of pairs. Even elements are the start |
+ * indices of substrings, and odd elements are the text nodes (or BR elements) |
+ * that contain the text for those substrings. |
+ * Substrings continue until the next index or the end of the source. |
+ * </p> |
+ * |
+ * @param {Node} node an HTML DOM subtree containing source-code. |
+ * @param {boolean} isPreformatted true if white-space in text nodes should |
+ * be considered significant. |
+ * @return {Object} source code and the text nodes in which they occur. |
+ */ |
+ function extractSourceSpans(node, isPreformatted) { |
+ var nocode = /(?:^|\s)nocode(?:\s|$)/; |
+ |
+ var chunks = []; |
+ var length = 0; |
+ var spans = []; |
+ var k = 0; |
+ |
+ function walk(node) { |
+ switch (node.nodeType) { |
+ case 1: // Element |
+ if (nocode.test(node.className)) { return; } |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ walk(child); |
+ } |
+ var nodeName = node.nodeName.toLowerCase(); |
+ if ('br' === nodeName || 'li' === nodeName) { |
+ chunks[k] = '\n'; |
+ spans[k << 1] = length++; |
+ spans[(k++ << 1) | 1] = node; |
+ } |
+ break; |
+ case 3: case 4: // Text |
+ var text = node.nodeValue; |
+ if (text.length) { |
+ if (!isPreformatted) { |
+ text = text.replace(/[ \t\r\n]+/g, ' '); |
+ } else { |
+ text = text.replace(/\r\n?/g, '\n'); // Normalize newlines. |
+ } |
+ // TODO: handle tabs here? |
+ chunks[k] = text; |
+ spans[k << 1] = length; |
+ length += text.length; |
+ spans[(k++ << 1) | 1] = node; |
+ } |
+ break; |
+ } |
+ } |
+ |
+ walk(node); |
+ |
+ return { |
+ sourceCode: chunks.join('').replace(/\n$/, ''), |
+ spans: spans |
+ }; |
+ } |
+ |
+ |
+ /** |
+ * Apply the given language handler to sourceCode and add the resulting |
+ * decorations to out. |
+ * @param {number} basePos the index of sourceCode within the chunk of source |
+ * whose decorations are already present on out. |
+ */ |
+ function appendDecorations(basePos, sourceCode, langHandler, out) { |
+ if (!sourceCode) { return; } |
+ var job = { |
+ sourceCode: sourceCode, |
+ basePos: basePos |
+ }; |
+ langHandler(job); |
+ out.push.apply(out, job.decorations); |
+ } |
+ |
+ var notWs = /\S/; |
+ |
+ /** |
+ * Given an element, if it contains only one child element and any text nodes |
+ * it contains contain only space characters, return the sole child element. |
+ * Otherwise returns undefined. |
+ * <p> |
+ * This is meant to return the CODE element in {@code <pre><code ...>} when |
+ * there is a single child element that contains all the non-space textual |
+ * content, but not to return anything where there are multiple child elements |
+ * as in {@code <pre><code>...</code><code>...</code></pre>} or when there |
+ * is textual content. |
+ */ |
+ function childContentWrapper(element) { |
+ var wrapper = undefined; |
+ for (var c = element.firstChild; c; c = c.nextSibling) { |
+ var type = c.nodeType; |
+ wrapper = (type === 1) // Element Node |
+ ? (wrapper ? element : c) |
+ : (type === 3) // Text Node |
+ ? (notWs.test(c.nodeValue) ? element : wrapper) |
+ : wrapper; |
+ } |
+ return wrapper === element ? undefined : wrapper; |
+ } |
+ |
+ /** Given triples of [style, pattern, context] returns a lexing function, |
+ * The lexing function interprets the patterns to find token boundaries and |
+ * returns a decoration list of the form |
+ * [index_0, style_0, index_1, style_1, ..., index_n, style_n] |
+ * where index_n is an index into the sourceCode, and style_n is a style |
+ * constant like PR_PLAIN. index_n-1 <= index_n, and style_n-1 applies to |
+ * all characters in sourceCode[index_n-1:index_n]. |
+ * |
+ * The stylePatterns is a list whose elements have the form |
+ * [style : string, pattern : RegExp, DEPRECATED, shortcut : string]. |
+ * |
+ * Style is a style constant like PR_PLAIN, or can be a string of the |
+ * form 'lang-FOO', where FOO is a language extension describing the |
+ * language of the portion of the token in $1 after pattern executes. |
+ * E.g., if style is 'lang-lisp', and group 1 contains the text |
+ * '(hello (world))', then that portion of the token will be passed to the |
+ * registered lisp handler for formatting. |
+ * The text before and after group 1 will be restyled using this decorator |
+ * so decorators should take care that this doesn't result in infinite |
+ * recursion. For example, the HTML lexer rule for SCRIPT elements looks |
+ * something like ['lang-js', /<[s]cript>(.+?)<\/script>/]. This may match |
+ * '<script>foo()<\/script>', which would cause the current decorator to |
+ * be called with '<script>' which would not match the same rule since |
+ * group 1 must not be empty, so it would be instead styled as PR_TAG by |
+ * the generic tag rule. The handler registered for the 'js' extension would |
+ * then be called with 'foo()', and finally, the current decorator would |
+ * be called with '<\/script>' which would not match the original rule and |
+ * so the generic tag rule would identify it as a tag. |
+ * |
+ * Pattern must only match prefixes, and if it matches a prefix, then that |
+ * match is considered a token with the same style. |
+ * |
+ * Context is applied to the last non-whitespace, non-comment token |
+ * recognized. |
+ * |
+ * Shortcut is an optional string of characters, any of which, if the first |
+ * character, gurantee that this pattern and only this pattern matches. |
+ * |
+ * @param {Array} shortcutStylePatterns patterns that always start with |
+ * a known character. Must have a shortcut string. |
+ * @param {Array} fallthroughStylePatterns patterns that will be tried in |
+ * order if the shortcut ones fail. May have shortcuts. |
+ * |
+ * @return {function (Object)} a |
+ * function that takes source code and returns a list of decorations. |
+ */ |
+ function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { |
+ var shortcuts = {}; |
+ var tokenizer; |
+ (function () { |
+ var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); |
+ var allRegexs = []; |
+ var regexKeys = {}; |
+ for (var i = 0, n = allPatterns.length; i < n; ++i) { |
+ var patternParts = allPatterns[i]; |
+ var shortcutChars = patternParts[3]; |
+ if (shortcutChars) { |
+ for (var c = shortcutChars.length; --c >= 0;) { |
+ shortcuts[shortcutChars.charAt(c)] = patternParts; |
+ } |
+ } |
+ var regex = patternParts[1]; |
+ var k = '' + regex; |
+ if (!regexKeys.hasOwnProperty(k)) { |
+ allRegexs.push(regex); |
+ regexKeys[k] = null; |
+ } |
+ } |
+ allRegexs.push(/[\0-\uffff]/); |
+ tokenizer = combinePrefixPatterns(allRegexs); |
+ })(); |
+ |
+ var nPatterns = fallthroughStylePatterns.length; |
+ |
+ /** |
+ * Lexes job.sourceCode and produces an output array job.decorations of |
+ * style classes preceded by the position at which they start in |
+ * job.sourceCode in order. |
+ * |
+ * @param {Object} job an object like <pre>{ |
+ * sourceCode: {string} sourceText plain text, |
+ * basePos: {int} position of job.sourceCode in the larger chunk of |
+ * sourceCode. |
+ * }</pre> |
+ */ |
+ var decorate = function (job) { |
+ var sourceCode = job.sourceCode, basePos = job.basePos; |
+ /** Even entries are positions in source in ascending order. Odd enties |
+ * are style markers (e.g., PR_COMMENT) that run from that position until |
+ * the end. |
+ * @type {Array.<number|string>} |
+ */ |
+ var decorations = [basePos, PR_PLAIN]; |
+ var pos = 0; // index into sourceCode |
+ var tokens = sourceCode.match(tokenizer) || []; |
+ var styleCache = {}; |
+ |
+ for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) { |
+ var token = tokens[ti]; |
+ var style = styleCache[token]; |
+ var match = void 0; |
+ |
+ var isEmbedded; |
+ if (typeof style === 'string') { |
+ isEmbedded = false; |
+ } else { |
+ var patternParts = shortcuts[token.charAt(0)]; |
+ if (patternParts) { |
+ match = token.match(patternParts[1]); |
+ style = patternParts[0]; |
+ } else { |
+ for (var i = 0; i < nPatterns; ++i) { |
+ patternParts = fallthroughStylePatterns[i]; |
+ match = token.match(patternParts[1]); |
+ if (match) { |
+ style = patternParts[0]; |
+ break; |
+ } |
+ } |
+ |
+ if (!match) { // make sure that we make progress |
+ style = PR_PLAIN; |
+ } |
+ } |
+ |
+ isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5); |
+ if (isEmbedded && !(match && typeof match[1] === 'string')) { |
+ isEmbedded = false; |
+ style = PR_SOURCE; |
+ } |
+ |
+ if (!isEmbedded) { styleCache[token] = style; } |
+ } |
+ |
+ var tokenStart = pos; |
+ pos += token.length; |
+ |
+ if (!isEmbedded) { |
+ decorations.push(basePos + tokenStart, style); |
+ } else { // Treat group 1 as an embedded block of source code. |
+ var embeddedSource = match[1]; |
+ var embeddedSourceStart = token.indexOf(embeddedSource); |
+ var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length; |
+ if (match[2]) { |
+ // If embeddedSource can be blank, then it would match at the |
+ // beginning which would cause us to infinitely recurse on the |
+ // entire token, so we catch the right context in match[2]. |
+ embeddedSourceEnd = token.length - match[2].length; |
+ embeddedSourceStart = embeddedSourceEnd - embeddedSource.length; |
+ } |
+ var lang = style.substring(5); |
+ // Decorate the left of the embedded source |
+ appendDecorations( |
+ basePos + tokenStart, |
+ token.substring(0, embeddedSourceStart), |
+ decorate, decorations); |
+ // Decorate the embedded source |
+ appendDecorations( |
+ basePos + tokenStart + embeddedSourceStart, |
+ embeddedSource, |
+ langHandlerForExtension(lang, embeddedSource), |
+ decorations); |
+ // Decorate the right of the embedded section |
+ appendDecorations( |
+ basePos + tokenStart + embeddedSourceEnd, |
+ token.substring(embeddedSourceEnd), |
+ decorate, decorations); |
+ } |
+ } |
+ job.decorations = decorations; |
+ }; |
+ return decorate; |
+ } |
+ |
+ /** returns a function that produces a list of decorations from source text. |
+ * |
+ * This code treats ", ', and ` as string delimiters, and \ as a string |
+ * escape. It does not recognize perl's qq() style strings. |
+ * It has no special handling for double delimiter escapes as in basic, or |
+ * the tripled delimiters used in python, but should work on those regardless |
+ * although in those cases a single string literal may be broken up into |
+ * multiple adjacent string literals. |
+ * |
+ * It recognizes C, C++, and shell style comments. |
+ * |
+ * @param {Object} options a set of optional parameters. |
+ * @return {function (Object)} a function that examines the source code |
+ * in the input job and builds the decoration list. |
+ */ |
+ function sourceDecorator(options) { |
+ var shortcutStylePatterns = [], fallthroughStylePatterns = []; |
+ if (options['tripleQuotedStrings']) { |
+ // '''multi-line-string''', 'single-line-string', and double-quoted |
+ shortcutStylePatterns.push( |
+ [PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, |
+ null, '\'"']); |
+ } else if (options['multiLineStrings']) { |
+ // 'multi-line-string', "multi-line-string" |
+ shortcutStylePatterns.push( |
+ [PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, |
+ null, '\'"`']); |
+ } else { |
+ // 'single-line-string', "single-line-string" |
+ shortcutStylePatterns.push( |
+ [PR_STRING, |
+ /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, |
+ null, '"\'']); |
+ } |
+ if (options['verbatimStrings']) { |
+ // verbatim-string-literal production from the C# grammar. See issue 93. |
+ fallthroughStylePatterns.push( |
+ [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]); |
+ } |
+ var hc = options['hashComments']; |
+ if (hc) { |
+ if (options['cStyleComments']) { |
+ if (hc > 1) { // multiline hash comments |
+ shortcutStylePatterns.push( |
+ [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']); |
+ } else { |
+ // Stop C preprocessor declarations at an unclosed open comment |
+ shortcutStylePatterns.push( |
+ [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/, |
+ null, '#']); |
+ } |
+ // #include <stdio.h> |
+ fallthroughStylePatterns.push( |
+ [PR_STRING, |
+ /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/, |
+ null]); |
+ } else { |
+ shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); |
+ } |
+ } |
+ if (options['cStyleComments']) { |
+ fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); |
+ fallthroughStylePatterns.push( |
+ [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); |
+ } |
+ if (options['regexLiterals']) { |
+ /** |
+ * @const |
+ */ |
+ var REGEX_LITERAL = ( |
+ // A regular expression literal starts with a slash that is |
+ // not followed by * or / so that it is not confused with |
+ // comments. |
+ '/(?=[^/*])' |
+ // and then contains any number of raw characters, |
+ + '(?:[^/\\x5B\\x5C]' |
+ // escape sequences (\x5C), |
+ + '|\\x5C[\\s\\S]' |
+ // or non-nesting character sets (\x5B\x5D); |
+ + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' |
+ // finally closed by a /. |
+ + '/'); |
+ fallthroughStylePatterns.push( |
+ ['lang-regex', |
+ new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')') |
+ ]); |
+ } |
+ |
+ var types = options['types']; |
+ if (types) { |
+ fallthroughStylePatterns.push([PR_TYPE, types]); |
+ } |
+ |
+ var keywords = ("" + options['keywords']).replace(/^ | $/g, ''); |
+ if (keywords.length) { |
+ fallthroughStylePatterns.push( |
+ [PR_KEYWORD, |
+ new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'), |
+ null]); |
+ } |
+ |
+ shortcutStylePatterns.push([PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0']); |
+ |
+ var punctuation = |
+ // The Bash man page says |
+ |
+ // A word is a sequence of characters considered as a single |
+ // unit by GRUB. Words are separated by metacharacters, |
+ // which are the following plus space, tab, and newline: { } |
+ // | & $ ; < > |
+ // ... |
+ |
+ // A word beginning with # causes that word and all remaining |
+ // characters on that line to be ignored. |
+ |
+ // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a |
+ // comment but empirically |
+ // $ echo {#} |
+ // {#} |
+ // $ echo \$# |
+ // $# |
+ // $ echo }# |
+ // }# |
+ |
+ // so /(?:^|[|&;<>\s])/ is more appropriate. |
+ |
+ // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3 |
+ // suggests that this definition is compatible with a |
+ // default mode that tries to use a single token definition |
+ // to recognize both bash/python style comments and C |
+ // preprocessor directives. |
+ |
+ // This definition of punctuation does not include # in the list of |
+ // follow-on exclusions, so # will not be broken before if preceeded |
+ // by a punctuation character. We could try to exclude # after |
+ // [|&;<>] but that doesn't seem to cause many major problems. |
+ // If that does turn out to be a problem, we should change the below |
+ // when hc is truthy to include # in the run of punctuation characters |
+ // only when not followint [|&;<>]. |
+ /^.[^\s\w\.$@\'\"\`\/\\]*/; |
+ |
+ fallthroughStylePatterns.push( |
+ // TODO(mikesamuel): recognize non-latin letters and numerals in idents |
+ [PR_LITERAL, /^@[a-z_$][a-z_$@0-9]*/i, null], |
+ [PR_TYPE, /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null], |
+ [PR_PLAIN, /^[a-z_$][a-z_$@0-9]*/i, null], |
+ [PR_LITERAL, |
+ new RegExp( |
+ '^(?:' |
+ // A hex number |
+ + '0x[a-f0-9]+' |
+ // or an octal or decimal number, |
+ + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)' |
+ // possibly in scientific notation |
+ + '(?:e[+\\-]?\\d+)?' |
+ + ')' |
+ // with an optional modifier like UL for unsigned long |
+ + '[a-z]*', 'i'), |
+ null, '0123456789'], |
+ // Don't treat escaped quotes in bash as starting strings. See issue 144. |
+ [PR_PLAIN, /^\\[\s\S]?/, null], |
+ [PR_PUNCTUATION, punctuation, null]); |
+ |
+ return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); |
+ } |
+ |
+ var decorateSource = sourceDecorator({ |
+ 'keywords': ALL_KEYWORDS, |
+ 'hashComments': true, |
+ 'cStyleComments': true, |
+ 'multiLineStrings': true, |
+ 'regexLiterals': true |
+ }); |
+ |
+ /** |
+ * Given a DOM subtree, wraps it in a list, and puts each line into its own |
+ * list item. |
+ * |
+ * @param {Node} node modified in place. Its content is pulled into an |
+ * HTMLOListElement, and each line is moved into a separate list item. |
+ * This requires cloning elements, so the input might not have unique |
+ * IDs after numbering. |
+ * @param {boolean} isPreformatted true iff white-space in text nodes should |
+ * be treated as significant. |
+ */ |
+ function numberLines(node, opt_startLineNum, isPreformatted) { |
+ var nocode = /(?:^|\s)nocode(?:\s|$)/; |
+ var lineBreak = /\r\n?|\n/; |
+ |
+ var document = node.ownerDocument; |
+ |
+ var li = document.createElement('li'); |
+ while (node.firstChild) { |
+ li.appendChild(node.firstChild); |
+ } |
+ // An array of lines. We split below, so this is initialized to one |
+ // un-split line. |
+ var listItems = [li]; |
+ |
+ function walk(node) { |
+ switch (node.nodeType) { |
+ case 1: // Element |
+ if (nocode.test(node.className)) { break; } |
+ if ('br' === node.nodeName) { |
+ breakAfter(node); |
+ // Discard the <BR> since it is now flush against a </LI>. |
+ if (node.parentNode) { |
+ node.parentNode.removeChild(node); |
+ } |
+ } else { |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ walk(child); |
+ } |
+ } |
+ break; |
+ case 3: case 4: // Text |
+ if (isPreformatted) { |
+ var text = node.nodeValue; |
+ var match = text.match(lineBreak); |
+ if (match) { |
+ var firstLine = text.substring(0, match.index); |
+ node.nodeValue = firstLine; |
+ var tail = text.substring(match.index + match[0].length); |
+ if (tail) { |
+ var parent = node.parentNode; |
+ parent.insertBefore( |
+ document.createTextNode(tail), node.nextSibling); |
+ } |
+ breakAfter(node); |
+ if (!firstLine) { |
+ // Don't leave blank text nodes in the DOM. |
+ node.parentNode.removeChild(node); |
+ } |
+ } |
+ } |
+ break; |
+ } |
+ } |
+ |
+ // Split a line after the given node. |
+ function breakAfter(lineEndNode) { |
+ // If there's nothing to the right, then we can skip ending the line |
+ // here, and move root-wards since splitting just before an end-tag |
+ // would require us to create a bunch of empty copies. |
+ while (!lineEndNode.nextSibling) { |
+ lineEndNode = lineEndNode.parentNode; |
+ if (!lineEndNode) { return; } |
+ } |
+ |
+ function breakLeftOf(limit, copy) { |
+ // Clone shallowly if this node needs to be on both sides of the break. |
+ var rightSide = copy ? limit.cloneNode(false) : limit; |
+ var parent = limit.parentNode; |
+ if (parent) { |
+ // We clone the parent chain. |
+ // This helps us resurrect important styling elements that cross lines. |
+ // E.g. in <i>Foo<br>Bar</i> |
+ // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>. |
+ var parentClone = breakLeftOf(parent, 1); |
+ // Move the clone and everything to the right of the original |
+ // onto the cloned parent. |
+ var next = limit.nextSibling; |
+ parentClone.appendChild(rightSide); |
+ for (var sibling = next; sibling; sibling = next) { |
+ next = sibling.nextSibling; |
+ parentClone.appendChild(sibling); |
+ } |
+ } |
+ return rightSide; |
+ } |
+ |
+ var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0); |
+ |
+ // Walk the parent chain until we reach an unattached LI. |
+ for (var parent; |
+ // Check nodeType since IE invents document fragments. |
+ (parent = copiedListItem.parentNode) && parent.nodeType === 1;) { |
+ copiedListItem = parent; |
+ } |
+ // Put it on the list of lines for later processing. |
+ listItems.push(copiedListItem); |
+ } |
+ |
+ // Split lines while there are lines left to split. |
+ for (var i = 0; // Number of lines that have been split so far. |
+ i < listItems.length; // length updated by breakAfter calls. |
+ ++i) { |
+ walk(listItems[i]); |
+ } |
+ |
+ // Make sure numeric indices show correctly. |
+ if (opt_startLineNum === (opt_startLineNum|0)) { |
+ listItems[0].setAttribute('value', opt_startLineNum); |
+ } |
+ |
+ var ol = document.createElement('ol'); |
+ ol.className = 'linenums'; |
+ var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0; |
+ for (var i = 0, n = listItems.length; i < n; ++i) { |
+ li = listItems[i]; |
+ // Stick a class on the LIs so that stylesheets can |
+ // color odd/even rows, or any other row pattern that |
+ // is co-prime with 10. |
+ li.className = 'L' + ((i + offset) % 10); |
+ if (!li.firstChild) { |
+ li.appendChild(document.createTextNode('\xA0')); |
+ } |
+ ol.appendChild(li); |
+ } |
+ |
+ node.appendChild(ol); |
+ } |
+ |
+ /** |
+ * Breaks {@code job.sourceCode} around style boundaries in |
+ * {@code job.decorations} and modifies {@code job.sourceNode} in place. |
+ * @param {Object} job like <pre>{ |
+ * sourceCode: {string} source as plain text, |
+ * spans: {Array.<number|Node>} alternating span start indices into source |
+ * and the text node or element (e.g. {@code <BR>}) corresponding to that |
+ * span. |
+ * decorations: {Array.<number|string} an array of style classes preceded |
+ * by the position at which they start in job.sourceCode in order |
+ * }</pre> |
+ * @private |
+ */ |
+ function recombineTagsAndDecorations(job) { |
+ var isIE8OrEarlier = /\bMSIE\s(\d+)/.exec(navigator.userAgent); |
+ isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8; |
+ var newlineRe = /\n/g; |
+ |
+ var source = job.sourceCode; |
+ var sourceLength = source.length; |
+ // Index into source after the last code-unit recombined. |
+ var sourceIndex = 0; |
+ |
+ var spans = job.spans; |
+ var nSpans = spans.length; |
+ // Index into spans after the last span which ends at or before sourceIndex. |
+ var spanIndex = 0; |
+ |
+ var decorations = job.decorations; |
+ var nDecorations = decorations.length; |
+ // Index into decorations after the last decoration which ends at or before |
+ // sourceIndex. |
+ var decorationIndex = 0; |
+ |
+ // Remove all zero-length decorations. |
+ decorations[nDecorations] = sourceLength; |
+ var decPos, i; |
+ for (i = decPos = 0; i < nDecorations;) { |
+ if (decorations[i] !== decorations[i + 2]) { |
+ decorations[decPos++] = decorations[i++]; |
+ decorations[decPos++] = decorations[i++]; |
+ } else { |
+ i += 2; |
+ } |
+ } |
+ nDecorations = decPos; |
+ |
+ // Simplify decorations. |
+ for (i = decPos = 0; i < nDecorations;) { |
+ var startPos = decorations[i]; |
+ // Conflate all adjacent decorations that use the same style. |
+ var startDec = decorations[i + 1]; |
+ var end = i + 2; |
+ while (end + 2 <= nDecorations && decorations[end + 1] === startDec) { |
+ end += 2; |
+ } |
+ decorations[decPos++] = startPos; |
+ decorations[decPos++] = startDec; |
+ i = end; |
+ } |
+ |
+ nDecorations = decorations.length = decPos; |
+ |
+ var sourceNode = job.sourceNode; |
+ var oldDisplay; |
+ if (sourceNode) { |
+ oldDisplay = sourceNode.style.display; |
+ sourceNode.style.display = 'none'; |
+ } |
+ try { |
+ var decoration = null; |
+ while (spanIndex < nSpans) { |
+ var spanStart = spans[spanIndex]; |
+ var spanEnd = spans[spanIndex + 2] || sourceLength; |
+ |
+ var decEnd = decorations[decorationIndex + 2] || sourceLength; |
+ |
+ var end = Math.min(spanEnd, decEnd); |
+ |
+ var textNode = spans[spanIndex + 1]; |
+ var styledText; |
+ if (textNode.nodeType !== 1 // Don't muck with <BR>s or <LI>s |
+ // Don't introduce spans around empty text nodes. |
+ && (styledText = source.substring(sourceIndex, end))) { |
+ // This may seem bizarre, and it is. Emitting LF on IE causes the |
+ // code to display with spaces instead of line breaks. |
+ // Emitting Windows standard issue linebreaks (CRLF) causes a blank |
+ // space to appear at the beginning of every line but the first. |
+ // Emitting an old Mac OS 9 line separator makes everything spiffy. |
+ if (isIE8OrEarlier) { |
+ styledText = styledText.replace(newlineRe, '\r'); |
+ } |
+ textNode.nodeValue = styledText; |
+ var document = textNode.ownerDocument; |
+ var span = document.createElement('span'); |
+ span.className = decorations[decorationIndex + 1]; |
+ var parentNode = textNode.parentNode; |
+ parentNode.replaceChild(span, textNode); |
+ span.appendChild(textNode); |
+ if (sourceIndex < spanEnd) { // Split off a text node. |
+ spans[spanIndex + 1] = textNode |
+ // TODO: Possibly optimize by using '' if there's no flicker. |
+ = document.createTextNode(source.substring(end, spanEnd)); |
+ parentNode.insertBefore(textNode, span.nextSibling); |
+ } |
+ } |
+ |
+ sourceIndex = end; |
+ |
+ if (sourceIndex >= spanEnd) { |
+ spanIndex += 2; |
+ } |
+ if (sourceIndex >= decEnd) { |
+ decorationIndex += 2; |
+ } |
+ } |
+ } finally { |
+ if (sourceNode) { |
+ sourceNode.style.display = oldDisplay; |
+ } |
+ } |
+ } |
+ |
+ |
+ /** Maps language-specific file extensions to handlers. */ |
+ var langHandlerRegistry = {}; |
+ /** Register a language handler for the given file extensions. |
+ * @param {function (Object)} handler a function from source code to a list |
+ * of decorations. Takes a single argument job which describes the |
+ * state of the computation. The single parameter has the form |
+ * {@code { |
+ * sourceCode: {string} as plain text. |
+ * decorations: {Array.<number|string>} an array of style classes |
+ * preceded by the position at which they start in |
+ * job.sourceCode in order. |
+ * The language handler should assigned this field. |
+ * basePos: {int} the position of source in the larger source chunk. |
+ * All positions in the output decorations array are relative |
+ * to the larger source chunk. |
+ * } } |
+ * @param {Array.<string>} fileExtensions |
+ */ |
+ function registerLangHandler(handler, fileExtensions) { |
+ for (var i = fileExtensions.length; --i >= 0;) { |
+ var ext = fileExtensions[i]; |
+ if (!langHandlerRegistry.hasOwnProperty(ext)) { |
+ langHandlerRegistry[ext] = handler; |
+ } else if (win['console']) { |
+ console['warn']('cannot override language handler %s', ext); |
+ } |
+ } |
+ } |
+ function langHandlerForExtension(extension, source) { |
+ if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) { |
+ // Treat it as markup if the first non whitespace character is a < and |
+ // the last non-whitespace character is a >. |
+ extension = /^\s*</.test(source) |
+ ? 'default-markup' |
+ : 'default-code'; |
+ } |
+ return langHandlerRegistry[extension]; |
+ } |
+ registerLangHandler(decorateSource, ['default-code']); |
+ registerLangHandler( |
+ createSimpleLexer( |
+ [], |
+ [ |
+ [PR_PLAIN, /^[^<?]+/], |
+ [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/], |
+ [PR_COMMENT, /^<\!--[\s\S]*?(?:-\->|$)/], |
+ // Unescaped content in an unknown language |
+ ['lang-', /^<\?([\s\S]+?)(?:\?>|$)/], |
+ ['lang-', /^<%([\s\S]+?)(?:%>|$)/], |
+ [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/], |
+ ['lang-', /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i], |
+ // Unescaped content in javascript. (Or possibly vbscript). |
+ ['lang-js', /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i], |
+ // Contains unescaped stylesheet content |
+ ['lang-css', /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i], |
+ ['lang-in.tag', /^(<\/?[a-z][^<>]*>)/i] |
+ ]), |
+ ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']); |
+ registerLangHandler( |
+ createSimpleLexer( |
+ [ |
+ [PR_PLAIN, /^[\s]+/, null, ' \t\r\n'], |
+ [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\''] |
+ ], |
+ [ |
+ [PR_TAG, /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i], |
+ [PR_ATTRIB_NAME, /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i], |
+ ['lang-uq.val', /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], |
+ [PR_PUNCTUATION, /^[=<>\/]+/], |
+ ['lang-js', /^on\w+\s*=\s*\"([^\"]+)\"/i], |
+ ['lang-js', /^on\w+\s*=\s*\'([^\']+)\'/i], |
+ ['lang-js', /^on\w+\s*=\s*([^\"\'>\s]+)/i], |
+ ['lang-css', /^style\s*=\s*\"([^\"]+)\"/i], |
+ ['lang-css', /^style\s*=\s*\'([^\']+)\'/i], |
+ ['lang-css', /^style\s*=\s*([^\"\'>\s]+)/i] |
+ ]), |
+ ['in.tag']); |
+ registerLangHandler( |
+ createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': CPP_KEYWORDS, |
+ 'hashComments': true, |
+ 'cStyleComments': true, |
+ 'types': C_TYPES |
+ }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': 'null,true,false' |
+ }), ['json']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': CSHARP_KEYWORDS, |
+ 'hashComments': true, |
+ 'cStyleComments': true, |
+ 'verbatimStrings': true, |
+ 'types': C_TYPES |
+ }), ['cs']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': JAVA_KEYWORDS, |
+ 'cStyleComments': true |
+ }), ['java']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': SH_KEYWORDS, |
+ 'hashComments': true, |
+ 'multiLineStrings': true |
+ }), ['bsh', 'csh', 'sh']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': PYTHON_KEYWORDS, |
+ 'hashComments': true, |
+ 'multiLineStrings': true, |
+ 'tripleQuotedStrings': true |
+ }), ['cv', 'py']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': PERL_KEYWORDS, |
+ 'hashComments': true, |
+ 'multiLineStrings': true, |
+ 'regexLiterals': true |
+ }), ['perl', 'pl', 'pm']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': RUBY_KEYWORDS, |
+ 'hashComments': true, |
+ 'multiLineStrings': true, |
+ 'regexLiterals': true |
+ }), ['rb']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': JSCRIPT_KEYWORDS, |
+ 'cStyleComments': true, |
+ 'regexLiterals': true |
+ }), ['js']); |
+ registerLangHandler(sourceDecorator({ |
+ 'keywords': COFFEE_KEYWORDS, |
+ 'hashComments': 3, // ### style block comments |
+ 'cStyleComments': true, |
+ 'multilineStrings': true, |
+ 'tripleQuotedStrings': true, |
+ 'regexLiterals': true |
+ }), ['coffee']); |
+ registerLangHandler( |
+ createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']); |
+ |
+ function applyDecorator(job) { |
+ var opt_langExtension = job.langExtension; |
+ |
+ try { |
+ // Extract tags, and convert the source code to plain text. |
+ var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre); |
+ /** Plain text. @type {string} */ |
+ var source = sourceAndSpans.sourceCode; |
+ job.sourceCode = source; |
+ job.spans = sourceAndSpans.spans; |
+ job.basePos = 0; |
+ |
+ // Apply the appropriate language handler |
+ langHandlerForExtension(opt_langExtension, source)(job); |
+ |
+ // Integrate the decorations and tags back into the source code, |
+ // modifying the sourceNode in place. |
+ recombineTagsAndDecorations(job); |
+ } catch (e) { |
+ if (win['console']) { |
+ console['log'](e && e['stack'] ? e['stack'] : e); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * @param sourceCodeHtml {string} The HTML to pretty print. |
+ * @param opt_langExtension {string} The language name to use. |
+ * Typically, a filename extension like 'cpp' or 'java'. |
+ * @param opt_numberLines {number|boolean} True to number lines, |
+ * or the 1-indexed number of the first line in sourceCodeHtml. |
+ */ |
+ function prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) { |
+ var container = document.createElement('pre'); |
+ // This could cause images to load and onload listeners to fire. |
+ // E.g. <img onerror="alert(1337)" src="nosuchimage.png">. |
+ // We assume that the inner HTML is from a trusted source. |
+ container.innerHTML = sourceCodeHtml; |
+ if (opt_numberLines) { |
+ numberLines(container, opt_numberLines, true); |
+ } |
+ |
+ var job = { |
+ langExtension: opt_langExtension, |
+ numberLines: opt_numberLines, |
+ sourceNode: container, |
+ pre: 1 |
+ }; |
+ applyDecorator(job); |
+ return container.innerHTML; |
+ } |
+ |
+ function prettyPrint(opt_whenDone) { |
+ function byTagName(tn) { return document.getElementsByTagName(tn); } |
+ // fetch a list of nodes to rewrite |
+ var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')]; |
+ var elements = []; |
+ for (var i = 0; i < codeSegments.length; ++i) { |
+ for (var j = 0, n = codeSegments[i].length; j < n; ++j) { |
+ elements.push(codeSegments[i][j]); |
+ } |
+ } |
+ codeSegments = null; |
+ |
+ var clock = Date; |
+ if (!clock['now']) { |
+ clock = { 'now': function () { return +(new Date); } }; |
+ } |
+ |
+ // The loop is broken into a series of continuations to make sure that we |
+ // don't make the browser unresponsive when rewriting a large page. |
+ var k = 0; |
+ var prettyPrintingJob; |
+ |
+ var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/; |
+ var prettyPrintRe = /\bprettyprint\b/; |
+ var prettyPrintedRe = /\bprettyprinted\b/; |
+ var preformattedTagNameRe = /pre|xmp/i; |
+ var codeRe = /^code$/i; |
+ var preCodeXmpRe = /^(?:pre|code|xmp)$/i; |
+ |
+ function doWork() { |
+ var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ? |
+ clock['now']() + 250 /* ms */ : |
+ Infinity); |
+ for (; k < elements.length && clock['now']() < endTime; k++) { |
+ var cs = elements[k]; |
+ var className = cs.className; |
+ if (prettyPrintRe.test(className) |
+ // Don't redo this if we've already done it. |
+ // This allows recalling pretty print to just prettyprint elements |
+ // that have been added to the page since last call. |
+ && !prettyPrintedRe.test(className)) { |
+ |
+ // make sure this is not nested in an already prettified element |
+ var nested = false; |
+ for (var p = cs.parentNode; p; p = p.parentNode) { |
+ var tn = p.tagName; |
+ if (preCodeXmpRe.test(tn) |
+ && p.className && prettyPrintRe.test(p.className)) { |
+ nested = true; |
+ break; |
+ } |
+ } |
+ if (!nested) { |
+ // Mark done. If we fail to prettyprint for whatever reason, |
+ // we shouldn't try again. |
+ cs.className += ' prettyprinted'; |
+ |
+ // If the classes includes a language extensions, use it. |
+ // Language extensions can be specified like |
+ // <pre class="prettyprint lang-cpp"> |
+ // the language extension "cpp" is used to find a language handler |
+ // as passed to PR.registerLangHandler. |
+ // HTML5 recommends that a language be specified using "language-" |
+ // as the prefix instead. Google Code Prettify supports both. |
+ // http://dev.w3.org/html5/spec-author-view/the-code-element.html |
+ var langExtension = className.match(langExtensionRe); |
+ // Support <pre class="prettyprint"><code class="language-c"> |
+ var wrapper; |
+ if (!langExtension && (wrapper = childContentWrapper(cs)) |
+ && codeRe.test(wrapper.tagName)) { |
+ langExtension = wrapper.className.match(langExtensionRe); |
+ } |
+ |
+ if (langExtension) { langExtension = langExtension[1]; } |
+ |
+ var preformatted; |
+ if (preformattedTagNameRe.test(cs.tagName)) { |
+ preformatted = 1; |
+ } else { |
+ var currentStyle = cs['currentStyle']; |
+ var whitespace = ( |
+ currentStyle |
+ ? currentStyle['whiteSpace'] |
+ : (document.defaultView |
+ && document.defaultView.getComputedStyle) |
+ ? document.defaultView.getComputedStyle(cs, null) |
+ .getPropertyValue('white-space') |
+ : 0); |
+ preformatted = whitespace |
+ && 'pre' === whitespace.substring(0, 3); |
+ } |
+ |
+ // Look for a class like linenums or linenums:<n> where <n> is the |
+ // 1-indexed number of the first line. |
+ var lineNums = cs.className.match(/\blinenums\b(?::(\d+))?/); |
+ lineNums = lineNums |
+ ? lineNums[1] && lineNums[1].length ? +lineNums[1] : true |
+ : false; |
+ if (lineNums) { numberLines(cs, lineNums, preformatted); } |
+ |
+ // do the pretty printing |
+ prettyPrintingJob = { |
+ langExtension: langExtension, |
+ sourceNode: cs, |
+ numberLines: lineNums, |
+ pre: preformatted |
+ }; |
+ applyDecorator(prettyPrintingJob); |
+ } |
+ } |
+ } |
+ if (k < elements.length) { |
+ // finish up in a continuation |
+ setTimeout(doWork, 250); |
+ } else if (opt_whenDone) { |
+ opt_whenDone(); |
+ } |
+ } |
+ |
+ doWork(); |
+ } |
+ |
+ /** |
+ * Contains functions for creating and registering new language handlers. |
+ * @type {Object} |
+ */ |
+ var PR = win['PR'] = { |
+ 'createSimpleLexer': createSimpleLexer, |
+ 'registerLangHandler': registerLangHandler, |
+ 'sourceDecorator': sourceDecorator, |
+ 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, |
+ 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, |
+ 'PR_COMMENT': PR_COMMENT, |
+ 'PR_DECLARATION': PR_DECLARATION, |
+ 'PR_KEYWORD': PR_KEYWORD, |
+ 'PR_LITERAL': PR_LITERAL, |
+ 'PR_NOCODE': PR_NOCODE, |
+ 'PR_PLAIN': PR_PLAIN, |
+ 'PR_PUNCTUATION': PR_PUNCTUATION, |
+ 'PR_SOURCE': PR_SOURCE, |
+ 'PR_STRING': PR_STRING, |
+ 'PR_TAG': PR_TAG, |
+ 'PR_TYPE': PR_TYPE, |
+ 'prettyPrintOne': win['prettyPrintOne'] = prettyPrintOne, |
+ 'prettyPrint': win['prettyPrint'] = prettyPrint |
+ }; |
+ |
+ // Make PR available via the Asynchronous Module Definition (AMD) API. |
+ // Per https://github.com/amdjs/amdjs-api/wiki/AMD: |
+ // The Asynchronous Module Definition (AMD) API specifies a |
+ // mechanism for defining modules such that the module and its |
+ // dependencies can be asynchronously loaded. |
+ // ... |
+ // To allow a clear indicator that a global define function (as |
+ // needed for script src browser loading) conforms to the AMD API, |
+ // any global define function SHOULD have a property called "amd" |
+ // whose value is an object. This helps avoid conflict with any |
+ // other existing JavaScript code that could have defined a define() |
+ // function that does not conform to the AMD API. |
+ if (typeof define === "function" && define['amd']) { |
+ define(function () { |
+ return PR; |
+ }); |
+ } |
+})(); |
+; |
+(function(scope) { |
+ |
+ var ContextFreeParser = { |
+ parse: function(text) { |
+ var top = {}; |
+ var entities = []; |
+ var current = top; |
+ var subCurrent = {}; |
+ |
+ var scriptDocCommentClause = '\\/\\*\\*([\\s\\S]*?)\\*\\/'; |
+ var htmlDocCommentClause = '<!--([\\s\\S]*?)-->'; |
+ |
+ // matches text between /** and */ inclusive and <!-- and --> inclusive |
+ var docCommentRegex = new RegExp(scriptDocCommentClause + '|' + htmlDocCommentClause, 'g'); |
+ |
+ // acquire all script doc comments |
+ var docComments = text.match(docCommentRegex) || []; |
+ |
+ // each match represents a single block of doc comments |
+ docComments.forEach(function(m) { |
+ // unify line ends, remove all comment characters, split into individual lines |
+ var lines = m.replace(/\r\n/g, '\n').replace(/^\s*\/\*\*|^\s*\*\/|^\s*\* ?|^\s*\<\!-\-|^s*\-\-\>/gm, '').split('\n'); |
+ |
+ // pragmas (@-rules) must occur on a line by themselves |
+ var pragmas = []; |
+ // filter lines whose first non-whitespace character is @ into the pragma list |
+ // (and out of the `lines` array) |
+ lines = lines.filter(function(l) { |
+ var m = l.match(/\s*@([\w-]*) (.*)/); |
+ if (!m) { |
+ return true; |
+ } |
+ pragmas.push(m); |
+ }); |
+ |
+ // collect all other text into a single block |
+ var code = lines.join('\n'); |
+ |
+ // process pragmas |
+ pragmas.forEach(function(m) { |
+ var pragma = m[1], content = m[2]; |
+ switch (pragma) { |
+ |
+ // currently all entities are either @class or @element |
+ case 'class': |
+ case 'element': |
+ current = { |
+ name: content, |
+ description: code |
+ }; |
+ entities.push(current); |
+ break; |
+ |
+ // an entity may have these describable sub-features |
+ case 'attribute': |
+ case 'property': |
+ case 'method': |
+ case 'event': |
+ subCurrent = { |
+ name: content, |
+ description: code |
+ }; |
+ var label = pragma == 'property' ? 'properties' : pragma + 's'; |
+ makePragma(current, label, subCurrent); |
+ break; |
+ |
+ // sub-feature pragmas |
+ case 'default': |
+ case 'type': |
+ subCurrent[pragma] = content; |
+ break; |
+ |
+ // everything else |
+ default: |
+ current[pragma] = content; |
+ break; |
+ } |
+ }); |
+ |
+ // utility function, yay hoisting |
+ function makePragma(object, pragma, content) { |
+ var p$ = object; |
+ var p = p$[pragma]; |
+ if (!p) { |
+ p$[pragma] = p = []; |
+ } |
+ p.push(content); |
+ } |
+ |
+ }); |
+ |
+ if (entities.length === 0) { |
+ entities.push({name: 'Entity', description: '**Undocumented**'}); |
+ } |
+ return entities; |
+ } |
+ }; |
+ |
+ if (typeof module !== 'undefined' && module.exports) { |
+ module.exports = ContextFreeParser; |
+ } else { |
+ scope.ContextFreeParser = ContextFreeParser; |
+ } |
+ |
+})(this);; |
+ |
+ |
+ Polymer('core-xhr', { |
+ |
+ /** |
+ * Sends a HTTP request to the server and returns the XHR object. |
+ * |
+ * @method request |
+ * @param {Object} inOptions |
+ * @param {String} inOptions.url The url to which the request is sent. |
+ * @param {String} inOptions.method The HTTP method to use, default is GET. |
+ * @param {boolean} inOptions.sync By default, all requests are sent asynchronously. To send synchronous requests, set to true. |
+ * @param {Object} inOptions.params Data to be sent to the server. |
+ * @param {Object} inOptions.body The content for the request body for POST method. |
+ * @param {Object} inOptions.headers HTTP request headers. |
+ * @param {String} inOptions.responseType The response type. Default is 'text'. |
+ * @param {boolean} inOptions.withCredentials Whether or not to send credentials on the request. Default is false. |
+ * @param {Object} inOptions.callback Called when request is completed. |
+ * @returns {Object} XHR object. |
+ */ |
+ request: function(options) { |
+ var xhr = new XMLHttpRequest(); |
+ var url = options.url; |
+ var method = options.method || 'GET'; |
+ var async = !options.sync; |
+ // |
+ var params = this.toQueryString(options.params); |
+ if (params && method == 'GET') { |
+ url += (url.indexOf('?') > 0 ? '&' : '?') + params; |
+ } |
+ var xhrParams = this.isBodyMethod(method) ? (options.body || params) : null; |
+ // |
+ xhr.open(method, url, async); |
+ if (options.responseType) { |
+ xhr.responseType = options.responseType; |
+ } |
+ if (options.withCredentials) { |
+ xhr.withCredentials = true; |
+ } |
+ this.makeReadyStateHandler(xhr, options.callback); |
+ this.setRequestHeaders(xhr, options.headers); |
+ xhr.send(xhrParams); |
+ if (!async) { |
+ xhr.onreadystatechange(xhr); |
+ } |
+ return xhr; |
+ }, |
+ |
+ toQueryString: function(params) { |
+ var r = []; |
+ for (var n in params) { |
+ var v = params[n]; |
+ n = encodeURIComponent(n); |
+ r.push(v == null ? n : (n + '=' + encodeURIComponent(v))); |
+ } |
+ return r.join('&'); |
+ }, |
+ |
+ isBodyMethod: function(method) { |
+ return this.bodyMethods[(method || '').toUpperCase()]; |
+ }, |
+ |
+ bodyMethods: { |
+ POST: 1, |
+ PUT: 1, |
+ DELETE: 1 |
+ }, |
+ |
+ makeReadyStateHandler: function(xhr, callback) { |
+ xhr.onreadystatechange = function() { |
+ if (xhr.readyState == 4) { |
+ callback && callback.call(null, xhr.response, xhr); |
+ } |
+ }; |
+ }, |
+ |
+ setRequestHeaders: function(xhr, headers) { |
+ if (headers) { |
+ for (var name in headers) { |
+ xhr.setRequestHeader(name, headers[name]); |
+ } |
+ } |
+ } |
+ |
+ }); |
+ |
+ ; |
+ |
+ |
+ Polymer('core-ajax', { |
+ /** |
+ * Fired when a response is received. |
+ * |
+ * @event core-response |
+ */ |
+ |
+ /** |
+ * Fired when an error is received. |
+ * |
+ * @event core-error |
+ */ |
+ |
+ /** |
+ * Fired whenever a response or an error is received. |
+ * |
+ * @event core-complete |
+ */ |
+ |
+ /** |
+ * The URL target of the request. |
+ * |
+ * @attribute url |
+ * @type string |
+ * @default '' |
+ */ |
+ url: '', |
+ |
+ /** |
+ * Specifies what data to store in the `response` property, and |
+ * to deliver as `event.response` in `response` events. |
+ * |
+ * One of: |
+ * |
+ * `text`: uses `XHR.responseText`. |
+ * |
+ * `xml`: uses `XHR.responseXML`. |
+ * |
+ * `json`: uses `XHR.responseText` parsed as JSON. |
+ * |
+ * `arraybuffer`: uses `XHR.response`. |
+ * |
+ * `blob`: uses `XHR.response`. |
+ * |
+ * `document`: uses `XHR.response`. |
+ * |
+ * @attribute handleAs |
+ * @type string |
+ * @default 'text' |
+ */ |
+ handleAs: '', |
+ |
+ /** |
+ * If true, automatically performs an Ajax request when either `url` or `params` changes. |
+ * |
+ * @attribute auto |
+ * @type boolean |
+ * @default false |
+ */ |
+ auto: false, |
+ |
+ /** |
+ * Parameters to send to the specified URL, as JSON. |
+ * |
+ * @attribute params |
+ * @type string (JSON) |
+ * @default '' |
+ */ |
+ params: '', |
+ |
+ /** |
+ * Returns the response object. |
+ * |
+ * @attribute response |
+ * @type Object |
+ * @default null |
+ */ |
+ response: null, |
+ |
+ /** |
+ * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. |
+ * Default is 'GET'. |
+ * |
+ * @attribute method |
+ * @type string |
+ * @default '' |
+ */ |
+ method: '', |
+ |
+ /** |
+ * HTTP request headers to send. |
+ * |
+ * Example: |
+ * |
+ * <core-ajax |
+ * auto |
+ * url="http://somesite.com" |
+ * headers='{"X-Requested-With": "XMLHttpRequest"}' |
+ * handleAs="json" |
+ * on-core-response="{{handleResponse}}"></core-ajax> |
+ * |
+ * @attribute headers |
+ * @type Object |
+ * @default null |
+ */ |
+ headers: null, |
+ |
+ /** |
+ * Optional raw body content to send when method === "POST". |
+ * |
+ * Example: |
+ * |
+ * <core-ajax method="POST" auto url="http://somesite.com" |
+ * body='{"foo":1, "bar":2}'> |
+ * </core-ajax> |
+ * |
+ * @attribute body |
+ * @type Object |
+ * @default null |
+ */ |
+ body: null, |
+ |
+ /** |
+ * Content type to use when sending data. |
+ * |
+ * @attribute contentType |
+ * @type string |
+ * @default 'application/x-www-form-urlencoded' |
+ */ |
+ contentType: 'application/x-www-form-urlencoded', |
+ |
+ /** |
+ * Set the withCredentials flag on the request. |
+ * |
+ * @attribute withCredentials |
+ * @type boolean |
+ * @default false |
+ */ |
+ withCredentials: false, |
+ |
+ /** |
+ * Additional properties to send to core-xhr. |
+ * |
+ * Can be set to an object containing default properties |
+ * to send as arguments to the `core-xhr.request()` method |
+ * which implements the low-level communication. |
+ * |
+ * @property xhrArgs |
+ * @type Object |
+ * @default null |
+ */ |
+ xhrArgs: null, |
+ |
+ ready: function() { |
+ this.xhr = document.createElement('core-xhr'); |
+ }, |
+ |
+ receive: function(response, xhr) { |
+ if (this.isSuccess(xhr)) { |
+ this.processResponse(xhr); |
+ } else { |
+ this.error(xhr); |
+ } |
+ this.complete(xhr); |
+ }, |
+ |
+ isSuccess: function(xhr) { |
+ var status = xhr.status || 0; |
+ return !status || (status >= 200 && status < 300); |
+ }, |
+ |
+ processResponse: function(xhr) { |
+ var response = this.evalResponse(xhr); |
+ this.response = response; |
+ this.fire('core-response', {response: response, xhr: xhr}); |
+ }, |
+ |
+ error: function(xhr) { |
+ var response = xhr.status + ': ' + xhr.responseText; |
+ this.fire('core-error', {response: response, xhr: xhr}); |
+ }, |
+ |
+ complete: function(xhr) { |
+ this.fire('core-complete', {response: xhr.status, xhr: xhr}); |
+ }, |
+ |
+ evalResponse: function(xhr) { |
+ return this[(this.handleAs || 'text') + 'Handler'](xhr); |
+ }, |
+ |
+ xmlHandler: function(xhr) { |
+ return xhr.responseXML; |
+ }, |
+ |
+ textHandler: function(xhr) { |
+ return xhr.responseText; |
+ }, |
+ |
+ jsonHandler: function(xhr) { |
+ var r = xhr.responseText; |
+ try { |
+ return JSON.parse(r); |
+ } catch (x) { |
+ return r; |
+ } |
+ }, |
+ |
+ documentHandler: function(xhr) { |
+ return xhr.response; |
+ }, |
+ |
+ blobHandler: function(xhr) { |
+ return xhr.response; |
+ }, |
+ |
+ arraybufferHandler: function(xhr) { |
+ return xhr.response; |
+ }, |
+ |
+ urlChanged: function() { |
+ if (!this.handleAs) { |
+ var ext = String(this.url).split('.').pop(); |
+ switch (ext) { |
+ case 'json': |
+ this.handleAs = 'json'; |
+ break; |
+ } |
+ } |
+ this.autoGo(); |
+ }, |
+ |
+ paramsChanged: function() { |
+ this.autoGo(); |
+ }, |
+ |
+ autoChanged: function() { |
+ this.autoGo(); |
+ }, |
+ |
+ // TODO(sorvell): multiple side-effects could call autoGo |
+ // during one micro-task, use a job to have only one action |
+ // occur |
+ autoGo: function() { |
+ if (this.auto) { |
+ this.goJob = this.job(this.goJob, this.go, 0); |
+ } |
+ }, |
+ |
+ /** |
+ * Performs an Ajax request to the specified URL. |
+ * |
+ * @method go |
+ */ |
+ go: function() { |
+ var args = this.xhrArgs || {}; |
+ // TODO(sjmiles): we may want XHR to default to POST if body is set |
+ args.body = this.body || args.body; |
+ args.params = this.params || args.params; |
+ if (args.params && typeof(args.params) == 'string') { |
+ args.params = JSON.parse(args.params); |
+ } |
+ args.headers = this.headers || args.headers || {}; |
+ if (args.headers && typeof(args.headers) == 'string') { |
+ args.headers = JSON.parse(args.headers); |
+ } |
+ if (this.contentType) { |
+ args.headers['content-type'] = this.contentType; |
+ } |
+ if (this.handleAs === 'arraybuffer' || this.handleAs === 'blob' || |
+ this.handleAs === 'document') { |
+ args.responseType = this.handleAs; |
+ } |
+ args.withCredentials = this.withCredentials; |
+ args.callback = this.receive.bind(this); |
+ args.url = this.url; |
+ args.method = this.method; |
+ return args.url && this.xhr.request(args); |
+ } |
+ |
+ }); |
+ |
+; |
+ |
+ |
+ Polymer('context-free-parser', { |
+ |
+ text: null, |
+ |
+ textChanged: function() { |
+ if (this.text) { |
+ var entities = ContextFreeParser.parse(this.text); |
+ if (!entities || entities.length === 0) { |
+ entities = [ |
+ {name: this.url.split('/').pop(), description: '**Undocumented**'} |
+ ]; |
+ } |
+ this.data = { classes: entities }; |
+ } |
+ }, |
+ |
+ dataChanged: function() { |
+ this.fire('data-ready'); |
+ } |
+ |
+ }); |
+ |
+; |
+ |
+ |
+ Polymer('core-doc-page', { |
+ |
+ hilight: function(event, detail, sender) { |
+ detail.code = prettyPrintOne((detail.code || '').replace(/</g,'<').replace(/>/g,'>')); |
+ }, |
+ |
+ homepageFilter: function(data) { |
+ if (!data) { |
+ return ''; |
+ } |
+ if (!data.homepage || data.homepage === 'github.io') { |
+ return '//polymer.github.io/' + data.name; |
+ } else { |
+ return data.homepage; |
+ } |
+ } |
+ |
+ }); |
+ |
+ ; |
+ |
+ Polymer('core-selection', { |
+ /** |
+ * If true, multiple selections are allowed. |
+ * |
+ * @attribute multi |
+ * @type boolean |
+ * @default false |
+ */ |
+ multi: false, |
+ ready: function() { |
+ this.clear(); |
+ }, |
+ clear: function() { |
+ this.selection = []; |
+ }, |
+ /** |
+ * Retrieves the selected item(s). |
+ * @method getSelection |
+ * @returns Returns the selected item(s). If the multi property is true, |
+ * getSelection will return an array, otherwise it will return |
+ * the selected item or undefined if there is no selection. |
+ */ |
+ getSelection: function() { |
+ return this.multi ? this.selection : this.selection[0]; |
+ }, |
+ /** |
+ * Indicates if a given item is selected. |
+ * @method isSelected |
+ * @param {any} item The item whose selection state should be checked. |
+ * @returns Returns true if `item` is selected. |
+ */ |
+ isSelected: function(item) { |
+ return this.selection.indexOf(item) >= 0; |
+ }, |
+ setItemSelected: function(item, isSelected) { |
+ if (item !== undefined && item !== null) { |
+ if (isSelected) { |
+ this.selection.push(item); |
+ } else { |
+ var i = this.selection.indexOf(item); |
+ if (i >= 0) { |
+ this.selection.splice(i, 1); |
+ } |
+ } |
+ this.fire("core-select", {isSelected: isSelected, item: item}); |
+ } |
+ }, |
+ /** |
+ * Set the selection state for a given `item`. If the multi property |
+ * is true, then the selected state of `item` will be toggled; otherwise |
+ * the `item` will be selected. |
+ * @method select |
+ * @param {any} item: The item to select. |
+ */ |
+ select: function(item) { |
+ if (this.multi) { |
+ this.toggle(item); |
+ } else if (this.getSelection() !== item) { |
+ this.setItemSelected(this.getSelection(), false); |
+ this.setItemSelected(item, true); |
+ } |
+ }, |
+ /** |
+ * Toggles the selection state for `item`. |
+ * @method toggle |
+ * @param {any} item: The item to toggle. |
+ */ |
+ toggle: function(item) { |
+ this.setItemSelected(item, !this.isSelected(item)); |
+ } |
+ }); |
+ ; |
+ |
+ |
+ Polymer('core-selector', { |
+ |
+ /** |
+ * Gets or sets the selected element. Default to use the index |
+ * of the item element. |
+ * |
+ * If you want a specific attribute value of the element to be |
+ * used instead of index, set "valueattr" to that attribute name. |
+ * |
+ * Example: |
+ * |
+ * <core-selector valueattr="label" selected="foo"> |
+ * <div label="foo"></div> |
+ * <div label="bar"></div> |
+ * <div label="zot"></div> |
+ * </core-selector> |
+ * |
+ * In multi-selection this should be an array of values. |
+ * |
+ * Example: |
+ * |
+ * <core-selector id="selector" valueattr="label" multi> |
+ * <div label="foo"></div> |
+ * <div label="bar"></div> |
+ * <div label="zot"></div> |
+ * </core-selector> |
+ * |
+ * this.$.selector.selected = ['foo', 'zot']; |
+ * |
+ * @attribute selected |
+ * @type Object |
+ * @default null |
+ */ |
+ selected: null, |
+ |
+ /** |
+ * If true, multiple selections are allowed. |
+ * |
+ * @attribute multi |
+ * @type boolean |
+ * @default false |
+ */ |
+ multi: false, |
+ |
+ /** |
+ * Specifies the attribute to be used for "selected" attribute. |
+ * |
+ * @attribute valueattr |
+ * @type string |
+ * @default 'name' |
+ */ |
+ valueattr: 'name', |
+ |
+ /** |
+ * Specifies the CSS class to be used to add to the selected element. |
+ * |
+ * @attribute selectedClass |
+ * @type string |
+ * @default 'core-selected' |
+ */ |
+ selectedClass: 'core-selected', |
+ |
+ /** |
+ * Specifies the property to be used to set on the selected element |
+ * to indicate its active state. |
+ * |
+ * @attribute selectedProperty |
+ * @type string |
+ * @default '' |
+ */ |
+ selectedProperty: '', |
+ |
+ /** |
+ * Specifies the attribute to set on the selected element to indicate |
+ * its active state. |
+ * |
+ * @attribute selectedAttribute |
+ * @type string |
+ * @default 'active' |
+ */ |
+ selectedAttribute: 'active', |
+ |
+ /** |
+ * Returns the currently selected element. In multi-selection this returns |
+ * an array of selected elements. |
+ * |
+ * @attribute selectedItem |
+ * @type Object |
+ * @default null |
+ */ |
+ selectedItem: null, |
+ |
+ /** |
+ * In single selection, this returns the model associated with the |
+ * selected element. |
+ * |
+ * @attribute selectedModel |
+ * @type Object |
+ * @default null |
+ */ |
+ selectedModel: null, |
+ |
+ /** |
+ * In single selection, this returns the selected index. |
+ * |
+ * @attribute selectedIndex |
+ * @type number |
+ * @default -1 |
+ */ |
+ selectedIndex: -1, |
+ |
+ /** |
+ * The target element that contains items. If this is not set |
+ * core-selector is the container. |
+ * |
+ * @attribute target |
+ * @type Object |
+ * @default null |
+ */ |
+ target: null, |
+ |
+ /** |
+ * This can be used to query nodes from the target node to be used for |
+ * selection items. Note this only works if the 'target' property is set. |
+ * |
+ * Example: |
+ * |
+ * <core-selector target="{{$.myForm}}" itemsSelector="input[type=radio]"></core-selector> |
+ * <form id="myForm"> |
+ * <label><input type="radio" name="color" value="red"> Red</label> <br> |
+ * <label><input type="radio" name="color" value="green"> Green</label> <br> |
+ * <label><input type="radio" name="color" value="blue"> Blue</label> <br> |
+ * <p>color = {{color}}</p> |
+ * </form> |
+ * |
+ * @attribute itemSelector |
+ * @type string |
+ * @default '' |
+ */ |
+ itemsSelector: '', |
+ |
+ /** |
+ * The event that would be fired from the item element to indicate |
+ * it is being selected. |
+ * |
+ * @attribute activateEvent |
+ * @type string |
+ * @default 'tap' |
+ */ |
+ activateEvent: 'tap', |
+ |
+ /** |
+ * Set this to true to disallow changing the selection via the |
+ * `activateEvent`. |
+ * |
+ * @attribute notap |
+ * @type boolean |
+ * @default false |
+ */ |
+ notap: false, |
+ |
+ ready: function() { |
+ this.activateListener = this.activateHandler.bind(this); |
+ this.observer = new MutationObserver(this.updateSelected.bind(this)); |
+ if (!this.target) { |
+ this.target = this; |
+ } |
+ }, |
+ |
+ get items() { |
+ if (!this.target) { |
+ return []; |
+ } |
+ var nodes = this.target !== this ? (this.itemsSelector ? |
+ this.target.querySelectorAll(this.itemsSelector) : |
+ this.target.children) : this.$.items.getDistributedNodes(); |
+ return Array.prototype.filter.call(nodes || [], function(n) { |
+ return n && n.localName !== 'template'; |
+ }); |
+ }, |
+ |
+ targetChanged: function(old) { |
+ if (old) { |
+ this.removeListener(old); |
+ this.observer.disconnect(); |
+ this.clearSelection(); |
+ } |
+ if (this.target) { |
+ this.addListener(this.target); |
+ this.observer.observe(this.target, {childList: true}); |
+ this.updateSelected(); |
+ } |
+ }, |
+ |
+ addListener: function(node) { |
+ node.addEventListener(this.activateEvent, this.activateListener); |
+ }, |
+ |
+ removeListener: function(node) { |
+ node.removeEventListener(this.activateEvent, this.activateListener); |
+ }, |
+ |
+ get selection() { |
+ return this.$.selection.getSelection(); |
+ }, |
+ |
+ selectedChanged: function() { |
+ this.updateSelected(); |
+ }, |
+ |
+ updateSelected: function() { |
+ this.validateSelected(); |
+ if (this.multi) { |
+ this.clearSelection(); |
+ this.selected && this.selected.forEach(function(s) { |
+ this.valueToSelection(s); |
+ }, this); |
+ } else { |
+ this.valueToSelection(this.selected); |
+ } |
+ }, |
+ |
+ validateSelected: function() { |
+ // convert to an array for multi-selection |
+ if (this.multi && !Array.isArray(this.selected) && |
+ this.selected !== null && this.selected !== undefined) { |
+ this.selected = [this.selected]; |
+ } |
+ }, |
+ |
+ clearSelection: function() { |
+ if (this.multi) { |
+ this.selection.slice().forEach(function(s) { |
+ this.$.selection.setItemSelected(s, false); |
+ }, this); |
+ } else { |
+ this.$.selection.setItemSelected(this.selection, false); |
+ } |
+ this.selectedItem = null; |
+ this.$.selection.clear(); |
+ }, |
+ |
+ valueToSelection: function(value) { |
+ var item = (value === null || value === undefined) ? |
+ null : this.items[this.valueToIndex(value)]; |
+ this.$.selection.select(item); |
+ }, |
+ |
+ updateSelectedItem: function() { |
+ this.selectedItem = this.selection; |
+ }, |
+ |
+ selectedItemChanged: function() { |
+ if (this.selectedItem) { |
+ var t = this.selectedItem.templateInstance; |
+ this.selectedModel = t ? t.model : undefined; |
+ } else { |
+ this.selectedModel = null; |
+ } |
+ this.selectedIndex = this.selectedItem ? |
+ parseInt(this.valueToIndex(this.selected)) : -1; |
+ }, |
+ |
+ valueToIndex: function(value) { |
+ // find an item with value == value and return it's index |
+ for (var i=0, items=this.items, c; (c=items[i]); i++) { |
+ if (this.valueForNode(c) == value) { |
+ return i; |
+ } |
+ } |
+ // if no item found, the value itself is probably the index |
+ return value; |
+ }, |
+ |
+ valueForNode: function(node) { |
+ return node[this.valueattr] || node.getAttribute(this.valueattr); |
+ }, |
+ |
+ // events fired from <core-selection> object |
+ selectionSelect: function(e, detail) { |
+ this.updateSelectedItem(); |
+ if (detail.item) { |
+ this.applySelection(detail.item, detail.isSelected); |
+ } |
+ }, |
+ |
+ applySelection: function(item, isSelected) { |
+ if (this.selectedClass) { |
+ item.classList.toggle(this.selectedClass, isSelected); |
+ } |
+ if (this.selectedProperty) { |
+ item[this.selectedProperty] = isSelected; |
+ } |
+ if (this.selectedAttribute && item.setAttribute) { |
+ if (isSelected) { |
+ item.setAttribute(this.selectedAttribute, ''); |
+ } else { |
+ item.removeAttribute(this.selectedAttribute); |
+ } |
+ } |
+ }, |
+ |
+ // event fired from host |
+ activateHandler: function(e) { |
+ if (!this.notap) { |
+ var i = this.findDistributedTarget(e.target, this.items); |
+ if (i >= 0) { |
+ var item = this.items[i]; |
+ var s = this.valueForNode(item) || i; |
+ if (this.multi) { |
+ if (this.selected) { |
+ this.addRemoveSelected(s); |
+ } else { |
+ this.selected = [s]; |
+ } |
+ } else { |
+ this.selected = s; |
+ } |
+ this.asyncFire('core-activate', {item: item}); |
+ } |
+ } |
+ }, |
+ |
+ addRemoveSelected: function(value) { |
+ var i = this.selected.indexOf(value); |
+ if (i >= 0) { |
+ this.selected.splice(i, 1); |
+ } else { |
+ this.selected.push(value); |
+ } |
+ this.valueToSelection(value); |
+ }, |
+ |
+ findDistributedTarget: function(target, nodes) { |
+ // find first ancestor of target (including itself) that |
+ // is in nodes, if any |
+ while (target && target != this) { |
+ var i = Array.prototype.indexOf.call(nodes, target); |
+ if (i >= 0) { |
+ return i; |
+ } |
+ target = target.parentNode; |
+ } |
+ } |
+ }); |
+ ; |
+ |
+ Polymer('core-menu',{}); |
+; |
+ |
+ |
+ Polymer('core-item', { |
+ |
+ /** |
+ * The URL of an image for the icon. |
+ * |
+ * @attribute src |
+ * @type string |
+ * @default '' |
+ */ |
+ |
+ /** |
+ * Specifies the icon from the Polymer icon set. |
+ * |
+ * @attribute icon |
+ * @type string |
+ * @default '' |
+ */ |
+ |
+ /** |
+ * Specifies the label for the menu item. |
+ * |
+ * @attribute label |
+ * @type string |
+ * @default '' |
+ */ |
+ |
+ }); |
+ |
+; |
+ |
+ |
+ Polymer('core-doc-toc', { |
+ |
+ searchAction: function() { |
+ this.$.searchBar.style.opacity = 1; |
+ this.$.searchBar.style.display = ''; |
+ }, |
+ |
+ closeSearchAction: function() { |
+ this.$.searchBar.style.opacity = 0; |
+ this.$.searchBar.style.display = 'none'; |
+ } |
+ |
+ }); |
+ |
+ ; |
+ |
+ |
+ Polymer('core-doc-viewer', { |
+ /** |
+ * A single file to parse for docs |
+ * |
+ * @attribute url |
+ * @type String |
+ * @default '' |
+ */ |
+ |
+ /** |
+ * Class documentation extracted from the parser |
+ * |
+ * @property classes |
+ * @type Array |
+ * @default [] |
+ */ |
+ classes: [], |
+ |
+ /** |
+ * Files to parse for docs |
+ * |
+ * @attribute sources |
+ * @type Array |
+ * @default [] |
+ */ |
+ sources: [], |
+ |
+ ready: function() { |
+ window.addEventListener('hashchange', this.parseLocationHash.bind(this)); |
+ this.parseLocationHash(); |
+ }, |
+ |
+ parseLocationHash: function() { |
+ this.route = window.location.hash.slice(1); |
+ }, |
+ |
+ routeChanged: function() { |
+ this.validateRoute(); |
+ }, |
+ |
+ validateRoute: function() { |
+ if (this.route) { |
+ this.classes.some(function(c) { |
+ if (c.name === this.route) { |
+ this.data = c; |
+ this.route = ''; |
+ return; |
+ } |
+ }, this); |
+ } |
+ }, |
+ |
+ selectedChanged: function() { |
+ this.data = this.classes[this.selected]; |
+ }, |
+ |
+ parserDataReady: function(event) { |
+ this.assimilateData(event.target.data); |
+ }, |
+ |
+ assimilateData: function(data) { |
+ this.classes = this.classes.concat(data.classes); |
+ this.classes.sort(function(a, b) { |
+ var na = a && a.name.toLowerCase(), nb = b && b.name.toLowerCase(); |
+ return (na < nb) ? -1 : (na == nb) ? 0 : 1; |
+ }); |
+ if (!this.data && !this.route && this.classes.length) { |
+ this.data = this.classes[0]; |
+ } |
+ if (this.classes.length > 1) { |
+ this.$.toc.style.display = 'block'; |
+ } |
+ this.validateRoute(); |
+ } |
+ |
+ }); |
+ |
+ ; |
+ |
+ |
+ Polymer('core-component-page', { |
+ |
+ moduleName: '', |
+ // TODO(sjmiles): needed this to force Object type for deserialization |
+ sources: [], |
+ |
+ ready: function() { |
+ this.moduleName = this.moduleName || this.findModuleName(); |
+ }, |
+ |
+ moduleNameChanged: function() { |
+ document.title = this.moduleName; |
+ this.url = !this.sources.length && this.moduleName ? this.moduleName + '.html' : ''; |
+ }, |
+ |
+ findModuleName: function() { |
+ var path = location.pathname.split('/'); |
+ var name = path.pop() || path.pop(); |
+ if (name.indexOf('.html') >= 0) { |
+ name = path.pop(); |
+ } |
+ return name || ''; |
+ } |
+ |
+ }); |
+ |
+ |