update bug daashboards

This commit is contained in:
2026-04-18 19:47:13 +07:00
parent e2fa4d2d7b
commit ef902b2604
271 changed files with 44303 additions and 308 deletions

View File

@@ -0,0 +1,393 @@
/* Copied from https://github.com/slatedocs/slate/blob/c4b4c0b8f83e891ca9fab6bbe9a1a88d5fe41292/stylesheets/print.css and unminified */
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block
}
audio,
canvas,
progress,
video {
display: inline-block;
vertical-align: baseline
}
audio:not([controls]) {
display: none;
height: 0
}
[hidden],
template {
display: none
}
a {
background-color: transparent
}
a:active,
a:hover {
outline: 0
}
abbr[title] {
border-bottom: 1px dotted
}
b,
strong {
font-weight: bold
}
dfn {
font-style: italic
}
h1 {
font-size: 2em;
margin: 0.67em 0
}
mark {
background: #ff0;
color: #000
}
small {
font-size: 80%
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline
}
sup {
top: -0.5em
}
sub {
bottom: -0.25em
}
img {
border: 0
}
svg:not(:root) {
overflow: hidden
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0
}
pre {
overflow: auto
}
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em
}
button,
input,
optgroup,
select,
textarea {
color: inherit;
font: inherit;
margin: 0
}
button {
overflow: visible
}
button,
select {
text-transform: none
}
button,
html input[type="button"],
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button;
cursor: pointer
}
button[disabled],
html input[disabled] {
cursor: default
}
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0
}
input {
line-height: normal
}
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box;
padding: 0
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto
}
input[type="search"] {
-webkit-appearance: textfield;
box-sizing: content-box
}
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none
}
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em
}
legend {
border: 0;
padding: 0
}
textarea {
overflow: auto
}
optgroup {
font-weight: bold
}
table {
border-collapse: collapse;
border-spacing: 0
}
td,
th {
padding: 0
}
.content h1,
.content h2,
.content h3,
.content h4,
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-size: 14px
}
.content h1,
.content h2,
.content h3,
.content h4 {
font-weight: bold
}
.content pre,
.content code {
font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
font-size: 12px;
line-height: 1.5
}
.content pre,
.content code {
word-break: break-all;
-webkit-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto
}
@font-face {
font-family: 'slate';
src: url(../fonts/slate.eot?-syv14m);
src: url(../fonts/slate.eot?#iefix-syv14m) format("embedded-opentype"), url(../fonts/slate.woff2?-syv14m) format("woff2"), url(../fonts/slate.woff?-syv14m) format("woff"), url(../fonts/slate.ttf?-syv14m) format("truetype"), url(../fonts/slate.svg?-syv14m#slate) format("svg");
font-weight: normal;
font-style: normal
}
.content aside.warning:before,
.content aside.notice:before,
.content aside.success:before {
font-family: 'slate';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1
}
.content aside.warning:before {
content: "\e600"
}
.content aside.notice:before {
content: "\e602"
}
.content aside.success:before {
content: "\e606"
}
.tocify,
.toc-footer,
.lang-selector,
.search,
#nav-button {
display: none
}
.tocify-wrapper>img {
margin: 0 auto;
display: block
}
.content {
font-size: 12px
}
.content pre,
.content code {
border: 1px solid #999;
border-radius: 5px;
font-size: 0.8em
}
.content pre code {
border: 0
}
.content pre {
padding: 1.3em
}
.content code {
padding: 0.2em
}
.content table {
border: 1px solid #999
}
.content table tr {
border-bottom: 1px solid #999
}
.content table td,
.content table th {
padding: 0.7em
}
.content p {
line-height: 1.5
}
.content a {
text-decoration: none;
color: #000
}
.content h1 {
font-size: 2.5em;
padding-top: 0.5em;
padding-bottom: 0.5em;
margin-top: 1em;
margin-bottom: 21px;
border: 2px solid #ccc;
border-width: 2px 0;
text-align: center
}
.content h2 {
font-size: 1.8em;
margin-top: 2em;
border-top: 2px solid #ccc;
padding-top: 0.8em
}
.content h1+h2,
.content h1+div+h2 {
border-top: none;
padding-top: 0;
margin-top: 0
}
.content h3,
.content h4 {
font-size: 0.8em;
margin-top: 1.5em;
margin-bottom: 0.8em;
text-transform: uppercase
}
.content h5,
.content h6 {
text-transform: uppercase
}
.content aside {
padding: 1em;
border: 1px solid #ccc;
border-radius: 5px;
margin-top: 1.5em;
margin-bottom: 1.5em;
line-height: 1.6
}
.content aside:before {
vertical-align: middle;
padding-right: 0.5em;
font-size: 14px
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
# To include an endpoint that isn't a part of your Laravel app (or belongs to a vendor package),
# you can define it in a custom.*.yaml file, like this one.
# Each custom file should contain an array of endpoints. Here's an example:
# See https://scribe.knuckles.wtf/laravel/documenting/custom-endpoints#extra-sorting-groups-in-custom-endpoint-files for more options
#- httpMethods:
# - POST
# uri: api/doSomething/{param}
# metadata:
# groupName: The group the endpoint belongs to. Can be a new group or an existing group.
# groupDescription: A description for the group. You don't need to set this for every endpoint; once is enough.
# subgroup: You can add a subgroup, too.
# title: Do something
# description: 'This endpoint allows you to do something.'
# authenticated: false
# headers:
# Content-Type: application/json
# Accept: application/json
# urlParameters:
# param:
# name: param
# description: A URL param for no reason.
# required: true
# example: 2
# type: integer
# queryParameters:
# speed:
# name: speed
# description: How fast the thing should be done. Can be `slow` or `fast`.
# required: false
# example: fast
# type: string
# bodyParameters:
# something:
# name: something
# description: The things we should do.
# required: true
# example:
# - string 1
# - string 2
# type: 'string[]'
# responses:
# - status: 200
# description: 'When the thing was done smoothly.'
# content: # Your response content can be an object, an array, a string or empty.
# {
# "hey": "ho ho ho"
# }
# responseFields:
# hey:
# name: hey
# description: Who knows?
# type: string # This is optional

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

View File

@@ -0,0 +1,149 @@
document.addEventListener('DOMContentLoaded', function() {
const updateHash = function (id) {
window.location.hash = `#${id}`;
};
const navButton = document.getElementById('nav-button');
const menuWrapper = document.querySelector('.tocify-wrapper');
function toggleSidebar(event) {
event.preventDefault();
if (menuWrapper) {
menuWrapper.classList.toggle('open');
navButton.classList.toggle('open');
}
}
function closeSidebar() {
if (menuWrapper) {
menuWrapper.classList.remove('open');
navButton.classList.remove('open');
}
}
navButton.addEventListener('click', toggleSidebar);
window.hljs.highlightAll();
const wrapper = document.getElementById('toc');
// https://jets.js.org/
window.jets = new window.Jets({
// *OR - Selects elements whose values contains at least one part of search substring
searchSelector: '*OR',
searchTag: '#input-search',
contentTag: '#toc li',
didSearch: function(term) {
wrapper.classList.toggle('jets-searching', String(term).length > 0)
},
// map these accent keys to plain values
diacriticsMap: {
a: 'ÀÁÂÃÄÅàáâãäåĀāąĄ',
c: 'ÇçćĆčČ',
d: 'đĐďĎ',
e: 'ÈÉÊËèéêëěĚĒēęĘ',
i: 'ÌÍÎÏìíîïĪī',
l: 'łŁ',
n: 'ÑñňŇńŃ',
o: 'ÒÓÔÕÕÖØòóôõöøŌō',
r: 'řŘ',
s: 'ŠšśŚ',
t: 'ťŤ',
u: 'ÙÚÛÜùúûüůŮŪū',
y: 'ŸÿýÝ',
z: 'ŽžżŻźŹ'
}
});
function hashChange() {
const currentItems = document.querySelectorAll('.tocify-subheader.visible, .tocify-item.tocify-focus');
Array.from(currentItems).forEach((elem) => {
elem.classList.remove('visible', 'tocify-focus');
});
const currentTag = document.querySelector(`a[href="${window.location.hash}"]`);
if (currentTag) {
const parent = currentTag.closest('.tocify-subheader');
if (parent) {
parent.classList.add('visible');
}
const siblings = currentTag.closest('.tocify-header');
if (siblings) {
Array.from(siblings.querySelectorAll('.tocify-subheader')).forEach((elem) => {
elem.classList.add('visible');
});
}
currentTag.parentElement.classList.add('tocify-focus');
// wait for dom changes to be done
setTimeout(() => {
currentTag.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
// only close the sidebar on level-2 events
if (currentTag.parentElement.classList.contains('level-2')) {
closeSidebar();
}
}, 1500);
}
}
let languages = JSON.parse(document.body.getAttribute('data-languages'));
// Support a key => value object where the key is the name, or an array of strings where the value is the name
if (!Array.isArray(languages)) {
languages = Object.values(languages);
}
// if there is no language use the first one
const currentLanguage = window.localStorage.getItem('language') || languages[0];
const languageStyle = document.getElementById('language-style');
const langSelector = document.querySelectorAll('.lang-selector button.lang-button');
function setActiveLanguage(newLanguage) {
window.localStorage.setItem('language', newLanguage);
if (!languageStyle) {
return;
}
const newStyle = languages.map((language) => {
return language === newLanguage
// the current one should be visible
? `body .content .${language}-example pre { display: block; }`
// the inactive one should be hidden
: `body .content .${language}-example pre { display: none; }`;
}).join(`\n`);
Array.from(langSelector).forEach((elem) => {
elem.classList.toggle('active', elem.getAttribute('data-language-name') === newLanguage);
});
const activeHash = window.location.hash.slice(1);
languageStyle.innerHTML = newStyle;
setTimeout(() => {
updateHash(activeHash);
}, 200);
}
setActiveLanguage(currentLanguage);
Array.from(langSelector).forEach((elem) => {
elem.addEventListener('click', () => {
const newLanguage = elem.getAttribute('data-language-name');
setActiveLanguage(newLanguage);
});
});
window.addEventListener('hashchange', hashChange, false);
const divs = document.querySelectorAll('.content h1[id], .content h2[id]');
document.addEventListener('scroll', () => {
divs.forEach(item => {
const rect = item.getBoundingClientRect();
if (rect.top > 0 && rect.top < 150) {
const location = window.location.toString().split('#')[0];
history.replaceState(null, null, location + '#' + item.id);
hashChange();
}
});
});
hashChange();
});

View File

@@ -0,0 +1,289 @@
window.abortControllers = {};
function cacheAuthValue() {
// Whenever the auth header is set for one endpoint, cache it for the others
window.lastAuthValue = '';
let authInputs = document.querySelectorAll(`.auth-value`)
authInputs.forEach(el => {
el.addEventListener('input', (event) => {
window.lastAuthValue = event.target.value;
authInputs.forEach(otherInput => {
if (otherInput === el) return;
// Don't block the main thread
setTimeout(() => {
otherInput.value = window.lastAuthValue;
}, 0);
});
});
});
}
window.addEventListener('DOMContentLoaded', cacheAuthValue);
function getCookie(name) {
if (!document.cookie) {
return null;
}
const cookies = document.cookie.split(';')
.map(c => c.trim())
.filter(c => c.startsWith(name + '='));
if (cookies.length === 0) {
return null;
}
return decodeURIComponent(cookies[0].split('=')[1]);
}
function tryItOut(endpointId) {
document.querySelector(`#btn-tryout-${endpointId}`).hidden = true;
document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = false;
const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`).hidden = false;
executeBtn.disabled = false;
// Show all input fields
document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
.forEach(el => el.style.display = 'block');
if (document.querySelector(`#form-${endpointId}`).dataset.authed === "1") {
const authElement = document.querySelector(`#auth-${endpointId}`);
authElement && (authElement.hidden = false);
}
// Expand all nested fields
document.querySelectorAll(`#form-${endpointId} details`)
.forEach(el => el.open = true);
}
function cancelTryOut(endpointId) {
if (window.abortControllers[endpointId]) {
window.abortControllers[endpointId].abort();
delete window.abortControllers[endpointId];
}
document.querySelector(`#btn-tryout-${endpointId}`).hidden = false;
const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
executeBtn.hidden = true;
executeBtn.textContent = executeBtn.dataset.initialText;
document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = true;
// Hide inputs
document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
.forEach(el => el.style.display = 'none');
document.querySelectorAll(`#form-${endpointId} details`)
.forEach(el => el.open = false);
const authElement = document.querySelector(`#auth-${endpointId}`);
authElement && (authElement.hidden = true);
document.querySelector('#execution-results-' + endpointId).hidden = true;
document.querySelector('#execution-error-' + endpointId).hidden = true;
// Revert to sample code blocks
document.querySelector('#example-requests-' + endpointId).hidden = false;
document.querySelector('#example-responses-' + endpointId).hidden = false;
}
function makeAPICall(method, path, body = {}, query = {}, headers = {}, endpointId = null) {
console.log({endpointId, path, body, query, headers});
if (!(body instanceof FormData) && typeof body !== "string") {
body = JSON.stringify(body)
}
const url = new URL(window.tryItOutBaseUrl + '/' + path.replace(/^\//, ''));
// We need this function because if you try to set an array or object directly to a URLSearchParams object,
// you'll get [object Object] or the array.toString()
function addItemToSearchParamsObject(key, value, searchParams) {
if (Array.isArray(value)) {
value.forEach((v, i) => {
// Append {filters: [first, second]} as filters[0]=first&filters[1]second
addItemToSearchParamsObject(key + '[' + i + ']', v, searchParams);
})
} else if (typeof value === 'object' && value !== null) {
Object.keys(value).forEach((i) => {
// Append {filters: {name: first}} as filters[name]=first
addItemToSearchParamsObject(key + '[' + i + ']', value[i], searchParams);
});
} else {
searchParams.append(key, value);
}
}
Object.keys(query)
.forEach(key => addItemToSearchParamsObject(key, query[key], url.searchParams));
window.abortControllers[endpointId] = new AbortController();
return fetch(url, {
method,
headers,
body: method === 'GET' ? undefined : body,
signal: window.abortControllers[endpointId].signal,
referrer: window.tryItOutBaseUrl,
mode: 'cors',
credentials: 'same-origin',
})
.then(response => Promise.all([response.status, response.statusText, response.text(), response.headers]));
}
function hideCodeSamples(endpointId) {
document.querySelector('#example-requests-' + endpointId).hidden = true;
document.querySelector('#example-responses-' + endpointId).hidden = true;
}
function handleResponse(endpointId, response, status, headers) {
hideCodeSamples(endpointId);
// Hide error views
document.querySelector('#execution-error-' + endpointId).hidden = true;
const responseContentEl = document.querySelector('#execution-response-content-' + endpointId);
// Check if the response contains Laravel's dd() default dump output
const isLaravelDump = response.includes('Sfdump');
// If it's a Laravel dd() dump, use innerHTML to render it safely
if (isLaravelDump) {
responseContentEl.innerHTML = response === '' ? responseContentEl.dataset.emptyResponseText : response;
} else {
// Otherwise, stick to textContent for regular responses
responseContentEl.textContent = response === '' ? responseContentEl.dataset.emptyResponseText : response;
}
// Prettify it if it's JSON
let isJson = false;
try {
const jsonParsed = JSON.parse(response);
if (jsonParsed !== null) {
isJson = true;
response = JSON.stringify(jsonParsed, null, 4);
responseContentEl.textContent = response;
}
} catch (e) {
}
isJson && window.hljs.highlightElement(responseContentEl);
const statusEl = document.querySelector('#execution-response-status-' + endpointId);
statusEl.textContent = ` (${status})`;
document.querySelector('#execution-results-' + endpointId).hidden = false;
statusEl.scrollIntoView({behavior: "smooth", block: "center"});
}
function handleError(endpointId, err) {
hideCodeSamples(endpointId);
// Hide response views
document.querySelector('#execution-results-' + endpointId).hidden = true;
// Show error views
let errorMessage = err.message || err;
const $errorMessageEl = document.querySelector('#execution-error-message-' + endpointId);
$errorMessageEl.textContent = errorMessage + $errorMessageEl.textContent;
const errorEl = document.querySelector('#execution-error-' + endpointId);
errorEl.hidden = false;
errorEl.scrollIntoView({behavior: "smooth", block: "center"});
}
async function executeTryOut(endpointId, form) {
const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
executeBtn.textContent = executeBtn.dataset.loadingText;
executeBtn.disabled = true;
executeBtn.scrollIntoView({behavior: "smooth", block: "center"});
let body;
let setter;
if (form.dataset.hasfiles === "1") {
body = new FormData();
setter = (name, value) => body.append(name, value);
} else if (form.dataset.isarraybody === "1") {
body = [];
setter = (name, value) => _.set(body, name, value);
} else {
body = {};
setter = (name, value) => _.set(body, name, value);
}
const bodyParameters = form.querySelectorAll('input[data-component=body]');
bodyParameters.forEach(el => {
let value = el.value;
if (el.type === 'number' && typeof value === 'string') {
value = parseFloat(value);
}
if (el.type === 'file' && el.files[0]) {
setter(el.name, el.files[0]);
return;
}
if (el.type !== 'radio') {
if (value === "" && el.required === false) {
// Don't include empty optional values in the request
return;
}
setter(el.name, value);
return;
}
if (el.checked) {
value = (value === 'false') ? false : true;
setter(el.name, value);
}
});
const query = {};
const queryParameters = form.querySelectorAll('input[data-component=query]');
queryParameters.forEach(el => {
if (el.type !== 'radio' || (el.type === 'radio' && el.checked)) {
if (el.value === '') {
// Don't include empty values in the request
return;
}
_.set(query, el.name, el.value);
}
});
let path = form.dataset.path;
const urlParameters = form.querySelectorAll('input[data-component=url]');
urlParameters.forEach(el => (path = path.replace(new RegExp(`\\{${el.name}\\??}`), el.value)));
const headers = Object.fromEntries(Array.from(form.querySelectorAll('input[data-component=header]'))
.map(el => [el.name, el.value]));
// When using FormData, the browser sets the correct content-type + boundary
let method = form.dataset.method;
if (body instanceof FormData) {
delete headers['Content-Type'];
// When using FormData with PUT or PATCH, use method spoofing so PHP can access the post body
if (['PUT', 'PATCH'].includes(form.dataset.method)) {
method = 'POST';
setter('_method', form.dataset.method);
}
}
let preflightPromise = Promise.resolve();
if (window.useCsrf && window.csrfUrl) {
preflightPromise = makeAPICall('GET', window.csrfUrl).then(() => {
headers['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
});
}
return preflightPromise.then(() => makeAPICall(method, path, body, query, headers, endpointId))
.then(([responseStatus, statusText, responseContent, responseHeaders]) => {
handleResponse(endpointId, responseContent, responseStatus, responseHeaders)
})
.catch(err => {
if (err.name === "AbortError") {
console.log("Request cancelled");
return;
}
console.log("Error while making request: ", err);
handleError(endpointId, err);
})
.finally(() => {
executeBtn.disabled = false;
executeBtn.textContent = executeBtn.dataset.initialText;
});
}

View File

@@ -0,0 +1,3 @@
@if($authenticated)@component('scribe::components.badges.base', ['colour' => "darkred", 'text' => 'requires authentication'])
@endcomponent
@endif

View File

@@ -0,0 +1 @@
<small class="badge badge-{{ $colour }}">{{ $text }}</small>

View File

@@ -0,0 +1,5 @@
@if($deprecated !== false)
@php($text = $deprecated === true ? 'deprecated' : "deprecated:$deprecated")
@component('scribe::components.badges.base', ['colour' => 'darkgoldenrod', 'text' => $text])
@endcomponent
@endif

View File

@@ -0,0 +1,5 @@
@component('scribe::components.badges.base', [
'colour' => \Knuckles\Scribe\Tools\WritingUtils::$httpMethodToCssColour[$method],
'text' => $method,
])
@endcomponent

View File

@@ -0,0 +1,76 @@
@php
$html ??= []; $class = $html['class'] ?? null;
@endphp
<b style="line-height: 2;"><code>{{ $name }}</code></b>&nbsp;&nbsp;
@if($type)<small>{{ $type }}</small>@endif&nbsp;
@if($isInput && !$required)<i>optional</i>@endif &nbsp;
@if($isInput && $deprecated)<i>deprecated</i>@endif &nbsp;
@if($isInput && empty($hasChildren))
@php
$isList = Str::endsWith($type, '[]');
$fullName = str_replace('[]', '.0', $fullName ?? $name);
$baseType = $isList ? substr($type, 0, -2) : $type;
// Ignore the first '[]': the frontend will take care of it
while (\Str::endsWith($baseType, '[]')) {
$fullName .= '.0';
$baseType = substr($baseType, 0, -2);
}
// When the body is an array, the item names will be ".0.thing"
$fullName = ltrim($fullName, '.');
$inputType = match($baseType) {
'number', 'integer' => 'number',
'file' => 'file',
default => 'text',
};
@endphp
@if($type === 'boolean')
<label data-endpoint="{{ $endpointId }}" style="display: none">
<input type="radio" name="{{ $fullName }}"
value="{{$component === 'body' ? 'true' : 1}}"
data-endpoint="{{ $endpointId }}"
data-component="{{ $component }}" @if($class)class="{{ $class }}"@endif
>
<code>true</code>
</label>
<label data-endpoint="{{ $endpointId }}" style="display: none">
<input type="radio" name="{{ $fullName }}"
value="{{$component === 'body' ? 'false' : 0}}"
data-endpoint="{{ $endpointId }}"
data-component="{{ $component }}" @if($class)class="{{ $class }}"@endif
>
<code>false</code>
</label>
@elseif($isList)
<input type="{{ $inputType }}" style="display: none"
@if($inputType === 'number')step="any"@endif
name="{{ $fullName."[0]" }}" @if($class)class="{{ $class }}"@endif
data-endpoint="{{ $endpointId }}"
data-component="{{ $component }}">
<input type="{{ $inputType }}" style="display: none"
name="{{ $fullName."[1]" }}" @if($class)class="{{ $class }}"@endif
data-endpoint="{{ $endpointId }}"
data-component="{{ $component }}">
@else
<input type="{{ $inputType }}" style="display: none"
@if($inputType === 'number')step="any"@endif
name="{{ $fullName }}" @if($class)class="{{ $class }}"@endif
data-endpoint="{{ $endpointId }}"
value="{!! (isset($example) && (is_string($example) || is_numeric($example))) ? $example : '' !!}"
data-component="{{ $component }}">
@endif
@endif
<br>
@php
if($example !== null && $example !== '' && !is_array($example)) {
$exampleAsString = $example;
if (is_bool($example)) {
$exampleAsString = $example ? "true" : "false";
}
$description .= " Example: `$exampleAsString`";
}
@endphp
{!! Parsedown::instance()->text(trim($description)) !!}
@if(!empty($enumValues))
Must be one of:
<ul style="list-style-type: square;">{!! implode(" ", array_map(fn($val) => "<li><code>$val</code></li>", $enumValues)) !!}</ul>
@endif

View File

@@ -0,0 +1,105 @@
@php
$isInput ??= true;
$level ??= 0;
@endphp
@foreach($fields as $name => $field)
@if($name === '[]')
@php
$description = "The request body is an array (<code>{$field['type']}</code>`)";
$description .= !empty($field['description']) ? ", representing ".lcfirst($field['description'])."." : '.';
if(count($field['__fields'])) $description .= " Each item has the following properties:";
@endphp
{!! Parsedown::instance()->text($description) !!}
@foreach($field['__fields'] as $subfieldName => $subfield)
@if(!empty($subfield['__fields']))
<x-scribe::nested-fields
:fields="[$subfieldName => $subfield]" :endpointId="$endpointId" :isInput="$isInput" :level="$level + 2"
/>
@else
<div style="margin-left: {{ ($level + 2) * 14 }}px; clear: unset;">
@component('scribe::components.field-details', [
'name' => $subfieldName,
'fullName' => $subfield['name'],
'type' => $subfield['type'] ?? 'string',
'required' => $subfield['required'] ?? false,
'deprecated' => $subfield['deprecated'] ?? false,
'description' => $subfield['description'] ?? '',
'example' => $subfield['example'] ?? '',
'enumValues' => $subfield['enumValues'] ?? null,
'endpointId' => $endpointId,
'hasChildren' => false,
'component' => 'body',
'isInput' => $isInput,
])
@endcomponent
</div>
@endif
@endforeach
@elseif(!empty($field['__fields']))
<div style="@if($level) margin-left: {{ $level * 14 }}px;@else padding-left: 28px; @endif clear: unset;">
<details>
<summary style="padding-bottom: 10px;">
@component('scribe::components.field-details', [
'name' => $name,
'fullName' => $field['name'],
'type' => $field['type'] ?? 'string',
'required' => $field['required'] ?? false,
'deprecated' => $field['deprecated'] ?? false,
'description' => $field['description'] ?? '',
'example' => $field['example'] ?? '',
'enumValues' => $field['enumValues'] ?? null,
'endpointId' => $endpointId,
'hasChildren' => true,
'component' => 'body',
'isInput' => $isInput,
])
@endcomponent
</summary>
@foreach($field['__fields'] as $subfieldName => $subfield)
@if(!empty($subfield['__fields']))
<x-scribe::nested-fields
:fields="[$subfieldName => $subfield]" :endpointId="$endpointId" :isInput="$isInput" :level="$level + 1"
/>
@else
<div style="margin-left: {{ ($level + 1) * 14 }}px; clear: unset;">
@component('scribe::components.field-details', [
'name' => $subfieldName,
'fullName' => $subfield['name'],
'type' => $subfield['type'] ?? 'string',
'required' => $subfield['required'] ?? false,
'deprecated' => $subfield['deprecated'] ?? false,
'description' => $subfield['description'] ?? '',
'example' => $subfield['example'] ?? '',
'enumValues' => $subfield['enumValues'] ?? null,
'endpointId' => $endpointId,
'hasChildren' => false,
'component' => 'body',
'isInput' => $isInput,
])
@endcomponent
</div>
@endif
@endforeach
</details>
</div>
@else
<div style="@if($level) margin-left: {{ ($level + 1) * 14 }}px;@else padding-left: 28px; @endif clear: unset;">
@component('scribe::components.field-details', [
'name' => $name,
'fullName' => $field['name'],
'type' => $field['type'] ?? 'string',
'required' => $field['required'] ?? false,
'deprecated' => $field['deprecated'] ?? false,
'description' => $field['description'] ?? '',
'example' => $field['example'] ?? '',
'enumValues' => $field['enumValues'] ?? null,
'endpointId' => $endpointId,
'hasChildren' => false,
'component' => 'body',
'isInput' => $isInput,
])
@endcomponent
</div>
@endif
@endforeach

View File

@@ -0,0 +1,34 @@
<!-- See https://github.com/stoplightio/elements/blob/main/docs/getting-started/elements/elements-options.md for config -->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{!! $metadata['title'] !!}</title>
<!-- Embed elements Elements via Web Component -->
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
<style>
body {
height: 100vh;
}
</style>
</head>
<body>
<elements-api
@foreach($htmlAttributes as $attribute => $value)
{{-- Attributes specified first override later ones --}}
{!! $attribute !!}="{!! $value !!}"
@endforeach
apiDescriptionUrl="{!! $metadata['openapi_spec_url'] !!}"
router="hash"
layout="sidebar"
hideTryIt="{!! ($tryItOut['enabled'] ?? true) ? '' : 'true'!!}"
@if(!empty($metadata['logo']))
logo="{!! $metadata['logo'] !!}"
@endif
/>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<!-- See https://rapidocweb.com/api.html for options -->
<!doctype html> <!-- Important: must specify -->
<html>
<head>
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 characters -->
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
</head>
<body>
<rapi-doc
@foreach($htmlAttributes as $attribute => $value)
{{-- Attributes specified first override later ones --}}
{!! $attribute !!}="{!! $value !!}"
@endforeach
spec-url="{!! $metadata['openapi_spec_url'] !!}"
render-style="read"
allow-try="{!! ($tryItOut['enabled'] ?? true) ? 'true' : 'false'!!}"
>
@if($metadata['logo'])
<img slot="logo" src="{!! $metadata['logo'] !!}"/>
@endif
</rapi-doc>
</body>
</html>

View File

@@ -0,0 +1,27 @@
<!doctype html>
<html>
<head>
<title>{!! $metadata['title'] !!}</title>
<meta charset="utf-8"/>
<meta
name="viewport"
content="width=device-width, initial-scale=1"/>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script
id="api-reference"
@foreach($htmlAttributes as $attribute => $value)
{{-- Attributes specified first override later ones --}}
{!! $attribute !!}="{!! $value !!}"
@endforeach
data-url="{!! $metadata['openapi_spec_url'] !!}">
</script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
@php
use Knuckles\Scribe\Tools\Utils as u;
@endphp
# {{ u::trans("scribe::headings.auth") }}
@if(!$isAuthed)
{!! u::trans("scribe::auth.none") !!}
@else
{!! $authDescription !!}
{!! $extraAuthInfo !!}
@endif

View File

@@ -0,0 +1,13 @@
@php
use Knuckles\Scribe\Tools\Utils as u;
@endphp
# {{ u::trans("scribe::headings.introduction") }}
{!! $description !!}
<aside>
<strong>{{ u::trans("scribe::labels.base_url") }}</strong>: <code>{!! $baseUrl !!}</code>
</aside>
{!! $introText !!}

View File

@@ -0,0 +1,34 @@
@php
use Knuckles\Scribe\Tools\WritingUtils as u;
/** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */
@endphp
```bash
curl --request {{$endpoint->httpMethods[0]}} \
{{$endpoint->httpMethods[0] == 'GET' ? '--get ' : ''}}"{!! rtrim($baseUrl, '/') !!}/{{ ltrim($endpoint->boundUri, '/') }}@if(count($endpoint->cleanQueryParameters))?{!! u::printQueryParamsAsString($endpoint->cleanQueryParameters) !!}@endif"@if(count($endpoint->headers)) \
@foreach($endpoint->headers as $header => $value)
--header "{{$header}}: {{ addslashes($value) }}"@if(! ($loop->last) || ($loop->last && count($endpoint->bodyParameters))) \
@endif
@endforeach
@endif
@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters)))
@foreach($endpoint->cleanBodyParameters as $parameter => $value)
@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue)
--form "{!! "$key=".$actualValue !!}"@if(!($loop->parent->last) || count($endpoint->fileParameters))\
@endif
@endforeach
@endforeach
@foreach($endpoint->fileParameters as $parameter => $value)
@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file)
--form "{!! "$key=@".$file->path() !!}" @if(!($loop->parent->last))\
@endif
@endforeach
@endforeach
@elseif(count($endpoint->cleanBodyParameters))
@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded')
--data "{!! http_build_query($endpoint->cleanBodyParameters, '', '&') !!}"
@else
--data "{!! addslashes(json_encode($endpoint->cleanBodyParameters, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) !!}"
@endif
@endif
```

View File

@@ -0,0 +1,62 @@
@php
use Knuckles\Scribe\Tools\WritingUtils as u;
/** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */
@endphp
```javascript
const url = new URL(
"{!! rtrim($baseUrl, '/') !!}/{{ ltrim($endpoint->boundUri, '/') }}"
);
@if(count($endpoint->cleanQueryParameters))
const params = {!! u::printQueryParamsAsKeyValue($endpoint->cleanQueryParameters, "\"", ":", 4, "{}") !!};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
@endif
@if(!empty($endpoint->headers))
const headers = {
@foreach($endpoint->headers as $header => $value)
"{{$header}}": "{{$value}}",
@endforeach
@empty($endpoint->headers['Accept'])
"Accept": "application/json",
@endempty
};
@endif
@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters)))
const body = new FormData();
@foreach($endpoint->cleanBodyParameters as $parameter => $value)
@foreach( u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue)
body.append('{!! $key !!}', '{!! $actualValue !!}');
@endforeach
@endforeach
@foreach($endpoint->fileParameters as $parameter => $value)
@foreach( u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file)
body.append('{!! $key !!}', document.querySelector('input[name="{!! $key !!}"]').files[0]);
@endforeach
@endforeach
@elseif(count($endpoint->cleanBodyParameters))
@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded')
let body = "{!! http_build_query($endpoint->cleanBodyParameters, '', '&') !!}";
@else
let body = {!! json_encode($endpoint->cleanBodyParameters, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) !!};
@endif
@endif
fetch(url, {
method: "{{$endpoint->httpMethods[0]}}",
@if(count($endpoint->headers))
headers,
@endif
@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters)))
body,
@elseif(count($endpoint->cleanBodyParameters))
@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded')
body,
@else
body: JSON.stringify(body),
@endif
@endif
}).then(response => response.json());
```

View File

@@ -0,0 +1,51 @@
@php
use Knuckles\Scribe\Tools\WritingUtils as u;
/** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */
@endphp
```php
$client = new \GuzzleHttp\Client();
$url = '{!! rtrim($baseUrl, '/') . '/' . ltrim($endpoint->boundUri, '/') !!}';
@if($endpoint->hasHeadersOrQueryOrBodyParams())
$response = $client->{{ strtolower($endpoint->httpMethods[0]) }}(
$url,
[
@if(!empty($endpoint->headers))
'headers' => {!! u::printPhpValue($endpoint->headers, 8) !!},
@endif
@if(!empty($endpoint->cleanQueryParameters))
'query' => {!! u::printQueryParamsAsKeyValue($endpoint->cleanQueryParameters, "'", " =>", 12, "[]", 8) !!},
@endif
@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && !empty($endpoint->cleanBodyParameters)))
'multipart' => [
@foreach($endpoint->cleanBodyParameters as $parameter => $value)
@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue)
[
'name' => '{!! $key !!}',
'contents' => '{!! $actualValue !!}'
],
@endforeach
@endforeach
@foreach($endpoint->fileParameters as $parameter => $value)
@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file)
[
'name' => '{!! $key !!}',
'contents' => fopen('{!! $file->path() !!}', 'r')
],
@endforeach
@endforeach
],
@elseif(count($endpoint->cleanBodyParameters))
@if ($endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded')
'form_params' => {!! u::printPhpValue($endpoint->cleanBodyParameters, 8) !!},
@else
'json' => {!! u::printPhpValue($endpoint->cleanBodyParameters, 8) !!},
@endif
@endif
]
);
@else
$response = $client->{{ strtolower($endpoint->httpMethods[0]) }}($url);
@endif
$body = $response->getBody();
print_r(json_decode((string) $body));
```

View File

@@ -0,0 +1,52 @@
@php
use Knuckles\Scribe\Tools\WritingUtils as u;
/** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */
@endphp
```python
import requests
import json
url = '{!! rtrim($baseUrl, '/') !!}/{{ $endpoint->boundUri }}'
@if($endpoint->hasFiles() || (isset($endpoint->headers['Content-Type']) && $endpoint->headers['Content-Type'] == 'multipart/form-data' && count($endpoint->cleanBodyParameters)))
files = {
@foreach($endpoint->cleanBodyParameters as $parameter => $value)
@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $actualValue)
'{!! $key !!}': (None, '{!! $actualValue !!}')@if(!($loop->parent->last) || count($endpoint->fileParameters)),
@endif
@endforeach
@endforeach
@foreach($endpoint->fileParameters as $parameter => $value)
@foreach(u::getParameterNamesAndValuesForFormData($parameter, $value) as $key => $file)
'{!! $key !!}': open('{!! $file->path() !!}', 'rb')@if(!($loop->parent->last)),
@endif
@endforeach
@endforeach
}
@endif
@if(count($endpoint->cleanBodyParameters))
payload = {!! json_encode($endpoint->cleanBodyParameters, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) !!}
@endif
@if(count($endpoint->cleanQueryParameters))
params = {!! u::printQueryParamsAsKeyValue($endpoint->cleanQueryParameters, "'", ":", 2, "{}") !!}
@endif
@if(!empty($endpoint->headers))
headers = {
@foreach($endpoint->headers as $header => $value)
'{{$header}}': '{{$value}}'@if(!($loop->last)),
@endif
@endforeach
}
@endif
@php
$optionalArguments = [];
if (count($endpoint->headers)) $optionalArguments[] = "headers=headers";
if (count($endpoint->fileParameters)) $optionalArguments[] = "files=files";
if (count($endpoint->cleanBodyParameters) && $endpoint->headers['Content-Type'] != 'multipart/form-data') $optionalArguments[] = (count($endpoint->fileParameters) || $endpoint->headers['Content-Type'] == 'application/x-www-form-urlencoded' ? "data=payload" : "json=payload");
if (count($endpoint->cleanQueryParameters)) $optionalArguments[] = "params=params";
$optionalArguments = implode(', ',$optionalArguments);
@endphp
response = requests.request('{{$endpoint->httpMethods[0]}}', url, {{ $optionalArguments }})
response.json()
```

View File

@@ -0,0 +1,190 @@
@php
use Knuckles\Scribe\Tools\Utils as u;
/** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */
@endphp
<h2 id="{!! $endpoint->fullSlug() !!}">{{ $endpoint->name() }}</h2>
<p>
@component('scribe::components.badges.auth', ['authenticated' => $endpoint->isAuthed()])
@endcomponent
@component('scribe::components.badges.deprecated', ['deprecated' => $endpoint->metadata->deprecated])
@endcomponent
</p>
{!! Parsedown::instance()->text($endpoint->metadata->description ?: '') !!}
<span id="example-requests-{!! $endpoint->endpointId() !!}">
<blockquote>{{ u::trans("scribe::endpoint.example_request") }}:</blockquote>
@foreach($metadata['example_languages'] as $language)
<div class="{{ $language }}-example">
@include("scribe::partials.example-requests.$language")
</div>
@endforeach
</span>
<span id="example-responses-{!! $endpoint->endpointId() !!}">
@if($endpoint->isGet() || $endpoint->hasResponses())
@foreach($endpoint->responses as $response)
<blockquote>
<p>{{ u::trans("scribe::endpoint.example_response") }} ({{ $response->fullDescription() }}):</p>
</blockquote>
@if(count($response->headers))
<details class="annotation">
<summary style="cursor: pointer;">
<small onclick="textContent = parentElement.parentElement.open ? 'Show headers' : 'Hide headers'">Show headers</small>
</summary>
<pre><code class="language-http">@foreach($response->headers as $header => $value)
{{ $header }}: {{ is_array($value) ? implode('; ', $value) : $value }}
@endforeach </code></pre></details> @endif
<pre>
@if($response->isBinary())
<code>{!! u::trans("scribe::endpoint.responses.binary") !!} - {{ htmlentities(str_replace("<<binary>>", "", $response->content)) }}</code>
@elseif($response->status == 204)
<code>{!! u::trans("scribe::endpoint.responses.empty") !!}</code>
@else
@php($parsed = json_decode($response->content))
{{-- If response is a JSON string, prettify it. Otherwise, just print it --}}
<code class="language-json" style="max-height: 300px;">{!! htmlentities($parsed != null ? json_encode($parsed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $response->content) !!}</code>
@endif </pre>
@endforeach
@endif
</span>
<span id="execution-results-{{ $endpoint->endpointId() }}" hidden>
<blockquote>{{ u::trans("scribe::try_it_out.received_response") }}<span
id="execution-response-status-{{ $endpoint->endpointId() }}"></span>:
</blockquote>
<pre class="json"><code id="execution-response-content-{{ $endpoint->endpointId() }}"
data-empty-response-text="<{{ u::trans("scribe::endpoint.responses.empty") }}>" style="max-height: 400px;"></code></pre>
</span>
<span id="execution-error-{{ $endpoint->endpointId() }}" hidden>
<blockquote>{{ u::trans("scribe::try_it_out.request_failed") }}:</blockquote>
<pre><code id="execution-error-message-{{ $endpoint->endpointId() }}">{{ "\n\n".u::trans("scribe::try_it_out.error_help") }}</code></pre>
</span>
<form id="form-{{ $endpoint->endpointId() }}" data-method="{{ $endpoint->httpMethods[0] }}"
data-path="{{ $endpoint->uri }}"
data-authed="{{ $endpoint->isAuthed() ? 1 : 0 }}"
data-hasfiles="{{ $endpoint->hasFiles() ? 1 : 0 }}"
data-isarraybody="{{ $endpoint->isArrayBody() ? 1 : 0 }}"
autocomplete="off"
onsubmit="event.preventDefault(); executeTryOut('{{ $endpoint->endpointId() }}', this);">
<h3>
{{ u::trans("scribe::endpoint.request") }}&nbsp;&nbsp;&nbsp;
@if($metadata['try_it_out']['enabled'] ?? false)
<button type="button"
style="background-color: #8fbcd4; padding: 5px 10px; border-radius: 5px; border-width: thin;"
id="btn-tryout-{{ $endpoint->endpointId() }}"
onclick="tryItOut('{{ $endpoint->endpointId() }}');">{{ u::trans("scribe::try_it_out.open") }}
</button>
<button type="button"
style="background-color: #c97a7e; padding: 5px 10px; border-radius: 5px; border-width: thin;"
id="btn-canceltryout-{{ $endpoint->endpointId() }}"
onclick="cancelTryOut('{{ $endpoint->endpointId() }}');" hidden>{{ u::trans("scribe::try_it_out.cancel") }}
</button>&nbsp;&nbsp;
<button type="submit"
style="background-color: #6ac174; padding: 5px 10px; border-radius: 5px; border-width: thin;"
id="btn-executetryout-{{ $endpoint->endpointId() }}"
data-initial-text="{{ u::trans("scribe::try_it_out.send") }}"
data-loading-text="{{ u::trans("scribe::try_it_out.loading") }}"
hidden>{{ u::trans("scribe::try_it_out.send") }}
</button>
@endif
</h3>
@foreach($endpoint->httpMethods as $method)
<p>
@component('scribe::components.badges.http-method', ['method' => $method])@endcomponent
<b><code>{{$endpoint->uri}}</code></b>
</p>
@endforeach
@if(count($endpoint->headers))
<h4 class="fancy-heading-panel"><b>{{ u::trans("scribe::endpoint.headers") }}</b></h4>
@foreach($endpoint->headers as $name => $example)
<?php
$htmlOptions = [];
if ($endpoint->isAuthed() && 'header' == $metadata['auth']['location'] && $metadata['auth']['name'] == $name) {
$htmlOptions = ['class' => 'auth-value'];
}
?>
<div style="padding-left: 28px; clear: unset;">
@component('scribe::components.field-details', [
'name' => $name,
'type' => null,
'required' => true,
'deprecated' => false,
'description' => null,
'example' => $example,
'endpointId' => $endpoint->endpointId(),
'component' => 'header',
'isInput' => true,
'html' => $htmlOptions,
])
@endcomponent
</div>
@endforeach
@endif
@if(count($endpoint->urlParameters))
<h4 class="fancy-heading-panel"><b>{{ u::trans("scribe::endpoint.url_parameters") }}</b></h4>
@foreach($endpoint->urlParameters as $attribute => $parameter)
<div style="padding-left: 28px; clear: unset;">
@component('scribe::components.field-details', [
'name' => $parameter->name,
'type' => $parameter->type ?? 'string',
'required' => $parameter->required,
'deprecated' => $parameter->deprecated,
'description' => $parameter->description,
'example' => $parameter->example ?? '',
'enumValues' => $parameter->enumValues,
'endpointId' => $endpoint->endpointId(),
'component' => 'url',
'isInput' => true,
])
@endcomponent
</div>
@endforeach
@endif
@if(count($endpoint->queryParameters))
<h4 class="fancy-heading-panel"><b>{{ u::trans("scribe::endpoint.query_parameters") }}</b></h4>
@foreach($endpoint->queryParameters as $attribute => $parameter)
<?php
$htmlOptions = [];
if ($endpoint->isAuthed() && 'query' == $metadata['auth']['location'] && $metadata['auth']['name'] == $attribute) {
$htmlOptions = ['class' => 'auth-value'];
}
?>
<div style="padding-left: 28px; clear: unset;">
@component('scribe::components.field-details', [
'name' => $parameter->name,
'type' => $parameter->type,
'required' => $parameter->required,
'deprecated' => $parameter->deprecated,
'description' => $parameter->description,
'example' => $parameter->example ?? '',
'enumValues' => $parameter->enumValues,
'endpointId' => $endpoint->endpointId(),
'component' => 'query',
'isInput' => true,
'html' => $htmlOptions,
])
@endcomponent
</div>
@endforeach
@endif
@if(count($endpoint->nestedBodyParameters))
<h4 class="fancy-heading-panel"><b>{{ u::trans("scribe::endpoint.body_parameters") }}</b></h4>
<x-scribe::nested-fields
:fields="$endpoint->nestedBodyParameters" :endpointId="$endpoint->endpointId()"
/>
@endif
</form>
@if(count($endpoint->responseFields))
<h3>{{ u::trans("scribe::endpoint.response") }}</h3>
<h4 class="fancy-heading-panel"><b>{{ u::trans("scribe::endpoint.response_fields") }}</b></h4>
<x-scribe::nested-fields
:fields="$endpoint->nestedResponseFields" :endpointId="$endpoint->endpointId()"
:isInput="false"
/>
@endif

View File

@@ -0,0 +1,21 @@
@foreach($groupedEndpoints as $group)
<h1 id="{!! Str::slug($group['name']) !!}">{!! $group['name'] !!}</h1>
{!! Parsedown::instance()->text($group['description']) !!}
@foreach($group['subgroups'] as $subgroupName => $subgroup)
@if($subgroupName !== "")
<h2 id="{!! Str::slug($group['name']) !!}-{!! Str::slug($subgroupName) !!}">{{ $subgroupName }}</h2>
@php($subgroupDescription = collect($subgroup)->first(fn ($e) => $e->metadata->subgroupDescription)?->metadata?->subgroupDescription)
@if($subgroupDescription)
<p>
{!! Parsedown::instance()->text($subgroupDescription) !!}
</p>
@endif
@endif
@foreach($subgroup as $endpoint)
@include("scribe::themes.default.endpoint")
@endforeach
@endforeach
@endforeach

View File

@@ -0,0 +1,74 @@
@php
use Knuckles\Scribe\Tools\WritingUtils as u;
@endphp
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{!! $metadata['title'] !!}</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{!! $assetPathPrefix !!}css/theme-default.style.css" media="screen">
<link rel="stylesheet" href="{!! $assetPathPrefix !!}css/theme-default.print.css" media="print">
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>
<link rel="stylesheet"
href="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/styles/obsidian.min.css">
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jets/0.14.1/jets.min.js"></script>
@if(isset($metadata['example_languages']))
<style id="language-style">
/* starts out as display none and is replaced with js later */
@foreach($metadata['example_languages'] as $lang)
body .content .{{ $lang }}-example code { display: none; }
@endforeach
</style>
@endif
@if($tryItOut['enabled'] ?? true)
<script>
var tryItOutBaseUrl = "{!! $tryItOut['base_url'] ?? $baseUrl !!}";
var useCsrf = Boolean({!! $tryItOut['use_csrf'] ?? null !!});
var csrfUrl = "{!! $tryItOut['csrf_url'] ?? null !!}";
</script>
<script src="{{ u::getVersionedAsset($assetPathPrefix.'js/tryitout.js') }}"></script>
@endif
<script src="{{ u::getVersionedAsset($assetPathPrefix.'js/theme-default.js') }}"></script>
</head>
<body data-languages="{{ json_encode($metadata['example_languages'] ?? []) }}">
@include("scribe::themes.default.sidebar")
<div class="page-wrapper">
<div class="dark-box"></div>
<div class="content">
{!! $intro !!}
{!! $auth !!}
@include("scribe::themes.default.groups")
{!! $append !!}
</div>
<div class="dark-box">
@if(isset($metadata['example_languages']))
<div class="lang-selector">
@foreach($metadata['example_languages'] as $name => $lang)
@php if (is_numeric($name)) $name = $lang; @endphp
<button type="button" class="lang-button" data-language-name="{{$lang}}">{{$name}}</button>
@endforeach
</div>
@endif
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,69 @@
@php
use Knuckles\Scribe\Tools\Utils as u;
@endphp
<a href="#" id="nav-button">
<span>
MENU
<img src="{!! $assetPathPrefix !!}images/navbar.png" alt="navbar-image"/>
</span>
</a>
<div class="tocify-wrapper">
@if($metadata['logo'] != false)
<img src="{{ $metadata['logo'] }}" alt="logo" class="logo" style="padding-top: 10px;" width="100%"/>
@endif
@isset($metadata['example_languages'])
<div class="lang-selector">
@foreach($metadata['example_languages'] as $name => $lang)
@php if (is_numeric($name)) $name = $lang; @endphp
<button type="button" class="lang-button" data-language-name="{{ $lang }}">{{ $name }}</button>
@endforeach
</div>
@endisset
<div class="search">
<input type="text" class="search" id="input-search" placeholder="{{ u::trans("scribe::labels.search") }}">
</div>
<div id="toc">
@foreach($headings as $h1)
<ul id="tocify-header-{{ $h1['slug'] }}" class="tocify-header">
<li class="tocify-item level-1" data-unique="{!! $h1['slug'] !!}">
<a href="#{!! $h1['slug'] !!}">{!! $h1['name'] !!}</a>
</li>
@if(count($h1['subheadings']) > 0)
<ul id="tocify-subheader-{!! $h1['slug'] !!}" class="tocify-subheader">
@foreach($h1['subheadings'] as $h2)
<li class="tocify-item level-2" data-unique="{!! $h2['slug'] !!}">
<a href="#{!! $h2['slug'] !!}">{!! $h2['name'] !!}</a>
</li>
@if(count($h2['subheadings']) > 0)
<ul id="tocify-subheader-{!! $h2['slug'] !!}" class="tocify-subheader">
@foreach($h2['subheadings'] as $h3)
<li class="tocify-item level-3" data-unique="{!! $h3['slug'] !!}">
<a href="#{!! $h3['slug'] !!}">{!! $h3['name'] !!}</a>
</li>
@endforeach
</ul>
@endif
@endforeach
</ul>
@endif
</ul>
@endforeach
</div>
<ul class="toc-footer" id="toc-footer">
@if($metadata['postman_collection_url'])
<li style="padding-bottom: 5px;"><a href="{!! $metadata['postman_collection_url'] !!}">{!! u::trans("scribe::links.postman") !!}</a></li>
@endif
@if($metadata['openapi_spec_url'])
<li style="padding-bottom: 5px;"><a href="{!! $metadata['openapi_spec_url'] !!}">{!! u::trans("scribe::links.openapi") !!}</a></li>
@endif
<li><a href="http://github.com/knuckleswtf/scribe">Documentation powered by Scribe </a></li>
</ul>
<ul class="toc-footer" id="last-updated">
<li>{{ $metadata['last_updated'] }}</li>
</ul>
</div>

View File

@@ -0,0 +1,70 @@
@php
$hasChildren ??= false;
$isArrayBody = $name == "[]";
$expandable = $hasChildren && !$isArrayBody;
@endphp
<div class="sl-flex sl-relative sl-max-w-full sl-py-2 sl-pl-3">
<div class="sl-w-1 sl-mt-2 sl-mr-3 sl--ml-3 sl-border-t"></div>
<div class="sl-stack sl-stack--vertical sl-stack--1 sl-flex sl-flex-1 sl-flex-col sl-items-stretch sl-max-w-full sl-ml-2 @if($expandable) sl-cursor-pointer @endif">
<div class="sl-flex sl-items-center sl-max-w-full">
@if($expandable)
<div class="sl-flex sl-justify-center sl-w-8 sl--ml-8 sl-pl-3 sl-text-muted expansion-chevrons" role="button">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-right"
class="svg-inline--fa fa-chevron-right fa-fw fa-sm sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
@endif
@unless($isArrayBody)
<div class="sl-flex sl-items-baseline sl-text-base">
<div class="sl-font-mono sl-font-semibold sl-mr-2">{{ $name }}</div>
@if($type)
<span class="sl-truncate sl-text-muted">{{ $type }}</span>
@endif
</div>
@if($required || $deprecated)
<div class="sl-flex-1 sl-h-px sl-mx-3"></div>
<div class="sl-flex sl-items-center">
@if($required)
<span class="sl-ml-2 sl-text-warning">required</span>
@endif
@if($deprecated)
<span class="sl-ml-2 sl-text-warning">deprecated</span>
@endif
</div>
@endif
@endunless
</div>
@if($description)
<div class="sl-prose sl-markdown-viewer" style="font-size: 12px;">
{!! Parsedown::instance()->text($description) !!}
</div>
@endif
@if(!empty($enumValues))
Must be one of:
<ul style="list-style-position: inside; list-style-type: square;">{!! implode(" ", array_map(fn($val) => "<li><code>$val</code></li>", $enumValues)) !!}</ul>
@endif
@if($isArrayBody)
<div class="sl-flex sl-items-baseline sl-text-base">
<div class="sl-font-mono sl-font-semibold sl-mr-2">array of:</div>
@if($required)
<div class="sl-flex-1 sl-h-px sl-mx-3"></div>
<span class="sl-ml-2 sl-text-warning">required</span>
@endif
</div>
@endif
@if(!$hasChildren && !is_null($example) && $example !== '')
<div class="sl-stack sl-stack--horizontal sl-stack--2 sl-flex sl-flex-row sl-items-baseline sl-text-muted">
<span>Example:</span> <!-- <span> important for spacing -->
<div class="sl-flex sl-flex-1 sl-flex-wrap" style="gap: 4px;">
<div class="sl-max-w-full sl-break-all sl-px-1 sl-bg-canvas-tint sl-text-muted sl-rounded sl-border">
{{ is_array($example) || is_bool($example) ? json_encode($example) : $example }}
</div>
</div>
</div>
@endif
</div>
</div>

View File

@@ -0,0 +1,38 @@
@php
$level ??= 0;
$levelNestingClass = match($level) {
0 => "sl-ml-px",
default => "sl-ml-7"
};
$expandable ??= !isset($fields["[]"]);
@endphp
@foreach($fields as $name => $field)
<div class="{{ $expandable ? 'expandable' : '' }} sl-text-sm sl-border-l {{ $levelNestingClass }}">
@component('scribe::themes.elements.components.field-details', [
'name' => $name,
'type' => $field['type'] ?? 'string',
'required' => $field['required'] ?? false,
'deprecated' => $field['deprecated'] ?? false,
'description' => $field['description'] ?? '',
'example' => $field['example'] ?? '',
'enumValues' => $field['enumValues'] ?? null,
'endpointId' => $endpointId,
'hasChildren' => !empty($field['__fields']),
'component' => 'body',
])
@endcomponent
@if(!empty($field['__fields']))
<div class="children" style="{{ $expandable ? 'display: none;' : '' }}">
@component('scribe::themes.elements.components.nested-fields', [
'fields' => $field['__fields'],
'endpointId' => $endpointId,
'level' => $level + 1,
'expandable'=> $expandable,
])
@endcomponent
</div>
@endif
</div>
@endforeach

View File

@@ -0,0 +1,281 @@
@php
use Knuckles\Scribe\Tools\Utils as u;
/** @var Knuckles\Camel\Output\OutputEndpointData $endpoint */
@endphp
<div class="sl-stack sl-stack--vertical sl-stack--8 HttpOperation sl-flex sl-flex-col sl-items-stretch sl-w-full">
<div class="sl-stack sl-stack--vertical sl-stack--5 sl-flex sl-flex-col sl-items-stretch">
<div class="sl-relative">
<div class="sl-stack sl-stack--horizontal sl-stack--5 sl-flex sl-flex-row sl-items-center">
<h2 class="sl-text-3xl sl-leading-tight sl-font-prose sl-text-heading sl-mt-5 sl-mb-1"
id="{!! $endpoint->fullSlug() !!}">
{{ $endpoint->name() }}
</h2>
</div>
</div>
<div class="sl-relative">
<div title="{!! rtrim($baseUrl, '/') . '/'. ltrim($endpoint->uri, '/') !!}"
class="sl-stack sl-stack--horizontal sl-stack--3 sl-inline-flex sl-flex-row sl-items-center sl-max-w-full sl-font-mono sl-py-2 sl-pr-4 sl-bg-canvas-50 sl-rounded-lg"
>
@foreach($endpoint->httpMethods as $method)
<div class="sl-text-lg sl-font-semibold sl-px-2.5 sl-py-1 sl-text-on-primary sl-rounded-lg"
style="background-color: {{ \Knuckles\Scribe\Tools\WritingUtils::$httpMethodToCssColour[$method] }};"
>
{{ $method }}
</div>
@endforeach
<div class="sl-flex sl-overflow-x-hidden sl-text-lg sl-select-all">
<div dir="rtl"
class="sl-overflow-x-hidden sl-truncate sl-text-muted">{!! rtrim($baseUrl, '/') !!}</div>
<div class="sl-flex-1 sl-font-semibold">/{{ ltrim($endpoint->uri, '/') }}</div>
</div>
@if($endpoint->metadata->authenticated)
<div class="sl-font-prose sl-font-semibold sl-px-1.5 sl-py-0.5 sl-text-on-primary sl-rounded-lg"
style="background-color: darkred"
>requires authentication
</div>
@endif
@if($endpoint->metadata->deprecated === true)
<div class="sl-font-prose sl-font-semibold sl-px-1.5 sl-py-0.5 sl-text-on-primary sl-rounded-lg"
style="background-color: darkgoldenrod"
>deprecated
</div>
@endif
@if(is_string($endpoint->metadata->deprecated))
<div class="sl-font-prose sl-font-semibold sl-px-1.5 sl-py-0.5 sl-text-on-primary sl-rounded-lg"
style="background-color: darkgoldenrod"
>deprecated:{{$endpoint->metadata->deprecated}}
</div>
@endif
</div>
</div>
{!! Parsedown::instance()->text($endpoint->metadata->description ?: '') !!}
</div>
<div class="sl-flex">
<div data-testid="two-column-left" class="sl-flex-1 sl-w-0">
<div class="sl-stack sl-stack--vertical sl-stack--10 sl-flex sl-flex-col sl-items-stretch">
<div class="sl-stack sl-stack--vertical sl-stack--8 sl-flex sl-flex-col sl-items-stretch">
@if(count($endpoint->headers))
<div class="sl-stack sl-stack--vertical sl-stack--5 sl-flex sl-flex-col sl-items-stretch">
<h3 class="sl-text-2xl sl-leading-snug sl-font-prose">
{{ u::trans("scribe::endpoint.headers") }}
</h3>
<div class="sl-text-sm">
@foreach($endpoint->headers as $header => $value)
@component('scribe::themes.elements.components.field-details', [
'name' => $header,
'type' => null,
'required' => false,
'deprecated' => false,
'description' => null,
'example' => $value,
'endpointId' => $endpoint->endpointId(),
'component' => 'header',
'isInput' => true,
])
@endcomponent
@endforeach
</div>
</div>
@endif
@if(count($endpoint->urlParameters))
<div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
<h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ u::trans("scribe::endpoint.url_parameters") }}</h3>
<div class="sl-text-sm">
@foreach($endpoint->urlParameters as $attribute => $parameter)
@component('scribe::themes.elements.components.field-details', [
'name' => $parameter->name,
'type' => $parameter->type ?? 'string',
'required' => $parameter->required,
'deprecated' => $parameter->deprecated,
'description' => $parameter->description,
'example' => $parameter->example ?? '',
'enumValues' => $parameter->enumValues,
'endpointId' => $endpoint->endpointId(),
'component' => 'url',
'isInput' => true,
])
@endcomponent
@endforeach
</div>
</div>
@endif
@if(count($endpoint->queryParameters))
<div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
<h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ u::trans("scribe::endpoint.query_parameters") }}</h3>
<div class="sl-text-sm">
@foreach($endpoint->queryParameters as $attribute => $parameter)
@component('scribe::themes.elements.components.field-details', [
'name' => $parameter->name,
'type' => $parameter->type,
'required' => $parameter->required,
'deprecated' => $parameter->deprecated,
'description' => $parameter->description,
'example' => $parameter->example ?? '',
'enumValues' => $parameter->enumValues,
'endpointId' => $endpoint->endpointId(),
'component' => 'query',
'isInput' => true,
])
@endcomponent
@endforeach
</div>
</div>
@endif
@if(count($endpoint->nestedBodyParameters))
<div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
<h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ u::trans("scribe::endpoint.body_parameters") }}</h3>
<div class="sl-text-sm">
@component('scribe::themes.elements.components.nested-fields', [
'fields' => $endpoint->nestedBodyParameters,
'endpointId' => $endpoint->endpointId(),
])
@endcomponent
</div>
</div>
@endif
@if(count($endpoint->responseFields))
<div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
<h3 class="sl-text-2xl sl-leading-snug sl-font-prose">{{ u::trans("scribe::endpoint.response_fields") }}</h3>
<div class="sl-text-sm">
@component('scribe::themes.elements.components.nested-fields', [
'fields' => $endpoint->nestedResponseFields,
'endpointId' => $endpoint->endpointId(),
'isInput' => false,
])
@endcomponent
</div>
</div>
@endif
</div>
</div>
</div>
<div data-testid="two-column-right" class="sl-relative sl-w-2/5 sl-ml-16" style="max-width: 500px;">
<div class="sl-stack sl-stack--vertical sl-stack--6 sl-flex sl-flex-col sl-items-stretch">
@if($metadata['try_it_out']['enabled'] ?? false)
@include("scribe::themes.elements.try_it_out")
@endif
@if($metadata['example_languages'])
<div class="sl-panel sl-outline-none sl-w-full sl-rounded-lg">
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-3 sl-pl-4 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-select-none">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl--ml-2">
{{ u::trans("scribe::endpoint.example_request") }}:
<select class="example-request-lang-toggle sl-text-base"
aria-label="Request Sample Language"
onchange="switchExampleLanguage(event.target.value);">
@foreach($metadata['example_languages'] as $language)
<option>{{ $language }}</option>
@endforeach
</select>
</div>
</div>
</div>
@foreach($metadata['example_languages'] as $index => $language)
<div class="sl-bg-canvas-100 example-request example-request-{{ $language }}"
style="{{ $index == 0 ? '' : 'display: none;' }}">
<div class="sl-px-0 sl-py-1">
<div style="max-height: 400px;" class="sl-overflow-y-auto sl-rounded">
@include("scribe::partials.example-requests.$language")
</div>
</div>
</div>
@endforeach
</div>
@endif
@if($endpoint->isGet() || $endpoint->hasResponses())
<div class="sl-panel sl-outline-none sl-w-full sl-rounded-lg">
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-3 sl-pl-4 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-select-none">
<div class="sl-flex sl-flex-1 sl-items-center sl-py-2">
<div class="sl--ml-2">
<div class="sl-h-sm sl-text-base sl-font-medium sl-px-1.5 sl-text-muted sl-rounded sl-border-transparent sl-border">
<div class="sl-mb-2 sl-inline-block">{{ u::trans("scribe::endpoint.example_response") }}:</div>
<div class="sl-mb-2 sl-inline-block">
<select
class="example-response-{{ $endpoint->endpointId() }}-toggle sl-text-base"
aria-label="Response sample"
onchange="switchExampleResponse('{{ $endpoint->endpointId() }}', event.target.value);">
@foreach($endpoint->responses as $index => $response)
<option value="{{ $index }}">{{ $response->fullDescription() }}</option>
@endforeach
</select></div>
</div>
</div>
</div>
<button type="button"
class="sl-button sl-h-sm sl-text-base sl-font-medium sl-px-1.5 hover:sl-bg-canvas-50 active:sl-bg-canvas-100 sl-text-muted hover:sl-text-body focus:sl-text-body sl-rounded sl-border-transparent sl-border disabled:sl-opacity-70">
<div class="sl-mx-0">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="copy"
class="svg-inline--fa fa-copy fa-fw fa-sm sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor"
d="M384 96L384 0h-112c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48H464c26.51 0 48-21.49 48-48V128h-95.1C398.4 128 384 113.6 384 96zM416 0v96h96L416 0zM192 352V128h-144c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h192c26.51 0 48-21.49 48-48L288 416h-32C220.7 416 192 387.3 192 352z"></path>
</svg>
</div>
</button>
</div>
@foreach($endpoint->responses as $index => $response)
<div class="sl-panel__content-wrapper sl-bg-canvas-100 example-response-{{ $endpoint->endpointId() }} example-response-{{ $endpoint->endpointId() }}-{{ $index }}"
style=" {{ $index == 0 ? '' : 'display: none;' }}"
>
<div class="sl-panel__content sl-p-0">@if(count($response->headers))
<details class="sl-pl-2">
<summary style="cursor: pointer; list-style: none;">
<small>
<span class="expansion-chevrons">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="chevron-right"
class="svg-inline--fa fa-chevron-right fa-fw sl-icon sl-text-muted"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</span>
Headers
</small>
</summary>
@php
$headerLines = [];
foreach($response->headers as $header => $value) {
$headerLines[] = $header . ': ' . (is_array($value) ? implode('; ', $value) : $value);
}
@endphp
<pre><code class="language-http">{{ implode("\n", $headerLines) }}</code></pre>
</details>
@endif
@if($response->isBinary())
<pre><code>[{{ u::trans("scribe::endpoint.responses.binary") }}] - {{ htmlentities(str_replace("<<binary>>", "", $response->content)) }}</code></pre>
@elseif($response->status == 204)
<pre><code>[{{ u::trans("scribe::endpoint.responses.empty") }}]</code></pre>
@else
@php($parsed = json_decode($response->content))
{{-- If response is a JSON string, prettify it. Otherwise, just print it --}}
<pre><code style="max-height: 300px;"
class="language-json sl-overflow-x-auto sl-overflow-y-auto">{!! htmlentities($parsed != null ? json_encode($parsed, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $response->content) !!}</code></pre>
@endif
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>

View File

@@ -0,0 +1,28 @@
@foreach($groupedEndpoints as $group)
<h1 id="{!! Str::slug($group['name']) !!}"
class="sl-text-5xl sl-leading-tight sl-font-prose sl-text-heading"
>
{!! $group['name'] !!}
</h1>
{!! Parsedown::instance()->text($group['description']) !!}
@foreach($group['subgroups'] as $subgroupName => $subgroup)
@if($subgroupName !== "")
<h2 id="{!! Str::slug($group['name']) !!}-{!! Str::slug($subgroupName) !!}"
class="sl-text-3xl sl-leading-tight sl-font-prose sl-text-heading sl-mt-5 sl-mb-3"
>
{{ $subgroupName }}
</h2>
@php($subgroupDescription = collect($subgroup)->first(fn ($e) => $e->metadata->subgroupDescription)?->metadata?->subgroupDescription)
@if($subgroupDescription)
{!! Parsedown::instance()->text($subgroupDescription) !!}
@endif
<br>
@endif
@foreach($subgroup as $endpoint)
@include("scribe::themes.elements.endpoint")
@endforeach
@endforeach
@endforeach

View File

@@ -0,0 +1,360 @@
@php
use Knuckles\Scribe\Tools\WritingUtils as u;
@endphp
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{!! $metadata['title'] !!}</title>
<link href="https://fonts.googleapis.com/css?family=PT+Sans&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{!! $assetPathPrefix !!}css/theme-elements.style.css" media="screen">
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>
<link rel="stylesheet"
href="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/styles/docco.min.css">
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<script type="module">
import {CodeJar} from 'https://medv.io/codejar/codejar.js'
window.CodeJar = CodeJar;
</script>
@if($tryItOut['enabled'] ?? true)
<script>
var tryItOutBaseUrl = "{!! $tryItOut['base_url'] ?? $baseUrl !!}";
var useCsrf = Boolean({{ $tryItOut['use_csrf'] ?? null }});
var csrfUrl = "{{ $tryItOut['csrf_url'] ?? null }}";
</script>
<script src="{{ u::getVersionedAsset($assetPathPrefix.'js/tryitout.js') }}"></script>
<style>
.code-editor, .response-content {
color: whitesmoke;
background-color: transparent;
}
/*
Problem: we want syntax highlighting for the Try It Out JSON body code editor
However, the Try It Out area uses a dark background, while request and response samples
(which are already highlighted) use a light background. HighlightJS can only use one theme per document.
Our options:
1. Change the bg of one. => No, it looks out of place on the page.
2. Use the same highlighting for both. => Nope, one would be unreadable.
3. Copy styles for a dark-bg h1js theme and prefix them for the CodeEditor, which is what we're doing.
Since it's only JSON, we only need a few styles anyway.
Styles taken from the Nord theme: https://github.com/highlightjs/highlight.js/blob/3997c9b430a568d5ad46d96693b90a74fc01ea7f/src/styles/nord.css#L2
*/
.code-editor > .hljs-attr {
color: #8FBCBB;
}
.code-editor > .hljs-string {
color: #A3BE8C;
}
.code-editor > .hljs-number {
color: #B48EAD;
}
.code-editor > .hljs-literal{
color: #81A1C1;
}
</style>
<script>
function tryItOut(btnElement) {
btnElement.disabled = true;
let endpointId = btnElement.dataset.endpoint;
let errorPanel = document.querySelector(`.tryItOut-error[data-endpoint=${endpointId}]`);
errorPanel.hidden = true;
let responsePanel = document.querySelector(`.tryItOut-response[data-endpoint=${endpointId}]`);
responsePanel.hidden = true;
let form = btnElement.form;
let { method, path, hasjsonbody: hasJsonBody} = form.dataset;
let body = {};
if (hasJsonBody === "1") {
body = form.querySelector('.code-editor').textContent;
} else if (form.dataset.hasfiles === "1") {
body = new FormData();
form.querySelectorAll('input[data-component=body]')
.forEach(el => {
if (el.type === 'file') {
if (el.files[0]) body.append(el.name, el.files[0])
} else body.append(el.name, el.value);
});
} else {
form.querySelectorAll('input[data-component=body]').forEach(el => {
_.set(body, el.name, el.value);
});
}
const urlParameters = form.querySelectorAll('input[data-component=url]');
urlParameters.forEach(el => (path = path.replace(new RegExp(`\\{${el.name}\\??}`), el.value)));
const headers = Object.fromEntries(Array.from(form.querySelectorAll('input[data-component=header]'))
.map(el => [el.name, (el.dataset.prefix || '') + el.value]));
const query = {}
form.querySelectorAll('input[data-component=query]').forEach(el => {
_.set(query, el.name, el.value);
});
let preflightPromise = Promise.resolve();
if (window.useCsrf && window.csrfUrl) {
preflightPromise = makeAPICall('GET', window.csrfUrl).then(() => {
headers['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
});
}
// content type has to be unset otherwise file upload won't work
if (form.dataset.hasfiles === "1") {
delete headers['Content-Type'];
}
return preflightPromise.then(() => makeAPICall(method, path, body, query, headers, endpointId))
.then(([responseStatus, statusText, responseContent, responseHeaders]) => {
responsePanel.hidden = false;
responsePanel.querySelector(`.response-status`).textContent = responseStatus + " " + statusText ;
let contentEl = responsePanel.querySelector(`.response-content`);
if (responseContent === '') {
contentEl.textContent = contentEl.dataset.emptyResponseText;
return;
}
// Prettify it if it's JSON
let isJson = false;
try {
const jsonParsed = JSON.parse(responseContent);
if (jsonParsed !== null) {
isJson = true;
responseContent = JSON.stringify(jsonParsed, null, 4);
}
} catch (e) {}
// Replace HTML entities
responseContent = responseContent.replace(/[<>&]/g, (i) => '&#' + i.charCodeAt(0) + ';');
contentEl.innerHTML = responseContent;
isJson && window.hljs.highlightElement(contentEl);
})
.catch(err => {
console.log(err);
let errorMessage = err.message || err;
errorPanel.hidden = false;
errorPanel.querySelector(`.error-message`).textContent = errorMessage;
})
.finally(() => { btnElement.disabled = false } );
}
window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.tryItOut-btn').forEach(el => {
el.addEventListener('click', () => tryItOut(el));
});
})
</script>
@endif
</head>
<body>
@if($metadata['example_languages'])
<script>
function switchExampleLanguage(lang) {
document.querySelectorAll(`.example-request`).forEach(el => el.style.display = 'none');
document.querySelectorAll(`.example-request-${lang}`).forEach(el => el.style.display = 'initial');
document.querySelectorAll(`.example-request-lang-toggle`).forEach(el => el.value = lang);
}
</script>
@endif
<script>
function switchExampleResponse(endpointId, index) {
document.querySelectorAll(`.example-response-${endpointId}`).forEach(el => el.style.display = 'none');
document.querySelectorAll(`.example-response-${endpointId}-${index}`).forEach(el => el.style.display = 'initial');
document.querySelectorAll(`.example-response-${endpointId}-toggle`).forEach(el => el.value = index);
}
/*
* Requirement: a div with class `expansion-chevrons`
* (or `expansion-chevrons-solid` to use the solid version).
* Also add the `expanded` class if your div is expanded by default.
*/
function toggleExpansionChevrons(evt) {
let elem = evt.currentTarget;
let chevronsArea = elem.querySelector('.expansion-chevrons');
const solid = chevronsArea.classList.contains('expansion-chevrons-solid');
const newState = chevronsArea.classList.contains('expanded') ? 'expand' : 'expanded';
if (newState === 'expanded') {
const selector = solid ? '#expanded-chevron-solid' : '#expanded-chevron';
const template = document.querySelector(selector);
const chevron = template.content.cloneNode(true);
chevronsArea.replaceChildren(chevron);
chevronsArea.classList.add('expanded');
} else {
const selector = solid ? '#expand-chevron-solid' : '#expand-chevron';
const template = document.querySelector(selector);
const chevron = template.content.cloneNode(true);
chevronsArea.replaceChildren(chevron);
chevronsArea.classList.remove('expanded');
}
}
/**
* 1. Make sure the children are inside the parent element
* 2. Add `expandable` class to the parent
* 3. Add `children` class to the children.
* 4. Wrap the default chevron SVG in a div with class `expansion-chevrons`
* (or `expansion-chevrons-solid` to use the solid version).
* Also add the `expanded` class if your div is expanded by default.
*/
function toggleElementChildren(evt) {
let elem = evt.currentTarget;
let children = elem.querySelector(`.children`);
if (!children) return;
if (children.contains(event.target)) return;
let oldState = children.style.display
if (oldState === 'none') {
children.style.removeProperty('display');
toggleExpansionChevrons(evt);
} else {
children.style.display = 'none';
toggleExpansionChevrons(evt);
}
evt.stopPropagation();
}
function highlightSidebarItem(evt = null) {
if (evt && evt.oldURL) {
let oldHash = new URL(evt.oldURL).hash.slice(1);
if (oldHash) {
let previousItem = window['sidebar'].querySelector(`#toc-item-${oldHash}`);
previousItem.classList.remove('sl-bg-primary-tint');
previousItem.classList.add('sl-bg-canvas-100');
}
}
let newHash = location.hash.slice(1);
if (newHash) {
let item = window['sidebar'].querySelector(`#toc-item-${newHash}`);
item.classList.remove('sl-bg-canvas-100');
item.classList.add('sl-bg-primary-tint');
}
}
addEventListener('DOMContentLoaded', () => {
highlightSidebarItem();
document.querySelectorAll('.code-editor').forEach(elem => CodeJar(elem, (editor) => {
// highlight.js does not trim old tags,
// which means highlighting doesn't update on type (only on paste)
// See https://github.com/antonmedv/codejar/issues/18
editor.textContent = editor.textContent
return hljs.highlightElement(editor)
}));
document.querySelectorAll('.expandable').forEach(el => {
el.addEventListener('click', toggleElementChildren);
});
document.querySelectorAll('details').forEach(el => {
el.addEventListener('toggle', toggleExpansionChevrons);
});
});
addEventListener('hashchange', highlightSidebarItem);
</script>
<div class="sl-elements sl-antialiased sl-h-full sl-text-base sl-font-ui sl-text-body sl-flex sl-inset-0">
@include("scribe::themes.elements.sidebar")
<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;">
<div class="sl-mb-10">
<div class="sl-mb-4">
<h1 class="sl-text-5xl sl-leading-tight sl-font-prose sl-font-semibold sl-text-heading">
{!! $metadata['title'] !!}
</h1>
@if($metadata['postman_collection_url'])
<a title="Download Postman collection" class="sl-mx-1"
href="{!! $metadata['postman_collection_url'] !!}" target="_blank">
<small>Postman collection </small>
</a>
@endif
@if($metadata['openapi_spec_url'])
<a title="Download OpenAPI spec" class="sl-mx-1"
href="{!! $metadata['openapi_spec_url'] !!}" target="_blank">
<small>OpenAPI spec </small>
</a>
@endif
</div>
<div class="sl-prose sl-markdown-viewer sl-my-4">
{!! $intro !!}
{!! $auth !!}
</div>
</div>
@include("scribe::themes.elements.groups")
<div class="sl-prose sl-markdown-viewer sl-my-5">
{!! $append !!}
</div>
</div>
</div>
<template id="expand-chevron">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="chevron-right"
class="svg-inline--fa fa-chevron-right fa-fw sl-icon sl-text-muted"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</template>
<template id="expanded-chevron">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="chevron-down"
class="svg-inline--fa fa-chevron-down fa-fw sl-icon sl-text-muted"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path fill="currentColor"
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>
</svg>
</template>
<template id="expand-chevron-solid">
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="caret-right"
class="svg-inline--fa fa-caret-right fa-fw sl-icon" role="img" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 512">
<path fill="currentColor"
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>
</svg>
</template>
<template id="expanded-chevron-solid">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</template>
</body>
</html>

