OLD | NEW |
(Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 <link rel="import" href="../polymer/polymer.html"> |
| 11 <link rel="import" href="../hydrolysis/hydrolysis-analyzer.html"> |
| 12 <link rel="import" href="../iron-doc-viewer/iron-doc-viewer.html"> |
| 13 <link rel="import" href="../iron-icons/iron-icons.html"> |
| 14 <link rel="import" href="../iron-ajax/iron-ajax.html"> |
| 15 <link rel="import" href="../iron-selector/iron-selector.html"> |
| 16 <link rel="import" href="../paper-header-panel/paper-header-panel.html"> |
| 17 <link rel="import" href="../paper-toolbar/paper-toolbar.html"> |
| 18 <link rel="import" href="../paper-styles/paper-styles.html"> |
| 19 |
| 20 <!-- |
| 21 Loads Polymer element and behavior documentation using |
| 22 [Hydrolysis](https://github.com/PolymerLabs/hydrolysis) and renders a complete |
| 23 documentation page including demos (if available). |
| 24 --> |
| 25 <dom-module id="iron-component-page"> |
| 26 <link rel="import" type="css" href="iron-component-page.css"> |
| 27 <template> |
| 28 <hydrolysis-analyzer id="analyzer" src="[[_srcUrl]]" transitive="[[transitiv
e]]" clean analyzer="{{_hydroDesc}}" loading="{{_hydroLoading}}"></hydrolysis-an
alyzer> |
| 29 <iron-ajax id="ajax" url="[[docSrc]]" handle-as="json" on-response="_handleA
jaxResponse"></iron-ajax> |
| 30 |
| 31 <paper-header-panel id="headerPanel" mode="waterfall"> |
| 32 <paper-toolbar catalog-hidden> |
| 33 <div class="flex"> |
| 34 <!-- TODO: Replace with paper-dropdown-menu when available --> |
| 35 <select id="active" value="{{active::change}}"> |
| 36 <template is="dom-repeat" items="[[docElements]]"> |
| 37 <option value="[[item.is]]">[[item.is]]</option> |
| 38 </template> |
| 39 <template is="dom-repeat" items="[[docBehaviors]]"> |
| 40 <option value="[[item.is]]">[[item.is]]</option> |
| 41 </template> |
| 42 </select> |
| 43 </div> |
| 44 <iron-selector attr-for-selected="view" selected="{{view}}" id="links" h
idden$="[[!docDemos.length]]"> |
| 45 <a view="docs"><iron-icon icon="description"></iron-icon> Docs</a> |
| 46 <a view="[[_demoView(docDemos.0.path)]]"><iron-icon icon="visibility">
</iron-icon> <span>Demo</span></a> |
| 47 </iron-selector> |
| 48 </paper-toolbar> |
| 49 <div id="content"> |
| 50 <iron-selector id="view" selected="[[_viewType(view)]]" attr-for-selecte
d="id"> |
| 51 <div id="docs"> |
| 52 <div id="catalog-heading" catalog-only> |
| 53 <h2><span>[[active]]</span> <span class="version" hidden$="[[!vers
ion]]">[[version]]</span></h2> |
| 54 </div> |
| 55 <iron-doc-viewer descriptor="{{_activeDescriptor}}" |
| 56 on-iron-doc-viewer-component-selected="_handleComponentSelectedEve
nt"></iron-doc-viewer> |
| 57 <div id="nodocs" hidden$="[[_activeDescriptor]]" class="layout fit h
orizontal center-center"> |
| 58 No documentation found. |
| 59 </div> |
| 60 </div> |
| 61 <iframe id="demo" src="[[_frameSrc(view, base)]]"></iframe> |
| 62 </iron-selector> |
| 63 </div> |
| 64 </paper-header-panel> |
| 65 </template> |
| 66 </dom-module> |
| 67 |
| 68 <script> |
| 69 (function() { |
| 70 // var hydrolysis = require('hydrolysis'); |
| 71 |
| 72 /** |
| 73 * @param {string} url |
| 74 * @return {string} `url` stripped of a file name, if one is present. This |
| 75 * considers URLs like "example.com/foo" to already be a base (no `.` is) |
| 76 * present in the final path part). |
| 77 */ |
| 78 function _baseUrl(url) { |
| 79 return url.match(/^(.*?)\/?([^\/]+\.[^\/]+)?$/)[1] + '/'; |
| 80 } |
| 81 |
| 82 Polymer({ |
| 83 is: 'iron-component-page', |
| 84 enableCustomStyleProperties: true, |
| 85 properties: { |
| 86 /** |
| 87 * The URL to an import that declares (or transitively imports) the |
| 88 * elements that you wish to see documented. |
| 89 * |
| 90 * If the URL is relative, it will be resolved relative to the master |
| 91 * document. |
| 92 * |
| 93 * If a `src` URL is not specified, it will resolve the name of the |
| 94 * directory containing this element, followed by `dirname.html`. For |
| 95 * example: |
| 96 * |
| 97 * `awesome-sauce/index.html`: |
| 98 * |
| 99 * <iron-doc-viewer></iron-doc-viewer> |
| 100 * |
| 101 * Would implicitly have `src="awesome-sauce.html"`. |
| 102 */ |
| 103 src: { |
| 104 type: String, |
| 105 observer: '_srcChanged', |
| 106 }, |
| 107 |
| 108 /** |
| 109 * The URL to a precompiled JSON descriptor. If you have precompiled |
| 110 * and stored a documentation set using Hydrolysis, you can load the |
| 111 * analyzer directly via AJAX by specifying this attribute. |
| 112 * |
| 113 * If a `doc-src` is not specified, it is ignored and the default |
| 114 * rules according to the `src` attribute are used. |
| 115 */ |
| 116 docSrc: { |
| 117 type: String, |
| 118 observer: '_srcChanged', |
| 119 }, |
| 120 |
| 121 /** |
| 122 * The relative root for determining paths to demos and default source |
| 123 * detection. |
| 124 */ |
| 125 base: { |
| 126 type: String, |
| 127 value: function() { |
| 128 return this.ownerDocument.baseURI; |
| 129 } |
| 130 }, |
| 131 |
| 132 /** |
| 133 * The element or behavior that will be displayed on the page. Defaults |
| 134 * to the element matching the name of the source file. |
| 135 */ |
| 136 active: { |
| 137 type: String, |
| 138 observer: '_activeChanged', |
| 139 notify: true |
| 140 }, |
| 141 |
| 142 /** |
| 143 * The current view. Can be `docs` or `demo`. |
| 144 */ |
| 145 view: { |
| 146 type: String, |
| 147 value: 'docs', |
| 148 notify: true |
| 149 }, |
| 150 |
| 151 /** |
| 152 * Whether _all_ dependencies should be loaded and documented. |
| 153 * |
| 154 * Turning this on will probably slow down the load process dramatically. |
| 155 */ |
| 156 transitive: { |
| 157 type: Boolean, |
| 158 value: false |
| 159 }, |
| 160 |
| 161 /** The Hydrolysis element descriptors that have been loaded. */ |
| 162 docElements: { |
| 163 type: Array, |
| 164 observer: '_descriptorsChanged', |
| 165 notify: true, |
| 166 readOnly: true |
| 167 }, |
| 168 |
| 169 /** The Hydrolysis behavior descriptors that have been loaded. */ |
| 170 docBehaviors: { |
| 171 type: Array, |
| 172 observer: '_descriptorsChanged', |
| 173 notify: true, |
| 174 readOnly: true |
| 175 }, |
| 176 |
| 177 /** |
| 178 * Demos for the currently selected element. |
| 179 */ |
| 180 docDemos: { |
| 181 type: Array, |
| 182 notify: true, |
| 183 readOnly: true |
| 184 }, |
| 185 |
| 186 /** |
| 187 * The currently displayed element. |
| 188 * |
| 189 * @type {!hydrolysis.ElementDescriptor} |
| 190 */ |
| 191 _activeDescriptor: Object, |
| 192 |
| 193 /** |
| 194 * Toggle flag to be used when this element is being displayed in the |
| 195 * Polymer Elements catalog. |
| 196 */ |
| 197 _catalog: { |
| 198 type: Boolean, |
| 199 value: false, |
| 200 reflectToAttribute: true |
| 201 }, |
| 202 /** |
| 203 * An optional version string. |
| 204 */ |
| 205 version: String, |
| 206 |
| 207 /** |
| 208 * The hydrolysis analyzer. |
| 209 * |
| 210 * @type {!hydrolysis.Analyzer} |
| 211 */ |
| 212 _analyzer: { |
| 213 type: Object, |
| 214 observer: '_analyzerChanged', |
| 215 }, |
| 216 _hydroDesc: { |
| 217 type: Object, |
| 218 observer: '_detectAnalyzer' |
| 219 }, |
| 220 _ajaxDesc: { |
| 221 type: Object, |
| 222 observer: '_detectAnalyzer' |
| 223 }, |
| 224 |
| 225 /** Whether the analyzer is loading source. */ |
| 226 _loading: { |
| 227 type: Boolean, |
| 228 observer: '_loadingChanged', |
| 229 }, |
| 230 _hydroLoading: { |
| 231 type: Boolean, |
| 232 observer: '_detectLoading' |
| 233 }, |
| 234 _ajaxLoading: { |
| 235 type: Boolean, |
| 236 observer: '_detectLoading' |
| 237 }, |
| 238 |
| 239 /** The complete URL to this component's demo. */ |
| 240 _demoUrl: { |
| 241 type: String, |
| 242 value: '', |
| 243 }, |
| 244 |
| 245 /** The complete URL to this component's source. */ |
| 246 _srcUrl: String, |
| 247 }, |
| 248 |
| 249 ready: function() { |
| 250 var elements = this._loadJson(); |
| 251 if (elements) { |
| 252 this.docElements = elements; |
| 253 this._loading = false; |
| 254 } else { |
| 255 // Make sure our change handlers trigger in all cases. |
| 256 if (!this.src && !this._catalog) { |
| 257 this._srcChanged(); |
| 258 } |
| 259 } |
| 260 }, |
| 261 |
| 262 /** |
| 263 * Loads an array of hydrolysis element descriptors (as JSON) from the text |
| 264 * content of this element, if present. |
| 265 * |
| 266 * @return {Array<hydrolysis.ElementDescriptor>} The descriptors, or `null`. |
| 267 */ |
| 268 _loadJson: function() { |
| 269 var textContent = ''; |
| 270 Array.prototype.forEach.call(Polymer.dom(this).childNodes, function(node)
{ |
| 271 textContent = textContent + node.textContent; |
| 272 }); |
| 273 textContent = textContent.trim(); |
| 274 if (textContent === '') return null; |
| 275 |
| 276 try { |
| 277 var json = JSON.parse(textContent); |
| 278 if (!Array.isArray(json)) return []; |
| 279 return json; |
| 280 } catch(error) { |
| 281 console.error('Failure when parsing JSON:', textContent, error); |
| 282 throw error; |
| 283 } |
| 284 }, |
| 285 |
| 286 _srcChanged: function() { |
| 287 var srcUrl; |
| 288 if (this.docSrc) { |
| 289 if (!this.$.ajax.lastRequest || (this.docSrc !== this.$.ajax.lastRequest
.url && this.docSrc !== this._lastDocSrc)) { |
| 290 this._ajaxLoading = true; |
| 291 this._ajaxDesc = null; |
| 292 this._activeDescriptor = null; |
| 293 this.$.ajax.generateRequest(); |
| 294 } |
| 295 this._lastDocSrc = this.docSrc; |
| 296 return; |
| 297 } else if (this.src) { |
| 298 srcUrl = new URL(this.src, this.base).toString(); |
| 299 } else { |
| 300 var base = _baseUrl(this.base); |
| 301 srcUrl = new URL(base.match(/([^\/]*)\/$/)[1] + ".html", base).toString(
); |
| 302 } |
| 303 |
| 304 // Rewrite gh-pages URLs to https://rawgit.com/ |
| 305 var match = srcUrl.match(/([^\/\.]+)\.github\.io\/([^\/]+)\/?([^\/]*)$/); |
| 306 if (match) { |
| 307 srcUrl = "https://cdn.rawgit.com/" + match[1] + "/" + match[2] + "/maste
r/" + match[3]; |
| 308 } |
| 309 |
| 310 this._baseUrl = _baseUrl(srcUrl); |
| 311 this._srcUrl = srcUrl; |
| 312 if (!this._hydroLoading) this.$.analyzer.analyze(); |
| 313 }, |
| 314 |
| 315 _frameSrc: function(view) { |
| 316 if (!view || view.indexOf("demo:") !== 0) return "about:blank"; |
| 317 var src = view.split(':')[1]; |
| 318 return new URL(src, this.base).toString(); |
| 319 }, |
| 320 |
| 321 _descriptorsChanged: function() { |
| 322 if (this._findDescriptor(this.active)) { |
| 323 this._activeChanged(); |
| 324 return; |
| 325 } |
| 326 |
| 327 if (this.docElements && this.docElements[0]) { |
| 328 this.active = this.docElements[0].is; |
| 329 } else if (this.docBehaviors && this.docBehaviors[0]) { |
| 330 this.active = this.docBehaviors[0].is; |
| 331 } else { |
| 332 this.active = null; |
| 333 } |
| 334 }, |
| 335 |
| 336 _findDescriptor: function(name) { |
| 337 if (!this._analyzer) return null; |
| 338 |
| 339 var descriptor = this._analyzer.elementsByTagName[name]; |
| 340 if (descriptor) return descriptor; |
| 341 |
| 342 for (var i = 0; i < this._analyzer.behaviors.length; i++) { |
| 343 if (this._analyzer.behaviors[i].is === name) { |
| 344 return this._analyzer.behaviors[i]; |
| 345 } |
| 346 } |
| 347 return null; |
| 348 }, |
| 349 |
| 350 _activeChanged: function() { |
| 351 this.async(function() { this.$.active.value = this.active; }); |
| 352 if (this._analyzer && this._analyzer.elementsByTagName) { |
| 353 this.$.headerPanel.scroller.scrollTop = 0; |
| 354 this._activeDescriptor = this._findDescriptor(this.active); |
| 355 if (this._activeDescriptor) { |
| 356 var hasDemo; |
| 357 var demos = this._activeDescriptor.demos; |
| 358 if (this.view && demos && demos.length) { |
| 359 var parts = this.view.split(':'); |
| 360 if (parts[0] == 'demo') { |
| 361 if (parts[1]) { |
| 362 hasDemo = demos.some(function(d, i) { |
| 363 if (d.path == parts[1]) { |
| 364 return true; |
| 365 } |
| 366 }); |
| 367 } |
| 368 if (!hasDemo) { |
| 369 this.view = 'demo:' + demos[0].path; |
| 370 hasDemo = true; |
| 371 } |
| 372 } |
| 373 } |
| 374 if (!hasDemo == undefined) { |
| 375 this.view = 'docs'; |
| 376 } |
| 377 if (this._activeDescriptor.is && !document.title) { |
| 378 document.title = this._activeDescriptor.is + " documentation"; |
| 379 } |
| 380 } |
| 381 this._setDocDemos(this._activeDescriptor ? this._activeDescriptor.demos
: []); |
| 382 } |
| 383 }, |
| 384 |
| 385 _loadingChanged: function() { |
| 386 this.toggleClass('loaded', !this._loading); |
| 387 }, |
| 388 _detectLoading: function() { |
| 389 this._loading = this.docSrc ? this._ajaxLoading : this._hydroLoading; |
| 390 }, |
| 391 |
| 392 _analyzerChanged: function() { |
| 393 this._setDocElements(this._analyzer ? this._analyzer.elements : []); |
| 394 this._setDocBehaviors(this._analyzer ? this._analyzer.behaviors : []); |
| 395 }, |
| 396 _detectAnalyzer: function() { |
| 397 this._analyzer = this.docSrc ? this._ajaxDesc : this._hydroDesc; |
| 398 }, |
| 399 |
| 400 _handleAjaxResponse: function(e, req) { |
| 401 this._ajaxLoading = false; |
| 402 this._ajaxLastUrl = req.url; |
| 403 this._ajaxDesc = req.response; |
| 404 }, |
| 405 |
| 406 _handleComponentSelectedEvent: function(ev) { |
| 407 var descriptor = this._findDescriptor(ev.detail); |
| 408 if (!descriptor) { |
| 409 console.warn("Could not navigate to ", ev.detail); |
| 410 } |
| 411 else { |
| 412 this.active = ev.detail; |
| 413 } |
| 414 }, |
| 415 |
| 416 /** |
| 417 * Renders this element into static HTML for offline use. |
| 418 * |
| 419 * This is mostly useful for debugging and one-off documentation generation. |
| 420 * If you want to integrate doc generation into your build process, you |
| 421 * probably want to be calling `hydrolysis.Analyzer.analyze()` directly. |
| 422 * |
| 423 * @return {string} The HTML for this element with all state baked in. |
| 424 */ |
| 425 marshal: function() { |
| 426 var jsonText = JSON.stringify(this.docElements || [], null, ' '); |
| 427 return '<' + this.is + '>\n' + |
| 428 jsonText.replace(/</g, '<').replace(/>/g, '>') + '\n' + |
| 429 '</' + this.is + '>'; |
| 430 }, |
| 431 |
| 432 _demoView: function(path) { |
| 433 return "demo:" + path; |
| 434 }, |
| 435 _viewType: function(view) { |
| 436 return view ? view.split(":")[0] : null; |
| 437 } |
| 438 }); |
| 439 })(); |
| 440 </script> |
OLD | NEW |