您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
swagger-ui 添加查找接口交互 此插件针对于2.0的swagger版本及以上,由SwaggerUI Search Supportting魔改而成.
// ==UserScript== // @name swagger-ui-2.7.0 // @namespace Violentmonkey Scripts // @match *://*/*/swagger-ui.html* // @match *://*/swagger-ui.html* // @grant none // @version 20230525 // @description swagger-ui 添加查找接口交互 此插件针对于2.0的swagger版本及以上,由SwaggerUI Search Supportting魔改而成. // @run-at document-end // @license MIT // ==/UserScript== window.addEventListener('load', function () { /** * @file: EventEmitter * @author: 高国祥,王勤奋[email protected],baibaiwuchang * @date: 20220214 * @description: */ function assertType(type) { if (typeof type !== 'string') { throw new TypeError('type is not type of String!') } } function assertFn(fn) { if (typeof fn !== 'function') { throw new TypeError('fn is not type of Function!') } } function EventEmitter() { this._events = {} } function on(type, fn) { assertType(type) assertFn(fn) this._events[type] = this._events[type] || [] this._events[type].push({ type: 'always', fn: fn }) } function prepend(type, fn) { assertType(type) assertFn(fn) this._events[type] = this._events[type] || [] this._events[type].unshift({ type: 'always', fn: fn }) } function prependOnce(type, fn) { assertType(type) assertFn(fn) this._events[type] = this._events[type] || [] this._events[type].unshift({ type: 'once', fn: fn }) } function once(type, fn) { assertType(type) assertFn(fn) this._events[type] = this._events[type] || [] this._events[type].push({ type: 'once', fn: fn }) } function off(type, nullOrFn) { assertType(type) if (!this._events[type]) return if (typeof nullOrFn === 'function') { var index = this._events[type].findIndex(function (event) { return event.fn === nullOrFn }) if (index >= 0) { this._events[type].splice(index, 1) } } else { delete this._events[type] } } function emit(type /*, arguments */) { assertType(type) var args = [].slice.call(arguments, 1) var self = this if (this._events[type]) { this._events[type].forEach(function (event) { event.fn.apply(null, args) if (event.type === 'once') { self.off(type, event.fn) } }) } } EventEmitter.prototype.on = EventEmitter.prototype.addListener = on EventEmitter.prototype.once = EventEmitter.prototype.addOnceListener = once EventEmitter.prototype.prepend = EventEmitter.prototype.prependListener = prepend EventEmitter.prototype.prependOnce = EventEmitter.prototype.prependOnceListener = prependOnce EventEmitter.prototype.off = EventEmitter.prototype.removeListener = off EventEmitter.prototype.emit = EventEmitter.prototype.trigger = emit if (typeof module !== 'undefined') { module.exports = EventEmitter } function KeyExtra(opt) { this._init(opt) } KeyExtra.prototype = new EventEmitter() KeyExtra.prototype.constructor = KeyExtra KeyExtra.prototype._init = function (opt) { var keyExtra = this // double key press var doublePressTimeoutMs = 600 var lastKeypressTime = 0 var lastKeyChar = null function doubleHandle(type) { return function (evt) { var thisCharCode = evt.key.toUpperCase() if (lastKeyChar === null) { lastKeyChar = thisCharCode lastKeypressTime = new Date() return } if (thisCharCode === lastKeyChar) { var thisKeypressTime = new Date() if (thisKeypressTime - lastKeypressTime <= doublePressTimeoutMs) { keyExtra.emit('double-' + type, thisCharCode) } } lastKeyChar = null lastKeypressTime = 0 } } document && document.addEventListener('keypress', doubleHandle('keypress')) document && document.addEventListener('keydown', doubleHandle('keydown')) } setTimeout( function () { ( function ($) { var swaggerVersion = 1 if (typeof SwaggerUIBundle === 'function') { // swagger-ui v2-v3 swaggerVersion = 2 var script = document.createElement('script') script.src = '//cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js' script.onload = function (ev) { registerSearchUI() } document.head.appendChild(script) return } if (typeof window.swaggerUi === 'undefined') { console.error('window.swaggerUi is not defined, so we consider that the page isn\'t swagger-ui.') return } if (typeof $ === 'undefined') { console.error('jQuery is not found, so we consider that the page isn\'t swagger-ui.') return } registerSearchUI() function registerSearchUI() { var $ = window.jQuery var dom = $('<div style="margin-top: 15px;"></div>') dom.attr('class', 'inject-dom-container') var btns = $('<div></div>') btns.attr('class', 'inject-btn-container') function listAll() { $('.collapseResource').click() } function hideAll() { $('.endpoints').css({ display: 'none' }) } function expendAll() { $('.expandResource').click() } swaggerVersion === 1 && btns.append( $('<button>List All</button>').on('click', listAll), $('<button>Hide All</button>').on('click', hideAll), $('<button>Expend All</button>').on('click', expendAll), ) swaggerVersion === 1 && dom.append(btns) dom.append([ '<div style="text-align: center;">', '<br/>', '<small style="margin-bottom: 10px">双击或长按Shift搜索 Esc关闭</small>', '</div>', '<div class="search-container" style="display: none;">', '<div class="search-main">', '<input class="search-input"/>', '<ul class="search-found-list">', '</ul>', '</div>', '</div>' ].join('')) var searchContainer = dom.find('.search-container') new KeyExtra() .on('double-keydown', function (charCode) { if (charCode === 'A' || charCode === 'SHIFT') { setTimeout(function () { $('body').css({ overflow: 'hidden' }) searchContainer.show() searchContainer.find('.search-input').focus().select() }, 0) } }) function hideSearch() { $('body').css({ overflow: '' }) searchContainer.hide() } document.addEventListener('keydown', function (evt) { if (evt.key === 'Escape') { hideSearch() } }) var COUNT = 20 function search(val) { val = typeof val !== 'string' ? '' : val.trim() if (!val) { foundListDom.empty() return } var type = '' if (/^(p|s|m): ([^]+)$/.test(val)) { type = RegExp.$1 val = RegExp.$2 } var keywords = val.split(/[+ ]/) var foundList = [] list.some(function (entity) { if (foundList.length === 30) { return true } var matched_types = [] var matched = keywords.every(function (keyword) { function find(type, keyword) { // console.log(entity); if (entity[type].toLowerCase().includes(keyword.toLowerCase())) { if (!matched_types.includes(type)) { matched_types.push(type) } return true } } if (type) { return find(type, keyword) } else { return ['p', 's', 'm'].some(function (type) { return find(type, keyword) }) } }) if (matched) { foundList.push({ type: matched_types.join(' '), entity: entity }) } }) foundListDom.empty() function item(data, i) { var html = '<li class="search-item ' + ( i === 0 ? 'active' : '' ) + '">' + '<span class="search-item-type">' + data.type + '</span>' + ': ' + '<span class="search-item-method">' + data.entity.m.toUpperCase() + '</span>' + ' ' + '<span class="search-item-path">' + data.entity.p + '</span>' + '<span class="search-item-summary">' + data.entity.s + '</span>' + '<button class="copy-route-btn" title="复制路由" onclick="navigator.clipboard.writeText(\'' + data.entity.p + '\').then(() => alert(\'路由已复制到剪贴板\'))">📋</button>' + '</li>' return $(html).on('click', function () { console.log('click', data) var path = (swaggerVersion === 1 ? data.entity.url : data.entity.url.slice(1)) var href = '#' + path if (swaggerVersion === 1) { var link = $('.toggleOperation[href=' + JSON.stringify(href) + ']') link.parents('ul.endpoints').css({ display: 'block' }) link[0].scrollIntoView() var operation = link.parents('.operation') var content = operation.find('.content') content.css('display') === 'none' && link[0].click() } else { // swagger 中文版本 var tag = data.entity.methodEntity.tags[0] tag = tag.replaceAll(")","\\)").replaceAll("(","\\(").replaceAll(" ","_") var tagDOM = $('#operations-tag-' + tag) if (!tagDOM.parent().hasClass('is-open')) { tagDOM.click() } path = path.replaceAll(")","\\)").replaceAll("(","\\(").replaceAll(" ","\\ ") path = path.replaceAll(/\//g, '-') var toggleDOM = $('#operations' + path) if (!toggleDOM.hasClass('is-open')) { toggleDOM.children().eq(0).click() } toggleDOM[0].scrollIntoView() } hideSearch() foundListDom.empty() }) } if (!foundList.length) { foundListDom.append( '<li class="search-item">' + 'Not Found :(' + '</li>' ) } else { foundListDom.append( foundList.map(item) ) var sumHeight = 1 var over = Array.from(foundListDom.children('.search-item')).some(function (dom, i) { if (i === COUNT) { return true } sumHeight += $(dom).prop('clientHeight') + 1 }) over && foundListDom.css({ 'max-height': sumHeight + 'px' }) } } var foundListDom = dom.find('.search-found-list') dom.find('.search-input') .on('input', function (evt) { search(evt.target.value) }) .on('focus', function (evt) { search(evt.target.value) }) // .on('blur', function (evt) { setTimeout(function () {foundListDom.empty()}, 300) }) .on('keydown', function (evt) { var activeIndex = null var listDoms = foundListDom.find('.search-item') function findActive() { Array.from(listDoms).some(function (dom, i) { if ($(dom).hasClass('active')) { $(dom).removeClass('active') activeIndex = i } }) } var crlKey = evt.metaKey || evt.ctrlKey var offset = crlKey ? COUNT : 1 var isUp = null var prevIndex = activeIndex switch (evt.keyCode) { case 38: // UP findActive() activeIndex = ( listDoms.length + activeIndex - offset ) % listDoms.length listDoms.eq(activeIndex).addClass('active') isUp = true break case 40: // DOWN findActive() activeIndex = ( activeIndex + offset ) % listDoms.length listDoms.eq(activeIndex).addClass('active') isUp = false break case 13: // ENTER findActive() listDoms[activeIndex] && listDoms[activeIndex].click() return } if (isUp === null) { return } evt.preventDefault() var rang = [ foundListDom.prop('scrollTop'), foundListDom.prop('scrollTop') + foundListDom.prop('clientHeight') - 10 ] // console.log(rang, listDoms[activeIndex].offsetTop) // console.dir(foundListDom[0]) // console.log('!', listDoms[activeIndex].offsetTop, rang); if (listDoms[activeIndex]) { if (!( listDoms[activeIndex].offsetTop >= rang[0] && listDoms[activeIndex].offsetTop <= rang[1] )) { // debugger; if (activeIndex === 0) { foundListDom[0].scrollTop = 0 } else if (activeIndex === listDoms.length - 1) { foundListDom[0].scrollTop = foundListDom.prop('scrollHeight') } else { foundListDom[0].scrollTop += isUp ? -foundListDom.prop('clientHeight') : foundListDom.prop('clientHeight') } } } //console.dir(foundListDom[0]) //console.dir(listDoms[activeIndex]); }) var list = [] var url if (swaggerVersion === 1) { url = window.swaggerUi.api && window.swaggerUi.api.url } else { url = $('.download-url-input').val() // global ui variable if (!url && typeof window.ui !== 'undefined') { var config = window.ui.getConfigs() url = config.url || (config.urls[0] && config.urls[0].url) } } if (url) { function analysisData(data) { console.log('data', data) $.each(data.paths, function (path, methodSet) { $.each(methodSet, function (method, methodEntity) { // @todo:: array ?? methodEntity.tags.join(',') methodEntity.operationId methodEntity.summary var u = ""; // url非中文的版本 if(swaggerVersion==1){ u = '!/' + methodEntity.tags.join(',').replace(/[^a-zA-Z\d]/g, function(str) { return str.charCodeAt(0); }) + '/' + methodEntity.operationId }else{ // url是中文的版本 u = '!/' + methodEntity.tags.join(',') + '/' + methodEntity.operationId } list.push({ methodEntity: methodEntity, url: u, s: methodEntity.summary, m: method, p: path }) }) }) console.log('list', list) dom.insertAfter( swaggerVersion === 1 ? $('#header') : $('.topbar')) if(swaggerVersion !=1){ // url hash为: #/(内部)债券基础信息接口/listBondRemainingTenorDTOUsingPOST_1 var urlHash = decodeURIComponent(window.location.hash); if(urlHash != null && urlHash.length > 2) { // 去掉第一个# urlHash = urlHash.slice(1); var urlHashArrays = urlHash.split("/"); var tag = urlHashArrays[1]; tag = tag.replaceAll(")","\\)").replaceAll("(","\\(").replaceAll(" ","_"); var tagDOM = $('#operations-tag-' + tag); if (!tagDOM.parent().hasClass('is-open')) { tagDOM.click(); } if(urlHashArrays.length == 3){ path = urlHash.replaceAll(")","\\)").replaceAll("(","\\(").replaceAll(" ","\\ "); path = path.replaceAll(/\//g, '-'); var toggleDOM = $('#operations' + path) if (!toggleDOM.hasClass('is-open')) { toggleDOM.children().eq(0).click(); } toggleDOM[0].scrollIntoView(); }else{ tagDOM[0].scrollIntoView(); } } } } $.ajax({ url: url, dataType: 'text', success: function (data) { if (/^\s*[{[]/.test(data)) { // json string is error data = eval('x = ' + data + '\n x;') analysisData(data) } else { // yaml text var script = document.createElement('script') script.src = '//cdn.bootcss.com/js-yaml/3.10.0/js-yaml.min.js' document.head.appendChild(script) script.onload = function () { data = jsyaml.safeLoad(data) analysisData(data) } } } }) } $('head').append( '<style type="text/css">' + '.inject-btn-container {' + 'text-align: center;' + '}' + '.inject-btn-container button {' + 'margin-left: 5px;' + 'margin-right: 5px;' + '}' + '.search-item-type{' + 'display: inline-block;' + 'min-width: 15px;' + '}' + '.search-item-method {' + 'display: inline-block;' + 'width: 65px;' + 'text-align: center' + '}' + '.search-item-summary {' + 'display: inline-block;' + 'width: auto;' + 'float: right;' + 'max-width: 200px;' + 'overflow: hidden;' + 'text-overflow: ellipsis;' + 'white-space: nowrap;' + 'text-align: right;' + '}' + '.search-main {' + 'position: static;' + 'margin: 40px auto 40px;' + 'width: 68%;' + 'min-width: 500px;' + '}' + '.search-container {' + 'overflow-y: auto;' + 'background-color: rgba(0, 0, 0, .3);' + 'position: fixed;' + 'left: 0;' + 'right: 0;' + 'top: 0;' + 'bottom: 0;' + 'z-index: 1000;' + '}' + '.search-input {' + 'line-height: 30px;' + 'font-size: 18px;' + 'display: block;' + 'margin: auto;' + 'width: 100%;' + 'border: none;' + 'border-bottom: 1px solid #89bf04;' + 'padding: 4px 10px 2px;' + 'box-sizing: border-box;' + 'height: 41px;' + 'border: 1px solid #ccc;' + 'border-color: #66B029;' + '}' + '.search-input:focus {' + 'outline: none;' + '}' + '.search-found-list {' + 'position: static;' + 'left: 0;' + 'right: 0;' + 'padding: 0;' // + 'max-height: 200px;' + 'overflow: auto;' + '' + '}' + '.search-found-list {' + 'margin-top: 2px;' + 'list-style: none;' + '}' + '.search-item.active, .search-item:hover {' + 'background-color: #eee;' + '}' + '.search-item {' + 'cursor: pointer;' + 'background-color: #fff;' + 'padding: 7px 15px;' + 'border: 1px solid #333;' + 'border-bottom: none;' + '}' + '.search-item:last-child {' + 'border-bottom: 1px solid #333;' + '}' + '.copy-route-btn {' + 'float: right;' + 'margin-left: 10px;' + 'background: none;' + 'border: none;' + 'cursor: pointer;' + 'font-size: 14px;' + 'color: #666;' + '}' + '.copy-route-btn:hover {' + 'color: #333;' + '}' + '</style>' ) // auto scrollIntoView by hash setTimeout(function () { var a = $('a[href="' + location.hash + '"]')[0] a && a.scrollIntoView() }, 200) } } )(window.jQuery) }, 1000 ) })