feat: added highlighting of search pattern and new search bar design
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="t('action.search')"
|
:title="t('action.search')"
|
||||||
svg="search"
|
svg="search"
|
||||||
@click.native="patternInputted = !patternInputted"
|
@click.native="toggleSearch = !toggleSearch"
|
||||||
/>
|
/>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -45,16 +45,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="patternInputted"
|
v-if="toggleSearch"
|
||||||
class="w-full p-2 sticky top-0 z-10 text-center border-b border-dividerLight"
|
class="w-full p-2 sticky top-0 z-10 text-center border-b border-dividerLight"
|
||||||
>
|
>
|
||||||
<input
|
<span
|
||||||
id=""
|
class="bg-primaryLight border-divider text-secondaryDark rounded inline-flex"
|
||||||
v-model="pattern"
|
>
|
||||||
type="text"
|
<ButtonSecondary svg="search" class="item-center" />
|
||||||
placeholder="Enter search pattern"
|
|
||||||
class="p-1 border rounded bg-primaryLight border-divider text-secondaryDark text-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>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -69,6 +75,7 @@
|
|||||||
v-for="(entry, index) in logEntries"
|
v-for="(entry, index) in logEntries"
|
||||||
:key="`entry-${index}`"
|
:key="`entry-${index}`"
|
||||||
:entry="entry"
|
:entry="entry"
|
||||||
|
:highlight-regex="pattern === '' ? undefined : patternRegex"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,6 +85,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from "@nuxtjs/composition-api"
|
import { ref, computed, watch } from "@nuxtjs/composition-api"
|
||||||
import { useThrottleFn, useScroll } from "@vueuse/core"
|
import { useThrottleFn, useScroll } from "@vueuse/core"
|
||||||
|
import { regexEscape } from "~/helpers/functional/regex"
|
||||||
import { useI18n } from "~/helpers/utils/composables"
|
import { useI18n } from "~/helpers/utils/composables"
|
||||||
|
|
||||||
export type LogEntryData = {
|
export type LogEntryData = {
|
||||||
@@ -136,11 +144,15 @@ const toggleAutoscroll = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pattern = ref("")
|
const pattern = ref("")
|
||||||
const patternInputted = ref(false)
|
const toggleSearch = ref(false)
|
||||||
|
|
||||||
|
const patternRegex = computed(
|
||||||
|
() => new RegExp(regexEscape(pattern.value), "gi")
|
||||||
|
)
|
||||||
|
|
||||||
const logEntries = computed(() => {
|
const logEntries = computed(() => {
|
||||||
if (pattern.value) {
|
if (patternRegex.value) {
|
||||||
return props.log.filter((entry) => entry.payload.includes(pattern.value))
|
return props.log.filter((entry) => entry.payload.match(patternRegex.value))
|
||||||
} else return props.log
|
} else return props.log
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,13 @@
|
|||||||
<span v-if="entry.prefix !== undefined" class="!inline">{{
|
<span v-if="entry.prefix !== undefined" class="!inline">{{
|
||||||
entry.prefix
|
entry.prefix
|
||||||
}}</span>
|
}}</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,6 +214,7 @@ import { LogEntryData } from "./Log.vue"
|
|||||||
import { useI18n } from "~/helpers/utils/composables"
|
import { useI18n } from "~/helpers/utils/composables"
|
||||||
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
import { isJSON } from "~/helpers/functional/json"
|
import { isJSON } from "~/helpers/functional/json"
|
||||||
|
import { regexFindAllMatches } from "~/helpers/functional/regex"
|
||||||
import useCopyResponse from "~/helpers/lenses/composables/useCopyResponse"
|
import useCopyResponse from "~/helpers/lenses/composables/useCopyResponse"
|
||||||
import useDownloadResponse from "~/helpers/lenses/composables/useDownloadResponse"
|
import useDownloadResponse from "~/helpers/lenses/composables/useDownloadResponse"
|
||||||
import { useCodemirror } from "~/helpers/editor/codemirror"
|
import { useCodemirror } from "~/helpers/editor/codemirror"
|
||||||
@@ -220,12 +227,55 @@ import {
|
|||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const props = defineProps<{ entry: LogEntryData }>()
|
const props = defineProps<{
|
||||||
|
entry: LogEntryData
|
||||||
|
highlightRegex?: RegExp
|
||||||
|
}>()
|
||||||
const outlineOptions = ref<any | null>(null)
|
const outlineOptions = ref<any | null>(null)
|
||||||
const editor = ref<any | null>(null)
|
const editor = ref<any | null>(null)
|
||||||
const linewrapEnabled = ref(true)
|
const linewrapEnabled = ref(true)
|
||||||
const logPayload = computed(() => props.entry.payload)
|
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">(
|
const selectedTab = ref<"json" | "raw">(
|
||||||
isJSON(props.entry.payload) ? "json" : "raw"
|
isJSON(props.entry.payload) ? "json" : "raw"
|
||||||
)
|
)
|
||||||
@@ -385,4 +435,8 @@ const iconName = computed(() => ICONS[props.entry.source].iconName)
|
|||||||
.ts-font {
|
.ts-font {
|
||||||
font-size: 0.6rem;
|
font-size: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
</style>
|
</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