npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

highlight-search

v1.1.3

Published

搜索建议组件。

Readme

Highlight search

搜索建议组件。

开始

安装组件:

 pnpm install highlight-search 

创建实例

import { createMEditor, parseFetchRes2FieldMapping, MappingItem, Suggest } from "highlight-search";

// for vue2
this.suggest=new Suggest({
 mapping: mappings,
 wordElement: () => {
  return document.querySelectorAll('.word')
 },
 async fetchQuery(params, options) {
 	const { field, operator } = params
	const queryParams = {
    	field: field.value,
        operator: operator,
        start_time: new Date("2022-12-01").getTime(),
        module: MODULE_NAME,
        end_time: new Date("2023-12-02").getTime()
    }
    try {
          // use fetch or axios to got mappingField List
        const res = await fetch(`/api/v1/sop/utils/xqlite/suggest/`, {
          signal: options?.canalSignal,
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(queryParams)
        })
        const info = await res.json() as {
          data: string[]
        }
        return [{
          title: "搜索建议",
          mappings: info.data.map((item) => {
            return {
              name: item,
              value: `${item}`,
              match: `${item}`
            }
          }),
          render(item: MappingItem) {
            return item.name
          },
        }]
      } catch (e) {
        console.warn("[meditor],fetchQuery error", e)
        return []
      }
    },    
})

// for vue3 
const suggest = ref(new Suggest({
    // .. same as vue2 config
}))

// 然后创建editor实例
const editor = createMeditor({
	// 要挂载到的dom元素,可以直接设置dom元素,或者通过函数返回
	target:HTMLElement | ()=>HTMLElement
    //将上面的suggest传递过来,保存响应式
    // vue2
    suggest:this.suggest;
    //vue3
	suggest:suggest.value,
    
	onSubmit:(value:string)=>{
		// 编辑器触发搜索时的回调,value是纯文本字符串,等同于editor.getValue()
      // 检索完毕之后,调用suggest.removeCache() 清空检索缓存
	}
})

createMeditor会返回editor实例,在原有TextBus的editor实例的基础上,还挂载了辅助函数。

suggest:搜索建议相关的所有东西都在这里处理。

Suggest对象上可能会用到的一些属性和函数:

| 名称 | 类型 | 说明 | | ---------------- | -------------------------- | -------------------------------------------- | | loading | boolean(只读) | 是否正在loading,目前仅在查询接口时会调用。 | | showList | FieldMapping[] (只读) | 应该展示的候选词,已经过滤并且按原分组分割 | | queryTypeByText | (text:string)=>MappingType | 根据传入的单词判断该字符串的类型 | | queryNextType | (text:string)=>MappingType | 根据传入的单词推断下一个字符串的类型 | | parseToFormatter | (text:string)=>Formatter[] | 根据传入的字符串返回解析格式之后的类型 | | removeCache | ()=>void | 清除缓存数据,建议每次提交检索之后都调用一次 |

editor实例上挂载的其他函数

| 名称 | 参数类型 | 说明 | | -------- | ------------------ | ---------------------------------------------------------- | | choose | (item:MappingItem) | 用于鼠标手动选中候选词的插入 | | setValue | (str:string) | 手动设置编辑器的内容,会自动解析格式并格式化渲染到编辑器中 | | getValue | - | 获取当前编辑器的内容,返回纯文本字符串 |

使用示例

Vue2

<template>
  <div class="editor-box">
    <h3>Highlight-search In Vue2.x</h3>
    <div class="tool-line">
      <button @click="handleSetValue()">设置默认值</button>
      <button @click="handleSearch()">获取输入的语句 </button>
      <span>{{ userInputValue }}</span>
      <span>loading:{{ suggest?.loading }}</span>
    </div>
    <div ref="editorRef" id="editor">
    </div>
    <!-- 以下内容可以任意写,搜索建议的数组是meditor?.suggest.showList,同时,loading和一些辅助函数也已经暴露出来了 -->
    <TransitionGroup tag="div" name="fade" class="suggest-list">
      <div v-if="suggest?.loading" :key="123">
        loading...
      </div>
      <template v-else-if="suggest?.showList?.length">
        <div class="word-box" v-for="(item, index) in suggest?.showList" :key="index">
          <div class="word-title">{{ item.title }} </div>
          <TransitionGroup tag="div" name="fade" class="words">
            <div class="word" v-for="(word, index) in item.mappings" :key="index" :class="{
              active: suggest.activeItem?.value === word.value
            }" @click="handelChoose(word)">
              {{ item?.render?.(word) ?? word.name }}
            </div>
          </TransitionGroup>
        </div>
      </template>
      <div :key="222" v-else>
        暂无搜索建议
      </div>
    </TransitionGroup>
  </div>
</template>

