SwaggerUI Search Supportting

swagger-ui-v1 添加查找接口交互

  1. // ==UserScript==
  2. // @name SwaggerUI Search Supportting
  3. // @namespace Violentmonkey Scripts
  4. // @match *://*/*/swagger-ui.html*
  5. // @match *://*/swagger-ui.html*
  6. // @grant none
  7. // @version 0.0.1.20180204232226
  8. // @description swagger-ui-v1 添加查找接口交互
  9. // ==/UserScript==
  10.  
  11. window.addEventListener('load', function () {
  12. /**
  13. * @file: EventEmitter
  14. * @author: Cuttle Cong
  15. * @date: 2017/11/1
  16. * @description:
  17. */
  18. function assertType(type) {
  19. if (typeof type !== 'string') {
  20. throw new TypeError('type is not type of String!')
  21. }
  22. }
  23.  
  24. function assertFn(fn) {
  25. if (typeof fn !== 'function') {
  26. throw new TypeError('fn is not type of Function!')
  27. }
  28. }
  29.  
  30. function EventEmitter() {
  31. this._events = {}
  32. }
  33.  
  34. function on(type, fn) {
  35. assertType(type)
  36. assertFn(fn)
  37. this._events[type] = this._events[type] || []
  38. this._events[type].push({
  39. type: 'always',
  40. fn: fn
  41. })
  42. }
  43.  
  44. function prepend(type, fn) {
  45. assertType(type)
  46. assertFn(fn)
  47. this._events[type] = this._events[type] || []
  48. this._events[type].unshift({
  49. type: 'always',
  50. fn: fn
  51. })
  52. }
  53.  
  54. function prependOnce(type, fn) {
  55. assertType(type)
  56. assertFn(fn)
  57. this._events[type] = this._events[type] || []
  58. this._events[type].unshift({
  59. type: 'once',
  60. fn: fn
  61. })
  62. }
  63.  
  64. function once(type, fn) {
  65. assertType(type)
  66. assertFn(fn)
  67. this._events[type] = this._events[type] || []
  68. this._events[type].push({
  69. type: 'once',
  70. fn: fn
  71. })
  72. }
  73.  
  74. function off(type, nullOrFn) {
  75. assertType(type)
  76. if (!this._events[type]) return
  77. if (typeof nullOrFn === 'function') {
  78. var index = this._events[type].findIndex(function (event) {
  79. return event.fn === nullOrFn
  80. })
  81. if (index >= 0) {
  82. this._events[type].splice(index, 1)
  83. }
  84. } else {
  85. delete this._events[type]
  86. }
  87. }
  88.  
  89. function emit(type /*, arguments */) {
  90. assertType(type)
  91. var args = [].slice.call(arguments, 1)
  92. var self = this
  93. if (this._events[type]) {
  94. this._events[type].forEach(function (event) {
  95. event.fn.apply(null, args)
  96. if (event.type === 'once') {
  97. self.off(type, event.fn)
  98. }
  99. })
  100. }
  101. }
  102.  
  103. EventEmitter.prototype.on = EventEmitter.prototype.addListener = on
  104. EventEmitter.prototype.once = EventEmitter.prototype.addOnceListener = once
  105. EventEmitter.prototype.prepend = EventEmitter.prototype.prependListener = prepend
  106. EventEmitter.prototype.prependOnce = EventEmitter.prototype.prependOnceListener = prependOnce
  107. EventEmitter.prototype.off = EventEmitter.prototype.removeListener = off
  108. EventEmitter.prototype.emit = EventEmitter.prototype.trigger = emit
  109.  
  110. if (typeof module !== 'undefined') {
  111. module.exports = EventEmitter
  112. }
  113.  
  114.  
  115. function KeyExtra(opt) {
  116. this._init(opt)
  117. }
  118.  
  119. KeyExtra.prototype = new EventEmitter()
  120. KeyExtra.prototype.constructor = KeyExtra
  121.  
  122. KeyExtra.prototype._init = function (opt) {
  123. var keyExtra = this
  124.  
  125. // double key press
  126. var doublePressTimeoutMs = 600
  127. var lastKeypressTime = 0
  128. var lastKeyChar = null
  129.  
  130. function doubleHandle(type) {
  131. return function (evt) {
  132. var thisCharCode = evt.key.toUpperCase()
  133. if (lastKeyChar === null) {
  134. lastKeyChar = thisCharCode
  135. lastKeypressTime = new Date()
  136. return
  137. }
  138. if (thisCharCode === lastKeyChar) {
  139. var thisKeypressTime = new Date()
  140. if (thisKeypressTime - lastKeypressTime <= doublePressTimeoutMs) {
  141. keyExtra.emit('double-' + type, thisCharCode)
  142. }
  143. }
  144. lastKeyChar = null
  145. lastKeypressTime = 0
  146. }
  147. }
  148.  
  149. document && document.addEventListener('keypress', doubleHandle('keypress'))
  150. document && document.addEventListener('keydown', doubleHandle('keydown'))
  151. }
  152.  
  153.  
  154. setTimeout(
  155. function () {
  156. (
  157. function ($) {
  158. var swaggerVersion = 1
  159. if (typeof SwaggerUIBundle === 'function') {
  160. // swagger-ui v2-v3
  161. swaggerVersion = 2
  162. var script = document.createElement('script')
  163. script.src = '//cdn.bootcss.com/jquery/1.8.0/jquery-1.8.0.min.js'
  164. script.onload = function (ev) {
  165. registerSearchUI()
  166. }
  167. document.head.appendChild(script)
  168. return
  169. }
  170.  
  171. if (typeof window.swaggerUi === 'undefined') {
  172. console.error('window.swaggerUi is not defined, so we consider that the page isn\'t swagger-ui.')
  173. return
  174. }
  175. if (typeof $ === 'undefined') {
  176. console.error('jQuery is not found, so we consider that the page isn\'t swagger-ui.')
  177. return
  178. }
  179. registerSearchUI()
  180.  
  181.  
  182. function registerSearchUI() {
  183. var $ = window.jQuery
  184. var dom = $('<div style="margin-top: 15px;"></div>')
  185. dom.attr('class', 'inject-dom-container')
  186.  
  187. var btns = $('<div></div>')
  188. btns.attr('class', 'inject-btn-container')
  189.  
  190. function listAll() {
  191. $('.collapseResource').click()
  192. }
  193.  
  194. function hideAll() {
  195. $('.endpoints').css({ display: 'none' })
  196. }
  197.  
  198. function expendAll() {
  199. $('.expandResource').click()
  200. }
  201.  
  202. swaggerVersion === 1 && btns.append(
  203. $('<button>List All</button>').on('click', listAll),
  204. $('<button>Hide All</button>').on('click', hideAll),
  205. $('<button>Expend All</button>').on('click', expendAll),
  206. )
  207.  
  208. swaggerVersion === 1 && dom.append(btns)
  209. dom.append([
  210. '<div style="text-align: center;">',
  211. '<h3 style="margin-bottom: 5px;">SwaggerUI Search Supportting</h3>',
  212. '<small style="margin-bottom: 10px">Double press "shift" or "A" could awake the search UI.</small>',
  213. '</div>',
  214. '<div class="search-container" style="display: none;">',
  215. '<div class="search-main">',
  216. '<input class="search-input"/>',
  217. '<ul class="search-found-list">',
  218. '</ul>',
  219. '</div>',
  220. '</div>'
  221. ].join(''))
  222.  
  223. var searchContainer = dom.find('.search-container')
  224. new KeyExtra()
  225. .on('double-keydown', function (charCode) {
  226. if (charCode === 'A' || charCode === 'SHIFT') {
  227. setTimeout(function () {
  228. $('body').css({ overflow: 'hidden' })
  229. searchContainer.show()
  230. searchContainer.find('.search-input').focus().select()
  231. }, 0)
  232. }
  233. })
  234.  
  235. function hideSearch() {
  236. $('body').css({ overflow: '' })
  237. searchContainer.hide()
  238. }
  239.  
  240. document.addEventListener('keydown', function (evt) {
  241. if (evt.key === 'Escape') {
  242. hideSearch()
  243. }
  244. })
  245.  
  246. var COUNT = 20
  247.  
  248. function search(val) {
  249. val = typeof val !== 'string' ? '' : val.trim()
  250.  
  251. if (!val) {
  252. foundListDom.empty()
  253. return
  254. }
  255.  
  256. var type = ''
  257. if (/^(p|s|m): ([^]+)$/.test(val)) {
  258. type = RegExp.$1
  259. val = RegExp.$2
  260. }
  261.  
  262. var keywords = val.split(/[+ ]/)
  263. var foundList = []
  264.  
  265. list.some(function (entity) {
  266. if (foundList.length === 30) {
  267. return true
  268. }
  269. var matched_types = []
  270. var matched = keywords.every(function (keyword) {
  271. function find(type, keyword) {
  272. // console.log(entity);
  273. if (entity[type].toLowerCase().includes(keyword.toLowerCase())) {
  274. if (!matched_types.includes(type)) {
  275. matched_types.push(type)
  276. }
  277. return true
  278. }
  279. }
  280.  
  281. if (type) {
  282. return find(type, keyword)
  283. }
  284. else {
  285. return ['p', 's', 'm'].some(function (type) {
  286. return find(type, keyword)
  287. })
  288. }
  289. })
  290.  
  291. if (matched) {
  292. foundList.push({
  293. type: matched_types.join(' '),
  294. entity: entity
  295. })
  296. }
  297. })
  298.  
  299. foundListDom.empty()
  300.  
  301. function item(data, i) {
  302. var html = '<li class="search-item ' + (
  303. i === 0 ? 'active' : ''
  304. ) + '">'
  305. + '<span class="search-item-type">' + data.type + '</span>'
  306. + ': '
  307. + '<span class="search-item-method">' + data.entity.m.toUpperCase() + '</span>'
  308. + ' '
  309. + '<span class="search-item-path">' + data.entity.p + '</span>'
  310. + '<span class="search-item-summary">' + data.entity.s + '</span>'
  311. + '</li>'
  312.  
  313. return $(html).on('click', function () {
  314. console.log('click', data)
  315. var path = (swaggerVersion === 1 ? data.entity.url : data.entity.url.slice(1))
  316. var href = '#' + path
  317. if (swaggerVersion === 1) {
  318. var link = $('.toggleOperation[href=' + JSON.stringify(href) + ']')
  319. link.parents('ul.endpoints').css({ display: 'block' })
  320. link[0].scrollIntoView()
  321. var operation = link.parents('.operation')
  322. var content = operation.find('.content')
  323. content.css('display') === 'none' && link[0].click()
  324. }
  325. else {
  326. var tag = data.entity.methodEntity.tags[0]
  327. var tagDOM = $('#operations-tag-' + tag)
  328. if (!tagDOM.parent().hasClass('is-open')) {
  329. tagDOM.click()
  330. }
  331.  
  332. path = path.replace(/\//g, '-')
  333. var toggleDOM = $('#operations' + path)
  334. if (!toggleDOM.hasClass('is-open')) {
  335. toggleDOM.children().eq(0).click()
  336. }
  337. toggleDOM[0].scrollIntoView()
  338. }
  339. hideSearch()
  340. foundListDom.empty()
  341. })
  342. }
  343.  
  344. if (!foundList.length) {
  345. foundListDom.append(
  346. '<li class="search-item">' + 'Not Found :(' + '</li>'
  347. )
  348. }
  349. else {
  350. foundListDom.append(
  351. foundList.map(item)
  352. )
  353.  
  354. var sumHeight = 1
  355. var over = Array.from(foundListDom.children('.search-item')).some(function (dom, i) {
  356. if (i === COUNT) {
  357. return true
  358. }
  359. sumHeight += $(dom).prop('clientHeight') + 1
  360. })
  361. over && foundListDom.css({ 'max-height': sumHeight + 'px' })
  362. }
  363. }
  364.  
  365. var foundListDom = dom.find('.search-found-list')
  366. dom.find('.search-input')
  367. .on('input', function (evt) {
  368. search(evt.target.value)
  369. })
  370. .on('focus', function (evt) {
  371. search(evt.target.value)
  372. })
  373. // .on('blur', function (evt) { setTimeout(function () {foundListDom.empty()}, 300) })
  374. .on('keydown', function (evt) {
  375. var activeIndex = null
  376. var listDoms = foundListDom.find('.search-item')
  377.  
  378. function findActive() {
  379. Array.from(listDoms).some(function (dom, i) {
  380. if ($(dom).hasClass('active')) {
  381. $(dom).removeClass('active')
  382. activeIndex = i
  383. }
  384. })
  385. }
  386.  
  387. var crlKey = evt.metaKey || evt.ctrlKey
  388. var offset = crlKey ? COUNT : 1
  389. var isUp = null
  390. var prevIndex = activeIndex
  391. switch (evt.keyCode) {
  392. case 38: // UP
  393. findActive()
  394. activeIndex = (
  395. listDoms.length + activeIndex - offset
  396. ) % listDoms.length
  397. listDoms.eq(activeIndex).addClass('active')
  398. isUp = true
  399. break
  400. case 40: // DOWN
  401. findActive()
  402. activeIndex = (
  403. activeIndex + offset
  404. ) % listDoms.length
  405. listDoms.eq(activeIndex).addClass('active')
  406. isUp = false
  407. break
  408. case 13: // ENTER
  409. findActive()
  410. listDoms[activeIndex] && listDoms[activeIndex].click()
  411. return
  412. }
  413. if (isUp === null) {
  414. return
  415. }
  416. evt.preventDefault()
  417. var rang = [
  418. foundListDom.prop('scrollTop'),
  419. foundListDom.prop('scrollTop') + foundListDom.prop('clientHeight') - 10
  420. ]
  421. // console.log(rang, listDoms[activeIndex].offsetTop)
  422. // console.dir(foundListDom[0])
  423. // console.log('!', listDoms[activeIndex].offsetTop, rang);
  424. if (listDoms[activeIndex]) {
  425. if (!(
  426. listDoms[activeIndex].offsetTop >= rang[0] && listDoms[activeIndex].offsetTop <= rang[1]
  427. )) {
  428. // debugger;
  429. if (activeIndex === 0) {
  430. foundListDom[0].scrollTop = 0
  431. } else if (activeIndex === listDoms.length - 1) {
  432. foundListDom[0].scrollTop = foundListDom.prop('scrollHeight')
  433. } else {
  434. foundListDom[0].scrollTop +=
  435. isUp ? -foundListDom.prop('clientHeight') : foundListDom.prop('clientHeight')
  436. }
  437. }
  438. }
  439.  
  440. //console.dir(foundListDom[0])
  441. //console.dir(listDoms[activeIndex]);
  442. })
  443.  
  444. var list = []
  445. var url
  446. if (swaggerVersion === 1) {
  447. url = window.swaggerUi.api && window.swaggerUi.api.url
  448. } else {
  449. url = $('.download-url-input').val()
  450. // global ui variable
  451. if (!url && typeof window.ui !== 'undefined') {
  452. var config = window.ui.getConfigs()
  453. url = config.url || (config.urls[0] && config.urls[0].url)
  454. }
  455. }
  456.  
  457. if (url) {
  458.  
  459. function analysisData(data) {
  460. console.log('data', data)
  461. $.each(data.paths, function (path, methodSet) {
  462. $.each(methodSet, function (method, methodEntity) {
  463. // @todo:: array ??
  464. methodEntity.tags.join(',')
  465. methodEntity.operationId
  466. methodEntity.summary
  467.  
  468. list.push({
  469. methodEntity: methodEntity,
  470. url: '!/' + methodEntity.tags.join(',') + '/' + methodEntity.operationId,
  471. s: methodEntity.summary,
  472. m: method,
  473. p: path
  474. })
  475. })
  476. })
  477.  
  478. console.log('list', list)
  479. dom.insertAfter( swaggerVersion === 1 ? $('#header') : $('.topbar'))
  480. }
  481.  
  482. $.ajax({
  483. url: url,
  484. dataType: 'text',
  485. success: function (data) {
  486. if (/^\s*[{[]/.test(data)) {
  487. // json string is error
  488. data = eval('x = ' + data + '\n x;')
  489. analysisData(data)
  490. } else {
  491. // yaml text
  492. var script = document.createElement('script')
  493. script.src = '//cdn.bootcss.com/js-yaml/3.10.0/js-yaml.min.js'
  494. document.head.appendChild(script)
  495. script.onload = function () {
  496. data = jsyaml.safeLoad(data)
  497. analysisData(data)
  498. }
  499. }
  500. }
  501. })
  502. }
  503.  
  504. $('head').append(
  505. '<style type="text/css">'
  506. + '.inject-btn-container {'
  507. + 'text-align: center;'
  508. + '}'
  509. + '.inject-btn-container button {'
  510. + 'margin-left: 5px;'
  511. + 'margin-right: 5px;'
  512. + '}'
  513. + '.search-item-type{'
  514. + 'display: inline-block;'
  515. + 'min-width: 15px;'
  516. + '}'
  517. + '.search-item-method {'
  518. + 'display: inline-block;'
  519. + 'width: 65px;'
  520. + 'text-align: center'
  521. + '}'
  522. + '.search-item-summary {'
  523. + 'display: inline-block;'
  524. + 'width: auto;'
  525. + 'float: right;'
  526. + 'max-width: 200px;'
  527. + 'overflow: hidden;'
  528. + 'text-overflow: ellipsis;'
  529. + 'white-space: nowrap;'
  530. + 'text-align: right;'
  531. + '}'
  532. + '.search-main {'
  533. + 'position: static;'
  534. + 'margin: 40px auto 40px;'
  535. + 'width: 68%;'
  536. + 'min-width: 500px;'
  537. + '}'
  538. + '.search-container {'
  539. + 'overflow-y: auto;'
  540. + 'background-color: rgba(0, 0, 0, .3);'
  541. + 'position: fixed;'
  542. + 'left: 0;'
  543. + 'right: 0;'
  544. + 'top: 0;'
  545. + 'bottom: 0;'
  546. + 'z-index: 1000;'
  547. + '}'
  548. + '.search-input {'
  549. + 'line-height: 30px;'
  550. + 'font-size: 18px;'
  551. + 'display: block;'
  552. + 'margin: auto;'
  553. + 'width: 100%;'
  554. + 'border: none;'
  555. + 'border-bottom: 1px solid #89bf04;'
  556. + 'padding: 4px 10px 2px;'
  557. + 'box-sizing: border-box;'
  558. + '}'
  559. + '.search-input:focus {'
  560. + 'outline: none;'
  561. + '}'
  562. + '.search-found-list {'
  563. + 'position: static;'
  564. + 'left: 0;'
  565. + 'right: 0;'
  566. + 'padding: 0;'
  567. // + 'max-height: 200px;'
  568. + 'overflow: auto;'
  569. + ''
  570. + '}'
  571. + '.search-found-list {'
  572. + 'margin-top: 2px;'
  573. + 'list-style: none;'
  574. + '}'
  575. + '.search-item.active, .search-item:hover {'
  576. + 'background-color: #eee;'
  577. + '}'
  578. + '.search-item {'
  579. + 'cursor: pointer;'
  580. + 'background-color: #fff;'
  581. + 'padding: 7px 15px;'
  582. + 'border: 1px solid #333;'
  583. + 'border-bottom: none;'
  584. + '}'
  585. + '.search-item:last-child {'
  586. + 'border-bottom: 1px solid #333;'
  587. + '}'
  588. + '</style>'
  589. )
  590.  
  591. // auto scrollIntoView by hash
  592. setTimeout(function () {
  593. var a = $('a[href="' + location.hash + '"]')[0]
  594. a && a.scrollIntoView()
  595. }, 200)
  596.  
  597. }
  598.  
  599.  
  600. }
  601. )(window.jQuery)
  602. },
  603. 1000
  604. )
  605.  
  606. })
  607.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址