Headers.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <template>
  2. <AppSection label="headers">
  3. <div
  4. class="
  5. bg-primary
  6. border-b border-dividerLight
  7. flex flex-1
  8. top-upperSecondaryStickyFold
  9. pl-4
  10. z-10
  11. sticky
  12. items-center
  13. justify-between
  14. "
  15. >
  16. <label class="font-semibold text-secondaryLight">
  17. {{ $t("request.header_list") }}
  18. </label>
  19. <div class="flex">
  20. <ButtonSecondary
  21. v-tippy="{ theme: 'tooltip' }"
  22. to="https://docs.hoppscotch.io/features/headers"
  23. blank
  24. :title="$t('app.wiki')"
  25. svg="help-circle"
  26. />
  27. <ButtonSecondary
  28. v-tippy="{ theme: 'tooltip' }"
  29. :title="$t('action.clear_all')"
  30. svg="trash-2"
  31. @click.native="bulkMode ? clearBulkEditor() : clearContent()"
  32. />
  33. <ButtonSecondary
  34. v-tippy="{ theme: 'tooltip' }"
  35. :title="$t('state.bulk_mode')"
  36. svg="edit"
  37. :class="{ '!text-accent': bulkMode }"
  38. @click.native="bulkMode = !bulkMode"
  39. />
  40. <ButtonSecondary
  41. v-tippy="{ theme: 'tooltip' }"
  42. :title="$t('add.new')"
  43. svg="plus"
  44. :disabled="bulkMode"
  45. @click.native="addHeader"
  46. />
  47. </div>
  48. </div>
  49. <div v-if="bulkMode" ref="bulkEditor"></div>
  50. <div v-else>
  51. <div
  52. v-for="(header, index) in headers$"
  53. :key="`header-${index}`"
  54. class="divide-x divide-dividerLight border-b border-dividerLight flex"
  55. >
  56. <SmartAutoComplete
  57. :placeholder="`${$t('count.header', { count: index + 1 })}`"
  58. :source="commonHeaders"
  59. :spellcheck="false"
  60. :value="header.key"
  61. autofocus
  62. styles="
  63. bg-transparent
  64. flex
  65. flex-1
  66. py-1
  67. px-4
  68. truncate
  69. "
  70. class="!flex flex-1"
  71. @input="
  72. updateHeader(index, {
  73. key: $event,
  74. value: header.value,
  75. active: header.active,
  76. })
  77. "
  78. />
  79. <SmartEnvInput
  80. v-model="header.value"
  81. :placeholder="`${$t('count.value', { count: index + 1 })}`"
  82. styles="
  83. bg-transparent
  84. flex
  85. flex-1
  86. py-1
  87. px-4
  88. "
  89. @change="
  90. updateHeader(index, {
  91. key: header.key,
  92. value: $event,
  93. active: header.active,
  94. })
  95. "
  96. />
  97. <span>
  98. <ButtonSecondary
  99. v-tippy="{ theme: 'tooltip' }"
  100. :title="
  101. header.hasOwnProperty('active')
  102. ? header.active
  103. ? $t('action.turn_off')
  104. : $t('action.turn_on')
  105. : $t('action.turn_off')
  106. "
  107. :svg="
  108. header.hasOwnProperty('active')
  109. ? header.active
  110. ? 'check-circle'
  111. : 'circle'
  112. : 'check-circle'
  113. "
  114. color="green"
  115. @click.native="
  116. updateHeader(index, {
  117. key: header.key,
  118. value: header.value,
  119. active: header.hasOwnProperty('active')
  120. ? !header.active
  121. : false,
  122. })
  123. "
  124. />
  125. </span>
  126. <span>
  127. <ButtonSecondary
  128. v-tippy="{ theme: 'tooltip' }"
  129. :title="$t('action.remove')"
  130. svg="trash"
  131. color="red"
  132. @click.native="deleteHeader(index)"
  133. />
  134. </span>
  135. </div>
  136. <div
  137. v-if="headers$.length === 0"
  138. class="
  139. flex flex-col
  140. text-secondaryLight
  141. p-4
  142. items-center
  143. justify-center
  144. "
  145. >
  146. <img
  147. :src="`/images/states/${$colorMode.value}/add_category.svg`"
  148. loading="lazy"
  149. class="
  150. flex-col
  151. my-4
  152. object-contain object-center
  153. h-16
  154. w-16
  155. inline-flex
  156. "
  157. :alt="$t('empty.headers')"
  158. />
  159. <span class="text-center pb-4">
  160. {{ $t("empty.headers") }}
  161. </span>
  162. <ButtonSecondary
  163. filled
  164. :label="`${$t('add.new')}`"
  165. svg="plus"
  166. class="mb-4"
  167. @click.native="addHeader"
  168. />
  169. </div>
  170. </div>
  171. </AppSection>
  172. </template>
  173. <script setup lang="ts">
  174. import { onBeforeUpdate, ref, useContext, watch } from "@nuxtjs/composition-api"
  175. import { useCodemirror } from "~/helpers/editor/codemirror"
  176. import {
  177. addRESTHeader,
  178. deleteAllRESTHeaders,
  179. deleteRESTHeader,
  180. restHeaders$,
  181. setRESTHeaders,
  182. updateRESTHeader,
  183. } from "~/newstore/RESTSession"
  184. import { commonHeaders } from "~/helpers/headers"
  185. import { useReadonlyStream } from "~/helpers/utils/composables"
  186. import { HoppRESTHeader } from "~/helpers/types/HoppRESTRequest"
  187. const {
  188. $toast,
  189. app: { i18n },
  190. } = useContext()
  191. const t = i18n.t.bind(i18n)
  192. const bulkMode = ref(false)
  193. const bulkHeaders = ref("")
  194. const bulkEditor = ref<any | null>(null)
  195. useCodemirror(bulkEditor, bulkHeaders, {
  196. extendedEditorConfig: {
  197. mode: "text/x-yaml",
  198. placeholder: `${t("state.bulk_mode_placeholder")}`,
  199. },
  200. linter: null,
  201. completer: null,
  202. })
  203. watch(bulkHeaders, () => {
  204. try {
  205. const transformation = bulkHeaders.value.split("\n").map((item) => ({
  206. key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
  207. value: item.substring(item.indexOf(":") + 1).trim(),
  208. active: !item.trim().startsWith("//"),
  209. }))
  210. setRESTHeaders(transformation)
  211. } catch (e) {
  212. $toast.error(`${t("error.something_went_wrong")}`, {
  213. icon: "error_outline",
  214. })
  215. console.error(e)
  216. }
  217. })
  218. const headers$ = useReadonlyStream(restHeaders$, [])
  219. watch(
  220. headers$,
  221. (newValue) => {
  222. if (
  223. (newValue[newValue.length - 1]?.key !== "" ||
  224. newValue[newValue.length - 1]?.value !== "") &&
  225. newValue.length
  226. )
  227. addHeader()
  228. },
  229. { deep: true }
  230. )
  231. onBeforeUpdate(() => editBulkHeadersLine(-1, null))
  232. const editBulkHeadersLine = (index: number, item?: HoppRESTHeader | null) => {
  233. const headers = headers$.value
  234. bulkHeaders.value = headers
  235. .reduce((all, header, pIndex) => {
  236. const current =
  237. index === pIndex && item != null
  238. ? `${item.active ? "" : "//"}${item.key}: ${item.value}`
  239. : `${header.active ? "" : "//"}${header.key}: ${header.value}`
  240. return [...all, current]
  241. }, [])
  242. .join("\n")
  243. }
  244. const clearBulkEditor = () => {
  245. bulkHeaders.value = ""
  246. }
  247. const addHeader = () => {
  248. const empty = { key: "", value: "", active: true }
  249. const index = headers$.value.length
  250. addRESTHeader(empty)
  251. editBulkHeadersLine(index, empty)
  252. }
  253. const updateHeader = (index: number, item: HoppRESTHeader) => {
  254. updateRESTHeader(index, item)
  255. editBulkHeadersLine(index, item)
  256. }
  257. const deleteHeader = (index: number) => {
  258. const headersBeforeDeletion = headers$.value
  259. deleteRESTHeader(index)
  260. editBulkHeadersLine(index, null)
  261. const deletedItem = headersBeforeDeletion[index]
  262. if (deletedItem.key || deletedItem.value) {
  263. $toast.success(t("state.deleted").toString(), {
  264. icon: "delete",
  265. action: [
  266. {
  267. text: t("action.undo").toString(),
  268. onClick: (_, toastObject) => {
  269. setRESTHeaders(headersBeforeDeletion as HoppRESTHeader[])
  270. editBulkHeadersLine(index, deletedItem)
  271. toastObject.goAway(0)
  272. },
  273. },
  274. ],
  275. })
  276. }
  277. }
  278. const clearContent = () => {
  279. deleteAllRESTHeaders()
  280. clearBulkEditor()
  281. }
  282. </script>