<script>
import { createMEditor, parseFetchRes2FieldMapping, Suggest } from 'highlight-search'
import "highlight-search/index.css"
const MODULE_NAME = "alarm"
export default {
  name: 'App',
  data() {
    return {
      userInputValue: '',
      meditor: {},
      suggest: {}
    }
  },
  mounted() {
    this.initData()
  },
  methods: {
    async initData() {
      const res = await fetch("/api/v1/sop/utils/xqlite/mappings/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
      })
      const info = await res.json()
      const mappings = parseFetchRes2FieldMapping(info.data, MODULE_NAME)
      this.suggest = new Suggest({
        mapping: mappings,
        wordElement: () => {
          return document.querySelectorAll('.word')
        },
        async fetchQuery(params, options) {
          const { field, operator } = params
          const queryParams = {
            field: field.value,
            operator: operator,
            start_time: new Date("2022-12-01").getTime(),
            module: MODULE_NAME,
            end_time: new Date("2023-12-02").getTime()
          }
          try {
            const res = await fetch(`/api/v1/sop/utils/xqlite/suggest/`, {
              signal: options?.canalSignal,
              method: 'POST',
              headers: {
                'Content-Type': 'application/json'
              },
              body: JSON.stringify(queryParams)
            })
            const info = await res.json()
            return [{
              title: "搜索建议",
              mappings: info.data.map((item) => {
                return {
                  name: item,
                  value: `${item}`,
                  match: `${item}`
                }
              }),
              render(item) {
                return item.name
              },
            }]
          } catch (e) {
            console.warn("[meditor],fetchQuery error", e)
            return []
          }
        }
      })
      this.meditor = await createMEditor(
        {
          target: this.$refs['editorRef'],
          suggest: this.suggest,
          onSubmit: (value) => {
            console.log('[MEditor]:onSubmit,str is"', value + '"')
            alert(value)
          }
        })
    },
    handelChoose(word) {
      this.meditor?.choose(word)
    },
    handleSetValue() {
      const info = "source_ip = 123 and destination_ip_province = 123.2.2.2 or log_name exist"
      this.meditor.setValue(info)
    },
    handleSearch() {
      const str = this.meditor?.getValue()
      console.log('meditor.value.suggest.showList', this.meditor.suggest.showList)
      this.userInputValue = str ?? ''
      this.meditor.suggest.removeCache()
    }
  }
}
</script>

<style lang="scss">
.editor-box {
  display: flex;
  flex-direction: column;
  height: 100vh;
  width: 100vw;
  overflow: hidden;
}

#editor {
  flex-shrink: 0;
  text-align: left;
  width: 100%;
}

.tool-line {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-bottom: 20px;

  button {
    margin: 0 20px;
  }
}

.suggest-list {
  flex: 1 0 0;
  overflow-y: auto;
  margin-top: 10px;
  width: 100%;
  display: flex;
  flex-direction: column;
  text-align: left;
  position: relative;
  align-items: flex-start;

  .word-box {
    width: calc(100% - 40px);
    padding: 20px;
    background-color: #eeeeee;

    .word-title {
      text-align: left;
      font-size: 20px;
      font-weight: bold;
      flex-shrink: 0;
      margin-left: 10px;
    }

    .words {
      display: flex;
      flex-wrap: wrap;

      .word {
        margin: 10px;
        padding: 6px 10px;
        background-color: #fff;
        border-radius: 2px;
        cursor: pointer;

        &:hover {
          background-color: #cccccc;
          color: #ffffff;
        }

        &.active {
          background-color: #000000;
          color: #ffffff;
        }
      }
    }
  }
}

/* 1. 声明过渡效果 */
.fade-move,
.fade-enter-active,
.fade-leave-active {
  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* 2. 声明进入和离开的状态 */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: scaleY(0.01) translate(30px, 0);
}

/* 3. 确保离开的项目被移除出了布局流
      以便正确地计算移动时的动画效果。 */
.fade-leave-active {
  position: absolute;
}
</style>

Vue3


<template>
  <div class="editor-box">
    <h3>highlight-search in Vue3 </h3>
    <div class="tool-line">
      <button @click="handleSetValue()">设置默认值</button>
      <button @click="handleSearch()">获取输入的语句 </button>
      <span>{{ userInputValue }}</span>
      <span>loading:{{ meditor?.suggest?.loading }}</span>
    </div>
    <div ref="editorRef" id="editor">
    </div>
    <!-- 以下内容可以任意写,搜索建议的数组是meditor?.suggest.showList,同时,loading和一些辅助函数也已经暴露出来了 -->
    <TransitionGroup tag="div" name="fade" class="suggest-list">
      <div v-if="suggest?.loading" :key="123">
        loading...
      </div>
      <div class="word-box" v-else-if="suggest?.showList?.length" v-for="(item, index) in suggest?.showList" :key="index">
        <div class="word-title">{{ item.title }} </div>
        <TransitionGroup tag="div" name="fade" class="words">
          <div class="word" v-for="(word, index) in item.mappings" :key="index" :class="{
            active: suggest?.activeItem?.value === word.value
          }" @click="handelChoose(word)">
            {{ item?.render?.(word) ?? word.name }}
          </div>
        </TransitionGroup>
      </div>
      <div :key="222" v-else>
        暂无搜索建议
      </div>
    </TransitionGroup>
  </div>
</template>

