Headers.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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. :disabled="bulkMode"
  32. @click.native="clearContent"
  33. />
  34. <ButtonSecondary
  35. v-tippy="{ theme: 'tooltip' }"
  36. :title="$t('state.bulk_mode')"
  37. svg="edit"
  38. :class="{ '!text-accent': bulkMode }"
  39. @click.native="bulkMode = !bulkMode"
  40. />
  41. <ButtonSecondary
  42. v-tippy="{ theme: 'tooltip' }"
  43. :title="$t('add.new')"
  44. svg="plus"
  45. :disabled="bulkMode"
  46. @click.native="addHeader"
  47. />
  48. </div>
  49. </div>
  50. <div v-if="bulkMode" ref="bulkEditor"></div>
  51. <div v-else>
  52. <div
  53. v-for="(header, index) in headers$"
  54. :key="`header-${index}`"
  55. class="divide-x divide-dividerLight border-b border-dividerLight flex"
  56. >
  57. <SmartAutoComplete
  58. :placeholder="`${$t('count.header', { count: index + 1 })}`"
  59. :source="commonHeaders"
  60. :spellcheck="false"
  61. :value="header.key"
  62. autofocus
  63. styles="
  64. bg-transparent
  65. flex
  66. flex-1
  67. py-1
  68. px-4
  69. truncate
  70. "
  71. :class="{ '!flex flex-1': EXPERIMENTAL_URL_BAR_ENABLED }"
  72. @input="
  73. updateHeader(index, {
  74. key: $event,
  75. value: header.value,
  76. active: header.active,
  77. })
  78. "
  79. />
  80. <SmartEnvInput
  81. v-if="EXPERIMENTAL_URL_BAR_ENABLED"
  82. v-model="header.value"
  83. :placeholder="`${$t('count.value', { count: index + 1 })}`"
  84. styles="
  85. bg-transparent
  86. flex
  87. flex-1
  88. py-1
  89. px-4
  90. "
  91. @change="
  92. updateHeader(index, {
  93. key: header.key,
  94. value: $event,
  95. active: header.active,
  96. })
  97. "
  98. />
  99. <input
  100. v-else
  101. class="bg-transparent flex flex-1 py-2 px-4"
  102. :placeholder="`${$t('count.value', { count: index + 1 })}`"
  103. :name="'value' + index"
  104. :value="header.value"
  105. @change="
  106. updateHeader(index, {
  107. key: header.key,
  108. value: $event.target.value,
  109. active: header.active,
  110. })
  111. "
  112. />
  113. <span>
  114. <ButtonSecondary
  115. v-tippy="{ theme: 'tooltip' }"
  116. :title="
  117. header.hasOwnProperty('active')
  118. ? header.active
  119. ? $t('action.turn_off')
  120. : $t('action.turn_on')
  121. : $t('action.turn_off')
  122. "
  123. :svg="
  124. header.hasOwnProperty('active')
  125. ? header.active
  126. ? 'check-circle'
  127. : 'circle'
  128. : 'check-circle'
  129. "
  130. color="green"
  131. @click.native="
  132. updateHeader(index, {
  133. key: header.key,
  134. value: header.value,
  135. active: header.hasOwnProperty('active')
  136. ? !header.active
  137. : false,
  138. })
  139. "
  140. />
  141. </span>
  142. <span>
  143. <ButtonSecondary
  144. v-tippy="{ theme: 'tooltip' }"
  145. :title="$t('action.remove')"
  146. svg="trash"
  147. color="red"
  148. @click.native="deleteHeader(index)"
  149. />
  150. </span>
  151. </div>
  152. <div
  153. v-if="headers$.length === 0"
  154. class="
  155. flex flex-col
  156. text-secondaryLight
  157. p-4
  158. items-center
  159. justify-center
  160. "
  161. >
  162. <span class="text-center pb-4">
  163. {{ $t("empty.headers") }}
  164. </span>
  165. <ButtonSecondary
  166. filled
  167. :label="`${$t('add.new')}`"
  168. svg="plus"
  169. @click.native="addHeader"
  170. />
  171. </div>
  172. </div>
  173. </AppSection>
  174. </template>
  175. <script setup lang="ts">
  176. import { ref, useContext, watch } from "@nuxtjs/composition-api"
  177. import { useCodemirror } from "~/helpers/editor/codemirror"
  178. import {
  179. addRESTHeader,
  180. deleteAllRESTHeaders,
  181. deleteRESTHeader,
  182. restHeaders$,
  183. setRESTHeaders,
  184. updateRESTHeader,
  185. } from "~/newstore/RESTSession"
  186. import { commonHeaders } from "~/helpers/headers"
  187. import { useSetting } from "~/newstore/settings"
  188. import { useReadonlyStream } from "~/helpers/utils/composables"
  189. import { HoppRESTHeader } from "~/helpers/types/HoppRESTRequest"
  190. const {
  191. $toast,
  192. app: { i18n },
  193. } = useContext()
  194. const t = i18n.t.bind(i18n)
  195. const bulkMode = ref(false)
  196. const bulkHeaders = ref("")
  197. const bulkEditor = ref<any | null>(null)
  198. useCodemirror(bulkEditor, bulkHeaders, {
  199. extendedEditorConfig: {
  200. mode: "text/x-yaml",
  201. placeholder: `${t("state.bulk_mode_placeholder")}`,
  202. },
  203. linter: null,
  204. completer: null,
  205. })
  206. watch(bulkHeaders, () => {
  207. try {
  208. const transformation = bulkHeaders.value.split("\n").map((item) => ({
  209. key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
  210. value: item.substring(item.indexOf(":") + 1).trim(),
  211. active: !item.trim().startsWith("//"),
  212. }))
  213. setRESTHeaders(transformation)
  214. } catch (e) {
  215. $toast.error(`${t("error.something_went_wrong")}`, {
  216. icon: "error_outline",
  217. })
  218. console.error(e)
  219. }
  220. })
  221. const headers$ = useReadonlyStream(restHeaders$, [])
  222. watch(
  223. headers$,
  224. (newValue) => {
  225. if (
  226. (newValue[newValue.length - 1]?.key !== "" ||
  227. newValue[newValue.length - 1]?.value !== "") &&
  228. newValue.length
  229. )
  230. addHeader()
  231. },
  232. { deep: true }
  233. )
  234. const addHeader = () => {
  235. addRESTHeader({ key: "", value: "", active: true })
  236. }
  237. const updateHeader = (index: number, item: HoppRESTHeader) => {
  238. updateRESTHeader(index, item)
  239. }
  240. const deleteHeader = (index: number) => {
  241. deleteRESTHeader(index)
  242. }
  243. const clearContent = () => {
  244. deleteAllRESTHeaders()
  245. }
  246. const EXPERIMENTAL_URL_BAR_ENABLED = useSetting("EXPERIMENTAL_URL_BAR_ENABLED")
  247. </script>