1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @constructor 7 * @extends {WebInspector.View} 8 */ 9WebInspector.DocumentationView = function() 10{ 11 WebInspector.View.call(this); 12 this.element.classList.add("documentation-view"); 13 this.registerRequiredCSS("documentationView.css"); 14} 15 16/** 17 * @param {string} url 18 * @param {string} searchItem 19 */ 20WebInspector.DocumentationView.showDocumentationURL = function(url, searchItem) 21{ 22 if (!WebInspector.DocumentationView._view) 23 WebInspector.DocumentationView._view = new WebInspector.DocumentationView(); 24 var view = WebInspector.DocumentationView._view; 25 view.element.removeChildren(); 26 WebInspector.inspectorView.showCloseableViewInDrawer("documentation", WebInspector.UIString("Documentation"), view); 27 view.showDocumentation(url, searchItem); 28} 29 30WebInspector.DocumentationView._languageToMimeType = { 31 "javascript": "text/javascript", 32 "html": "text/html" 33}; 34 35WebInspector.DocumentationView.prototype = { 36 /** 37 * @param {string} url 38 * @param {string} searchItem 39 */ 40 showDocumentation: function(url, searchItem) 41 { 42 if (!url) { 43 this._createEmptyPage(); 44 return; 45 } 46 loadXHR(url) 47 .then(this._createArticle.bind(this, searchItem), this._createEmptyPage.bind(this)) 48 .catch(this._createEmptyPage.bind(this)); 49 }, 50 51 /** 52 * @param {string} searchItem 53 * @param {string} responseText 54 */ 55 _createArticle: function(searchItem, responseText) 56 { 57 var json = JSON.parse(responseText); 58 var pages = json["query"]["pages"]; 59 var wikiKeys = Object.keys(pages); 60 if (wikiKeys.length === 1 && wikiKeys[0] === "-1") { 61 this._createEmptyPage(); 62 return; 63 } 64 var wikiMarkupText = pages[wikiKeys[0]]["revisions"]["0"]["*"]; 65 var article; 66 try { 67 article = WebInspector.JSArticle.parse(wikiMarkupText); 68 } catch (error) { 69 console.error("Article could not be parsed. " + error.message); 70 } 71 if (!article) { 72 this._createEmptyPage(); 73 return; 74 } 75 76 this.element.removeChildren(); 77 var renderer = new WebInspector.DocumentationView.Renderer(article, searchItem); 78 this.element.appendChild(renderer.renderJSArticle()); 79 }, 80 81 _createEmptyPage: function() 82 { 83 this.element.removeChildren(); 84 var emptyPage = this.element.createChild("div", "documentation-empty-page fill"); 85 var pageTitle = emptyPage.createChild("div", "documentation-not-found"); 86 pageTitle.textContent = WebInspector.UIString("No documentation found."); 87 emptyPage.createChild("div", "documentation-empty-page-align"); 88 }, 89 90 __proto__: WebInspector.View.prototype 91} 92 93/** 94 * @constructor 95 * @param {!WebInspector.JSArticle} article 96 * @param {string} searchItem 97 */ 98WebInspector.DocumentationView.Renderer = function(article, searchItem) 99{ 100 this._searchItem = searchItem; 101 this._element = document.createElement("div"); 102 this._article = article; 103} 104 105WebInspector.DocumentationView.Renderer.prototype = { 106 /** 107 * @return {!Element} 108 */ 109 renderJSArticle: function() 110 { 111 this._element.appendChild(this._createPageTitle(this._article.pageTitle, this._searchItem)); 112 var signatureElement = this._createSignatureSection(this._article.parameters, this._article.methods); 113 if (signatureElement) 114 this._element.appendChild(signatureElement); 115 116 var descriptionElement = this._element.createChild("div", "documentation-description"); 117 var summarySection = this._article.summary ? this._renderBlock(this._article.summary) : null; 118 if (summarySection) 119 descriptionElement.appendChild(summarySection); 120 var parametersSection = this._createParametersSection(this._article.parameters); 121 if (parametersSection) 122 descriptionElement.appendChild(parametersSection); 123 124 var examplesSection = this._createExamplesSection(this._article.examples); 125 if (examplesSection) { 126 var examplesTitle = this._element.createChild("div", "documentation-title"); 127 examplesTitle.textContent = WebInspector.UIString("Examples"); 128 descriptionElement = this._element.createChild("div", "documentation-description"); 129 descriptionElement.appendChild(examplesSection); 130 } 131 132 var remarksSection = this._article.remarks ? this._renderBlock(this._article.remarks) : null; 133 if (remarksSection) { 134 var remarksTitle = this._element.createChild("div", "documentation-title"); 135 remarksTitle.textContent = WebInspector.UIString("Remarks"); 136 descriptionElement = this._element.createChild("div", "documentation-description"); 137 descriptionElement.appendChild(remarksSection); 138 } 139 return this._element; 140 }, 141 142 /** 143 * @param {string} titleText 144 * @param {string} searchItem 145 * @return {!Element} 146 */ 147 _createPageTitle: function(titleText, searchItem) 148 { 149 var pageTitle = document.createElementWithClass("div", "documentation-page-title"); 150 if (titleText) 151 pageTitle.textContent = titleText; 152 else if (searchItem) 153 pageTitle.textContent = searchItem; 154 return pageTitle; 155 }, 156 157 /** 158 * @param {!Array.<!WebInspector.JSArticle.Parameter>} parameters 159 * @param {?WebInspector.JSArticle.Method} method 160 * @return {?Element} 161 */ 162 _createSignatureSection: function(parameters, method) 163 { 164 if (!parameters.length && !method) 165 return null; 166 var signature = document.createElementWithClass("div", "documentation-method-signature monospace"); 167 if (method && method.returnValueName) { 168 var returnTypeElement = signature.createChild("span", "documentation-parameter-data-type-value"); 169 returnTypeElement.textContent = method.returnValueName; 170 } 171 var methodName = signature.createChild("span", "documentation-method-name"); 172 methodName.textContent = this._searchItem.split(".").peekLast() + "("; 173 for (var i = 0; i < parameters.length; ++i) { 174 if (i > 0) 175 signature.createTextChild(",") 176 var parameterType = signature.createChild("span", "documentation-parameter-data-type-value"); 177 parameterType.textContent = parameters[i].dataType; 178 var parameterName = signature.createChild("span", "documentation-parameter-name"); 179 parameterName.textContent = parameters[i].name; 180 } 181 182 signature.createTextChild(")"); 183 return signature; 184 }, 185 186 /** 187 * @param {!Array.<!WebInspector.JSArticle.Parameter>} parameters 188 * @return {?Element} 189 */ 190 _createParametersSection: function(parameters) 191 { 192 if (!parameters.length) 193 return null; 194 var table = document.createElementWithClass("table", "documentation-table"); 195 var tableBody = table.createChild("tbody"); 196 var headerRow = tableBody.createChild("tr", "documentation-table-row"); 197 var tableHeader = headerRow.createChild("th", "documentation-table-header"); 198 tableHeader.textContent = WebInspector.UIString("Parameters"); 199 tableHeader.colSpan = 3; 200 for (var i = 0; i < parameters.length; ++i) { 201 var tableRow = tableBody.createChild("tr", "documentation-table-row"); 202 var type = tableRow.createChild("td", "documentation-table-cell"); 203 type.textContent = parameters[i].dataType; 204 var name = tableRow.createChild("td", "documentation-table-cell"); 205 name.textContent = parameters[i].optional ? WebInspector.UIString("(optional)\n") : ""; 206 name.textContent += parameters[i].name; 207 var description = tableRow.createChild("td", "documentation-table-cell"); 208 if (parameters[i].description) 209 description.appendChild(this._renderBlock(/** @type {!WebInspector.WikiParser.Block} */(parameters[i].description))); 210 } 211 return table; 212 }, 213 214 /** 215 * @param {!Array.<!WebInspector.JSArticle.Example>} examples 216 */ 217 _createExamplesSection: function(examples) 218 { 219 if (!examples.length) 220 return; 221 222 var section = document.createElementWithClass("div", "documentation-section"); 223 224 for (var i = 0; i < examples.length; ++i) { 225 var example = section.createChild("div", "documentation-example"); 226 var exampleDescription = example.createChild("div", "documentation-example-description-section"); 227 if (examples[i].description) { 228 var description = this._renderBlock(/** @type {!WebInspector.WikiParser.Block} */(examples[i].description)); 229 description.classList.add("documentation-text"); 230 exampleDescription.appendChild(description); 231 } 232 var code = example.createChild("div", "documentation-code source-code"); 233 code.textContent = examples[i].code; 234 if (!examples[i].language) 235 continue; 236 var syntaxHighlighter = new WebInspector.DOMSyntaxHighlighter(WebInspector.DocumentationView._languageToMimeType[examples[i].language.toLowerCase()], true); 237 syntaxHighlighter.syntaxHighlightNode(code); 238 } 239 return section; 240 }, 241 242 /** 243 * @param {!WebInspector.WikiParser.ArticleElement} article 244 * @return {!Element} 245 */ 246 _renderBlock: function(article) 247 { 248 var element; 249 var elementTypes = WebInspector.WikiParser.ArticleElement.Type; 250 251 switch (article.type()) { 252 case elementTypes.Inline: 253 element = document.createElement("span"); 254 break; 255 case elementTypes.Link: 256 element = document.createElementWithClass("a", "documentation-link"); 257 element.href = article.url(); 258 if (!article.children().length) 259 element.textContent = article.url(); 260 break; 261 case elementTypes.Code: 262 element = document.createElementWithClass("span", "documentation-code-tag"); 263 break; 264 case elementTypes.CodeBlock: 265 element = document.createElementWithClass("pre", "documentation-code source-code"); 266 element.textContent = article.code(); 267 break; 268 case elementTypes.PlainText: 269 element = document.createElement("span"); 270 element.textContent = article.text(); 271 if (article.isHighlighted()) 272 element.classList.add("documentation-highlighted-text"); 273 break; 274 case elementTypes.Block: 275 element = document.createElement(article.hasBullet() ? "li" : "div"); 276 if (!article.hasBullet()) 277 element.classList.add("documentation-paragraph"); 278 break; 279 case elementTypes.Table: 280 return this._renderTable(/** @type {!WebInspector.WikiParser.Table} */(article)); 281 default: 282 throw new Error("Unknown ArticleElement type " + article.type()); 283 } 284 285 if (article.type() === WebInspector.WikiParser.ArticleElement.Type.Block 286 || article.type() === WebInspector.WikiParser.ArticleElement.Type.Code 287 || article.type() === WebInspector.WikiParser.ArticleElement.Type.Inline) { 288 for (var i = 0; i < article.children().length; ++i) { 289 var child = this._renderBlock(article.children()[i]); 290 if (child) 291 element.appendChild(child); 292 } 293 } 294 295 return element; 296 }, 297 298 /** 299 * @param {!WebInspector.WikiParser.Table} table 300 * @return {!Element} 301 */ 302 _renderTable: function(table) 303 { 304 var tableElement = document.createElementWithClass("table", "documentation-table"); 305 var tableBody = tableElement.createChild("tbody"); 306 var headerRow = tableBody.createChild("tr", "documentation-table-row"); 307 for (var i = 0; i < table.columnNames().length; ++i) { 308 var tableHeader = headerRow.createChild("th", "documentation-table-header"); 309 tableHeader.appendChild(this._renderBlock(table.columnNames()[i])); 310 } 311 for (var i = 0; i < table.rows().length; ++i) { 312 var tableRow = tableBody.createChild("tr", "documentation-table-row"); 313 var row = table.rows()[i]; 314 for (var j = 0; j < row.length; ++j) { 315 var cell = tableRow.createChild("td", "documentation-table-cell"); 316 cell.appendChild(this._renderBlock(row[j])); 317 } 318 } 319 320 return tableElement; 321 } 322} 323 324/** 325 * @constructor 326 * @implements {WebInspector.ContextMenu.Provider} 327 */ 328WebInspector.DocumentationView.ContextMenuProvider = function() 329{ 330} 331 332WebInspector.DocumentationView.ContextMenuProvider.prototype = { 333 /** 334 * @param {!Event} event 335 * @param {!WebInspector.ContextMenu} contextMenu 336 * @param {!Object} target 337 */ 338 appendApplicableItems: function(event, contextMenu, target) 339 { 340 if (!(target instanceof WebInspector.CodeMirrorTextEditor)) 341 return; 342 WebInspector.DocumentationCatalog.instance().startLoadingIfNeeded(); 343 if (WebInspector.DocumentationCatalog.instance().isLoading()) { 344 var itemName = WebInspector.useLowerCaseMenuTitles() ? "Loading documentation..." : "Loading Documentation..."; 345 contextMenu.appendItem(itemName, function() {}, true); 346 return; 347 } 348 var textEditor = /** @type {!WebInspector.CodeMirrorTextEditor} */ (target); 349 var descriptors = this._determineDescriptors(textEditor); 350 if (!descriptors.length) 351 return; 352 if (descriptors.length === 1) { 353 var formatString = WebInspector.useLowerCaseMenuTitles() ? "Show documentation for %s.%s" : "Show Documentation for %s.%s"; 354 var methodName = String.sprintf("%s.%s", descriptors[0].name(), descriptors[0].searchItem()); 355 contextMenu.appendItem(WebInspector.UIString(formatString, descriptors[0].name(), descriptors[0].searchItem()), WebInspector.DocumentationView.showDocumentationURL.bind(null, descriptors[0].url(), methodName)); 356 return; 357 } 358 var subMenuItem = contextMenu.appendSubMenuItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Show documentation for..." : "Show Documentation for...")); 359 for (var i = 0; i < descriptors.length; ++i) { 360 var methodName = String.sprintf("%s.%s", descriptors[i].name(), descriptors[i].searchItem()); 361 subMenuItem.appendItem(methodName, WebInspector.DocumentationView.showDocumentationURL.bind(null, descriptors[i].url(), methodName)); 362 } 363 }, 364 365 /** 366 * @param {!WebInspector.CodeMirrorTextEditor} textEditor 367 * @return {!Array.<!WebInspector.DocumentationCatalog.ItemDescriptor>} 368 */ 369 _determineDescriptors: function(textEditor) 370 { 371 var catalog = WebInspector.DocumentationCatalog.instance(); 372 var textSelection = textEditor.selection().normalize(); 373 var previousTokenText = findPreviousToken(textSelection); 374 375 if (!textSelection.isEmpty()) { 376 if (textSelection.startLine !== textSelection.endLine) 377 return []; 378 return computeDescriptors(textSelection); 379 } 380 381 var descriptors = computeDescriptors(getTokenRangeByColumn(textSelection.startColumn)); 382 if (descriptors.length) 383 return descriptors; 384 385 return computeDescriptors(getTokenRangeByColumn(textSelection.startColumn - 1)); 386 387 /** 388 * @param {number} column 389 * @return {?WebInspector.TextRange} 390 */ 391 function getTokenRangeByColumn(column) 392 { 393 var token = textEditor.tokenAtTextPosition(textSelection.startLine, column); 394 if (!token) 395 return null; 396 return new WebInspector.TextRange(textSelection.startLine, token.startColumn, textSelection.startLine, token.endColumn); 397 } 398 399 /** 400 * @param {?WebInspector.TextRange} textRange 401 * @return {!Array.<!WebInspector.DocumentationCatalog.ItemDescriptor>} 402 */ 403 function computeDescriptors(textRange) 404 { 405 if (!textRange) 406 return []; 407 var propertyName = textEditor.copyRange(textRange); 408 var descriptors = catalog.itemDescriptors(propertyName); 409 if (descriptors.length) 410 return descriptors; 411 if (propertyName.toUpperCase() !== propertyName || !previousTokenText || !window[previousTokenText] || !window[previousTokenText][propertyName]) 412 return []; 413 return catalog.constantDescriptors(previousTokenText); 414 } 415 416 /** 417 * @param {!WebInspector.TextRange} textRange 418 * @return {?string} 419 */ 420 function findPreviousToken(textRange) 421 { 422 var line = textEditor.line(textRange.startLine); 423 if (textRange.startColumn < 3 || line[textRange.startColumn - 1] !== ".") 424 return null; 425 var token = textEditor.tokenAtTextPosition(textRange.startLine, textRange.startColumn - 2); 426 return token ? line.substring(token.startColumn, token.endColumn) : null; 427 } 428 } 429}