<script setup lang="ts" name="App">
import { onMounted, ref, watch } from "vue";
import { createMEditor, parseFetchRes2FieldMapping, MappingItem, Suggest } from "highlight-search";
import "highlight-search/index.css";
const editorRef = ref<HTMLElement>()
const meditor = ref()
const suggest = ref()
const userInputValue = ref('')
const MODULE_NAME = "alarm"
const initEditor = async () => {
  const res = await fetch("/api/v1/sop/utils/xqlite/mappings/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
  })
  const info = await res.json()
  const mappings = parseFetchRes2FieldMapping(info.data, MODULE_NAME)
  suggest.value = new Suggest({
    mapping: mappings,
    wordElement: () => {
      return document.querySelectorAll('.word')
    },
    async fetchQuery(params, options) {
      const { field, operator } = params
      const queryParams = {
        field: field.value,
        operator: operator,
        start_time: new Date("2022-12-01").getTime(),
        module: MODULE_NAME,
        end_time: new Date("2023-12-02").getTime()
      }
      try {
        const res = await fetch(`/api/v1/sop/utils/xqlite/suggest/`, {
          signal: options?.canalSignal,
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(queryParams)
        })
        const info = await res.json() as {
          data: string[]
        }
        return [{
          title: "搜索建议",
          mappings: info.data.map((item) => {
            return {
              name: item,
              value: `${item}`,
              match: `${item}`
            }
          }),
          render(item: MappingItem) {
            return item.name
          },
        }]
      } catch (e) {
        console.warn("[meditor],fetchQuery error", e)
        return []
      }
    },
  })
  const editor = await createMEditor(
    {
      target: editorRef.value!,
      suggest: suggest.value,
      onSubmit: (value: any) => {
        alert(value)
      }
    })
  meditor.value = editor
}

watch(() => suggest, (val) => {
  console.log('val', val)
}, {
  deep: true
})

onMounted(() => {
  initEditor()
})
const handelChoose = (word: MappingItem) => {
  meditor.value?.choose(word)
}
const handleSetValue = () => {
  const info = "source_ip = 123 and destination_ip_province = 123.2.2.2 or log_name exist"
  meditor.value?.setValue(info)
}
const handleSearch = () => {
  const str = meditor.value?.getValue()
  console.log('meditor.value.suggest.showList', meditor.value?.suggest?.showList)
  userInputValue.value = str ?? ''
  suggest.value.removeCache()
}

</script>

<style scoped lang="scss">
.editor-box {
  display: flex;
  flex-direction: column;
  height: 100vh;
  width: 100%;
  overflow: hidden;
}

#editor {
  flex-shrink: 0;
  text-align: left;
  width: 100%;
}

.tool-line {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-bottom: 20px;

  button {
    margin: 0 20px;
  }
}

.suggest-list {
  flex: 1 0 0;
  overflow-y: auto;
  margin-top: 10px;
  width: 100%;
  display: flex;
  flex-direction: column;
  text-align: left;
  position: relative;
  align-items: flex-start;

  .word-box {
    width: calc(100% - 40px);
    padding: 20px;
    background-color: #eeeeee;

    .word-title {
      text-align: left;
      font-size: 20px;
      font-weight: bold;
      flex-shrink: 0;
      margin-left: 10px;
    }

    .words {
      display: flex;
      flex-wrap: wrap;

      .word {
        margin: 10px;
        padding: 6px 10px;
        background-color: #fff;
        border-radius: 2px;
        cursor: pointer;

        &:hover {
          background-color: #cccccc;
          color: #ffffff;
        }

        &.active {
          background-color: #000000;
          color: #ffffff;
        }
      }
    }
  }
}

/* 1. 声明过渡效果 */
.fade-move,
.fade-enter-active,
.fade-leave-active {
  transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}

/* 2. 声明进入和离开的状态 */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: scaleY(0.01) translate(30px, 0);
}

/* 3. 确保离开的项目被移除出了布局流
      以便正确地计算移动时的动画效果。 */
.fade-leave-active {
  position: absolute;
}
</style>

自定义样式

插件并不会自带任何样式,但是预设了一套基本的样式。

你可以引用查看简单的示例

import "highlight-search/index.css"

实际渲染的dom结构是这样的(最理想的情况下,实际上会存在各种随机的嵌套)

<div data-component="root" class="meditor-root">
    <span class="meditor-comp-mappingField">source_ip</span>
    <span class="meditor-comp-comparator">&nbsp;=</span>
    <span class="meditor-comp-inputValue">&nbsp;123</span>
    <span class="meditor-comp-logical">&nbsp;and</span>
    <span class="meditor-comp-mappingField">&nbsp;dest_port</span>
    <span class="meditor-comp-comparator">&nbsp;=</span>
    <span class="meditor-comp-inputValue">&nbsp;80</span>
</div>

涉及到的class类名

  • meditor-root : root元素,所有的内容都在改dom元素下。
  • meditor-comp-mappingField :MappingField 对应的class类名。( 其余的同理)

深入原理

需求理解

需求拆分

设计实现

兼容处理