Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 <!DOCTYPE html> | 1 <!DOCTYPE html> |
| 2 <!-- | 2 <!-- |
| 3 Copyright 2016 The Chromium Authors. All rights reserved. | 3 Copyright 2016 The Chromium Authors. All rights reserved. |
| 4 Use of this source code is governed by a BSD-style license that can be | 4 Use of this source code is governed by a BSD-style license that can be |
| 5 found in the LICENSE file. | 5 found in the LICENSE file. |
| 6 --> | 6 --> |
| 7 | 7 |
| 8 <link rel="import" href="/components/iron-flex-layout/iron-flex-layout-classes.h tml"> | 8 <link rel="import" href="/components/iron-flex-layout/iron-flex-layout-classes.h tml"> |
| 9 <link rel="import" href="/components/paper-button/paper-button.html"> | 9 <link rel="import" href="/components/paper-button/paper-button.html"> |
| 10 <link rel="import" href="/components/paper-icon-button/paper-icon-button.html"> | 10 <link rel="import" href="/components/paper-icon-button/paper-icon-button.html"> |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 74 on-click="onAddButtonClicked" | 74 on-click="onAddButtonClicked" |
| 75 disabled$="{{!enableAddSeries}}">Add</paper-button> | 75 disabled$="{{!enableAddSeries}}">Add</paper-button> |
| 76 </div> | 76 </div> |
| 77 | 77 |
| 78 <div id="suite-description"></div> | 78 <div id="suite-description"></div> |
| 79 </template> | 79 </template> |
| 80 | 80 |
| 81 <script> | 81 <script> |
| 82 'use strict'; | 82 'use strict'; |
| 83 | 83 |
| 84 // TODO(eakuefner): generalize this request memoization pattern. See | |
| 85 // https://github.com/catapult-project/catapult/issues/3441. | |
| 86 tr.exportTo('d', function() { | |
| 87 class Subtests { | |
| 88 constructor() { | |
| 89 this.subtests_ = new Map(); | |
|
sullivan
2017/04/14 17:42:17
The naming here is still pretty confusing to me, a
eakuefner
2017/04/14 17:44:41
Done. I think that, once generalized, this will be
| |
| 90 } | |
| 91 | |
| 92 // TODO(eakuefner): implemement cancellation by wrapping | |
| 93 async getSubtestsForPath(path) { | |
| 94 if (this.subtests_.has(path)) { | |
| 95 return await this.subtests_.get(path); | |
| 96 } | |
| 97 const subtests = this.fetchAndProcessSubtests_(path); | |
| 98 this.subtests_[path] = subtests; | |
| 99 return await subtests; | |
| 100 } | |
| 101 | |
| 102 prepopulate(obj) { | |
| 103 for (const [path, subtests] of Object.entries(obj)) { | |
| 104 this.subtests_.set(path, Promise.resolve(subtests)); | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 async fetchAndProcessSubtests_(path) { | |
| 109 const params = { | |
| 110 type: 'pattern' | |
| 111 }; | |
| 112 params.p = `${path}/*`; | |
| 113 const fullSubtests = await simple_xhr.asPromise( | |
| 114 '/list_tests', params); | |
| 115 // TODO(eakuefner): Standardize logic for dealing with test paths on | |
| 116 // the client side. See | |
| 117 // https://github.com/catapult-project/catapult/issues/3443. | |
| 118 const subtests = []; | |
| 119 for (const fullSubtest of fullSubtests) { | |
| 120 subtests.push({ | |
| 121 name: fullSubtest.substring(fullSubtest.lastIndexOf('/') + 1)}); | |
| 122 } | |
| 123 return subtests; | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 return {Subtests}; | |
| 128 }); | |
| 129 | |
| 84 Polymer({ | 130 Polymer({ |
| 85 | 131 |
| 86 is: 'test-picker', | 132 is: 'test-picker', |
| 87 properties: { | 133 properties: { |
| 88 SUBTEST_LABEL: { | 134 SUBTEST_LABEL: { |
| 89 type: String, | 135 type: String, |
| 90 value: 'Subtest', | 136 value: 'Subtest', |
| 91 }, | 137 }, |
| 92 DEPRECATED_TAG: { | 138 DEPRECATED_TAG: { |
| 93 type: String, | 139 type: String, |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 127 disabled: false, | 173 disabled: false, |
| 128 }, | 174 }, |
| 129 { | 175 { |
| 130 datalist: [], | 176 datalist: [], |
| 131 placeholder: 'Bot', | 177 placeholder: 'Bot', |
| 132 disabled: true, | 178 disabled: true, |
| 133 } | 179 } |
| 134 ]) | 180 ]) |
| 135 }, | 181 }, |
| 136 | 182 |
| 183 subtests: { | |
| 184 type: Object, | |
| 185 value: () => new d.Subtests() | |
| 186 }, | |
| 187 | |
| 137 xsrfToken: { notify: true } | 188 xsrfToken: { notify: true } |
| 138 | 189 |
| 139 }, | 190 }, |
| 140 | 191 |
| 141 computeAnd: (x, y) => x && y, | 192 computeAnd: (x, y) => x && y, |
| 142 | 193 |
| 143 ready: function() { | 194 ready: function() { |
| 144 this.pageStateLoading = true; | 195 this.pageStateLoading = true; |
| 145 this.hasChart = false; | 196 this.hasChart = false; |
| 146 this.enableAddSeries = false; | 197 this.enableAddSeries = false; |
| 147 this.selectedSuite = null; | 198 this.selectedSuite = null; |
| 148 this.suiteDescription = null; | 199 this.suiteDescription = null; |
| 200 this.updatingSubtestMenus = false; | |
| 149 this.set('selectionModels.0.datalist', this.getSuiteItems()); | 201 this.set('selectionModels.0.datalist', this.getSuiteItems()); |
| 150 }, | 202 }, |
| 151 | 203 |
| 152 testSuitesChanged: function() { | 204 testSuitesChanged: function() { |
| 153 this.set('selectionModels.0.datalist', this.getSuiteItems()); | 205 this.set('selectionModels.0.datalist', this.getSuiteItems()); |
| 154 this.getSelectionMenu(0).items = this.selectionModels[0].datalist; | 206 this.getSelectionMenu(0).items = this.selectionModels[0].datalist; |
| 155 }, | 207 }, |
| 156 | 208 |
| 157 /** | 209 /** |
| 158 * Gets a list of menu items for test suites. | 210 * Gets a list of menu items for test suites. |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 220 } | 272 } |
| 221 return botMenuItems; | 273 return botMenuItems; |
| 222 }, | 274 }, |
| 223 | 275 |
| 224 /** | 276 /** |
| 225 * Handles dropdown menu select; updates the subsequent menu accordingly. | 277 * Handles dropdown menu select; updates the subsequent menu accordingly. |
| 226 */ | 278 */ |
| 227 onDropdownSelect: function(event) { | 279 onDropdownSelect: function(event) { |
| 228 var model = event.model; | 280 var model = event.model; |
| 229 var boxIndex = model.index; | 281 var boxIndex = model.index; |
| 282 if (this.updatingSubtestMenus) return; | |
| 230 if (boxIndex === undefined) { | 283 if (boxIndex === undefined) { |
| 231 return; | 284 return; |
| 232 } else if (boxIndex == 0) { | 285 } else if (boxIndex == 0) { |
| 233 this.updateTestSuiteDescription(); | 286 this.updateTestSuiteDescription(); |
| 234 this.updateBotMenu(); | 287 this.updateBotMenu(); |
| 235 } else if (boxIndex == 1) { | |
| 236 this.sendSubtestRequest(); | |
| 237 } else { | 288 } else { |
| 238 // Update all the next dropdown menus for subtests. | 289 // Update all the next dropdown menus for subtests. |
| 239 this.updateSubtestMenus(boxIndex + 1); | 290 this.updateSubtestMenus(boxIndex + 1); |
| 240 } | 291 } |
| 241 }, | 292 }, |
| 242 | 293 |
| 243 updateTestSuiteDescription: function() { | 294 updateTestSuiteDescription: function() { |
| 244 // Display the test suite description if there is one. | 295 // Display the test suite description if there is one. |
| 245 var descriptionElement = this.$['suite-description']; | 296 var descriptionElement = this.$['suite-description']; |
| 246 var suite = this.getSelectionMenu(0).selectedName; | 297 var suite = this.getSelectionMenu(0).selectedName; |
| 247 if (!this.testSuites[suite]) { | 298 if (!this.testSuites[suite]) { |
| 248 Polymer.dom(descriptionElement).innerHTML = ''; | 299 Polymer.dom(descriptionElement).innerHTML = ''; |
| 249 return; | 300 return; |
| 250 } | 301 } |
| 251 | 302 |
| 252 var description = this.testSuites[suite]['des']; | 303 var description = this.testSuites[suite]['des']; |
| 253 if (description) { | 304 if (description) { |
| 254 var descriptionHTML = '<b>' + suite + '</b>: '; | 305 var descriptionHTML = '<b>' + suite + '</b>: '; |
| 255 descriptionHTML += this.convertMarkdownLinks(description); | 306 descriptionHTML += this.convertMarkdownLinks(description); |
| 256 Polymer.dom(descriptionElement).innerHTML = descriptionHTML; | 307 Polymer.dom(descriptionElement).innerHTML = descriptionHTML; |
| 257 } else { | 308 } else { |
| 258 Polymer.dom(descriptionElement).innerHTML = ''; | 309 Polymer.dom(descriptionElement).innerHTML = ''; |
| 259 } | 310 } |
| 260 }, | 311 }, |
| 261 | 312 |
| 262 /** | 313 /** |
| 263 * Updates bot dropdown menu with bot items. | 314 * Updates bot dropdown menu with bot items. |
| 264 */ | 315 */ |
| 265 updateBotMenu: function() { | 316 updateBotMenu: async function() { |
| 266 var menu = this.getSelectionMenu(1); | 317 var menu = this.getSelectionMenu(1); |
| 267 var botItems = this.getBotItems(); | 318 var botItems = this.getBotItems(); |
| 268 menu.set('items', botItems); | 319 menu.set('items', botItems); |
| 269 menu.set('disabled', botItems.length === 0); | 320 menu.set('disabled', botItems.length === 0); |
| 270 this.subtestDict = null; | 321 this.subtestDict = null; |
| 271 // If there's a selection, send a subtest request. | 322 // If there's a selection, update the subtest menus. |
| 272 if (menu.selectedItem) { | 323 if (menu.selectedItem) { |
| 273 this.sendSubtestRequest(); | 324 this.updatingSubtestMenus = true; |
| 325 await this.updateSubtestMenus(2); | |
| 326 this.updatingSubtestMenus = false; | |
| 274 } else { | 327 } else { |
| 275 // Clear all subtest menus. | 328 // Clear all subtest menus. |
| 276 this.splice('selectionModels', 2); | 329 this.splice('selectionModels', 2); |
| 277 } | 330 } |
| 278 this.updateAddButtonState(); | 331 this.updateAddButtonState(); |
| 279 }, | 332 }, |
| 280 | 333 |
| 281 /** | 334 /** |
| 282 * Sends a request for subtestDict base on selected test suite and bot. | |
| 283 */ | |
| 284 sendSubtestRequest: function() { | |
| 285 if (this.subtestXhr) { | |
| 286 this.subtestXhr.abort(); | |
| 287 this.subtestXhr = null; | |
| 288 } | |
| 289 var bot = this.getCheckedBot(); | |
| 290 // If no bot is selected, just leave the current subtests. | |
| 291 if (bot === null) { | |
| 292 return; | |
| 293 } | |
| 294 var suite = this.getCheckedSuite(); | |
| 295 if (!suite) { | |
| 296 return; | |
| 297 } | |
| 298 | |
| 299 this.loading = true; | |
| 300 | |
| 301 var params = { | |
| 302 type: 'sub_tests', | |
| 303 suite: suite, | |
| 304 bots: bot, | |
| 305 xsrf_token: this.xsrfToken | |
| 306 }; | |
| 307 this.subtestXhr = simple_xhr.send( | |
| 308 '/list_tests', | |
| 309 params, | |
| 310 function(response) { | |
| 311 this.loading = false; | |
| 312 this.subtestDict = response; | |
| 313 // Start at first subtest menu. | |
| 314 this.updateSubtestMenus(2); | |
| 315 }.bind(this), | |
| 316 function(error) { | |
| 317 // TODO: Display error. | |
| 318 this.loading = false; | |
| 319 }.bind(this) | |
| 320 ); | |
| 321 }, | |
| 322 | |
| 323 /** | |
| 324 * Updates all subtest menus starting at 'startIndex'. | 335 * Updates all subtest menus starting at 'startIndex'. |
| 325 */ | 336 */ |
| 326 updateSubtestMenus: function(startIndex) { | 337 updateSubtestMenus: async function(startIndex) { |
| 327 var subtestDict = this.getSubtestAtIndex(startIndex); | 338 let subtests = await this.subtests.getSubtestsForPath( |
| 339 this.getCurrentSelectedPathUpTo(startIndex)); | |
| 328 | 340 |
| 329 // Update existing subtest menu. | 341 // Update existing subtest menu. |
| 330 for (var i = startIndex; i < this.selectionModels.length; i++) { | 342 for (var i = startIndex; i < this.selectionModels.length; i++) { |
| 331 // Remove the rest of the menu if no subtestDict. | 343 // Remove the rest of the menu if no subtests. |
| 332 if (!subtestDict || Object.keys(subtestDict).length == 0) { | 344 if (subtests.length === 0) { |
| 333 this.splice('selectionModels', i); | 345 this.splice('selectionModels', i); |
| 334 this.updateAddButtonState(); | 346 this.updateAddButtonState(); |
| 335 return; | 347 return; |
| 336 } | 348 } |
| 337 var subtestItems = this.getSubtestItems(subtestDict); | 349 const menu = this.getSelectionMenu(i); |
| 338 var menu = this.getSelectionMenu(i); | 350 this.updatingSubtestMenus = true; |
| 339 menu.set('items', subtestItems); | 351 menu.set('items', subtests); |
| 352 this.updatingSubtestMenus = false; | |
| 340 | 353 |
| 341 // If there are selected item, update next menu. | 354 // If there is a selected item, update the next menu. |
| 342 if (menu.selectedItem) { | 355 if (menu.selectedItem) { |
| 343 subtestDict = subtestDict[menu.selectedName]['sub_tests']; | 356 const selectedPath = this.getCurrentSelectedPathUpTo(i + 1, false); |
| 357 if (selectedPath !== undefined) { | |
| 358 subtests = await this.subtests.getSubtestsForPath(selectedPath); | |
| 359 } else { | |
| 360 subtests = []; | |
| 361 } | |
| 344 } else { | 362 } else { |
| 345 subtestDict = null; | 363 subtests = []; |
| 346 } | 364 } |
| 347 } | 365 } |
| 348 | 366 |
| 349 // Check if we still need to add a subtest menu. | 367 // If we reached the last iteration but still have subtests, that means |
| 350 if (subtestDict && Object.keys(subtestDict).length > 0) { | 368 // that the last extant subtest selection still has subtests and we need |
| 351 var subtestItems = this.getSubtestItems(subtestDict); | 369 // to put those in a new menu. |
| 370 if (subtests.length > 0) { | |
| 352 this.push('selectionModels', { | 371 this.push('selectionModels', { |
| 353 placeholder: this.SUBTEST_LABEL, | 372 placeholder: this.SUBTEST_LABEL, |
| 354 datalist: subtestItems, | 373 datalist: subtests, |
| 355 disabled: false, | 374 disabled: false, |
| 356 }); | 375 }); |
| 376 this.updatingSubtestMenus = true; | |
| 357 Polymer.dom.flush(); | 377 Polymer.dom.flush(); |
| 358 var menu = this.getSelectionMenu(this.selectionModels.length - 1); | 378 const menu = this.getSelectionMenu(this.selectionModels.length - 1); |
| 359 menu.set('items', subtestItems); | 379 menu.set('items', subtests); |
| 380 this.updatingSubtestMenus = false; | |
| 360 } | 381 } |
| 361 | 382 |
| 362 this.updateAddButtonState(); | 383 this.updateAddButtonState(); |
| 363 }, | 384 }, |
| 364 | 385 |
| 365 updateAddButtonState: async function() { | 386 updateAddButtonState: async function() { |
| 366 this.enableAddSeries = ( | 387 this.enableAddSeries = ( |
| 367 (await this.getCurrentSelection()) instanceof Array); | 388 (await this.getCurrentSelection()) instanceof Array); |
| 368 }, | 389 }, |
| 369 | 390 |
| 370 getSubtestAtIndex: function(index) { | |
| 371 var subtestDict = this.subtestDict; | |
| 372 for (var i = 2; i < index; i++) { | |
| 373 var test = this.getSelectionMenu(i).selectedName; | |
| 374 if (test in subtestDict) { | |
| 375 subtestDict = subtestDict[test]['sub_tests']; | |
| 376 } else { | |
| 377 return null; | |
| 378 } | |
| 379 } | |
| 380 return subtestDict; | |
| 381 }, | |
| 382 | |
| 383 getSubtestItems: function(subtestDict) { | |
| 384 var subtestItems = []; | |
| 385 var subtestNames = Object.keys(subtestDict).sort(); | |
| 386 for (var i = 0; i < subtestNames.length; i++) { | |
| 387 var name = subtestNames[i]; | |
| 388 subtestItems.push({ | |
| 389 name: name, | |
| 390 tag: (subtestDict[name]['deprecated'] ? this.DEPRECATED_TAG : '') | |
| 391 }); | |
| 392 } | |
| 393 return subtestItems; | |
| 394 }, | |
| 395 | |
| 396 getCheckedBot: function() { | 391 getCheckedBot: function() { |
| 397 var botMenu = this.getSelectionMenu(1); | 392 var botMenu = this.getSelectionMenu(1); |
| 398 if (botMenu.selectedItem) { | 393 if (botMenu.selectedItem) { |
| 399 let item = botMenu.selectedItem; | 394 let item = botMenu.selectedItem; |
| 400 return item['group'] + '/' + item['name']; | 395 return item['group'] + '/' + item['name']; |
| 401 } | 396 } |
| 402 return null; | 397 return null; |
| 403 }, | 398 }, |
| 404 | 399 |
| 405 getCheckedSuite: function() { | 400 getCheckedSuite: function() { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 430 * Fires add event on 'Add' button clicked. | 425 * Fires add event on 'Add' button clicked. |
| 431 */ | 426 */ |
| 432 onAddButtonClicked: function(event, detail) { | 427 onAddButtonClicked: function(event, detail) { |
| 433 this.fire('add'); | 428 this.fire('add'); |
| 434 }, | 429 }, |
| 435 | 430 |
| 436 /** | 431 /** |
| 437 * Gets the current selection from the menus. | 432 * Gets the current selection from the menus. |
| 438 */ | 433 */ |
| 439 getCurrentSelection: async function() { | 434 getCurrentSelection: async function() { |
| 440 var level = 0; | 435 const path = this.getCurrentSelectedPathUpTo(-1, true); |
| 441 var parts = []; | |
| 442 while (true) { | |
| 443 var menu = this.getSelectionMenu(level); | |
| 444 if (!menu || !menu.selectedItem) { | |
| 445 // A selection is only valid if it specifies at least one subtest | |
| 446 // component, which is the third level. | |
| 447 if (level <= 2) return null; | |
| 448 break; | |
| 449 } else { | |
| 450 // We want to collect all the subtest components so we can form | |
| 451 // the full test path after this loop is done. | |
| 452 if (level >= 2) parts.push(menu.selectedItem.name); | |
| 453 } | |
| 454 level += 1; | |
| 455 } | |
| 456 | |
| 457 var suite = this.getSelectionMenu(0).selectedItem.name; | |
| 458 var bot = this.getCheckedBot(); | |
| 459 parts.unshift(suite); | |
| 460 parts.unshift(bot); | |
| 461 | |
| 462 var path = parts.join('/'); | |
| 463 | 436 |
| 464 // If the paths are the same, this means that the selected path has | 437 // If the paths are the same, this means that the selected path has |
| 465 // already been confirmed as valid by the previous request to | 438 // already been confirmed as valid by the previous request to |
| 466 // /list_tests. | 439 // /list_tests. |
| 467 // TODO(eakuefner): Update the naming of these variables to make this | 440 // TODO(eakuefner): Update the naming of these variables to make this |
| 468 // possible to understand without the comment. | 441 // possible to understand without the comment. |
| 469 if (this.currentSelectedPath_ === path) { | 442 if (this.currentSelectedPath_ === path) { |
| 470 return this.currentSelectedTests_; | 443 return this.currentSelectedTests_; |
| 471 } | 444 } |
| 472 | 445 |
| 473 this.loading = true; | 446 this.loading = true; |
| 474 let selectedTests; | 447 let selectedTests; |
| 475 let unselectedTests; | 448 let unselectedTests; |
| 476 try { | 449 try { |
| 477 [selectedTests, unselectedTests] = await Promise.all([ | 450 [selectedTests, unselectedTests] = await Promise.all([ |
| 478 this.getSubtestsForPath(path, true), | 451 this.getSubtestsForPath(path, true), |
| 479 this.getSubtestsForPath(path, false)]); | 452 this.getSubtestsForPath(path, false)]); |
| 480 } catch (e) { | 453 } catch (e) { |
| 481 // TODO(eakuefner): Improve this error handling. | |
| 482 this.loading = false; | 454 this.loading = false; |
| 483 return null; | 455 return null; |
| 484 } | 456 } |
| 485 this.loading = false; | 457 this.loading = false; |
| 486 this.currentSelectedPath_ = path; | 458 this.currentSelectedPath_ = path; |
| 487 this.currentSelectedTests_ = selectedTests; | 459 this.currentSelectedTests_ = selectedTests; |
| 488 this.currentUnselectedTests_ = unselectedTests; | 460 this.currentUnselectedTests_ = unselectedTests; |
| 489 this.updateSubtestMenus(2); | 461 this.updatingSubtestMenus = true; |
| 462 await this.updateSubtestMenus(2); | |
| 463 this.updatingSubtestMenus = false; | |
| 490 return selectedTests; | 464 return selectedTests; |
| 491 }, | 465 }, |
| 492 | 466 |
| 467 getCurrentSelectedPathUpTo: function(maxLevel, onlyValid) { | |
| 468 let level = 0; | |
| 469 const parts = []; | |
| 470 while (true) { | |
| 471 if (maxLevel !== -1 && level >= maxLevel) { | |
| 472 break; | |
| 473 } | |
| 474 const menu = this.getSelectionMenu(level); | |
| 475 if (onlyValid && (!menu || !menu.selectedItem)) { | |
| 476 // A selection is only valid if it specifies at least one subtest | |
| 477 // component, which is the third level. | |
| 478 if (level <= 2) return undefined; | |
| 479 break; | |
| 480 } else { | |
| 481 // We want to collect all the subtest components so we can form | |
| 482 // the full test path after this loop is done. | |
| 483 if (level >= 2) parts.push(menu.selectedItem.name); | |
| 484 } | |
| 485 level += 1; | |
| 486 } | |
| 487 | |
| 488 const suite = this.getSelectionMenu(0).selectedItem.name; | |
| 489 const bot = this.getCheckedBot(); | |
| 490 parts.unshift(suite); | |
| 491 parts.unshift(bot); | |
| 492 | |
| 493 if (parts.length < maxLevel) return undefined; | |
| 494 return parts.join('/'); | |
| 495 }, | |
| 496 | |
| 493 getCurrentSelectedPath: function() { | 497 getCurrentSelectedPath: function() { |
| 494 return this.currentSelectedPath_; | 498 return this.currentSelectedPath_; |
| 495 }, | 499 }, |
| 496 | 500 |
| 497 getCurrentUnselected: function() { | 501 getCurrentUnselected: function() { |
| 498 return this.currentUnselectedTests_; | 502 return this.currentUnselectedTests_; |
| 499 }, | 503 }, |
| 500 | 504 |
| 501 getSubtestsForPath: async function(path, returnSelected) { | 505 getSubtestsForPath: async function(path, returnSelected) { |
| 502 // TODO(eakuefner): Reimplement cancellation and memoize. | 506 // TODO(eakuefner): Reimplement cancellation and memoize. |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 551 * Converts a link in markdown format to a HTML link (anchor elements). | 555 * Converts a link in markdown format to a HTML link (anchor elements). |
| 552 * @param {string} text A link in markdown format. | 556 * @param {string} text A link in markdown format. |
| 553 * @return {string} A hyperlink string. | 557 * @return {string} A hyperlink string. |
| 554 */ | 558 */ |
| 555 convertMarkdownLinks: function(text) { | 559 convertMarkdownLinks: function(text) { |
| 556 return text.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>'); | 560 return text.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>'); |
| 557 } | 561 } |
| 558 }); | 562 }); |
| 559 </script> | 563 </script> |
| 560 </dom-module> | 564 </dom-module> |
| OLD | NEW |