documentation.vue 7.8 KB

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