feat: added highlighting of search pattern and new search bar design
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.search')"
|
||||
svg="search"
|
||||
@click.native="patternInputted = !patternInputted"
|
||||
@click.native="toggleSearch = !toggleSearch"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
@@ -45,16 +45,22 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="patternInputted"
|
||||
v-if="toggleSearch"
|
||||
class="w-full p-2 sticky top-0 z-10 text-center border-b border-dividerLight"
|
||||
>
|
||||
<input
|
||||
id=""
|
||||
v-model="pattern"
|
||||
type="text"
|
||||
placeholder="Enter search pattern"
|
||||
class="p-1 border rounded bg-primaryLight border-divider text-secondaryDark text-center"
|
||||
/>
|
||||
<span
|
||||
class="bg-primaryLight border-divider text-secondaryDark rounded inline-flex"
|
||||
>
|
||||
<ButtonSecondary svg="search" class="item-center" />
|
||||
|
||||
<input
|
||||
id=""
|
||||
v-model="pattern"
|
||||
type="text"
|
||||
placeholder="Enter search pattern"
|
||||
class="rounded w-64 bg-primaryLight text-secondaryDark text-center"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -69,6 +75,7 @@
|
||||
v-for="(entry, index) in logEntries"
|
||||
:key="`entry-${index}`"
|
||||
:entry="entry"
|
||||
:highlight-regex="pattern === '' ? undefined : patternRegex"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,6 +85,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "@nuxtjs/composition-api"
|
||||
import { useThrottleFn, useScroll } from "@vueuse/core"
|
||||
import { regexEscape } from "~/helpers/functional/regex"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
|
||||
export type LogEntryData = {
|
||||
@@ -136,11 +144,15 @@ const toggleAutoscroll = () => {
|
||||
}
|
||||
|
||||
const pattern = ref("")
|
||||
const patternInputted = ref(false)
|
||||
const toggleSearch = ref(false)
|
||||
|
||||
const patternRegex = computed(
|
||||
() => new RegExp(regexEscape(pattern.value), "gi")
|
||||
)
|
||||
|
||||
const logEntries = computed(() => {
|
||||
if (pattern.value) {
|
||||
return props.log.filter((entry) => entry.payload.includes(pattern.value))
|
||||
if (patternRegex.value) {
|
||||
return props.log.filter((entry) => entry.payload.match(patternRegex.value))
|
||||
} else return props.log
|
||||
})
|
||||
|
||||
|
||||
@@ -31,7 +31,13 @@
|
||||
<span v-if="entry.prefix !== undefined" class="!inline">{{
|
||||
entry.prefix
|
||||
}}</span>
|
||||
{{ entry.payload }}
|
||||
<span
|
||||
v-for="(section, index) in highlightingSections"
|
||||
:key="index"
|
||||
class="!inline"
|
||||
:class="section.mode === 'highlight' ? 'highlight' : ''"
|
||||
>{{ section.text }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -208,6 +214,7 @@ import { LogEntryData } from "./Log.vue"
|
||||
import { useI18n } from "~/helpers/utils/composables"
|
||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||
import { isJSON } from "~/helpers/functional/json"
|
||||
import { regexFindAllMatches } from "~/helpers/functional/regex"
|
||||
import useCopyResponse from "~/helpers/lenses/composables/useCopyResponse"
|
||||
import useDownloadResponse from "~/helpers/lenses/composables/useDownloadResponse"
|
||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||
@@ -220,12 +227,55 @@ import {
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{ entry: LogEntryData }>()
|
||||
const props = defineProps<{
|
||||
entry: LogEntryData
|
||||
highlightRegex?: RegExp
|
||||
}>()
|
||||
const outlineOptions = ref<any | null>(null)
|
||||
const editor = ref<any | null>(null)
|
||||
const linewrapEnabled = ref(true)
|
||||
const logPayload = computed(() => props.entry.payload)
|
||||
|
||||
type HighlightSection = {
|
||||
mode: "normal" | "highlight"
|
||||
text: string
|
||||
}
|
||||
|
||||
const highlightingSections = computed<HighlightSection[]>(() => {
|
||||
if (!props.highlightRegex)
|
||||
return [{ mode: "normal", text: props.entry.payload }]
|
||||
|
||||
const line = props.entry.payload.split("\n")[0]
|
||||
|
||||
const ranges = pipe(line, regexFindAllMatches(props.highlightRegex))
|
||||
|
||||
const result: HighlightSection[] = []
|
||||
let point = 0
|
||||
|
||||
ranges.forEach(({ startIndex, endIndex }) => {
|
||||
if (point < startIndex)
|
||||
result.push({
|
||||
mode: "normal",
|
||||
text: line.slice(point, startIndex),
|
||||
})
|
||||
|
||||
result.push({
|
||||
mode: "highlight",
|
||||
text: line.slice(startIndex, endIndex + 1),
|
||||
})
|
||||
|
||||
point = endIndex + 1
|
||||
})
|
||||
|
||||
if (point < line.length)
|
||||
result.push({
|
||||
mode: "normal",
|
||||
text: line.slice(point, line.length),
|
||||
})
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const selectedTab = ref<"json" | "raw">(
|
||||
isJSON(props.entry.payload) ? "json" : "raw"
|
||||
)
|
||||
@@ -385,4 +435,8 @@ const iconName = computed(() => ICONS[props.entry.source].iconName)
|
||||
.ts-font {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: yellow;
|
||||
}
|
||||
</style>
|
||||
|
||||
33
packages/hoppscotch-app/helpers/functional/regex.ts
Normal file
33
packages/hoppscotch-app/helpers/functional/regex.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Escapes special regex characters in a string.
|
||||
* @param text The string to transform
|
||||
* @returns Escaped string
|
||||
*/
|
||||
export const regexEscape = (text: string) =>
|
||||
text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
|
||||
|
||||
export type RegexMatch = {
|
||||
matchString: string
|
||||
startIndex: number
|
||||
endIndex: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the regex match ranges for a given input
|
||||
* @param regex The Regular Expression to find from
|
||||
* @param input The input string to get match ranges from
|
||||
* @returns An array of `RegexMatch` objects giving info about the matches
|
||||
*/
|
||||
export const regexFindAllMatches = (regex: RegExp) => (input: string) => {
|
||||
const matches: RegexMatch[] = []
|
||||
|
||||
// eslint-disable-next-line no-cond-assign, prettier/prettier
|
||||
for (let match; match = regex.exec(input); match !== null)
|
||||
matches.push({
|
||||
matchString: match[0],
|
||||
startIndex: match.index,
|
||||
endIndex: match.index + match[0].length - 1,
|
||||
})
|
||||
|
||||
return matches
|
||||
}
|
||||
Reference in New Issue
Block a user