Просмотр исходного кода

Support subgroups in UI; simplify data passed to templates

shalvah 2 лет назад
Родитель
Сommit
2ebf40b5e5

+ 1 - 0
camel/Extraction/Metadata.php

@@ -8,6 +8,7 @@ class Metadata extends BaseDTO
 {
     public ?string $groupName;
     public ?string $subgroup;
+    public ?string $subgroupDescription;
 
     /**
      * Name of the group that this group should be placed just before.

+ 13 - 0
camel/Output/OutputEndpointData.php

@@ -4,6 +4,7 @@ namespace Knuckles\Camel\Output;
 
 use Illuminate\Http\UploadedFile;
 use Illuminate\Routing\Route;
+use Illuminate\Support\Str;
 use Knuckles\Camel\BaseDTO;
 use Knuckles\Camel\Extraction\Metadata;
 use Knuckles\Camel\Extraction\ResponseCollection;
@@ -149,6 +150,18 @@ class OutputEndpointData extends BaseDTO
         return $this->httpMethods[0] . str_replace(['/', '?', '{', '}', ':', '\\', '+', '|'], '-', $this->uri);
     }
 
+    public function name(): string
+    {
+        return $this->metadata->title ?: ($this->httpMethods[0] . " " . $this->uri);
+    }
+
+    public function fullSlug(): string
+    {
+        $groupSlug = Str::slug($this->metadata->groupName);
+        $endpointId = $this->endpointId();
+        return "$groupSlug-$endpointId";
+    }
+
     public function hasResponses(): bool
     {
         return count($this->responses) > 0;

+ 7 - 0
resources/css/theme-default.style.css

@@ -492,6 +492,9 @@ html {
     white-space: nowrap;
     text-overflow: ellipsis
 }
+.tocify-wrapper .tocify-item.level-3>a {
+    padding: 0 25px;
+}
 
 .tocify-wrapper li,
 .tocify-wrapper ul {
@@ -532,6 +535,10 @@ html {
     font-size: 12px
 }
 
+.tocify-wrapper .tocify-subheader .tocify-item.level-3>a {
+    padding-left: 35px;
+}
+
 .tocify-wrapper .tocify-subheader>li:last-child {
     box-shadow: none
 }

+ 7 - 6
resources/views/themes/default/endpoint.blade.php

@@ -2,7 +2,7 @@
     /** @var  Knuckles\Camel\Output\OutputEndpointData $endpoint */
 @endphp
 
-<h2 id="{!! Str::slug($group['name']) !!}-{!! $endpoint->endpointId() !!}">{{ $endpoint->metadata->title ?: ($endpoint->httpMethods[0]." ".$endpoint->uri)}}</h2>
+<h2 id="{!! $endpoint->fullSlug() !!}">{{ $endpoint->name() }}</h2>
 
 <p>
 @component('scribe::components.badges.auth', ['authenticated' => $endpoint->metadata->authenticated])
@@ -97,11 +97,12 @@
     @if($endpoint->metadata->authenticated && $metadata['auth']['location'] === 'header')
         <p>
             <label id="auth-{{ $endpoint->endpointId() }}" hidden>{{ $metadata['auth']['name'] }} header:
-                <b><code>{{ $metadata['auth']['prefix'] }}</code></b><input type="text"
-                                                                name="{{ $metadata['auth']['name'] }}"
-                                                                data-prefix="{{ $metadata['auth']['prefix'] }}"
-                                                                data-endpoint="{{ $endpoint->endpointId() }}"
-                                                                data-component="header"></label>
+                <b><code>{{ $metadata['auth']['prefix'] }}</code></b>
+                <input type="text"
+                       name="{{ $metadata['auth']['name'] }}"
+                       data-prefix="{{ $metadata['auth']['prefix'] }}"
+                       data-endpoint="{{ $endpoint->endpointId() }}"
+                       data-component="header"></label>
         </p>
     @endif
     @if(count($endpoint->urlParameters))

+ 13 - 2
resources/views/themes/default/groups.blade.php

@@ -3,8 +3,19 @@
 
     {!! Parsedown::instance()->text($group['description']) !!}
 
-    @foreach($group['endpoints'] as $endpoint)
-        @include("scribe::themes.default.endpoint")
+    @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
 

+ 20 - 90
resources/views/themes/default/sidebar.blade.php

@@ -23,101 +23,31 @@
     </div>
 
     <div id="toc">
-        @php
-            $previousH1 = null;
-            $inSubHeading = false;
-            $headingsCount = 0;
-        @endphp
-        @foreach($headingsBeforeEndpoints as $heading)
-            @if($heading['level'] === 1)
-                @if($previousH1)
-                    </ul>
-                @endif
-                @if($inSubHeading)
-                    @php($inSubHeading = false)
-                    </ul>
-                @endif
-                <ul id="tocify-header-{{ $headingsCount }}" class="tocify-header">
-                    <li class="tocify-item level-1" data-unique="{!! $heading['slug'] !!}">
-                        <a href="#{!! $heading['slug'] !!}">{!! $heading['text'] !!}</a>
-                    </li>
-                @php($previousH1 = $heading)
-                @php($headingsCount += 1)
-            @elseif ($heading['level'] === 2 && $previousH1)
-                @if(!$inSubHeading)
-                    <ul id="tocify-subheader-{!! $previousH1['slug'] !!}" class="tocify-subheader">
-                    @php($inSubHeading = true)
-                @endif
-                    <li class="tocify-item level-2"
-                        data-unique="{!! $previousH1['slug'] !!}-{!! $heading['slug'] !!}">
-                        <a href="#{!! $heading['slug'] !!}">{{ $heading['text'] }}</a>
-                    </li>
-            @endif
-
-            @if($loop->last)
-                    @if($inSubHeading)
-                    </ul>
-                    @endif
-                </ul>
-            @endif
-        @endforeach
-
-        @foreach($groupedEndpoints as $group)
-            <ul id="tocify-header-{{ $loop->index + $headingsCount }}" class="tocify-header">
-                <li class="tocify-item level-1" data-unique="{!! Str::slug($group['name']) !!}">
-                    <a href="#{!! Str::slug($group['name']) !!}">{!! $group['name'] !!}</a>
+        @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($group['endpoints']) > 0)
-                    <ul id="tocify-subheader-{!! Str::slug($group['name']) !!}" class="tocify-subheader">
-                @endif
-                @foreach($group['endpoints'] as $endpoint)
-                    <li class="tocify-item level-2" data-unique="{!! Str::slug($group['name']) !!}-{!! $endpoint->endpointId() !!}">
-                        <a href="#{!! Str::slug($group['name']) !!}-{!! $endpoint->endpointId() !!}">{{ $endpoint->metadata->title ?: ($endpoint->httpMethods[0]." ".$endpoint->uri)}}</a>
-                    </li>
-                @endforeach
-                @if (count($group['endpoints']) > 0)
+                @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
-
-        @php($previousH1 = null)
-        @php($inSubHeading = false)
-        @php($headingsCount += count($groupedEndpoints))
-
-        @foreach($headingsAfterEndpoints as $heading)
-            @if($heading['level'] === 1)
-                @if($previousH1)
-                    </ul>
-                @endif
-                @if($inSubHeading)
-                    @php($inSubHeading = false)
-                    </ul>
-                @endif
-                <ul id="tocify-header-{{ $headingsCount }}" class="tocify-header">
-                    <li class="tocify-item level-1" data-unique="{!! $heading['slug'] !!}">
-                        <a href="#{!! $heading['slug'] !!}">{!! $heading['text'] !!}</a>
-                    </li>
-                @php($previousH1 = $heading)
-                @php($headingsCount += 1)
-            @elseif ($heading['level'] === 2 && $previousH1)
-                @if(!$inSubHeading)
-                    <ul id="tocify-subheader-{!! $previousH1['slug'] !!}" class="tocify-subheader">
-                    @php($inSubHeading = true)
-                @endif
-                    <li class="tocify-item level-2"
-                        data-unique="{!! $previousH1['slug'] !!}-{!! $heading['slug'] !!}">
-                        <a href="#{!! $heading['slug'] !!}">{{ $heading['text'] }}</a>
-                    </li>
-            @endif
-
-            @if($loop->last)
-                    @if($inSubHeading)
-                    </ul>
-                    @endif
-                </ul>
-            @endif
-        @endforeach
     </div>
 
     @if(isset($metadata['links']))

