Skip to content

Commit d40a68b

Browse files
committed
API: Re-ordered routes, Improved navigation
Updated route order to follow some kind of logic. Updated scrolling sidebar to not be so cut-off in various scenarios. Added new nav helper to quick jump to specific API models. Closes #5865
1 parent 4a57933 commit d40a68b

File tree

5 files changed

+145
-81
lines changed

5 files changed

+145
-81
lines changed

resources/js/components/api-nav.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {Component} from "./component";
2+
3+
export class ApiNav extends Component {
4+
private select!: HTMLSelectElement;
5+
private sidebar!: HTMLElement;
6+
private body!: HTMLElement;
7+
8+
setup() {
9+
this.select = this.$refs.select as HTMLSelectElement;
10+
this.sidebar = this.$refs.sidebar;
11+
this.body = this.$el.ownerDocument.documentElement;
12+
this.select.addEventListener('change', () => {
13+
const section = this.select.value;
14+
const sidebarTarget = document.getElementById(`sidebar-header-${section}`);
15+
const contentTarget = document.getElementById(`section-${section}`);
16+
if (sidebarTarget && contentTarget) {
17+
18+
const sidebarPos = sidebarTarget.getBoundingClientRect().top - this.sidebar.getBoundingClientRect().top + this.sidebar.scrollTop;
19+
this.sidebar.scrollTo({
20+
top: sidebarPos - 120,
21+
behavior: 'smooth',
22+
});
23+
24+
const bodyPos = contentTarget.getBoundingClientRect().top + this.body.scrollTop;
25+
this.body.scrollTo({
26+
top: bodyPos - 20,
27+
behavior: 'smooth',
28+
});
29+
}
30+
});
31+
}
32+
}

resources/js/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export {AddRemoveRows} from './add-remove-rows';
22
export {AjaxDeleteRow} from './ajax-delete-row';
33
export {AjaxForm} from './ajax-form';
4+
export {ApiNav} from './api-nav';
45
export {Attachments} from './attachments';
56
export {AttachmentsList} from './attachments-list';
67
export {AutoSuggest} from './auto-suggest';

resources/sass/_blocks.scss

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,19 @@
274274

