documentation.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <template>
  2. <Splitpanes
  3. class="smart-splitter"
  4. :dbl-click-splitter="false"
  5. :horizontal="!(windowInnerWidth.x.value >= 768)"
  6. >
  7. <Pane class="hide-scrollbar !overflow-auto">
  8. <Splitpanes
  9. class="smart-splitter"
  10. :dbl-click-splitter="false"
  11. :horizontal="COLUMN_LAYOUT"
  12. >
  13. <Pane class="hide-scrollbar !overflow-auto">
  14. <AppSection label="import">
  15. <div class="flex p-4 items-start justify-between">
  16. <label>
  17. {{ $t("documentation.generate_message") }}
  18. </label>
  19. <span
  20. class="
  21. bg-accentDark
  22. rounded
  23. text-accentContrast
  24. py-1
  25. px-2
  26. inline-flex
  27. "
  28. >
  29. BETA
  30. </span>
  31. </div>
  32. <div
  33. class="
  34. bg-primary
  35. border-b border-dividerLight
  36. flex
  37. top-0
  38. z-10
  39. items-start
  40. justify-between
  41. sticky
  42. "
  43. >
  44. <label for="collectionUpload">
  45. <ButtonSecondary
  46. v-tippy="{ theme: 'tooltip' }"
  47. title="JSON"
  48. svg="folder"
  49. class="!rounded-none"
  50. :label="$t('import.collections')"
  51. @click.native="$refs.collectionUpload.click()"
  52. />
  53. </label>
  54. <input
  55. ref="collectionUpload"
  56. class="input"
  57. name="collectionUpload"
  58. type="file"
  59. @change="uploadCollection"
  60. />
  61. <ButtonSecondary
  62. v-tippy="{ theme: 'tooltip' }"
  63. :title="$t('action.clear')"
  64. svg="trash-2"
  65. @click.native="collectionJSON = '[]'"
  66. />
  67. </div>
  68. <textarea-autosize
  69. id="import-curl"
  70. v-model="collectionJSON"
  71. class="font-mono p-4 bg-primary"
  72. autofocus
  73. rows="8"
  74. />
  75. <div
  76. class="
  77. bg-primary
  78. border-t border-b border-dividerLight
  79. flex
  80. p-4
  81. bottom-0
  82. z-10
  83. justify-between
  84. items-start
  85. sticky
  86. "
  87. >
  88. <ButtonPrimary
  89. :label="$t('documentation.generate')"
  90. @click.native="getDoc"
  91. />
  92. </div>
  93. </AppSection>
  94. </Pane>
  95. <Pane class="hide-scrollbar !overflow-auto">
  96. <AppSection label="documentation">
  97. <div class="flex flex-col">
  98. <div
  99. v-if="items.length === 0"
  100. class="
  101. flex flex-col
  102. text-secondaryLight
  103. p-4
  104. items-center
  105. justify-center
  106. "
  107. >
  108. <i class="opacity-75 pb-2 material-icons">topic</i>
  109. <span class="text-center">
  110. {{ $t("helpers.generate_documentation_first") }}
  111. </span>
  112. </div>
  113. <div
  114. v-else
  115. class="
  116. bg-primary
  117. border-b border-dividerLight
  118. flex flex-1
  119. p-4
  120. top-0
  121. z-10
  122. sticky
  123. "
  124. >
  125. <span
  126. v-tippy="{ theme: 'tooltip' }"
  127. :title="
  128. !currentUser
  129. ? $t('export.require_github')
  130. : currentUser.provider !== 'github.com'
  131. ? $t('export.require_github')
  132. : 'Beta'
  133. "
  134. >
  135. <ButtonPrimary
  136. :disabled="
  137. !currentUser
  138. ? true
  139. : currentUser.provider !== 'github.com'
  140. ? true
  141. : false
  142. "
  143. :label="$t('export.create_secret_gist')"
  144. @click.native="createDocsGist"
  145. />
  146. </span>
  147. </div>
  148. <div
  149. v-for="(collection, index) in items"
  150. :key="`collection-${index}`"
  151. >
  152. <DocsCollection :collection="collection" />
  153. </div>
  154. </div>
  155. </AppSection>
  156. </Pane>
  157. </Splitpanes>
  158. </Pane>
  159. <Pane
  160. v-if="RIGHT_SIDEBAR"
  161. max-size="35"
  162. size="25"
  163. min-size="20"
  164. class="hide-scrollbar !overflow-auto"
  165. >
  166. <aside>
  167. <Collections
  168. :selected="selected"
  169. :doc="true"
  170. @use-collection="useSelectedCollection($event)"
  171. @remove-collection="removeSelectedCollection($event)"
  172. />
  173. </aside>
  174. </Pane>
  175. </Splitpanes>
  176. </template>
  177. <script>
  178. import { defineComponent } from "@nuxtjs/composition-api"
  179. import { Splitpanes, Pane } from "splitpanes"
  180. import "splitpanes/dist/splitpanes.css"
  181. import Mustache from "mustache"
  182. import { currentUser$ } from "~/helpers/fb/auth"
  183. import DocsTemplate from "~/assets/md/docs.md"
  184. import folderContents from "~/assets/md/folderContents.md"
  185. import folderBody from "~/assets/md/folderBody.md"
  186. import { useSetting } from "~/newstore/settings"
  187. import { useReadonlyStream } from "~/helpers/utils/composables"
  188. import useWindowSize from "~/helpers/utils/useWindowSize"
  189. export default defineComponent({
  190. components: { Splitpanes, Pane },
  191. setup() {
  192. return {
  193. windowInnerWidth: useWindowSize(),
  194. RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
  195. COLUMN_LAYOUT: useSetting("COLUMN_LAYOUT"),
  196. currentUser: useReadonlyStream(currentUser$, null),
  197. }
  198. },
  199. data() {
  200. return {
  201. collectionJSON: "[]",
  202. items: [],
  203. docsMarkdown: "",
  204. selected: [],
  205. }
  206. },
  207. head() {
  208. return {
  209. title: `${this.$t("navigation.doc")} • Hoppscotch`,
  210. }
  211. },
  212. methods: {
  213. async createDocsGist() {
  214. await this.$axios
  215. .$post(
  216. "https://api.github.com/gists",
  217. {
  218. files: {
  219. "api-docs.md": {
  220. content: this.docsMarkdown,
  221. },
  222. },
  223. },
  224. {
  225. headers: {
  226. Authorization: `token ${this.currentUser.accessToken}`,
  227. Accept: "application/vnd.github.v3+json",
  228. },
  229. }
  230. )
  231. .then((res) => {
  232. this.$toast.success(this.$t("export.gist_created"), {
  233. icon: "done",
  234. })
  235. window.open(res.html_url)
  236. })
  237. .catch((e) => {
  238. this.$toast.error(this.$t("error.something_went_wrong"), {
  239. icon: "error_outline",
  240. })
  241. console.error(e)
  242. })
  243. },
  244. uploadCollection() {
  245. const file = this.$refs.collectionUpload.files[0]
  246. if (file !== undefined && file !== null) {
  247. const reader = new FileReader()
  248. reader.onload = ({ target }) => {
  249. this.collectionJSON = target.result
  250. }
  251. reader.readAsText(file)
  252. this.$toast.success(this.$t("state.file_imported"), {
  253. icon: "attach_file",
  254. })
  255. } else {
  256. this.$toast.error(this.$t("action.choose_file"), {
  257. icon: "attach_file",
  258. })
  259. }
  260. this.$refs.collectionUpload.value = ""
  261. },
  262. assignIDs(items, pref, nestingLevel) {
  263. for (let i = 0; i < items.length; ++i) {
  264. items[i].id = `&emsp;${pref}${i + 1}.`
  265. items[i].ref = `${items[i].name.split(" ").join("-")}`
  266. items[i].nestingLevel = nestingLevel
  267. items[i].folders = this.assignIDs(
  268. items[i].folders,
  269. items[i].id,
  270. nestingLevel + "#"
  271. )
  272. for (let j = 0; j < items[i].requests.length; ++j) {
  273. items[i].requests[j].id = `&emsp;${items[i].id}${i + 1}`
  274. items[i].requests[j].ref = `${items[i].requests[j].name
  275. .split(" ")
  276. .join("-")}`
  277. items[i].requests[j].nestingLevel = nestingLevel + "#"
  278. }
  279. }
  280. return items
  281. },
  282. getDoc() {
  283. try {
  284. this.items = JSON.parse(this.collectionJSON)
  285. this.assignIDs(this.items, "", "#")
  286. this.$toast.clear()
  287. this.$toast.success(this.$t("state.docs_generated"), {
  288. icon: "book",
  289. })
  290. const docsMarkdown = Mustache.render(
  291. DocsTemplate,
  292. {
  293. collections: this.items,
  294. isHeaders() {
  295. return this.headers.length
  296. },
  297. isParams() {
  298. return this.params.length
  299. },
  300. isAuth() {
  301. return this.auth !== "None"
  302. },
  303. isAuthBasic() {
  304. return this.httpUser && this.httpPassword
  305. },
  306. isRawParams() {
  307. return this.rawParams && this.rawParams !== ""
  308. },
  309. isPreRequestScript() {
  310. return this.preRequestScript && this.preRequestScript !== ""
  311. },
  312. isTestScript() {
  313. return this.testScript && this.testScript !== ""
  314. },
  315. },
  316. {
  317. folderContents,
  318. folderBody,
  319. }
  320. )
  321. this.docsMarkdown = docsMarkdown.replace(/^\s*[\r\n]/gm, "\n\n")
  322. } catch (e) {
  323. console.error(e)
  324. this.$toast.error(this.$t("error.something_went_wrong"), {
  325. icon: "error_outline",
  326. })
  327. }
  328. },
  329. useSelectedCollection(collection) {
  330. if (this.selected.find((coll) => coll === collection)) {
  331. return
  332. }
  333. this.selected.push(collection)
  334. const importCollection = JSON.stringify(this.selected, null, 2)
  335. this.collectionJSON = JSON.stringify(
  336. JSON.parse(importCollection),
  337. null,
  338. 2
  339. )
  340. },
  341. removeSelectedCollection(collection) {
  342. this.selected = this.selected.filter((coll) => coll !== collection)
  343. const importCollection = JSON.stringify(this.selected, null, 2)
  344. this.collectionJSON = JSON.stringify(
  345. JSON.parse(importCollection),
  346. null,
  347. 2
  348. )
  349. },
  350. },
  351. })
  352. </script>