return strpos($route->uri, 'api/') === 0;
})->map(function ($route) {
[$controller, $controllerMethod] = explode('@', $route->action['uses']);
- $baseModelName = explode('/', $route->uri)[1];
+ $baseModelName = explode('.', explode('/', $route->uri)[1])[0];
$shortName = $baseModelName . '-' . $controllerMethod;
return [
'name' => $shortName,
public function display()
{
$docs = $this->getDocs();
- dd($docs);
- // TODO - Build view for API docs
- return view('');
+ return view('api-docs.index', [
+ 'docs' => $docs,
+ ]);
}
/**
use BookStack\Entities\Book;
use BookStack\Entities\Repos\BookRepo;
+use BookStack\Exceptions\NotifyException;
use BookStack\Facades\Activity;
+use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
class BooksApiController extends ApiController
{
}
/**
- * Create a new book.
- * @throws \Illuminate\Validation\ValidationException
+ * Create a new book in the system.
+ * @throws ValidationException
*/
public function create(Request $request)
{
/**
* Update the details of a single book.
- * @throws \Illuminate\Validation\ValidationException
+ * @throws ValidationException
*/
public function update(Request $request, string $id)
{
}
/**
- * Delete a book from the system.
- * @throws \BookStack\Exceptions\NotifyException
- * @throws \Illuminate\Contracts\Container\BindingResolutionException
+ * Delete a single book from the system.
+ * @throws NotifyException
+ * @throws BindingResolutionException
*/
public function delete(string $id)
{
--- /dev/null
+import Code from "../services/code"
+class DetailsHighlighter {
+
+ constructor(elem) {
+ this.elem = elem;
+ this.dealtWith = false;
+ elem.addEventListener('toggle', this.onToggle.bind(this));
+ }
+
+ onToggle() {
+ if (this.dealtWith) return;
+
+ Code.highlightWithin(this.elem);
+ this.dealtWith = true;
+ }
+}
+
+export default DetailsHighlighter;
\ No newline at end of file
import entityPermissionsEditor from "./entity-permissions-editor";
import templateManager from "./template-manager";
import newUserPassword from "./new-user-password";
+import detailsHighlighter from "./details-highlighter";
const componentMapping = {
'dropdown': dropdown,
'entity-permissions-editor': entityPermissionsEditor,
'template-manager': templateManager,
'new-user-password': newUserPassword,
+ 'details-highlighter': detailsHighlighter,
};
window.components = {};
* Highlight pre elements on a page
*/
function highlight() {
- let codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
- for (let i = 0; i < codeBlocks.length; i++) {
- highlightElem(codeBlocks[i]);
+ const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
+ for (const codeBlock of codeBlocks) {
+ highlightElem(codeBlock);
+ }
+}
+
+/**
+ * Highlight all code blocks within the given parent element
+ * @param {HTMLElement} parent
+ */
+function highlightWithin(parent) {
+ const codeBlocks = parent.querySelectorAll('pre');
+ for (const codeBlock of codeBlocks) {
+ highlightElem(codeBlock);
}
}
export default {
highlight: highlight,
+ highlightWithin: highlightWithin,
wysiwygView: wysiwygView,
popupEditor: popupEditor,
setMode: setMode,
.tag-list div:last-child .tag-item {
margin-bottom: 0;
+}
+
+/**
+ * API Docs
+ */
+.api-method {
+ font-size: 0.75rem;
+ background-color: #888;
+ padding: $-xs;
+ line-height: 1.3;
+ opacity: 0.7;
+ vertical-align: top;
+ border-radius: 3px;
+ color: #FFF;
+ display: inline-block;
+ min-width: 60px;
+ text-align: center;
+ font-weight: bold;
+ &[data-method="GET"] { background-color: #077b70 }
+ &[data-method="POST"] { background-color: #cf4d03 }
+ &[data-method="PUT"] { background-color: #0288D1 }
+ &[data-method="DELETE"] { background-color: #ab0f0e }
}
\ No newline at end of file
}
}
+.text-mono {
+ font-family: $mono;
+}
+
+.text-uppercase {
+ text-transform: uppercase;
+}
+
+.text-capitals {
+ text-transform: capitalize;
+}
+
.code-base {
background-color: #F8F8F8;
font-size: 0.80em;
--- /dev/null
+@extends('simple-layout')
+
+@section('body')
+
+ <div class="container pt-xl">
+
+ <div class="grid right-focus reverse-collapse">
+
+ <div>
+ @foreach($docs as $model => $endpoints)
+ <p class="text-uppercase text-muted mb-xm mt-l"><strong>{{ $model }}</strong></p>
+
+ @foreach($endpoints as $endpoint)
+ <div class="mb-xs">
+ <a href="#{{ $endpoint['name'] }}" class="text-mono">
+ <span class="api-method" data-method="{{ $endpoint['method'] }}">{{ $endpoint['method'] }}</span>
+ /{{ $endpoint['uri'] }}
+ </a>
+ </div>
+ @endforeach
+ @endforeach
+ </div>
+
+ <div>
+ @foreach($docs as $model => $endpoints)
+ <section class="card content-wrap auto-height">
+ <h1 class="list-heading text-capitals">{{ $model }}</h1>
+
+ @foreach($endpoints as $endpoint)
+ <h5 id="{{ $endpoint['name'] }}" class="text-mono mb-m">
+ <span class="api-method" data-method="{{ $endpoint['method'] }}">{{ $endpoint['method'] }}</span>
+ {{ url($endpoint['uri']) }}
+ </h5>
+ <p class="mb-m">{{ $endpoint['description'] ?? '' }}</p>
+ @if($endpoint['example_response'] ?? false)
+ <details details-highlighter>
+ <summary class="text-muted">Example Response</summary>
+ <pre><code class="language-json">{{ $endpoint['example_response'] }}</code></pre>
+ </details>
+ <hr class="mt-m">
+ @endif
+ @endforeach
+ </section>
+ @endforeach
+ </div>
+
+ </div>
+
+
+ </div>
+@stop
\ No newline at end of file