275275
.sticky-sidebar {
276276
position: sticky;
277-
top: vars.$m;
278-
max-height: calc(100vh - #{vars.$m});
277+
top: 0;
278+
padding-left: 2px;
279+
max-height: calc(100vh);
279280
overflow-y: auto;
281+
.sticky-sidebar-header {
282+
position: sticky;
283+
top: 0;
284+
background: #F2F2F2;
285+
background: linear-gradient(180deg,rgba(242, 242, 242, 1) 66%, rgba(242, 242, 242, 0) 100%);
286+
z-index: 4;
287+
}
288+
}
289+
.dark-mode .sticky-sidebar-header {
290+
background: #111;
291+
background: linear-gradient(180deg,rgba(17, 17, 17, 1) 66%, rgba(17, 17, 17, 0) 100%);
280292
}

resources/views/api-docs/index.blade.php

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,65 @@
22

33
@section('body')
44

5-
<div class="container pt-xl">
5+
<div component="api-nav" class="container">
66

77
<div class="grid right-focus reverse-collapse">
88
<div>
99

10-
<div class="sticky-sidebar">
11-
<p class="text-uppercase text-muted mb-xm mt-l"><strong>Getting Started</strong></p>
10+
<div refs="api-nav@sidebar" class="sticky-sidebar">
1211

13-
<div class="text-mono">
14-
<div class="mb-xs"><a href="#authentication">Authentication</a></div>
15-
<div class="mb-xs"><a href="#request-format">Request Format</a></div>
16-
<div class="mb-xs"><a href="#listing-endpoints">Listing Endpoints</a></div>
17-
<div class="mb-xs"><a href="#error-handling">Error Handling</a></div>
18-
<div class="mb-xs"><a href="#rate-limits">Rate Limits</a></div>
19-
<div class="mb-xs"><a href="#content-security">Content Security</a></div>
12+
<div class="sticky-sidebar-header py-xl">
13+
<select refs="api-nav@select" name="navigation" id="navigation">
14+
<option value="getting-started" selected>Jump To Section</option>
15+
<option value="getting-started">Getting Started</option>
16+
@foreach($docs as $model => $endpoints)
17+
<option value="{{ str_replace(' ', '-', $model) }}">{{ ucfirst($model) }}</option>
18+
@if($model === 'docs' || $model === 'shelves')
19+
<hr>
20+
@endif
21+
@endforeach
22+
</select>
23+
</div>
24+
25+
<div class="mb-xl">
26+
<p id="sidebar-header-getting-started" class="text-uppercase text-muted mb-xm"><strong>Getting Started</strong></p>
27+
<div class="text-mono">
28+
<div class="mb-xs"><a href="#authentication">Authentication</a></div>
29+
<div class="mb-xs"><a href="#request-format">Request Format</a></div>
30+
<div class="mb-xs"><a href="#listing-endpoints">Listing Endpoints</a></div>
31+
<div class="mb-xs"><a href="#error-handling">Error Handling</a></div>
32+
<div class="mb-xs"><a href="#rate-limits">Rate Limits</a></div>
33+
<div class="mb-xs"><a href="#content-security">Content Security</a></div>
34+
</div>
2035
</div>
2136

2237
@foreach($docs as $model => $endpoints)
23-
<p class="text-uppercase text-muted mb-xm mt-l"><strong>{{ $model }}</strong></p>
38+
<div class="mb-xl">
39+
<p id="sidebar-header-{{ str_replace(' ', '-', $model) }}" class="text-uppercase text-muted mb-xm"><strong>{{ $model }}</strong></p>
2440

25-
@foreach($endpoints as $endpoint)
26-
<div class="mb-xs">
27-
<a href="#{{ $endpoint['name'] }}" class="text-mono inline block mr-s">
28-
<span class="api-method" data-method="{{ $endpoint['method'] }}">{{ $endpoint['method'] }}</span>
29-
</a>
30-
<a href="#{{ $endpoint['name'] }}" class="text-mono">
31-
{{ $endpoint['controller_method_kebab'] }}
32-
</a>
33-
</div>
34-
@endforeach
41+
@foreach($endpoints as $endpoint)
42+
<div class="mb-xs">
43+
<a href="#{{ $endpoint['name'] }}" class="text-mono inline block mr-s">
44+
<span class="api-method" data-method="{{ $endpoint['method'] }}">{{ $endpoint['method'] }}</span>
45+
</a>
46+
<a href="#{{ $endpoint['name'] }}" class="text-mono">
47+
{{ $endpoint['controller_method_kebab'] }}
48+
</a>
49+
</div>
50+
@endforeach
51+
</div>
3552
@endforeach
3653
</div>
3754
</div>
3855

39-
<div style="overflow: auto;">
56+
<div class="pt-xl" style="overflow: auto;">
4057

41-
<section component="code-highlighter" class="card content-wrap auto-height">
58+
<section id="section-getting-started" component="code-highlighter" class="card content-wrap auto-height">
4259
@include('api-docs.parts.getting-started')
4360
</section>
4461

4562
@foreach($docs as $model => $endpoints)
46-
<section class="card content-wrap auto-height">
63+
<section id="section-{{ str_replace(' ', '-', $model) }}" class="card content-wrap auto-height">
4764
<h1 class="list-heading text-capitals">{{ $model }}</h1>
4865
@if($endpoints[0]['model_description'])
4966
<p>{{ $endpoints[0]['model_description'] }}</p>

routes/api.php

Lines changed: 57 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/**
44
* Routes for the BookStack API.
5-
* Routes have a uri prefix of /api/.
5+
* Routes have a URI prefix of /api/.
66
* Controllers all end with "ApiController"
77
*/
88

@@ -19,25 +19,18 @@
1919
use BookStack\Users\Controllers\UserApiController;
2020
use Illuminate\Support\Facades\Route;
2121

22-
Route::get('docs.json', [ApiDocsController::class, 'json']);
23-
24-
Route::get('attachments', [AttachmentApiController::class, 'list']);
25-
Route::post('attachments', [AttachmentApiController::class, 'create']);
26-
Route::get('attachments/{id}', [AttachmentApiController::class, 'read']);
27-
Route::put('attachments/{id}', [AttachmentApiController::class, 'update']);
28-
Route::delete('attachments/{id}', [AttachmentApiController::class, 'delete']);
22+
// Main Entity Routes
2923

30-
Route::get('books', [EntityControllers\BookApiController::class, 'list']);
31-
Route::post('books', [EntityControllers\BookApiController::class, 'create']);
32-
Route::get('books/{id}', [EntityControllers\BookApiController::class, 'read']);
33-
Route::put('books/{id}', [EntityControllers\BookApiController::class, 'update']);
34-
Route::delete('books/{id}', [EntityControllers\BookApiController::class, 'delete']);
35-
36-
Route::get('books/{id}/export/html', [ExportControllers\BookExportApiController::class, 'exportHtml']);
37-
Route::get('books/{id}/export/pdf', [ExportControllers\BookExportApiController::class, 'exportPdf']);
38-
Route::get('books/{id}/export/plaintext', [ExportControllers\BookExportApiController::class, 'exportPlainText']);
39-
Route::get('books/{id}/export/markdown', [ExportControllers\BookExportApiController::class, 'exportMarkdown']);
40-
Route::get('books/{id}/export/zip', [ExportControllers\BookExportApiController::class, 'exportZip']);
24+
Route::get('pages', [EntityControllers\PageApiController::class, 'list']);
25+
Route::post('pages', [EntityControllers\PageApiController::class, 'create']);
26+
Route::get('pages/{id}', [EntityControllers\PageApiController::class, 'read']);
27+
Route::put('pages/{id}', [EntityControllers\PageApiController::class, 'update']);
28+
Route::delete('pages/{id}', [EntityControllers\PageApiController::class, 'delete']);
29+
Route::get('pages/{id}/export/html', [ExportControllers\PageExportApiController::class, 'exportHtml']);
30+
Route::get('pages/{id}/export/pdf', [ExportControllers\PageExportApiController::class, 'exportPdf']);
31+
Route::get('pages/{id}/export/plaintext', [ExportControllers\PageExportApiController::class, 'exportPlainText']);
32+
Route::get('pages/{id}/export/markdown', [ExportControllers\PageExportApiController::class, 'exportMarkdown']);
33+
Route::get('pages/{id}/export/zip', [ExportControllers\PageExportApiController::class, 'exportZip']);
4134

4235
Route::get('chapters', [EntityControllers\ChapterApiController::class, 'list']);
4336
Route::post('chapters', [EntityControllers\ChapterApiController::class, 'create']);
@@ -50,51 +43,51 @@
5043
Route::get('chapters/{id}/export/markdown', [ExportControllers\ChapterExportApiController::class, 'exportMarkdown']);
5144
Route::get('chapters/{id}/export/zip', [ExportControllers\ChapterExportApiController::class, 'exportZip']);
5245

53-
Route::get('pages', [EntityControllers\PageApiController::class, 'list']);
54-
Route::post('pages', [EntityControllers\PageApiController::class, 'create']);
55-
Route::get('pages/{id}', [EntityControllers\PageApiController::class, 'read']);
56-
Route::put('pages/{id}', [EntityControllers\PageApiController::class, 'update']);
57-
Route::delete('pages/{id}', [EntityControllers\PageApiController::class, 'delete']);
46+
Route::get('books', [EntityControllers\BookApiController::class, 'list']);
47+
Route::post('books', [EntityControllers\BookApiController::class, 'create']);
48+
Route::get('books/{id}', [EntityControllers\BookApiController::class, 'read']);
49+
Route::put('books/{id}', [EntityControllers\BookApiController::class, 'update']);
50+
Route::delete('books/{id}', [EntityControllers\BookApiController::class, 'delete']);
51+
Route::get('books/{id}/export/html', [ExportControllers\BookExportApiController::class, 'exportHtml']);
52+
Route::get('books/{id}/export/pdf', [ExportControllers\BookExportApiController::class, 'exportPdf']);
53+
Route::get('books/{id}/export/plaintext', [ExportControllers\BookExportApiController::class, 'exportPlainText']);
54+
Route::get('books/{id}/export/markdown', [ExportControllers\BookExportApiController::class, 'exportMarkdown']);
55+
Route::get('books/{id}/export/zip', [ExportControllers\BookExportApiController::class, 'exportZip']);
5856

59-
Route::get('pages/{id}/export/html', [ExportControllers\PageExportApiController::class, 'exportHtml']);
60-
Route::get('pages/{id}/export/pdf', [ExportControllers\PageExportApiController::class, 'exportPdf']);
61-
Route::get('pages/{id}/export/plaintext', [ExportControllers\PageExportApiController::class, 'exportPlainText']);
62-
Route::get('pages/{id}/export/markdown', [ExportControllers\PageExportApiController::class, 'exportMarkdown']);
63-
Route::get('pages/{id}/export/zip', [ExportControllers\PageExportApiController::class, 'exportZip']);
57+
Route::get('shelves', [EntityControllers\BookshelfApiController::class, 'list']);
58+
Route::post('shelves', [EntityControllers\BookshelfApiController::class, 'create']);
59+
Route::get('shelves/{id}', [EntityControllers\BookshelfApiController::class, 'read']);
60+
Route::put('shelves/{id}', [EntityControllers\BookshelfApiController::class, 'update']);
61+
Route::delete('shelves/{id}', [EntityControllers\BookshelfApiController::class, 'delete']);
6462

65-
Route::get('image-gallery', [ImageGalleryApiController::class, 'list']);
66-
Route::post('image-gallery', [ImageGalleryApiController::class, 'create']);
67-
Route::get('image-gallery/url/data', [ImageGalleryApiController::class, 'readDataForUrl']);
68-
Route::get('image-gallery/{id}', [ImageGalleryApiController::class, 'read']);
69-
Route::get('image-gallery/{id}/data', [ImageGalleryApiController::class, 'readData']);
70-
Route::put('image-gallery/{id}', [ImageGalleryApiController::class, 'update']);
71-
Route::delete('image-gallery/{id}', [ImageGalleryApiController::class, 'delete']);
63+
// Additional Model Routes, in alphabetical order
7264

73-
Route::get('search', [SearchApiController::class, 'all']);
65+
Route::get('attachments', [AttachmentApiController::class, 'list']);
66+
Route::post('attachments', [AttachmentApiController::class, 'create']);
67+
Route::get('attachments/{id}', [AttachmentApiController::class, 'read']);
68+
Route::put('attachments/{id}', [AttachmentApiController::class, 'update']);
69+
Route::delete('attachments/{id}', [AttachmentApiController::class, 'delete']);
70+
71+
Route::get('audit-log', [ActivityControllers\AuditLogApiController::class, 'list']);
7472

7573
Route::get('comments', [ActivityControllers\CommentApiController::class, 'list']);
7674
Route::post('comments', [ActivityControllers\CommentApiController::class, 'create']);
7775
Route::get('comments/{id}', [ActivityControllers\CommentApiController::class, 'read']);
7876
Route::put('comments/{id}', [ActivityControllers\CommentApiController::class, 'update']);
7977
Route::delete('comments/{id}', [ActivityControllers\CommentApiController::class, 'delete']);
8078

81-
Route::get('shelves', [EntityControllers\BookshelfApiController::class, 'list']);
82-
Route::post('shelves', [EntityControllers\BookshelfApiController::class, 'create']);
83-
Route::get('shelves/{id}', [EntityControllers\BookshelfApiController::class, 'read']);
84-
Route::put('shelves/{id}', [EntityControllers\BookshelfApiController::class, 'update']);
85-
Route::delete('shelves/{id}', [EntityControllers\BookshelfApiController::class, 'delete']);
79+
Route::get('content-permissions/{contentType}/{contentId}', [ContentPermissionApiController::class, 'read']);
80+
Route::put('content-permissions/{contentType}/{contentId}', [ContentPermissionApiController::class, 'update']);
8681

87-
Route::get('users', [UserApiController::class, 'list']);
88-
Route::post('users', [UserApiController::class, 'create']);
89-
Route::get('users/{id}', [UserApiController::class, 'read']);
90-
Route::put('users/{id}', [UserApiController::class, 'update']);
91-
Route::delete('users/{id}', [UserApiController::class, 'delete']);
82+
Route::get('docs.json', [ApiDocsController::class, 'json']);
9283

93-
Route::get('roles', [RoleApiController::class, 'list']);
94-
Route::post('roles', [RoleApiController::class, 'create']);
95-
Route::get('roles/{id}', [RoleApiController::class, 'read']);
96-
Route::put('roles/{id}', [RoleApiController::class, 'update']);
97-
Route::delete('roles/{id}', [RoleApiController::class, 'delete']);
84+
Route::get('image-gallery', [ImageGalleryApiController::class, 'list']);
85+
Route::post('image-gallery', [ImageGalleryApiController::class, 'create']);
86+
Route::get('image-gallery/url/data', [ImageGalleryApiController::class, 'readDataForUrl']);
87+
Route::get('image-gallery/{id}', [ImageGalleryApiController::class, 'read']);
88+
Route::get('image-gallery/{id}/data', [ImageGalleryApiController::class, 'readData']);
89+
Route::put('image-gallery/{id}', [ImageGalleryApiController::class, 'update']);
90+
Route::delete('image-gallery/{id}', [ImageGalleryApiController::class, 'delete']);
9891

9992
Route::get('imports', [ExportControllers\ImportApiController::class, 'list']);
10093
Route::post('imports', [ExportControllers\ImportApiController::class, 'create']);
@@ -106,9 +99,18 @@
10699
Route::put('recycle-bin/{deletionId}', [EntityControllers\RecycleBinApiController::class, 'restore']);
107100
Route::delete('recycle-bin/{deletionId}', [EntityControllers\RecycleBinApiController::class, 'destroy']);
108101

109-
Route::get('content-permissions/{contentType}/{contentId}', [ContentPermissionApiController::class, 'read']);
110-
Route::put('content-permissions/{contentType}/{contentId}', [ContentPermissionApiController::class, 'update']);
102+
Route::get('roles', [RoleApiController::class, 'list']);
103+
Route::post('roles', [RoleApiController::class, 'create']);
104+
Route::get('roles/{id}', [RoleApiController::class, 'read']);
105+
Route::put('roles/{id}', [RoleApiController::class, 'update']);
106+
Route::delete('roles/{id}', [RoleApiController::class, 'delete']);
111107

112-
Route::get('audit-log', [ActivityControllers\AuditLogApiController::class, 'list']);
108+
Route::get('search', [SearchApiController::class, 'all']);
113109

114110
Route::get('system', [SystemApiController::class, 'read']);
111+
112+
Route::get('users', [UserApiController::class, 'list']);
113+
Route::post('users', [UserApiController::class, 'create']);
114+
Route::get('users/{id}', [UserApiController::class, 'read']);
115+
Route::put('users/{id}', [UserApiController::class, 'update']);
116+
Route::delete('users/{id}', [UserApiController::class, 'delete']);

0 commit comments

Comments
 (0)