index.blade.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. @php
  2. use Knuckles\Scribe\Tools\WritingUtils as u;
  3. @endphp
  4. <!doctype html>
  5. <html lang="en">
  6. <head>
  7. <meta charset="utf-8">
  8. <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
  9. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  10. <title>{!! $metadata['title'] !!}</title>
  11. <link href="https://fonts.googleapis.com/css?family=PT+Sans&display=swap" rel="stylesheet">
  12. <link rel="stylesheet" href="{!! $assetPathPrefix !!}css/theme-elements.style.css" media="screen">
  13. <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>
  14. <link rel="stylesheet"
  15. href="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/styles/docco.min.css">
  16. <script src="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/highlight.min.js"></script>
  17. <script>hljs.highlightAll();</script>
  18. <script type="module">
  19. import {CodeJar} from 'https://medv.io/codejar/codejar.js'
  20. window.CodeJar = CodeJar;
  21. </script>
  22. @if($tryItOut['enabled'] ?? true)
  23. <script>
  24. var baseUrl = "{{ $tryItOut['base_url'] ?? config('app.url') }}";
  25. var useCsrf = Boolean({{ $tryItOut['use_csrf'] ?? null }});
  26. var csrfUrl = "{{ $tryItOut['csrf_url'] ?? null }}";
  27. </script>
  28. <script src="{{ u::getVersionedAsset($assetPathPrefix.'js/tryitout.js') }}"></script>
  29. <style>
  30. .code-editor, .response-content {
  31. color: whitesmoke;
  32. background-color: transparent;
  33. }
  34. /*
  35. Problem: we want syntax highlighting for the Try It Out JSON body code editor
  36. However, the Try It Out area uses a dark background, while request and response samples
  37. (which are already highlighted) use a light background. HighlightJS can only use one theme per document.
  38. Our options:
  39. 1. Change the bg of one. => No, it looks out of place on the page.
  40. 2. Use the same highlighting for both. => Nope, one would be unreadable.
  41. 3. Copy styles for a dark-bg h1js theme and prefix them for the CodeEditor, which is what we're doing.
  42. Since it's only JSON, we only need a few styles anyway.
  43. Styles taken from the Nord theme: https://github.com/highlightjs/highlight.js/blob/3997c9b430a568d5ad46d96693b90a74fc01ea7f/src/styles/nord.css#L2
  44. */
  45. .code-editor > .hljs-attr {
  46. color: #8FBCBB;
  47. }
  48. .code-editor > .hljs-string {
  49. color: #A3BE8C;
  50. }
  51. .code-editor > .hljs-number {
  52. color: #B48EAD;
  53. }
  54. .code-editor > .hljs-literal{
  55. color: #81A1C1;
  56. }
  57. </style>
  58. <script>
  59. function tryItOut(btnElement) {
  60. btnElement.disabled = true;
  61. let endpointId = btnElement.dataset.endpoint;
  62. let errorPanel = document.querySelector(`.tryItOut-error[data-endpoint=${endpointId}]`);
  63. errorPanel.hidden = true;
  64. let responsePanel = document.querySelector(`.tryItOut-response[data-endpoint=${endpointId}]`);
  65. responsePanel.hidden = true;
  66. let form = btnElement.form;
  67. let { method, path, hasjsonbody } = form.dataset;
  68. let body = {};
  69. if (hasjsonbody === "1") {
  70. body = form.querySelector('.code-editor').textContent;
  71. } else if (form.dataset.hasfiles === "1") {
  72. body = new FormData();
  73. form.querySelectorAll('input[data-component=body]')
  74. .forEach(el => {
  75. if (el.type === 'file') {
  76. if (el.files[0]) body.append(el.name, el.files[0])
  77. } else body.append(el.name, el.value);
  78. });
  79. } else {
  80. form.querySelectorAll('input[data-component=body]').forEach(el => {
  81. _.set(body, el.name, el.value);
  82. });
  83. }
  84. const urlParameters = form.querySelectorAll('input[data-component=url]');
  85. urlParameters.forEach(el => (path = path.replace(new RegExp(`\\{${el.name}\\??}`), el.value)));
  86. const headers = Object.fromEntries(Array.from(form.querySelectorAll('input[data-component=header]'))
  87. .map(el => [el.name, (el.dataset.prefix || '') + el.value]));
  88. const query = {}
  89. form.querySelectorAll('input[data-component=query]').forEach(el => {
  90. _.set(query, el.name, el.value);
  91. });
  92. let preflightPromise = Promise.resolve();
  93. if (window.useCsrf && window.csrfUrl) {
  94. preflightPromise = makeAPICall('GET', window.csrfUrl).then(() => {
  95. headers['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
  96. });
  97. }
  98. return preflightPromise.then(() => makeAPICall(method, path, body, query, headers, endpointId))
  99. .then(([responseStatus, statusText, responseContent, responseHeaders]) => {
  100. responsePanel.hidden = false;
  101. responsePanel.querySelector(`.response-status`).textContent = responseStatus + " " + statusText ;
  102. let contentEl = responsePanel.querySelector(`.response-content`);
  103. if (responseContent === '') {
  104. contentEl.textContent = '<Empty response>'
  105. return;
  106. }
  107. // Prettify it if it's JSON
  108. let isJson = false;
  109. try {
  110. const jsonParsed = JSON.parse(responseContent);
  111. if (jsonParsed !== null) {
  112. isJson = true;
  113. responseContent = JSON.stringify(jsonParsed, null, 4);
  114. }
  115. } catch (e) {}
  116. contentEl.innerHTML = responseContent;
  117. isJson && window.hljs.highlightElement(contentEl);
  118. })
  119. .catch(err => {
  120. console.log(err);
  121. let errorMessage = err.message || err;
  122. errorPanel.hidden = false;
  123. errorPanel.querySelector(`.error-message`).textContent = errorMessage;
  124. })
  125. .finally(() => { btnElement.disabled = false } );
  126. }
  127. window.addEventListener('DOMContentLoaded', () => {
  128. document.querySelectorAll('.tryItOut-btn').forEach(el => {
  129. el.addEventListener('click', () => tryItOut(el));
  130. });
  131. })
  132. </script>
  133. @endif
  134. <script src="{{ u::getVersionedAsset($assetPathPrefix.'js/theme-elements.js') }}"></script>
  135. </head>
  136. <body>
  137. @if($metadata['example_languages'])
  138. <script>
  139. function switchExampleLanguage(lang) {
  140. document.querySelectorAll(`.example-request`).forEach(el => el.style.display = 'none');
  141. document.querySelectorAll(`.example-request-${lang}`).forEach(el => el.style.display = 'initial');
  142. document.querySelectorAll(`.example-request-lang-toggle`).forEach(el => el.value = lang);
  143. }
  144. </script>
  145. @endif
  146. <script>
  147. function switchExampleResponse(endpointId, index) {
  148. document.querySelectorAll(`.example-response-${endpointId}`).forEach(el => el.style.display = 'none');
  149. document.querySelectorAll(`.example-response-${endpointId}-${index}`).forEach(el => el.style.display = 'initial');
  150. document.querySelectorAll(`.example-response-${endpointId}-toggle`).forEach(el => el.value = index);
  151. }
  152. /*
  153. * Requirement: a div with class `expansion-chevrons`
  154. * (or `expansion-chevrons-solid` to use the solid version).
  155. * Also add the `expanded` class if your div is expanded by default.
  156. */
  157. function toggleExpansionChevrons(evt) {
  158. let elem = evt.currentTarget;
  159. let chevronsArea = elem.querySelector('.expansion-chevrons');
  160. const solid = chevronsArea.classList.contains('expansion-chevrons-solid');
  161. const newState = chevronsArea.classList.contains('expanded') ? 'expand' : 'expanded';
  162. if (newState === 'expanded') {
  163. const selector = solid ? '#expanded-chevron-solid' : '#expanded-chevron';
  164. const template = document.querySelector(selector);
  165. const chevron = template.content.cloneNode(true);
  166. chevronsArea.replaceChildren(chevron);
  167. chevronsArea.classList.add('expanded');
  168. } else {
  169. const selector = solid ? '#expand-chevron-solid' : '#expand-chevron';
  170. const template = document.querySelector(selector);
  171. const chevron = template.content.cloneNode(true);
  172. chevronsArea.replaceChildren(chevron);
  173. chevronsArea.classList.remove('expanded');
  174. }
  175. }
  176. /**
  177. * 1. Make sure the children are inside the parent element
  178. * 2. Add `expandable` class to the parent
  179. * 3. Add `children` class to the children.
  180. * 4. Wrap the default chevron SVG in a div with class `expansion-chevrons`
  181. * (or `expansion-chevrons-solid` to use the solid version).
  182. * Also add the `expanded` class if your div is expanded by default.
  183. */
  184. function toggleElementChildren(evt) {
  185. let elem = evt.currentTarget;
  186. let children = elem.querySelector(`.children`);
  187. if (!children) return;
  188. if (children.contains(event.target)) return;
  189. let oldState = children.style.display
  190. if (oldState === 'none') {
  191. children.style.removeProperty('display');
  192. toggleExpansionChevrons(evt);
  193. } else {
  194. children.style.display = 'none';
  195. toggleExpansionChevrons(evt);
  196. }
  197. evt.stopPropagation();
  198. }
  199. function highlightSidebarItem(evt = null) {
  200. if (evt && evt.oldURL) {
  201. let oldHash = new URL(evt.oldURL).hash.slice(1);
  202. if (oldHash) {
  203. let previousItem = window['sidebar'].querySelector(`#toc-item-${oldHash}`);
  204. previousItem.classList.remove('sl-bg-primary-tint');
  205. previousItem.classList.add('sl-bg-canvas-100');
  206. }
  207. }
  208. let newHash = location.hash.slice(1);
  209. if (newHash) {
  210. let item = window['sidebar'].querySelector(`#toc-item-${newHash}`);
  211. item.classList.remove('sl-bg-canvas-100');
  212. item.classList.add('sl-bg-primary-tint');
  213. }
  214. }
  215. addEventListener('DOMContentLoaded', () => {
  216. highlightSidebarItem();
  217. document.querySelectorAll('.code-editor').forEach(elem => CodeJar(elem, (editor) => {
  218. // highlight.js does not trim old tags,
  219. // which means highlighting doesn't update on type (only on paste)
  220. // See https://github.com/antonmedv/codejar/issues/18
  221. editor.textContent = editor.textContent
  222. return hljs.highlightElement(editor)
  223. }));
  224. document.querySelectorAll('.expandable').forEach(el => {
  225. el.addEventListener('click', toggleElementChildren);
  226. });
  227. document.querySelectorAll('details').forEach(el => {
  228. el.addEventListener('toggle', toggleExpansionChevrons);
  229. });
  230. });
  231. addEventListener('hashchange', highlightSidebarItem);
  232. </script>
  233. <div class="sl-elements sl-antialiased sl-h-full sl-text-base sl-font-ui sl-text-body sl-flex sl-inset-0">
  234. @include("scribe::themes.elements.sidebar")
  235. <div class="sl-overflow-y-auto sl-flex-1 sl-w-full sl-px-16 sl-bg-canvas sl-py-16" style="max-width: 1500px;">
  236. <div class="sl-mb-10">
  237. <div class="sl-mb-4">
  238. <h1 class="sl-text-5xl sl-leading-tight sl-font-prose sl-font-semibold sl-text-heading">
  239. {!! $metadata['title'] !!}
  240. </h1>
  241. @if($metadata['postman_collection_url'])
  242. <a title="Download Postman collection" class="sl-mx-1"
  243. href="{!! $metadata['postman_collection_url'] !!}" target="_blank">
  244. <small>Postman collection →</small>
  245. </a>
  246. @endif
  247. @if($metadata['openapi_spec_url'])
  248. <a title="Download OpenAPI spec" class="sl-mx-1"
  249. href="{!! $metadata['openapi_spec_url'] !!}" target="_blank">
  250. <small>OpenAPI spec →</small>
  251. </a>
  252. @endif
  253. </div>
  254. <div class="sl-prose sl-markdown-viewer sl-my-4">
  255. {!! $intro !!}
  256. {!! $auth !!}
  257. </div>
  258. </div>
  259. @include("scribe::themes.elements.groups")
  260. <div class="sl-prose sl-markdown-viewer sl-my-5">
  261. {!! $append !!}
  262. </div>
  263. </div>
  264. </div>
  265. <template id="expand-chevron">
  266. <svg aria-hidden="true" focusable="false" data-prefix="fas"
  267. data-icon="chevron-right"
  268. class="svg-inline--fa fa-chevron-right fa-fw sl-icon sl-text-muted"
  269. xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
  270. <path fill="currentColor"
  271. d="M96 480c-8.188 0-16.38-3.125-22.62-9.375c-12.5-12.5-12.5-32.75 0-45.25L242.8 256L73.38 86.63c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25l-192 192C112.4 476.9 104.2 480 96 480z"></path>
  272. </svg>
  273. </template>
  274. <template id="expanded-chevron">
  275. <svg aria-hidden="true" focusable="false" data-prefix="fas"
  276. data-icon="chevron-down"
  277. class="svg-inline--fa fa-chevron-down fa-fw sl-icon sl-text-muted"
  278. xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
  279. <path fill="currentColor"
  280. d="M224 416c-8.188 0-16.38-3.125-22.62-9.375l-192-192c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L224 338.8l169.4-169.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-192 192C240.4 412.9 232.2 416 224 416z"></path>
  281. </svg>
  282. </template>
  283. <template id="expand-chevron-solid">
  284. <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="caret-right"
  285. class="svg-inline--fa fa-caret-right fa-fw sl-icon" role="img" xmlns="http://www.w3.org/2000/svg"
  286. viewBox="0 0 256 512">
  287. <path fill="currentColor"
  288. d="M118.6 105.4l128 127.1C252.9 239.6 256 247.8 256 255.1s-3.125 16.38-9.375 22.63l-128 127.1c-9.156 9.156-22.91 11.9-34.88 6.943S64 396.9 64 383.1V128c0-12.94 7.781-24.62 19.75-29.58S109.5 96.23 118.6 105.4z"></path>
  289. </svg>
  290. </template>
  291. <template id="expanded-chevron-solid">
  292. <svg aria-hidden="true" focusable="false" data-prefix="fas"
  293. data-icon="caret-down"
  294. class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
  295. xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
  296. <path fill="currentColor"
  297. d="M310.6 246.6l-127.1 128C176.4 380.9 168.2 384 160 384s-16.38-3.125-22.63-9.375l-127.1-128C.2244 237.5-2.516 223.7 2.438 211.8S19.07 192 32 192h255.1c12.94 0 24.62 7.781 29.58 19.75S319.8 237.5 310.6 246.6z"></path>
  298. </svg>
  299. </template>
  300. </body>
  301. </html>