View File

@@ -0,0 +1,92 @@
<div id="sidebar" class="sl-flex sl-overflow-y-auto sl-flex-col sl-sticky sl-inset-y-0 sl-pt-8 sl-bg-canvas-100 sl-border-r"
style="width: calc((100% - 1800px) / 2 + 300px); padding-left: calc((100% - 1800px) / 2); min-width: 300px; max-height: 100vh">
<div class="sl-flex sl-items-center sl-mb-5 sl-ml-4">
@if($metadata['logo'] != false)
<div class="sl-inline sl-overflow-x-hidden sl-overflow-y-hidden sl-mr-3 sl-rounded-lg"
style="background-color: transparent;">
<img src="{{ $metadata['logo'] }}" height="30px" width="30px" alt="logo">
</div>
@endif
<h4 class="sl-text-paragraph sl-leading-snug sl-font-prose sl-font-semibold sl-text-heading">
{{ $metadata['title'] }}
</h4>
</div>
<div class="sl-flex sl-overflow-y-auto sl-flex-col sl-flex-grow sl-flex-shrink">
<div class="sl-overflow-y-auto sl-w-full sl-bg-canvas-100">
<div class="sl-my-3">
@foreach($headings as $h1)
<div class="expandable">
<div title="{!! $h1['name'] !!}" id="toc-item-{!! $h1['slug'] !!}"
class="sl-flex sl-items-center sl-h-md sl-pr-4 sl-pl-4 sl-bg-canvas-100 hover:sl-bg-canvas-200 sl-cursor-pointer sl-select-none">
<a href="#{!! $h1['slug'] !!}"
class="sl-flex-1 sl-items-center sl-truncate sl-mr-1.5 sl-p-0">{!! $h1['name'] !!}</a>
@if(count($h1['subheadings']) > 0)
<div class="sl-flex sl-items-center sl-text-xs expansion-chevrons">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="chevron-right"
class="svg-inline--fa fa-chevron-right fa-fw sl-icon sl-text-muted"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
@endif
</div>
@if(count($h1['subheadings']) > 0)
<div class="children" style="display: none;">
@foreach($h1['subheadings'] as $h2)
<div class="expandable">
<div class="sl-flex sl-items-center sl-h-md sl-pr-4 sl-pl-8 sl-bg-canvas-100 hover:sl-bg-canvas-200 sl-cursor-pointer sl-select-none"
id="toc-item-{!! $h2['slug'] !!}">
<div class="sl-flex-1 sl-items-center sl-truncate sl-mr-1.5 sl-p-0" title="{!! $h2['name'] !!}">
<a class="ElementsTableOfContentsItem sl-block sl-no-underline"
href="#{!! $h2['slug'] !!}">
{!! $h2['name'] !!}
</a>
</div>
@if(count($h2['subheadings']) > 0)
<div class="sl-flex sl-items-center sl-text-xs expansion-chevrons">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="chevron-right"
class="svg-inline--fa fa-chevron-right fa-fw sl-icon sl-text-muted"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
@endif
</div>
@if(count($h2['subheadings']) > 0)
<div class="children" style="display: none;">
@foreach($h2['subheadings'] as $h3)
<a class="ElementsTableOfContentsItem sl-block sl-no-underline"
href="#{!! $h3['slug'] !!}">
<div title="{!! $h3['name'] !!}" id="toc-item-{!! $h3['slug'] !!}"
class="sl-flex sl-items-center sl-h-md sl-pr-4 sl-pl-12 sl-bg-canvas-100 hover:sl-bg-canvas-200 sl-cursor-pointer sl-select-none">
{!! $h3['name'] !!}
</div>
</a>
@endforeach
</div>
@endif
</div>
@endforeach
</div>
@endif
</div>
@endforeach
</div>
</div>
<div class="sl-flex sl-items-center sl-px-4 sl-py-3 sl-border-t">
{{ $metadata['last_updated'] }}
</div>
<div class="sl-flex sl-items-center sl-px-4 sl-py-3 sl-border-t">
<a href="http://github.com/knuckleswtf/scribe">Documentation powered by Scribe </a>
</div>
</div>
</div>

