swagger-ui-2.7.0

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 20250620
// @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),
              )

              if (swaggerVersion === 1) {
                dom.append(btns)
                // 调整按钮区域和提示文字的间距
                btns.css('margin-bottom', '5px')
              }
              dom.append([
                '<div class="search-tip-container">',
                '<small class="search-tip clickable-tip">点击这里搜索 | 双击A/Shift快捷搜索 | Esc关闭</small>',
                '</div>',
                '<div class="search-container" style="display: none;">',
                '<div class="search-main">',
                '<input class="search-input" placeholder="🔍 搜索接口... (支持路径、方法、描述)"/>',
                '<ul class="search-found-list">',
                '</ul>',
                '</div>',
                '</div>'
              ].join(''))

              var searchContainer = dom.find('.search-container')

              // 添加点击提示文字打开搜索的功能
              dom.find('.clickable-tip').on('click', function() {
                setTimeout(function () {
                  $('body').css({ overflow: 'hidden' })
                  searchContainer.show()
                  searchContainer.find('.search-input').focus().select()
                }, 0)
              })

              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 method = data.entity.m.toUpperCase()
                  var methodClass = 'method-' + data.entity.m.toLowerCase()
                  var html = '<li class="search-item ' + (
                                                         i === 0 ? 'active' : ''
                                                       ) + '">'
                             + '<div class="search-item-content">'
                             + '<span class="search-item-type">' + data.type + '</span>'
                             + '<span class="search-item-method ' + methodClass + '">' + method + '</span>'
                             + '<span class="search-item-path">' + data.entity.p + '</span>'
                             + '</div>'
                             + '<div class="search-item-right">'
                             + '<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>'
                             + '</div>'
                             + '</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">'
                + ':root {'
                + '--primary-color: #3498db;'
                + '--primary-dark: #2980b9;'
                + '--success-color: #27ae60;'
                + '--danger-color: #e74c3c;'
                + '--warning-color: #f39c12;'
                + '--info-color: #17a2b8;'
                + '--light-bg: #f8f9fa;'
                + '--dark-text: #2c3e50;'
                + '--light-text: #6c757d;'
                + '--border-color: #dee2e6;'
                + '--shadow-sm: 0 2px 4px rgba(0,0,0,0.1);'
                + '--shadow-md: 0 4px 8px rgba(0,0,0,0.15);'
                + '--shadow-lg: 0 8px 24px rgba(0,0,0,0.2);'
                + '--border-radius: 8px;'
                + '--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);'
                + '}'
                + '.inject-btn-container {'
                + 'text-align: center;'
                + 'margin: 20px 0 10px 0;'
                + 'padding: 12px 15px 8px 15px;'
                + 'border-radius: var(--border-radius);'
                + '}'
                + '.inject-btn-container button {'
                + 'margin: 0 6px;'
                + 'padding: 8px 16px;'
                + 'background: white;'
                + 'border: 2px solid var(--border-color);'
                + 'border-radius: 6px;'
                + 'color: var(--dark-text);'
                + 'font-weight: 500;'
                + 'cursor: pointer;'
                + 'transition: var(--transition);'
                + 'font-size: 13px;'
                + '}'
                + '.inject-btn-container button:hover {'
                + 'border-color: var(--primary-color);'
                + 'color: var(--primary-color);'
                + 'transform: translateY(-1px);'
                + 'box-shadow: var(--shadow-sm);'
                + '}'
                + '.inject-btn-container button:active {'
                + 'transform: translateY(0);'
                + '}'
                + '.search-item-type{'
                + 'display: inline-block;'
                + 'min-width: 20px;'
                + 'font-size: 12px;'
                + 'font-weight: 600;'
                + 'color: var(--primary-color);'
                + 'text-transform: uppercase;'
                + '}'
                + '.search-item-method {'
                + 'display: inline-block;'
                + 'min-width: 60px;'
                + 'text-align: center;'
                + 'font-weight: 600;'
                + 'font-size: 11px;'
                + 'padding: 6px 12px;'
                + 'border-radius: 4px;'
                + 'color: white;'
                + 'text-transform: uppercase;'
                + 'letter-spacing: 0.5px;'
                + 'box-shadow: 0 1px 2px rgba(0,0,0,0.1);'
                + '}'
                + '.search-item .search-item-method {'
                + 'background: #6c757d;'
                + 'color: white;'
                + '}'
                + '.method-get {'
                + 'background: #61affe !important;'
                + 'color: white !important;'
                + '}'
                + '.method-post {'
                + 'background: #49cc90 !important;'
                + 'color: white !important;'
                + '}'
                + '.method-put {'
                + 'background: #fca130 !important;'
                + 'color: white !important;'
                + '}'
                + '.method-delete {'
                + 'background: #f93e3e !important;'
                + 'color: white !important;'
                + '}'
                + '.method-patch {'
                + 'background: #50e3c2 !important;'
                + 'color: white !important;'
                + '}'
                + '.method-head {'
                + 'background: #9012fe !important;'
                + 'color: white !important;'
                + '}'
                + '.method-options {'
                + 'background: #0d5aa7 !important;'
                + 'color: white !important;'
                + '}'
                + '.search-item-summary {'
                + 'width: 240px;'
                + 'overflow: hidden;'
                + 'text-overflow: ellipsis;'
                + 'white-space: nowrap;'
                + 'color: var(--light-text);'
                + 'font-size: 13px;'
                + 'font-style: italic;'
                + 'text-align: right;'
                + 'flex-shrink: 0;'
                + '}'
                + '.search-main {'
                + 'position: static;'
                + 'margin: 40px auto;'
                + 'width: 70%;'
                + 'max-width: 800px;'
                + 'min-width: 500px;'
                + 'background: white;'
                + 'border-radius: 12px;'
                + 'box-shadow: var(--shadow-lg);'
                + 'overflow: hidden;'
                + 'backdrop-filter: blur(20px);'
                + '}'
                + '.search-container {'
                + 'overflow-y: auto;'
                + 'background: linear-gradient(135deg, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0.4) 100%);'
                + 'backdrop-filter: blur(8px);'
                + 'position: fixed;'
                + 'left: 0;'
                + 'right: 0;'
                + 'top: 0;'
                + 'bottom: 0;'
                + 'z-index: 10000;'
                + 'animation: fadeIn 0.3s ease-out;'
                + '}'
                + '@keyframes fadeIn {'
                + 'from { opacity: 0; }'
                + 'to { opacity: 1; }'
                + '}'
                + '.search-input {'
                + 'line-height: 30px;'
                + 'font-size: 18px;'
                + 'display: block;'
                + 'margin: 0;'
                + 'width: 100%;'
                + 'border: none;'
                + 'padding: 20px 24px;'
                + 'box-sizing: border-box;'
                + 'height: 70px;'
                + 'background: white;'
                + 'color: var(--dark-text);'
                + 'border-bottom: 3px solid var(--border-color);'
                + 'transition: var(--transition);'
                + 'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;'
                + '}'
                + '.search-input::placeholder {'
                + 'color: var(--light-text);'
                + 'font-style: italic;'
                + '}'
                + '.search-input:focus {'
                + 'outline: none;'
                + 'border-bottom-color: var(--primary-color);'
                + 'box-shadow: inset 0 -3px 0 var(--primary-color);'
                + '}'
                + '.search-found-list {'
                + 'position: static;'
                + 'left: 0;'
                + 'right: 0;'
                + 'padding: 0;'
                + 'margin: 0;'
                + 'max-height: 60vh;'
                + 'overflow-y: auto;'
                + 'background: white;'
                + '}'
                + '.search-found-list::-webkit-scrollbar {'
                + 'width: 6px;'
                + '}'
                + '.search-found-list::-webkit-scrollbar-track {'
                + 'background: var(--light-bg);'
                + '}'
                + '.search-found-list::-webkit-scrollbar-thumb {'
                + 'background: var(--light-text);'
                + 'border-radius: 3px;'
                + '}'
                + '.search-found-list::-webkit-scrollbar-thumb:hover {'
                + 'background: var(--dark-text);'
                + '}'
                + '.search-found-list {'
                + 'list-style: none;'
                + '}'
                + '.search-item.active {'
                + 'background: linear-gradient(135deg, #e3f2fd 0%, #f1f8e9 100%);'
                + 'border-left: 4px solid var(--primary-color);'
                + 'transform: translateX(4px);'
                + '}'
                + '.search-item:hover {'
                + 'background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);'
                + 'transform: translateX(2px);'
                + '}'
                + '.search-item {'
                + 'cursor: pointer;'
                + 'background: white;'
                + 'padding: 12px 20px;'
                + 'border-bottom: 1px solid var(--border-color);'
                + 'transition: var(--transition);'
                + 'position: relative;'
                + 'display: flex;'
                + 'align-items: center;'
                + 'justify-content: flex-start;'
                + '}'
                + '.search-item-content {'
                + 'display: flex;'
                + 'align-items: center;'
                + 'gap: 12px;'
                + 'flex: 1;'
                + 'min-width: 0;'
                + '}'
                + '.search-item-right {'
                + 'display: flex;'
                + 'align-items: center;'
                + 'gap: 8px;'
                + 'flex-shrink: 0;'
                + 'width: 320px;'
                + 'justify-content: flex-end;'
                + 'margin-left: auto;'
                + '}'
                + '.search-item:last-child {'
                + 'border-bottom: none;'
                + '}'
                + '.search-item:before {'
                + 'content: "";'
                + 'position: absolute;'
                + 'left: 0;'
                + 'top: 0;'
                + 'bottom: 0;'
                + 'width: 0;'
                + 'background: var(--primary-color);'
                + 'transition: var(--transition);'
                + '}'
                + '.search-item:hover:before {'
                + 'width: 3px;'
                + '}'
                + '.search-item-path {'
                + 'font-family: "Monaco", "Menlo", "Consolas", monospace;'
                + 'font-size: 14px;'
                + 'color: var(--dark-text);'
                + 'font-weight: 500;'
                + 'max-width: 400px;'
                + 'overflow: hidden;'
                + 'text-overflow: ellipsis;'
                + 'white-space: nowrap;'
                + '}'
                + '.copy-route-btn {'
                + 'padding: 8px 12px;'
                + 'background: linear-gradient(135deg, var(--success-color), #2ecc71);'
                + 'border: none;'
                + 'border-radius: 6px;'
                + 'cursor: pointer;'
                + 'font-size: 12px;'
                + 'color: white;'
                + 'transition: var(--transition);'
                + 'font-weight: 500;'
                + 'box-shadow: var(--shadow-sm);'
                + 'width: 60px;'
                + 'flex-shrink: 0;'
                + '}'
                + '.copy-route-btn:hover {'
                + 'background: linear-gradient(135deg, #2ecc71, var(--success-color));'
                + 'transform: translateY(-1px);'
                + 'box-shadow: var(--shadow-md);'
                + '}'
                + '.copy-route-btn:active {'
                + 'transform: translateY(0);'
                + '}'
                + '@media (max-width: 768px) {'
                + '.search-main {'
                + 'width: 95%;'
                + 'min-width: auto;'
                + 'margin: 20px auto;'
                + '}'
                + '.search-item {'
                + 'padding: 12px 16px;'
                + 'flex-direction: column;'
                + 'align-items: flex-start;'
                + 'gap: 8px;'
                + '}'
                + '.search-item-content {'
                + 'width: 100%;'
                + '}'
                + '.search-item-path {'
                + 'max-width: 250px;'
                + '}'
                + '.search-item-right {'
                + 'width: 100%;'
                + 'justify-content: space-between;'
                + 'margin-left: 0;'
                + '}'
                + '.search-item-summary {'
                + 'width: auto;'
                + 'max-width: 200px;'
                + 'text-align: left;'
                + '}'
                + '.copy-route-btn {'
                + 'width: 50px;'
                + '}'
                + '.search-tip-container {'
                + 'margin: 0 0 15px 0;'
                + '}'
                + '.search-tip {'
                + 'font-size: 11px;'
                + 'padding: 6px 12px;'
                + '}'
                + '}'
                + '.search-tip-container {'
                + 'text-align: center;'
                + 'margin: 0 0 20px 0;'
                + 'padding: 0;'
                + '}'
                + '.search-tip {'
                + 'display: inline-block;'
                + 'background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);'
                + 'color: var(--light-text);'
                + 'padding: 6px 14px;'
                + 'border-radius: 20px;'
                + 'font-size: 11px;'
                + 'font-weight: 500;'
                + 'border: 1px solid var(--border-color);'
                + 'box-shadow: var(--shadow-sm);'
                + 'letter-spacing: 0.3px;'
                + 'transition: var(--transition);'
                + '}'
                + '.clickable-tip {'
                + 'cursor: pointer;'
                + 'user-select: none;'
                + '}'
                + '.clickable-tip:hover {'
                + 'border-color: var(--primary-color);'
                + 'color: var(--primary-color);'
                + 'transform: translateY(-1px);'
                + 'box-shadow: var(--shadow-sm);'
                + '}'
                + '.clickable-tip:active {'
                + 'transform: translateY(0);'
                + 'box-shadow: var(--shadow-sm);'
                + '}'
                + '.search-container small {'
                + 'background: rgba(255, 255, 255, 0.95);'
                + 'padding: 12px 20px;'
                + 'border-radius: 8px;'
                + 'box-shadow: var(--shadow-sm);'
                + 'color: var(--dark-text);'
                + 'font-weight: 500;'
                + 'backdrop-filter: blur(10px);'
                + '}'
                + '</style>'
              )

              // auto scrollIntoView by hash
              setTimeout(function () {
                var a = $('a[href="' + location.hash + '"]')[0]
                a && a.scrollIntoView()
              }, 200)

            }


          }
        )(window.jQuery)
      },
      1000
    )
  })