Compare commits
2 Commits
feat/fe-ac
...
feat/realt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d13b381097 | ||
|
|
663da34e08 |
@@ -7,6 +7,12 @@
|
||||
{{ title }}
|
||||
</label>
|
||||
<div>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.search')"
|
||||
svg="search"
|
||||
@click.native="toggleSearch = !toggleSearch"
|
||||
/>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.delete')"
|
||||
@@ -37,6 +43,26 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="toggleSearch"
|
||||
class="w-full p-2 sticky top-0 z-10 text-center border-b border-dividerLight"
|
||||
>
|
||||
<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
|
||||
v-if="log.length !== 0"
|
||||
ref="logs"
|
||||
@@ -46,9 +72,10 @@
|
||||
class="flex flex-col h-auto h-full border-r divide-y divide-dividerLight border-dividerLight"
|
||||
>
|
||||
<RealtimeLogEntry
|
||||
v-for="(entry, index) in log"
|
||||
v-for="(entry, index) in logEntries"
|
||||
:key="`entry-${index}`"
|
||||
:entry="entry"
|
||||
:highlight-regex="pattern === '' ? undefined : patternRegex"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,8 +83,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, PropType, computed, watch } from "@nuxtjs/composition-api"
|
||||
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 = {
|
||||
@@ -68,13 +96,7 @@ export type LogEntryData = {
|
||||
event: "connecting" | "connected" | "disconnected" | "error"
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
log: { type: Array as PropType<LogEntryData[]>, default: () => [] },
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
})
|
||||
const props = defineProps<{ log: LogEntryData[]; title: string }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "delete"): void
|
||||
@@ -121,6 +143,19 @@ const toggleAutoscroll = () => {
|
||||
autoScrollEnabled.value = !autoScrollEnabled.value
|
||||
}
|
||||
|
||||
const pattern = ref("")
|
||||
const toggleSearch = ref(false)
|
||||
|
||||
const patternRegex = computed(
|
||||
() => new RegExp(regexEscape(pattern.value), "gi")
|
||||
)
|
||||
|
||||
const logEntries = computed(() => {
|
||||
if (patternRegex.value) {
|
||||
return props.log.filter((entry) => entry.payload.match(patternRegex.value))
|
||||
} else return props.log
|
||||
})
|
||||
|
||||
const toggleAutoscrollColor = computed(() =>
|
||||
autoScrollEnabled.value ? "text-green-500" : "text-red-500"
|
||||
)
|
||||
|
||||
@@ -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