Commit d13b3810 authored by Joel Jacob Stephen's avatar Joel Jacob Stephen
Browse files

feat: added highlighting of search pattern and new search bar design

parent 663da34e
Showing with 113 additions and 14 deletions
+113 -14
......@@ -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>
/**
* 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
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment