documentation.vue 9.6 KB

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