+ 20 - 2
src/Extracting/Strategies/Metadata/GetFromDocBlocks.php

@@ -26,6 +26,7 @@ class GetFromDocBlocks extends Strategy
             'groupName' => $routeGroupName,
             'groupDescription' => $routeGroupDescription,
             'subgroup' => $this->getEndpointSubGroup($methodDocBlock, $classDocBlock),
+            'subgroupDescription' => $this->getEndpointSubGroupDescription($methodDocBlock, $classDocBlock),
             'title' => $routeTitle ?: $methodDocBlock->getShortDescription(),
             'description' => $methodDocBlock->getLongDescription()->getContents(),
             'authenticated' => $this->getAuthStatusFromDocBlock($methodDocBlock, $classDocBlock),
@@ -101,13 +102,30 @@ class GetFromDocBlocks extends Strategy
     protected function getEndpointSubGroup(DocBlock $methodDocBlock, DocBlock $controllerDocBlock): ?string
     {
         foreach ($methodDocBlock->getTags() as $tag) {
-            if ($tag->getName() === 'subgroup') {
+            if (strtolower($tag->getName()) === 'subgroup') {
                 return trim($tag->getContent());
             }
         }
 
         foreach ($controllerDocBlock->getTags() as $tag) {
-            if ($tag->getName() === 'subgroup') {
+            if (strtolower($tag->getName()) === 'subgroup') {
+                return trim($tag->getContent());
+            }
+        }
+
+        return null;
+    }
+
+    protected function getEndpointSubGroupDescription(DocBlock $methodDocBlock, DocBlock $controllerDocBlock): ?string
+    {
+        foreach ($methodDocBlock->getTags() as $tag) {
+            if (strtolower($tag->getName()) === 'subgroupdescription') {
+                return trim($tag->getContent());
+            }
+        }
+
+        foreach ($controllerDocBlock->getTags() as $tag) {
+            if (strtolower($tag->getName()) === 'subgroupdescription') {
                 return trim($tag->getContent());
             }
         }

+ 79 - 8
src/Writing/HtmlWriter.php

@@ -3,6 +3,8 @@
 namespace Knuckles\Scribe\Writing;
 
 use Illuminate\Support\Facades\View;
+use Illuminate\Support\Str;
+use Knuckles\Camel\Output\OutputEndpointData;
 use Knuckles\Scribe\Tools\DocumentationConfig;
 use Knuckles\Scribe\Tools\MarkdownParser;
 use Knuckles\Scribe\Tools\Utils;
@@ -44,6 +46,9 @@ class HtmlWriter
         $append = file_exists($appendFile) ? $this->transformMarkdownFileToHTML($appendFile) : '';
         $headingsAfterEndpoints = $this->markdownParser->headings;
 
+        foreach ($groupedEndpoints as &$group) {
+                $group['subgroups'] = collect($group['endpoints'])->groupBy('metadata.subgroup')->all();
+        }
         $theme = $this->config->get('theme') ?? 'default';
         $output = View::make("scribe::themes.$theme.index", [
             'metadata' => $this->getMetadata(),
@@ -52,10 +57,9 @@ class HtmlWriter
             'intro' => $intro,
             'auth' => $auth,
             'groupedEndpoints' => $groupedEndpoints,
+            'headings' => $this->getHeadings($headingsBeforeEndpoints, $groupedEndpoints, $headingsAfterEndpoints),
             'append' => $append,
             'assetPathPrefix' => $this->assetPathPrefix,
-            'headingsBeforeEndpoints' => $headingsBeforeEndpoints,
-            'headingsAfterEndpoints' => $headingsAfterEndpoints,
         ])->render();
 
         if (!is_dir($destinationFolder)) {
@@ -67,11 +71,11 @@ class HtmlWriter
         // Copy assets
         $assetsFolder = __DIR__ . '/../../resources';
         // Prune older versioned assets
-        if (is_dir($destinationFolder.'/css')) {
-            Utils::deleteDirectoryAndContents($destinationFolder.'/css');
+        if (is_dir($destinationFolder . '/css')) {
+            Utils::deleteDirectoryAndContents($destinationFolder . '/css');
         }
-        if (is_dir($destinationFolder.'/js')) {
-            Utils::deleteDirectoryAndContents($destinationFolder.'/js');
+        if (is_dir($destinationFolder . '/js')) {
+            Utils::deleteDirectoryAndContents($destinationFolder . '/js');
         }
         Utils::copyDirectory("{$assetsFolder}/images/", "{$destinationFolder}/images");
 
@@ -90,7 +94,7 @@ class HtmlWriter
                 if (!is_dir($destination)) {
                     mkdir($destination, 0777, true);
                 }
-                copy($path, $destination.$fileName);
+                copy($path, $destination . $fileName);
             }
         }
     }
@@ -116,7 +120,7 @@ class HtmlWriter
         if ($auth['in'] === 'bearer' || $auth['in'] === 'basic') {
             $auth['name'] = 'Authorization';
             $auth['location'] = 'header';
-            $auth['prefix'] = ucfirst($auth['in']).' ';
+            $auth['prefix'] = ucfirst($auth['in']) . ' ';
         } else {
             $auth['location'] = $auth['in'];
             $auth['prefix'] = '';
@@ -132,4 +136,71 @@ class HtmlWriter
             'links' => array_merge($links, ['<a href="http://github.com/knuckleswtf/scribe">Documentation powered by Scribe ✍</a>']),
         ];
     }
+
+    protected function getHeadings(array $headingsBeforeEndpoints, array $endpointsByGroupAndSubgroup, array $headingsAfterEndpoints)
+    {
+        $headings = [];
+
+        $lastL1ElementIndex = null;
+        foreach ($headingsBeforeEndpoints as $heading) {
+            $element = [
+                'slug' => $heading['slug'],
+                'name' => $heading['text'],
+                'subheadings' => [],
+            ];;
+            if ($heading['level'] === 1) {
+                $headings[] = $element;
+                $lastL1ElementIndex = count($headings) - 1;
+            } elseif ($heading['level'] === 2 && !is_null($lastL1ElementIndex)) {
+                $headings[$lastL1ElementIndex]['subheadings'][] = $element;
+            }
+        }
+
+        $headings = array_merge($headings, array_map(function ($group) {
+            $groupSlug = Str::slug($group['name']);
+
+            return [
+                'slug' => $groupSlug,
+                'name' => $group['name'],
+                'subheadings' => collect($group['subgroups'])->flatMap(function ($endpoints, $subgroupName) use ($groupSlug) {
+                    if ($subgroupName === "") {
+                        return $endpoints->map(fn(OutputEndpointData $endpoint) => [
+                            'slug' => $endpoint->fullSlug(),
+                            'name' => $endpoint->name(),
+                            'subheadings' => []
+                        ])->all();
+                    }
+
+                    return [
+                        [
+                            'slug' => "$groupSlug-" . Str::slug($subgroupName),
+                            'name' => $subgroupName,
+                            'subheadings' => $endpoints->map(fn($endpoint) => [
+                                'slug' => $endpoint->fullSlug(),
+                                'name' => $endpoint->name(),
+                                'subheadings' => []
+                            ])->all(),
+                        ],
+                    ];
+                })->all(),
+            ];
+        }, $endpointsByGroupAndSubgroup));
+
+        $lastL1ElementIndex = null;
+        foreach ($headingsAfterEndpoints as $heading) {
+            $element = [
+                'slug' => $heading['slug'],
+                'name' => $heading['text'],
+                'subheadings' => [],
+            ];;
+            if ($heading['level'] === 1) {
+                $headings[] = $element;
+                $lastL1ElementIndex = count($headings) - 1;
+            } elseif ($heading['level'] === 2 && !is_null($lastL1ElementIndex)) {
+                $headings[$lastL1ElementIndex]['subheadings'][] = $element;
+            }
+        }
+
+        return $headings;
+    }
 }

+ 2 - 0
tests/Strategies/Metadata/GetFromDocBlocksTest.php

@@ -68,6 +68,7 @@ DOCBLOCK;
 /**
   * @authenticated
   * @subgroup Scheiße
+  * @subgroupDescription Heilige Scheiße
   */
 DOCBLOCK;
         $results = $strategy->getMetadataFromDocBlock(new DocBlock($methodDocblock), new DocBlock($classDocblock));
@@ -75,6 +76,7 @@ DOCBLOCK;
         $this->assertTrue($results['authenticated']);
         $this->assertSame(null, $results['groupName']);
         $this->assertSame('Scheiße', $results['subgroup']);
+        $this->assertSame('Heilige Scheiße', $results['subgroupDescription']);
         $this->assertSame('', $results['groupDescription']);
         $this->assertSame('Endpoint title.', $results['title']);
         $this->assertSame("", $results['description']);