feat(common): add erase response functionality with keybindings (#5435)
Co-authored-by: nivedin <nivedinp@gmail.com> Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
fdbec04703
commit
4c1911c007
11 changed files with 326 additions and 28 deletions
|
|
@ -12,6 +12,7 @@
|
|||
"clear_cache": "Clear Cache",
|
||||
"clear_history": "Clear all History",
|
||||
"clear_unpinned": "Clear Unpinned",
|
||||
"clear_response": "Clear Response",
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
"connect": "Connect",
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@
|
|||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<div v-if="response.body" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.download_file'
|
||||
|
|
@ -16,6 +15,34 @@
|
|||
:icon="downloadIcon"
|
||||
@click="downloadResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="!isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => responseMoreActionsTippy?.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMore"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="responseMoreActionsTippy"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 items-center justify-center overflow-auto">
|
||||
|
|
@ -25,7 +52,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useDownloadResponse } from "@composables/lens-actions"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
|
|
@ -37,6 +64,9 @@ import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
|||
import * as A from "fp-ts/Array"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { objFieldMatches } from "~/helpers/functional/object"
|
||||
import { HoppRESTRequestResponse } from "@hoppscotch/data"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import IconMore from "~icons/lucide/more-horizontal"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
|
|
@ -44,6 +74,11 @@ const props = defineProps<{
|
|||
response: HoppRESTResponse & {
|
||||
type: "success" | "fail"
|
||||
}
|
||||
isEditable: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:response", val: HoppRESTRequestResponse | HoppRESTResponse): void
|
||||
}>()
|
||||
|
||||
const audiosrc = computed(() =>
|
||||
|
|
@ -54,6 +89,8 @@ const audiosrc = computed(() =>
|
|||
)
|
||||
)
|
||||
|
||||
const responseMoreActionsTippy = ref<HTMLElement | null>(null)
|
||||
|
||||
const responseType = computed(() =>
|
||||
pipe(
|
||||
props.response,
|
||||
|
|
@ -78,5 +115,10 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||
})
|
||||
)
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
defineActionHandler("response.file.download", () => downloadResponse())
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<div v-if="response.body" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body && !previewEnabled"
|
||||
v-if="!previewEnabled"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
|
|
@ -17,7 +17,6 @@
|
|||
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpResponseBody')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${
|
||||
previewEnabled ? t('hide.preview') : t('response.preview_html')
|
||||
|
|
@ -26,7 +25,6 @@
|
|||
@click.prevent="doTogglePreview"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.download_file'
|
||||
|
|
@ -35,7 +33,7 @@
|
|||
@click="downloadResponse"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-if="!isEditable"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="
|
||||
isSavable
|
||||
|
|
@ -51,7 +49,6 @@
|
|||
@click="isSavable ? saveAsExample() : null"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.copy'
|
||||
|
|
@ -59,6 +56,34 @@
|
|||
:icon="copyIcon"
|
||||
@click="copyResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="!isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => responseMoreActionsTippy?.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMore"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="responseMoreActionsTippy"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -101,6 +126,8 @@ import IconEye from "~icons/lucide/eye"
|
|||
import IconEyeOff from "~icons/lucide/eye-off"
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import IconMore from "~icons/lucide/more-horizontal"
|
||||
import { HoppRESTRequestResponse } from "@hoppscotch/data"
|
||||
import { computedAsync } from "@vueuse/core"
|
||||
import { useScrollerRef } from "~/composables/useScrollerRef"
|
||||
|
|
@ -126,9 +153,14 @@ const { containerRef } = useScrollerRef(
|
|||
|
||||
const emit = defineEmits<{
|
||||
(e: "save-as-example"): void
|
||||
(
|
||||
e: "update:response",
|
||||
val: HoppRESTRequestResponse | HoppRESTResponse | null
|
||||
): void
|
||||
}>()
|
||||
|
||||
const htmlResponse = ref<any | null>(null)
|
||||
const responseMoreActionsTippy = ref<HTMLElement | null>(null)
|
||||
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpResponseBody")
|
||||
|
||||
const responseName = computed(() => {
|
||||
|
|
@ -174,6 +206,10 @@ const doTogglePreview = async () => {
|
|||
|
||||
const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
const saveAsExample = () => {
|
||||
emit("save-as-example")
|
||||
}
|
||||
|
|
@ -196,6 +232,7 @@ useCodemirror(
|
|||
defineActionHandler("response.preview.toggle", () => doTogglePreview())
|
||||
defineActionHandler("response.file.download", () => downloadResponse())
|
||||
defineActionHandler("response.copy", () => copyResponse())
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
defineActionHandler("response.save-as-example", () => {
|
||||
props.isSavable ? saveAsExample() : null
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@
|
|||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<div v-if="response.body" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.download_file'
|
||||
|
|
@ -16,6 +15,34 @@
|
|||
:icon="downloadIcon"
|
||||
@click="downloadResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="!isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => responseMoreActionsTippy?.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMore"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="responseMoreActionsTippy"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
|
|
@ -40,14 +67,23 @@ import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
|||
import * as A from "fp-ts/Array"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { objFieldMatches } from "~/helpers/functional/object"
|
||||
import { HoppRESTRequestResponse } from "@hoppscotch/data"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import IconMore from "~icons/lucide/more-horizontal"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
response: HoppRESTResponse & { type: "success" | "fail" }
|
||||
isEditable: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:response", val: HoppRESTRequestResponse | HoppRESTResponse): void
|
||||
}>()
|
||||
|
||||
const imageSource = ref("")
|
||||
const responseMoreActionsTippy = ref<HTMLElement | null>(null)
|
||||
|
||||
const responseType = computed(() =>
|
||||
pipe(
|
||||
|
|
@ -101,5 +137,10 @@ onMounted(() => {
|
|||
reader.readAsDataURL(blob)
|
||||
})
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
defineActionHandler("response.file.download", () => downloadResponse())
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
@click="copyResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="showResponse"
|
||||
v-if="showResponse && response.body && !isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
|
|
@ -87,6 +87,12 @@
|
|||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
|
|
@ -252,6 +258,7 @@ import IconMore from "~icons/lucide/more-horizontal"
|
|||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconNetwork from "~icons/lucide/network"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import * as LJSON from "lossless-json"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as E from "fp-ts/Either"
|
||||
|
|
@ -458,6 +465,11 @@ const saveAsExample = () => {
|
|||
}
|
||||
|
||||
const { copyIcon, copyResponse } = useCopyResponse(jsonBodyText)
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
const { downloadIcon, downloadResponse } = useDownloadResponse(
|
||||
"application/json",
|
||||
jsonBodyText,
|
||||
|
|
@ -520,6 +532,7 @@ const toggleFilterState = () => {
|
|||
|
||||
defineActionHandler("response.file.download", () => downloadResponse())
|
||||
defineActionHandler("response.copy", () => copyResponse())
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
defineActionHandler("response.save-as-example", () => {
|
||||
props.isSavable ? saveAsExample() : null
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@
|
|||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<div v-if="response.body" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.download_file'
|
||||
|
|
@ -16,6 +15,34 @@
|
|||
:icon="downloadIcon"
|
||||
@click="downloadResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="!isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => responseMoreActionsTippy?.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMore"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="responseMoreActionsTippy"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
<vue-pdf-embed
|
||||
|
|
@ -27,20 +54,29 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
import { computed, ref } from "vue"
|
||||
import VuePdfEmbed from "vue-pdf-embed"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useDownloadResponse } from "@composables/lens-actions"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||
import { HoppRESTRequestResponse } from "@hoppscotch/data"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import IconMore from "~icons/lucide/more-horizontal"
|
||||
|
||||
const t = useI18n()
|
||||
const responseMoreActionsTippy = ref<HTMLElement | null>(null)
|
||||
|
||||
const props = defineProps<{
|
||||
response: HoppRESTResponse & {
|
||||
type: "success" | "fail"
|
||||
}
|
||||
isEditable: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:response", val: HoppRESTRequestResponse | HoppRESTResponse): void
|
||||
}>()
|
||||
|
||||
const pdfsrc = computed(() =>
|
||||
|
|
@ -61,5 +97,10 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||
`${filename}.pdf`
|
||||
)
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
defineActionHandler("response.file.download", () => downloadResponse())
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,8 @@
|
|||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<div v-if="response.body" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
|
|
@ -17,7 +16,6 @@
|
|||
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpResponseBody')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.download_file'
|
||||
|
|
@ -42,7 +40,6 @@
|
|||
@click="isSavable ? saveAsExample() : null"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.copy'
|
||||
|
|
@ -50,6 +47,34 @@
|
|||
:icon="copyIcon"
|
||||
@click="copyResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="showResponse && !isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => responseMoreActionsTippy?.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMore"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="responseMoreActionsTippy"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -64,6 +89,8 @@
|
|||
<script setup lang="ts">
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import IconMore from "~icons/lucide/more-horizontal"
|
||||
import { ref, computed, reactive } from "vue"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import * as S from "fp-ts/string"
|
||||
|
|
@ -139,6 +166,10 @@ const saveAsExample = () => {
|
|||
emit("save-as-example")
|
||||
}
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
const responseType = computed(() =>
|
||||
pipe(
|
||||
props.response,
|
||||
|
|
@ -178,6 +209,7 @@ const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
|||
|
||||
const rawResponse = ref<any | null>(null)
|
||||
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpResponseBody")
|
||||
const responseMoreActionsTippy = ref<HTMLElement | null>(null)
|
||||
|
||||
useCodemirror(
|
||||
rawResponse,
|
||||
|
|
@ -202,4 +234,5 @@ useCodemirror(
|
|||
|
||||
defineActionHandler("response.file.download", () => downloadResponse())
|
||||
defineActionHandler("response.copy", () => copyResponse())
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@
|
|||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<div v-if="response.body" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.download_file'
|
||||
|
|
@ -16,6 +15,34 @@
|
|||
:icon="downloadIcon"
|
||||
@click="downloadResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="!isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => responseMoreActionsTippy?.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMore"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="responseMoreActionsTippy"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 items-center justify-center overflow-auto">
|
||||
|
|
@ -25,7 +52,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { useI18n } from "@composables/i18n"
|
||||
import { useDownloadResponse } from "@composables/lens-actions"
|
||||
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
|
||||
|
|
@ -37,6 +64,9 @@ import * as RNEA from "fp-ts/ReadonlyNonEmptyArray"
|
|||
import * as A from "fp-ts/Array"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { objFieldMatches } from "~/helpers/functional/object"
|
||||
import { HoppRESTRequestResponse } from "@hoppscotch/data"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import IconMore from "~icons/lucide/more-horizontal"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
|
|
@ -44,6 +74,11 @@ const props = defineProps<{
|
|||
response: HoppRESTResponse & {
|
||||
type: "success" | "fail"
|
||||
}
|
||||
isEditable: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:response", val: HoppRESTRequestResponse | HoppRESTResponse): void
|
||||
}>()
|
||||
|
||||
const videosrc = computed(() =>
|
||||
|
|
@ -54,6 +89,8 @@ const videosrc = computed(() =>
|
|||
)
|
||||
)
|
||||
|
||||
const responseMoreActionsTippy = ref<HTMLElement | null>(null)
|
||||
|
||||
const responseType = computed(() =>
|
||||
pipe(
|
||||
props.response,
|
||||
|
|
@ -78,5 +115,10 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||
})
|
||||
)
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
defineActionHandler("response.file.download", () => downloadResponse())
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@
|
|||
<label class="truncate font-semibold text-secondaryLight">
|
||||
{{ t("response.body") }}
|
||||
</label>
|
||||
<div class="flex">
|
||||
<div v-if="response.body" class="flex">
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('state.linewrap')"
|
||||
:class="{ '!text-accent': WRAP_LINES }"
|
||||
|
|
@ -16,7 +15,6 @@
|
|||
@click.prevent="toggleNestedSetting('WRAP_LINES', 'httpResponseBody')"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.download_file'
|
||||
|
|
@ -25,7 +23,7 @@
|
|||
@click="downloadResponse"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body && !isEditable"
|
||||
v-if="!isEditable"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="
|
||||
isSavable
|
||||
|
|
@ -41,7 +39,6 @@
|
|||
@click="isSavable ? saveAsExample() : null"
|
||||
/>
|
||||
<HoppButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||
:title="`${t(
|
||||
'action.copy'
|
||||
|
|
@ -49,6 +46,34 @@
|
|||
:icon="copyIcon"
|
||||
@click="copyResponse"
|
||||
/>
|
||||
<tippy
|
||||
v-if="!isEditable"
|
||||
interactive
|
||||
trigger="click"
|
||||
theme="popover"
|
||||
:on-shown="() => responseMoreActionsTippy?.focus()"
|
||||
>
|
||||
<HoppButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.more')"
|
||||
:icon="IconMore"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div
|
||||
ref="responseMoreActionsTippy"
|
||||
class="flex flex-col focus:outline-none"
|
||||
tabindex="0"
|
||||
@keyup.escape="hide()"
|
||||
>
|
||||
<HoppSmartItem
|
||||
:label="t('action.clear_response')"
|
||||
:icon="IconEraser"
|
||||
:shortcut="[getSpecialKey(), 'Delete']"
|
||||
@click="eraseResponse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -61,6 +86,8 @@
|
|||
<script setup lang="ts">
|
||||
import IconWrapText from "~icons/lucide/wrap-text"
|
||||
import IconSave from "~icons/lucide/save"
|
||||
import IconEraser from "~icons/lucide/eraser"
|
||||
import IconMore from "~icons/lucide/more-horizontal"
|
||||
import { computed, ref, reactive } from "vue"
|
||||
import { flow, pipe } from "fp-ts/function"
|
||||
import * as S from "fp-ts/string"
|
||||
|
|
@ -103,8 +130,19 @@ const { containerRef } = useScrollerRef(
|
|||
|
||||
const emit = defineEmits<{
|
||||
(e: "save-as-example"): void
|
||||
(
|
||||
e: "update:response",
|
||||
val:
|
||||
| (HoppRESTResponse & { type: "success" | "fail" })
|
||||
| HoppRESTRequestResponse
|
||||
| null
|
||||
): void
|
||||
}>()
|
||||
|
||||
const eraseResponse = () => {
|
||||
emit("update:response", null)
|
||||
}
|
||||
|
||||
const { responseBodyText } = useResponseBody(props.response)
|
||||
|
||||
const isHttpResponse = computed(() => {
|
||||
|
|
@ -158,6 +196,7 @@ const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|||
const { copyIcon, copyResponse } = useCopyResponse(responseBodyText)
|
||||
|
||||
const xmlResponse = ref<any | null>(null)
|
||||
const responseMoreActionsTippy = ref<HTMLElement | null>(null)
|
||||
const WRAP_LINES = useNestedSetting("WRAP_LINES", "httpResponseBody")
|
||||
|
||||
const saveAsExample = () => {
|
||||
|
|
@ -184,4 +223,5 @@ defineActionHandler("response.copy", () => copyResponse())
|
|||
defineActionHandler("response.save-as-example", () => {
|
||||
props.isSavable ? saveAsExample() : null
|
||||
})
|
||||
defineActionHandler("response.erase", () => eraseResponse())
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export type HoppAction =
|
|||
| "response.schema.toggle" // Toggle response data schema
|
||||
| "response.file.download" // Download response as file
|
||||
| "response.copy" // Copy response to clipboard
|
||||
| "response.erase" // Erase/clear response
|
||||
| "response.save" // Save response
|
||||
| "response.save-as-example" // Save response as example
|
||||
| "modals.login.toggle" // Login to Hoppscotch
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ type Key =
|
|||
| "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
|
||||
| "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3"
|
||||
| "4" | "5" | "6" | "7" | "8" | "9" | "up" | "down" | "left"
|
||||
| "right" | "/" | "?" | "." | "enter" | "tab"
|
||||
| "right" | "/" | "?" | "." | "enter" | "tab" | "delete" | "backspace"
|
||||
/* eslint-enable */
|
||||
|
||||
type ModifierBasedShortcutKey = `${ModifierKeys}-${Key}`
|
||||
|
|
@ -77,6 +77,8 @@ const baseBindings: {
|
|||
"ctrl-.": "response.copy",
|
||||
"ctrl-e": "response.save-as-example",
|
||||
"ctrl-shift-l": "editor.format",
|
||||
"ctrl-delete": "response.erase",
|
||||
"ctrl-backspace": "response.erase",
|
||||
}
|
||||
|
||||
// Desktop-only bindings
|
||||
|
|
@ -225,6 +227,11 @@ function getPressedKey(ev: KeyboardEvent): Key | null {
|
|||
// Check for Tab key
|
||||
if (key === "tab") return "tab"
|
||||
|
||||
// Check for Delete key
|
||||
if (key === "delete") return "delete"
|
||||
|
||||
if (key === "backspace") return "backspace"
|
||||
|
||||
// Check letter keys
|
||||
const isLetter = key.length === 1 && key >= "a" && key <= "z"
|
||||
if (isLetter) return key as Key
|
||||
|
|
|
|||
Loading…
Reference in a new issue