View File

@@ -0,0 +1,322 @@
@php
use Knuckles\Scribe\Tools\Utils as u;
/** @var \Knuckles\Camel\Output\OutputEndpointData $endpoint */
@endphp
<div class="sl-inverted">
<div class="sl-overflow-y-hidden sl-rounded-lg">
<form class="TryItPanel sl-bg-canvas-100 sl-rounded-lg"
data-method="{{ $endpoint->httpMethods[0] }}"
data-path="{{ $endpoint->uri }}"
data-hasfiles="{{ $endpoint->hasFiles() ? 1 : 0 }}"
data-hasjsonbody="{{ $endpoint->hasJsonBody() ? 1 : 0 }}">
@if($endpoint->isAuthed() && $metadata['auth']['location'] !== 'body')
<div class="sl-panel sl-outline-none sl-w-full expandable">
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-4 sl-pl-3 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-cursor-pointer sl-select-none"
role="button">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl-flex sl-items-center sl-mr-1.5 expansion-chevrons expansion-chevrons-solid expanded">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
Auth
</div>
</div>
<div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
<div class="ParameterGrid sl-p-4">
<label aria-hidden="true"
for="auth-{{ $endpoint->endpointId() }}">{{ $metadata['auth']['name'] }}</label>
<span class="sl-mx-3">:</span>
<div class="sl-flex sl-flex-1">
<div class="sl-input sl-flex-1 sl-relative">
<code>{{ $metadata['auth']['prefix'] }}</code>
<input aria-label="{{ $metadata['auth']['name'] }}"
id="auth-{{ $endpoint->endpointId() }}"
data-component="{{ $metadata['auth']['location'] }}"
data-prefix="{{ $metadata['auth']['prefix'] }}"
name="{{ $metadata['auth']['name'] }}"
placeholder="{{ $metadata['auth']['placeholder'] }}"
class="auth-value sl-relative {{ $metadata['auth']['prefix'] ? 'sl-w-3/5' : 'sl-w-full sl-pr-2.5 sl-pl-2.5' }} sl-h-md sl-text-base sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border">
</div>
</div>
</div>
</div>
</div>
@endif
@if(count($endpoint->headers))
<div class="sl-panel sl-outline-none sl-w-full expandable">
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-4 sl-pl-3 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-cursor-pointer sl-select-none"
role="button">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl-flex sl-items-center sl-mr-1.5 expansion-chevrons expansion-chevrons-solid expanded">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
Headers
</div>
</div>
<div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
<div class="ParameterGrid sl-p-4">
@foreach($endpoint->headers as $name => $example)
@php
if($endpoint->isAuthed() && $metadata['auth']['location'] === 'header' && $name === $metadata['auth']['name']) continue;
@endphp
<label aria-hidden="true"
for="header-{{ $endpoint->endpointId() }}-{{ $name }}">{{ $name }}</label>
<span class="sl-mx-3">:</span>
<div class="sl-flex sl-flex-1">
<div class="sl-input sl-flex-1 sl-relative">
<input aria-label="{{ $name }}" name="{{ $name }}"
id="header-{{ $endpoint->endpointId() }}-{{ $name }}"
value="{{ $example }}" data-component="header"
class="sl-relative sl-w-full sl-h-md sl-text-base sl-pr-2.5 sl-pl-2.5 sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border">
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
@if(count($endpoint->urlParameters))
<div class="sl-panel sl-outline-none sl-w-full expandable">
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-4 sl-pl-3 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-cursor-pointer sl-select-none"
role="button">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl-flex sl-items-center sl-mr-1.5 expansion-chevrons expansion-chevrons-solid expanded">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
URL Parameters
</div>
</div>
<div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
<div class="ParameterGrid sl-p-4">
@foreach($endpoint->urlParameters as $name => $parameter)
<label aria-hidden="true"
for="urlparam-{{ $endpoint->endpointId() }}-{{ $name }}">{{ $name }}</label>
<span class="sl-mx-3">:</span>
<div class="sl-flex sl-flex-1">
<div class="sl-input sl-flex-1 sl-relative">
<input aria-label="{{ $name }}" name="{{ $name }}"
id="urlparam-{{ $endpoint->endpointId() }}-{{ $name }}"
placeholder="{{ $parameter->description }}"
value="{{ $parameter->example }}" data-component="url"
class="sl-relative sl-w-full sl-h-md sl-text-base sl-pr-2.5 sl-pl-2.5 sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border">
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
@if(count($endpoint->queryParameters))
<div class="sl-panel sl-outline-none sl-w-full expandable">
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-4 sl-pl-3 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-cursor-pointer sl-select-none"
role="button">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl-flex sl-items-center sl-mr-1.5 expansion-chevrons expansion-chevrons-solid expanded">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
Query Parameters
</div>
</div>
<div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
<div class="ParameterGrid sl-p-4">
@foreach($endpoint->queryParameters as $name => $parameter)
@php
/** @var \Knuckles\Camel\Output\Parameter $parameter */
if ($parameter->type == 'object') // Skip; individual object children are listed
continue;
if (str_contains($name, "[]"))
// This likely belongs to an obj-array (eg objs[].a); we only show the parent (objs[]), so skip
continue;
if($endpoint->isAuthed() && $metadata['auth']['location'] === 'query'
&& $name === $metadata['auth']['name']) continue;
@endphp
<label aria-hidden="true"
for="queryparam-{{ $endpoint->endpointId() }}-{{ $name }}">{{ $name }}</label>
<span class="sl-mx-3">:</span>
<div class="sl-flex sl-flex-1">
<div class="sl-input sl-flex-1 sl-relative">
@if(str_ends_with($parameter->type, '[]'))
<input aria-label="{{ $name }}" name="{{ $name }}"
id="queryparam-{{ $endpoint->endpointId() }}-{{ $name }}"
placeholder="{{ $parameter->description }}"
value="{{ json_encode($parameter->example, JSON_HEX_TAG | JSON_HEX_AMP) }}" data-component="query"
class="sl-relative sl-w-full sl-h-md sl-text-base sl-pr-2.5 sl-pl-2.5 sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border"
>
@else
<input aria-label="{{ $name }}" name="{{ $name }}"
id="queryparam-{{ $endpoint->endpointId() }}-{{ $name }}"
placeholder="{{ $parameter->description }}"
value="{{ $parameter->example }}" data-component="query"
class="sl-relative sl-w-full sl-h-md sl-text-base sl-pr-2.5 sl-pl-2.5 sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border"
>
@endif
</div>
</div>
@endforeach
</div>
</div>
</div>
@endif
@if(count($endpoint->bodyParameters))
<div class="sl-panel sl-outline-none sl-w-full expandable">
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-4 sl-pl-3 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-cursor-pointer sl-select-none"
role="button">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl-flex sl-items-center sl-mr-1.5 expansion-chevrons expansion-chevrons-solid expanded">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
Body
</div>
</div>
<div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
@if($endpoint->hasJsonBody())
<div class="TextRequestBody sl-p-4">
<div class="code-editor language-json"
id="json-body-{{ $endpoint->endpointId() }}"
style="font-family: var(--font-code); font-size: 12px; line-height: var(--lh-code);"
>{!! json_encode($endpoint->getSampleBody(), JSON_PRETTY_PRINT | JSON_HEX_TAG | JSON_HEX_AMP) !!}</div>
</div>
@else
<div class="ParameterGrid sl-p-4">
@foreach($endpoint->bodyParameters as $name => $parameter)
@php
/** @var \Knuckles\Camel\Output\Parameter $parameter */
if ($parameter->type == 'object') // Skip; individual object children are listed
continue;
if (str_contains($name, "[]"))
// This likely belongs to an obj-array (eg objs[].a); we only show the parent (objs[]), so skip
continue;
@endphp
<label aria-hidden="true"
for="bodyparam-{{ $endpoint->endpointId() }}-{{ $name }}">{{ $name }}</label>
<span class="sl-mx-3">:</span>
<div class="sl-flex sl-flex-1">
<div class="sl-input sl-flex-1 sl-relative">
@if($parameter->type == 'file')
<input aria-label="{{ $name }}" name="{{ $name }}"
id="bodyparam-{{ $endpoint->endpointId() }}-{{ $name }}"
type="file" data-component="body"
class="sl-relative sl-w-full sl-h-md sl-text-base sl-pr-2.5 sl-pl-2.5 sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border"
>
@elseif(str_ends_with($parameter->type, '[]'))
<input aria-label="{{ $name }}" name="{{ $name }}"
id="bodyparam-{{ $endpoint->endpointId() }}-{{ $name }}"
placeholder="{{ $parameter->description }}"
value="{{ json_encode($parameter->example, JSON_HEX_TAG | JSON_HEX_AMP) }}" data-component="body"
class="sl-relative sl-w-full sl-h-md sl-text-base sl-pr-2.5 sl-pl-2.5 sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border"
>
@else
<input aria-label="{{ $name }}" name="{{ $name }}"
id="bodyparam-{{ $endpoint->endpointId() }}-{{ $name }}"
placeholder="{{ $parameter->description }}"
value="{{ $parameter->example }}" data-component="body"
class="sl-relative sl-w-full sl-h-md sl-text-base sl-pr-2.5 sl-pl-2.5 sl-rounded sl-border-transparent hover:sl-border-input focus:sl-border-primary sl-border"
>
@endif
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
@endif
<div class="SendButtonHolder sl-mt-4 sl-p-4 sl-pt-0">
<div class="sl-stack sl-stack--horizontal sl-stack--2 sl-flex sl-flex-row sl-items-center">
<button type="button" data-endpoint="{{ $endpoint->endpointId() }}"
class="tryItOut-btn sl-button sl-h-sm sl-text-base sl-font-medium sl-px-1.5 sl-bg-primary hover:sl-bg-primary-dark active:sl-bg-primary-darker disabled:sl-bg-canvas-100 sl-text-on-primary disabled:sl-text-body sl-rounded sl-border-transparent sl-border disabled:sl-opacity-70"
>
{{ u::trans("scribe::try_it_out.send") }}
</button>
</div>
</div>
<div data-endpoint="{{ $endpoint->endpointId() }}"
class="tryItOut-error expandable sl-panel sl-outline-none sl-w-full" hidden>
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-4 sl-pl-3 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-cursor-pointer sl-select-none"
role="button">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl-flex sl-items-center sl-mr-1.5 expansion-chevrons expansion-chevrons-solid expanded">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
{{ u::trans("scribe::try_it_out.request_failed") }}
</div>
</div>
<div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
<div class="sl-panel__content sl-p-4">
<p class="sl-pb-2"><strong class="error-message"></strong></p>
<p class="sl-pb-2">{{ u::trans("scribe::try_it_out.error_help") }}</p>
</div>
</div>
</div>
<div data-endpoint="{{ $endpoint->endpointId() }}"
class="tryItOut-response expandable sl-panel sl-outline-none sl-w-full" hidden>
<div class="sl-panel__titlebar sl-flex sl-items-center sl-relative focus:sl-z-10 sl-text-base sl-leading-none sl-pr-4 sl-pl-3 sl-bg-canvas-200 sl-text-body sl-border-input focus:sl-border-primary sl-cursor-pointer sl-select-none"
role="button">
<div class="sl-flex sl-flex-1 sl-items-center sl-h-lg">
<div class="sl-flex sl-items-center sl-mr-1.5 expansion-chevrons expansion-chevrons-solid expanded">
<svg aria-hidden="true" focusable="false" data-prefix="fas"
data-icon="caret-down"
class="svg-inline--fa fa-caret-down fa-fw sl-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
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>
</svg>
</div>
{{ u::trans("scribe::try_it_out.received_response") }}
</div>
</div>
<div class="sl-panel__content-wrapper sl-bg-canvas-100 children" role="region">
<div class="sl-panel__content sl-p-4">
<p class="sl-pb-2 response-status"></p>
<pre><code class="sl-pb-2 response-content language-json"
data-empty-response-text="<{{ u::trans("scribe::endpoint.responses.empty") }}>"
style="max-height: 300px;"></code></pre>
</div>
</div>
</div>
</form>
</div>
</div>