diff --git a/app/application/app/Dto/Builder/Documentation.php b/app/application/app/Dto/Builder/Documentation.php
index fdec08d..c0202de 100644
--- a/app/application/app/Dto/Builder/Documentation.php
+++ b/app/application/app/Dto/Builder/Documentation.php
@@ -2,14 +2,22 @@
namespace App\Dto\Builder;
+use App\Dto\Builder\DocumentationCategory\Category;
+
final readonly class Documentation
{
public function __construct(
- private ?bool $isPublic = null,
+ private ?bool $isPublic = null,
+ private ?Category $categoryId = null,
) { }
public function isPublic(): ?bool
{
return $this->isPublic;
}
+
+ public function getCategoryId(): ?Category
+ {
+ return $this->categoryId;
+ }
}
diff --git a/app/application/app/Dto/Builder/DocumentationCategory.php b/app/application/app/Dto/Builder/DocumentationCategory.php
index 242a20c..7bdc68c 100644
--- a/app/application/app/Dto/Builder/DocumentationCategory.php
+++ b/app/application/app/Dto/Builder/DocumentationCategory.php
@@ -2,14 +2,22 @@
namespace App\Dto\Builder;
+use App\Dto\Builder\DocumentationCategory\Category;
+
final readonly class DocumentationCategory
{
public function __construct(
- private ?bool $isPublic = null,
+ private ?bool $isPublic = null,
+ private ?Category $parentId = null,
) { }
public function isPublic(): ?bool
{
return $this->isPublic;
}
+
+ public function getParentId(): ?Category
+ {
+ return $this->parentId;
+ }
}
diff --git a/app/application/app/Dto/Builder/DocumentationCategory/Category.php b/app/application/app/Dto/Builder/DocumentationCategory/Category.php
new file mode 100644
index 0000000..73e0e3a
--- /dev/null
+++ b/app/application/app/Dto/Builder/DocumentationCategory/Category.php
@@ -0,0 +1,20 @@
+categoryId;
+ }
+
+ public function isCategoryNull(): bool
+ {
+ return $this->getCategoryId() === null;
+ }
+}
diff --git a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php
index ed702cb..f7831bb 100644
--- a/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php
+++ b/app/application/app/Dto/Service/Admin/Project/Translation/Translations.php
@@ -2,6 +2,7 @@
namespace App\Dto\Service\Admin\Project\Translation;
+use App\Enums\DocumentationVersionStatus;
use App\Exceptions\Dto\Admin\Project\Transaction\TranslationsException;
final class Translations
@@ -23,7 +24,7 @@ public function getTranslations(): array
public static function getTranslationCodes(): array
{
- return [
+ $translations = [
'site.Menu',
'site.Powered by service',
'site.About project',
@@ -38,6 +39,17 @@ public static function getTranslationCodes(): array
'site.attributes.message',
'site.Message sent successfully',
'Server Error',
+ 'site.Documentation',
+ 'site.Documentation not created',
+ 'site.Choose version',
+ 'site.alert-status-not-supported',
+ 'site.alert-status-future',
];
+
+ foreach (DocumentationVersionStatus::cases() as $status) {
+ $translations[] = $status->getCodeForTranslation();
+ }
+
+ return $translations;
}
}
diff --git a/app/application/app/Dto/Service/Site/Documentation.php b/app/application/app/Dto/Service/Site/Documentation.php
new file mode 100644
index 0000000..a8d141c
--- /dev/null
+++ b/app/application/app/Dto/Service/Site/Documentation.php
@@ -0,0 +1,47 @@
+project;
+ }
+
+ public function getVersion(): DocumentationVersion
+ {
+ return $this->version;
+ }
+
+ public function getWebsiteTranslations(): WebsiteTranslations
+ {
+ return $this->websiteTranslations;
+ }
+
+ public function getUser(): ?User
+ {
+ return $this->user;
+ }
+
+ public function toArray(): array
+ {
+ return [
+ 'project' => $this->getProject(),
+ 'version' => $this->getVersion(),
+ 'websiteTranslations' => $this->getWebsiteTranslations(),
+ ];
+ }
+}
diff --git a/app/application/app/Enums/CacheTag.php b/app/application/app/Enums/CacheTag.php
index 316900c..a9e7b6f 100644
--- a/app/application/app/Enums/CacheTag.php
+++ b/app/application/app/Enums/CacheTag.php
@@ -9,6 +9,7 @@ enum CacheTag: string
{
case Project = 'project';
case ProjectTranslation = 'project_translation';
+ case DocumantationVersion = 'documantation_version';
public function getCache(): TaggedCache
{
diff --git a/app/application/app/Enums/DocumentationVersionStatus.php b/app/application/app/Enums/DocumentationVersionStatus.php
index 14ab233..7cb4c9d 100644
--- a/app/application/app/Enums/DocumentationVersionStatus.php
+++ b/app/application/app/Enums/DocumentationVersionStatus.php
@@ -2,6 +2,7 @@
namespace App\Enums;
+use App\Services\WebsiteTranslations;
use Illuminate\Support\Collection;
enum DocumentationVersionStatus: int
@@ -11,9 +12,18 @@ enum DocumentationVersionStatus: int
case CurrentVersion = 100;
case FutureVersion = 150;
- public function getTitle(): string
+ public function getTitle(?WebsiteTranslations $websiteTranslations = null): string
{
- return __('version-status.' . $this->name);
+ if (\is_null($websiteTranslations)) {
+ return __($this->getCodeForTranslation());
+ }
+
+ return $websiteTranslations->translate($this->getCodeForTranslation());
+ }
+
+ public function getCodeForTranslation(): string
+ {
+ return 'version-status.' . $this->name;
}
public static function toArray(): array
diff --git a/app/application/app/Enums/Site/ProjectSection.php b/app/application/app/Enums/Site/ProjectSection.php
index 740c7d8..b61c8ec 100644
--- a/app/application/app/Enums/Site/ProjectSection.php
+++ b/app/application/app/Enums/Site/ProjectSection.php
@@ -5,15 +5,18 @@
use App\Models\Project;
use App\Models\ProjectLanguage;
-enum ProjectSection
+enum ProjectSection: string
{
- case Home;
- case Feedback;
- case FeedbackSend;
+ case Home = 'home';
+ case Feedback = 'feedback';
+ case FeedbackSend = 'feedback.send';
+ case Documentation = 'documentation';
+ case DocumentationVersion = 'documentation.version';
+ case DocumentationCategory = 'documentation.category';
+ case DocumentationView = 'documentation.view';
- public function url(Project $project, ?ProjectLanguage $language = null): string
+ public function url(Project $project, ?ProjectLanguage $language = null, array $parameters = []): string
{
- $parameters = [];
$prefixProject = '';
if ($project->http_host === null) {
$prefixProject = 'project.';
@@ -26,12 +29,7 @@ public function url(Project $project, ?ProjectLanguage $language = null): string
$prefixLanguage = '-language';
}
- $route = match ($this) {
- self::Home => \route($prefixProject . 'home' . $prefixLanguage, $parameters, false),
-
- self::Feedback => \route($prefixProject . 'feedback' . $prefixLanguage, $parameters, false),
- self::FeedbackSend => \route($prefixProject . 'feedback.send' . $prefixLanguage, $parameters, false),
- };
+ $route = \route($prefixProject . $this->value . $prefixLanguage, $parameters, false);
return $project->http_host . $route;
}
diff --git a/app/application/app/Http/Controllers/Site/DocumentationController.php b/app/application/app/Http/Controllers/Site/DocumentationController.php
new file mode 100644
index 0000000..feb935e
--- /dev/null
+++ b/app/application/app/Http/Controllers/Site/DocumentationController.php
@@ -0,0 +1,96 @@
+get('project');
+ $websiteTranslations = $request->get('websiteTranslations');
+
+ $result = $this->documentationService->defaultVersion($project, $request->user());
+ if ($result->isError()) {
+ if ($result->getCode() === 404) {
+ return view('site.projects.documentation.no-default-version', [
+ 'project' => $project,
+ 'websiteTranslations' => $websiteTranslations,
+ ]);
+ }
+ $this->errors($result);
+ }
+
+ $url = \App\Enums\Site\ProjectSection::DocumentationVersion->url($project, $websiteTranslations->getLanguage(), ['version' => $result->getVersion()->slug]);
+ /**
+ * 302 redirect because the documentation version can change at any time.
+ */
+ return redirect($url, 302);
+ }
+
+ public function index(Request $request): View
+ {
+ $documentation = new Documentation(
+ project: $request->get('project'),
+ version: $request->get('version'),
+ websiteTranslations: $request->get('websiteTranslations'),
+ user: $request->user(),
+ );
+ $result = $this->documentationService->index($documentation);
+
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+
+ return view('site.projects.documentation.index', $result->getData());
+ }
+
+ public function category(string $slug, Request $request): View
+ {
+ $documentation = new Documentation(
+ project: $request->get('project'),
+ version: $request->get('version'),
+ websiteTranslations: $request->get('websiteTranslations'),
+ user: $request->user(),
+ );
+ $result = $this->documentationService->category($slug, $documentation);
+
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+ if ($result->isTranslation()) {
+ return $this->viewPageWithoutTranslation($result);
+ }
+
+ return view('site.projects.documentation.category', $result->getData());
+ }
+
+ public function view(string $slug, Request $request): View
+ {
+ $documentation = new Documentation(
+ project: $request->get('project'),
+ version: $request->get('version'),
+ websiteTranslations: $request->get('websiteTranslations'),
+ user: $request->user(),
+ );
+ $result = $this->documentationService->view($slug, $documentation);
+
+ if ($result->isError()) {
+ $this->errors($result);
+ }
+ if ($result->isTranslation()) {
+ return $this->viewPageWithoutTranslation($result);
+ }
+
+ return view('site.projects.documentation.view', $result->getData());
+ }
+}
diff --git a/app/application/app/Http/Middleware/DocumentationVersion.php b/app/application/app/Http/Middleware/DocumentationVersion.php
new file mode 100644
index 0000000..e71fb39
--- /dev/null
+++ b/app/application/app/Http/Middleware/DocumentationVersion.php
@@ -0,0 +1,40 @@
+get('project');
+ $versionSlug = $request->route()?->parameter('version');
+ if ($versionSlug === null || $project === null) {
+ \abort(Response::HTTP_NOT_FOUND);
+ }
+
+ $seconds = 3600;
+ $version = CacheTag::DocumantationVersion->getCache()->remember(self::class . $project->id . '-' . $versionSlug, $seconds, function () use ($project, $versionSlug) {
+ return $project->documentationVersions()->where('slug', $versionSlug)->first() ?? false;
+ });
+ if ($version === false) {
+ \abort(Response::HTTP_NOT_FOUND);
+ }
+
+ unset($request->route()->parameters['version']);
+
+ $request->attributes->set('version', $version);
+
+ if (
+ $version->is_public === false
+ && ( $request->user() === null || $request->user()->cannot('view', $version) )
+ ) {
+ \abort(Response::HTTP_FORBIDDEN);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/application/app/Models/Documentation.php b/app/application/app/Models/Documentation.php
index e237937..dd11136 100644
--- a/app/application/app/Models/Documentation.php
+++ b/app/application/app/Models/Documentation.php
@@ -2,6 +2,8 @@
namespace App\Models;
+use App\Models\Scopes\SortScope;
+use Illuminate\Database\Eloquent\Attributes\ScopedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -9,6 +11,7 @@
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
+#[ScopedBy([SortScope::class])]
final class Documentation extends Model
{
use HasFactory, SoftDeletes;
diff --git a/app/application/app/Models/DocumentationVersion.php b/app/application/app/Models/DocumentationVersion.php
index 384b08d..4025555 100644
--- a/app/application/app/Models/DocumentationVersion.php
+++ b/app/application/app/Models/DocumentationVersion.php
@@ -3,6 +3,7 @@
namespace App\Models;
use App\Enums\DocumentationVersionStatus;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -37,6 +38,16 @@ final class DocumentationVersion extends Model
'status',
];
+ /**
+ * The "booted" method of the model.
+ */
+ protected static function booted(): void
+ {
+ static::addGlobalScope('status', function (Builder $builder) {
+ $builder->orderBy('status', 'desc');
+ });
+ }
+
/**
* Get the attributes that should be cast.
*
diff --git a/app/application/app/Providers/AppServiceProvider.php b/app/application/app/Providers/AppServiceProvider.php
index 20abed2..670578a 100644
--- a/app/application/app/Providers/AppServiceProvider.php
+++ b/app/application/app/Providers/AppServiceProvider.php
@@ -59,6 +59,8 @@ public function boot(): void
Route::pattern('language', '[a-z_]+');
Route::pattern('project', '[a-z0-9_-]+');
+ Route::pattern('slug', '[a-z0-9._-]+');
+ Route::pattern('version', '[a-z0-9._-]+');
$this->configureRateLimiting();
Gate::define('AdminPanel', [\App\Policies\AdminPanel::class, 'view']);
diff --git a/app/application/app/Repositories/DocumentationCategoryRepository.php b/app/application/app/Repositories/DocumentationCategoryRepository.php
index 2f9f47f..81e2d65 100644
--- a/app/application/app/Repositories/DocumentationCategoryRepository.php
+++ b/app/application/app/Repositories/DocumentationCategoryRepository.php
@@ -4,6 +4,7 @@
use App\Contracts\Search;
use App\Models\DocumentationCategory;
+use App\Models\DocumentationVersion;
use App\Models\ProjectLanguage;
use App\Services\DocumentationCategory\BuilderCommand;
use App\Services\Search\CreateSearchInstanceCommand;
@@ -35,9 +36,19 @@ public function getCategoryById(int $id): ?DocumentationCategory
return DocumentationCategory::query()->where('id', $id)->first();
}
- public function getCategoryByCode(string $code): ?DocumentationCategory
+ public function getCategoryBySlugWithContent(string $slug, int $versionId, ProjectLanguage $language): ?DocumentationCategory
{
- return DocumentationCategory::query()->where('code', $code)->first();
+ $with = [
+ 'content' => function (HasOne $hasOne) use ($language) {
+ $hasOne->where('language_id', $language->id);
+ }
+ ];
+
+ return DocumentationCategory::query()
+ ->where('version_id', $versionId)
+ ->where('slug', $slug)
+ ->with($with)
+ ->first();
}
public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool
diff --git a/app/application/app/Repositories/DocumentationRepository.php b/app/application/app/Repositories/DocumentationRepository.php
index 2a2072f..d6d22e8 100644
--- a/app/application/app/Repositories/DocumentationRepository.php
+++ b/app/application/app/Repositories/DocumentationRepository.php
@@ -5,9 +5,11 @@
use App\Dto\Builder\Documentation as DocumentationBuilderDto;
use App\Contracts\Search;
use App\Models\Documentation;
+use App\Models\ProjectLanguage;
use App\Services\Documentation\BuilderCommand;
use App\Services\Search\CreateSearchInstanceCommand;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str;
final readonly class DocumentationRepository
@@ -32,9 +34,19 @@ public function getDocumentationById(int $id): ?Documentation
return Documentation::query()->where('id', $id)->first();
}
- public function getDocumentationByCode(string $code): ?Documentation
+ public function getDocumentationBySlugWithContent(string $slug, int $versionId, ProjectLanguage $language): ?Documentation
{
- return Documentation::query()->where('code', $code)->first();
+ $with = [
+ 'content' => function (HasOne $hasOne) use ($language) {
+ $hasOne->where('language_id', $language->id);
+ }
+ ];
+
+ return Documentation::query()
+ ->where('version_id', $versionId)
+ ->where('slug', $slug)
+ ->with($with)
+ ->first();
}
public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool
diff --git a/app/application/app/ServiceResults/Site/DocumentationService/DefaultVersion.php b/app/application/app/ServiceResults/Site/DocumentationService/DefaultVersion.php
new file mode 100644
index 0000000..f329d70
--- /dev/null
+++ b/app/application/app/ServiceResults/Site/DocumentationService/DefaultVersion.php
@@ -0,0 +1,17 @@
+version;
+ }
+}
diff --git a/app/application/app/Services/Admin/Project/DocumentationVersionService.php b/app/application/app/Services/Admin/Project/DocumentationVersionService.php
index ae9d904..22ef657 100644
--- a/app/application/app/Services/Admin/Project/DocumentationVersionService.php
+++ b/app/application/app/Services/Admin/Project/DocumentationVersionService.php
@@ -5,6 +5,7 @@
use App\Dto\QuerySettingsDto;
use App\Dto\Builder\DocumentationVersion as DocumentationVersionBuilderDto;
use App\Dto\Service\Admin\Project\DocumentationVersion\StoreUpdate;
+use App\Enums\CacheTag;
use App\Enums\DocumentationVersionStatus;
use App\Models\DocumentationVersion;
use App\Models\User;
@@ -14,6 +15,7 @@
use App\ServiceResults\ServiceResultError;
use App\ServiceResults\ServiceResultSuccess;
use App\ServiceResults\StoreUpdateResult;
+use App\Services\ClearCacheCommandHandler;
use App\Services\DocumentationVersion\DocumentationVersionCommandHandler;
use App\Services\Service;
use Illuminate\Support\Facades\DB;
@@ -24,6 +26,7 @@ public function __construct(
private readonly ProjectRepository $projectRepository,
private readonly DocumentationVersionRepository $documentationVersionRepository,
private readonly DocumentationVersionCommandHandler $documentationVersionCommandHandler,
+ private readonly ClearCacheCommandHandler $clearCacheCommandHandler,
) { }
public function index(int $projectId, DocumentationVersionBuilderDto $documentationVersionBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
@@ -138,6 +141,7 @@ public function store(int $projectId, StoreUpdate $data, User $user): ServiceRes
$dataVersion = $this->getDataVersion($data);
return $this->documentationVersionCommandHandler->handleStore($project, $dataVersion);
});
+ $this->clearCacheCommandHandler->byTag(CacheTag::DocumantationVersion);
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
@@ -174,6 +178,7 @@ public function update(int $projectId, int $versionId, StoreUpdate $data, User $
$dataVersion = $this->getDataVersion($data);
return $this->documentationVersionCommandHandler->handleUpdate($version, $dataVersion);
});
+ $this->clearCacheCommandHandler->byTag(CacheTag::DocumantationVersion);
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
@@ -202,6 +207,7 @@ public function destroy(int $projectId, int $versionId, User $user): ServiceResu
DB::transaction(function () use ($version) {
$this->documentationVersionCommandHandler->handleDestroy($version);
});
+ $this->clearCacheCommandHandler->byTag(CacheTag::DocumantationVersion);
} catch (\Throwable $e) {
report($e);
return $this->errService(__('Server Error'));
diff --git a/app/application/app/Services/Documentation/BuilderCommand.php b/app/application/app/Services/Documentation/BuilderCommand.php
index 24485cc..65c14bf 100644
--- a/app/application/app/Services/Documentation/BuilderCommand.php
+++ b/app/application/app/Services/Documentation/BuilderCommand.php
@@ -10,10 +10,12 @@
{
public function execute(Relation | Builder $query, DocumentationBuilderDto $documentationBuilderDto): Relation | Builder
{
- if ($documentationBuilderDto->isPublic() !== null) {
- $query->where('is_public', $documentationBuilderDto->isPublic());
- }
-
- return $query;
+ return $query
+ ->when($documentationBuilderDto->isPublic(), function (Builder $query) use ($documentationBuilderDto) {
+ $query->where('is_public', $documentationBuilderDto->isPublic());
+ })
+ ->when($documentationBuilderDto->getCategoryId(), function (Builder $query) use ($documentationBuilderDto) {
+ $query->where('category_id', $documentationBuilderDto->getCategoryId()->getCategoryId());
+ });
}
}
diff --git a/app/application/app/Services/DocumentationCategory/BuilderCommand.php b/app/application/app/Services/DocumentationCategory/BuilderCommand.php
index c7e2b8c..2eaf1d6 100644
--- a/app/application/app/Services/DocumentationCategory/BuilderCommand.php
+++ b/app/application/app/Services/DocumentationCategory/BuilderCommand.php
@@ -10,10 +10,12 @@
{
public function execute(Relation | Builder $query, DocumentationCategoryBuilderDto $documentationCategoryBuilderDto): Relation | Builder
{
- if ($documentationCategoryBuilderDto->isPublic() !== null) {
- $query->where('is_public', $documentationCategoryBuilderDto->isPublic());
- }
-
- return $query;
+ return $query
+ ->when($documentationCategoryBuilderDto->isPublic(), function (Builder $query) use ($documentationCategoryBuilderDto) {
+ $query->where('is_public', $documentationCategoryBuilderDto->isPublic());
+ })
+ ->when($documentationCategoryBuilderDto->getParentId(), function (Builder $query) use ($documentationCategoryBuilderDto) {
+ $query->where('parent_id', $documentationCategoryBuilderDto->getParentId()->getCategoryId());
+ });
}
}
diff --git a/app/application/app/Services/Site/DocumentationService.php b/app/application/app/Services/Site/DocumentationService.php
new file mode 100644
index 0000000..1c9bfaf
--- /dev/null
+++ b/app/application/app/Services/Site/DocumentationService.php
@@ -0,0 +1,151 @@
+cannot('viewAny', DocumentationVersion::class)) {
+ $isPublic = 1;
+ }
+ $version = CacheTag::DocumantationVersion->getCache()
+ ->remember(self::class . $project->id . '_' . $isPublic ?? 0, $seconds, function () use ($project, $isPublic) {
+ $versions = $project->documentationVersions()
+ ->when($isPublic, function (Builder $query) {
+ $query->where('is_public', 1);
+ })
+ ->limit(10)
+ ->get();
+ return $versions->firstWhere('status', DocumentationVersionStatus::CurrentVersion)
+ ??
+ $versions->first() ?? false;
+ });
+
+ if ($version === false) {
+ return $this->errNotFound(__('Not Found'));
+ }
+
+ return new DefaultVersion($version);
+ }
+
+ public function index(Documentation $documentation): ServiceResultError | ServiceResultArray
+ {
+ return $this->result(array_merge($documentation->toArray(), [
+ 'categories' => $this->getCategories($documentation, null),
+ 'documentations' => $this->getDocumentations($documentation, null),
+ ]));
+ }
+
+ public function category(string $slug, Documentation $documentation): ServiceResultError | PagePossibleWithoutTranslation
+ {
+ $category = $this->documentationCategoryRepository->getCategoryBySlugWithContent($slug, $documentation->getVersion()->id, $documentation->getWebsiteTranslations()->getLanguage());
+ if (!$category) {
+ return $this->errNotFound(__('Not Found'));
+ }
+ if (
+ $category->is_public === false &&
+ ($documentation->getUser() === null || $documentation->getUser()->cannot('view', $category))
+ ) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ $data = array_merge($documentation->toArray(), [
+ 'category' => $category,
+ 'categories' => $this->getCategories($documentation, $category->id),
+ 'documentations' => $this->getDocumentations($documentation, $category->id),
+ ]);
+ return $this->resultSitePage($documentation->getProject(), $documentation->getWebsiteTranslations(), $data, \is_null($category->content?->title));
+ }
+
+ public function view(string $slug, Documentation $documentation): ServiceResultError | PagePossibleWithoutTranslation
+ {
+ $document = $this->documentationRepository->getDocumentationBySlugWithContent($slug, $documentation->getVersion()->id, $documentation->getWebsiteTranslations()->getLanguage());
+ if (!$document) {
+ return $this->errNotFound(__('Not Found'));
+ }
+ if (
+ $document->is_public === false &&
+ ($documentation->getUser() === null || $documentation->getUser()->cannot('view', $document))
+ ) {
+ return $this->errFobidden(__('Access is denied'));
+ }
+
+ $data = array_merge($documentation->toArray(), [
+ 'documentation' => $document,
+ ]);
+ return $this->resultSitePage($documentation->getProject(), $documentation->getWebsiteTranslations(), $data, \is_null($document->content?->title));
+ }
+
+ private function getCategories(Documentation $documentation, ?int $parentId): Collection
+ {
+ $isPublic = null;
+ if ($documentation->getUser() === null || $documentation->getUser()->cannot('viewAny', DocumentationCategory::class)) {
+ $isPublic = true;
+ }
+ $builderDto = new DocumentationCategoryBuilderDto(
+ isPublic: $isPublic,
+ parentId: new DocumentationCategoryBuilderDto\Category($parentId),
+ );
+ $with = [
+ 'content' => function (HasOne $hasOne) use ($documentation) {
+ $hasOne->where('language_id', $documentation->getWebsiteTranslations()->getLanguage()->id);
+ }
+ ];
+ return $this->documentationCategoryRepository->getCategories(
+ $documentation->getVersion()->id,
+ $builderDto,
+ $with
+ )->all();
+ }
+
+ private function getDocumentations(Documentation $documentation, ?int $categoryId): Collection
+ {
+ $isPublic = null;
+ if ($documentation->getUser() === null || $documentation->getUser()->cannot('viewAny', ModelDocumentation::class)) {
+ $isPublic = true;
+ }
+ $builderDto = new DocumentationBuilderDto(
+ isPublic: $isPublic,
+ categoryId: new DocumentationCategoryBuilderDto\Category($categoryId),
+ );
+ $with = [
+ 'content' => function (HasOne $hasOne) use ($documentation) {
+ $hasOne->where('language_id', $documentation->getWebsiteTranslations()->getLanguage()->id);
+ }
+ ];
+ return $this->documentationRepository->getDocumentations(
+ $documentation->getVersion()->id,
+ $builderDto,
+ $with
+ )->all();
+ }
+}
diff --git a/app/application/app/View/Components/Site/ChooseVersion.php b/app/application/app/View/Components/Site/ChooseVersion.php
new file mode 100644
index 0000000..e2f63fb
--- /dev/null
+++ b/app/application/app/View/Components/Site/ChooseVersion.php
@@ -0,0 +1,48 @@
+user) || $this->user->cannot('viewAny', DocumentationVersion::class)) {
+ $isPublic = 1;
+ }
+
+ $seconds = 3600 * 12;
+ $versions = CacheTag::DocumantationVersion->getCache()
+ ->remember(self::class . $this->project->id . '-' . $isPublic ?? 0, $seconds, function () use ($isPublic) {
+ return $this->project->documentationVersions()
+ ->when($isPublic, function (Builder $query) {
+ $query->where('is_public', 1);
+ })
+ ->get();
+ });
+
+ return view('components.site.choose-version', [
+ 'websiteTranslations' => $this->websiteTranslations,
+ 'versions' => $versions,
+ 'version' => $this->version,
+ 'project' => $this->project,
+ ]);
+ }
+}
diff --git a/app/application/app/View/Components/Site/DocumentationVersion.php b/app/application/app/View/Components/Site/DocumentationVersion.php
new file mode 100644
index 0000000..dfd0dc0
--- /dev/null
+++ b/app/application/app/View/Components/Site/DocumentationVersion.php
@@ -0,0 +1,23 @@
+ $this->websiteTranslations,
+ 'version' => $this->version,
+ ]);
+ }
+}
diff --git a/app/application/lang/en/site.php b/app/application/lang/en/site.php
index edb4844..21b7500 100644
--- a/app/application/lang/en/site.php
+++ b/app/application/lang/en/site.php
@@ -16,4 +16,9 @@
'email' => 'email',
'message' => 'message',
],
+ 'Documentation' => 'Documentation',
+ 'Documentation not created' => 'Documentation not created',
+ 'Choose version' => 'Choose version',
+ 'alert-status-not-supported' => 'WARNING! You are viewing the documentation for an older version.',
+ 'alert-status-future' => 'WARNING! You\'re browsing the documentation for an upcoming version. The documentation and features of this release are subject to change.',
];
diff --git a/app/application/lang/ru/site.php b/app/application/lang/ru/site.php
index c7fa6e4..188f154 100644
--- a/app/application/lang/ru/site.php
+++ b/app/application/lang/ru/site.php
@@ -16,4 +16,9 @@
'email' => 'email',
'message' => 'сообщение',
],
+ 'Documentation' => 'Документация',
+ 'Documentation not created' => 'Документация не создана',
+ 'Choose version' => 'Выберите версию',
+ 'alert-status-not-supported' => 'ВНИМАНИЕ! Вы просматриваете документацию для старой версии.',
+ 'alert-status-future' => 'ВНИМАНИЕ! Вы просматриваете документацию к будущей версии. Документация и функции этого выпуска могут быть изменены.',
];
diff --git a/app/application/resources/site/js/_choose-documentation-version.js b/app/application/resources/site/js/_choose-documentation-version.js
new file mode 100644
index 0000000..ba5ac52
--- /dev/null
+++ b/app/application/resources/site/js/_choose-documentation-version.js
@@ -0,0 +1,8 @@
+let blockDocumentationVersion = document.querySelector('#documentation-version');
+blockDocumentationVersion.querySelector('.documentation-version__button').addEventListener('click', (e) => {
+ if (blockDocumentationVersion.classList.contains('active')) {
+ blockDocumentationVersion.classList.remove('active');
+ } else {
+ blockDocumentationVersion.classList.add('active');
+ }
+});
diff --git a/app/application/resources/site/js/app.js b/app/application/resources/site/js/app.js
index ade6ee6..9d10b37 100644
--- a/app/application/resources/site/js/app.js
+++ b/app/application/resources/site/js/app.js
@@ -1,2 +1,3 @@
import './_menu.js';
import './_choose-language.js';
+import './_choose-documentation-version.js';
diff --git a/app/application/resources/site/scss/app.scss b/app/application/resources/site/scss/app.scss
index 3236656..3438838 100644
--- a/app/application/resources/site/scss/app.scss
+++ b/app/application/resources/site/scss/app.scss
@@ -43,7 +43,8 @@ body {
grid-template-columns: 1fr 1fr;
grid-template-areas:
"logo menu"
- "language language";
+ "language language"
+ "documentation-version documentation-version";
padding: 8px;
box-shadow: 0 3px 9px rgba(0,0,0,0.48);
background: #eee;
@@ -195,6 +196,77 @@ body.mobile-menu-open {
border-radius: 5px 5px 0 0;
}
}
+
+#documentation-version {
+ grid-area: documentation-version;
+ margin-top: 10px;
+
+ .status {
+ font-size: 13px;
+ color: #444;
+ display: block;
+ }
+ .status.status__100 {
+ color: #0a6f4d;
+ }
+ .status.status__50 {
+ color: #0a53be;
+ }
+ .status.status__0 {
+ color: #6c0e22;
+ }
+}
+.documentation-version__block {
+ position: relative;
+}
+.documentation-version__button {
+ border: 0;
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48);
+ background: #ccc;
+ width: 100%;
+ cursor: pointer;
+ font-size: 18px;
+ position: relative;
+ padding: 10px;
+ border-radius: 5px;
+ color: #000;
+}
+.documentation-version__button__str {
+ position: absolute;
+ top: calc(50% - 8px);
+ right: 10px;
+}
+.documentation-version__list {
+ background: #ddd;
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ width: 100%;
+ list-style: none;
+ box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.48);
+ padding: 10px 0;
+ margin: 0;
+ border-radius: 0 0 5px 5px;
+ z-index: 100;
+
+ a {
+ text-decoration: none;
+ color: #000;
+ font-size: 16px;
+ display: block;
+ padding: 10px 20px;
+ }
+}
+#documentation-version.active {
+ .documentation-version__list {
+ display: block;
+ }
+ .documentation-version__button {
+ border-radius: 5px 5px 0 0;
+ }
+}
+
.main-container {
display: flex;
flex-direction: column;
@@ -225,6 +297,15 @@ body.mobile-menu-open {
font-weight: bold;
}
}
+.alert-version {
+ padding: 20px;
+ margin-top: 20px;
+ box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
+ font-size: 16px;
+}
+.alert-version.version-status-not-supported {
+ color: #6c0e22;
+}
@media (min-width: 1000px) {
body {
@@ -232,8 +313,8 @@ body.mobile-menu-open {
margin: 0 auto;
}
.header {
- grid-template-areas: "logo language";
- grid-template-columns: 1fr 200px;
+ grid-template-areas: "logo documentation-version language";
+ grid-template-columns: 1fr 200px 200px;
}
.header_logo {
width: 150px;
@@ -250,6 +331,7 @@ body.mobile-menu-open {
.main-container {
flex-direction: row;
}
+
#language {
margin-right: 10px;
}
@@ -259,6 +341,17 @@ body.mobile-menu-open {
.language__button {
padding: 7px 10px 7px 0;
}
+
+ #documentation-version {
+ margin-right: 10px;
+ }
+ .documentation-version__block {
+ margin-top: 0px;
+ }
+ .documentation-version__button {
+ padding: 7px 10px 7px 0;
+ }
+
#menu {
flex: none;
background: #eee;
diff --git a/app/application/resources/views/components/site/choose-version.blade.php b/app/application/resources/views/components/site/choose-version.blade.php
new file mode 100644
index 0000000..9f6820c
--- /dev/null
+++ b/app/application/resources/views/components/site/choose-version.blade.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/application/resources/views/components/site/documentation-version.blade.php b/app/application/resources/views/components/site/documentation-version.blade.php
new file mode 100644
index 0000000..816cec0
--- /dev/null
+++ b/app/application/resources/views/components/site/documentation-version.blade.php
@@ -0,0 +1,10 @@
+@if($version->status === \App\Enums\DocumentationVersionStatus::NotSupported)
+
+ {{ $websiteTranslations->translate('site.alert-status-not-supported') }}
+
+@endif
+@if($version->status === \App\Enums\DocumentationVersionStatus::FutureVersion)
+
+ {{ $websiteTranslations->translate('site.alert-status-future') }}
+
+@endif
diff --git a/app/application/resources/views/layout/site.blade.php b/app/application/resources/views/layout/site.blade.php
index 570c009..2026b25 100644
--- a/app/application/resources/views/layout/site.blade.php
+++ b/app/application/resources/views/layout/site.blade.php
@@ -30,6 +30,9 @@
+ @if($attributes->has('documentationVersion'))
+
+ @endif
@@ -37,11 +40,15 @@
+ @if($attributes->has('documentationVersion'))
+
+ @endif
@yield('h1', '')
@includeWhen($errors->any(), 'layout.site._errors', ['errors' => $errors->all()])
diff --git a/app/application/resources/views/site/projects/documentation/category.blade.php b/app/application/resources/views/site/projects/documentation/category.blade.php
new file mode 100644
index 0000000..06e4931
--- /dev/null
+++ b/app/application/resources/views/site/projects/documentation/category.blade.php
@@ -0,0 +1,13 @@
+@section('meta_title', $category->content?->title . ' - ' . $project->name . ' ' . $version->title)
+@section('h1', $category->content?->title)
+
+
+ @foreach($categories as $category)
+ @continue(! $category->content?->title)
+ {{ $category->content->title }}
+ @endforeach
+ @foreach($documentations as $documentation)
+ @continue(! $documentation->content?->title)
+ {{ $documentation->content->title }}
+ @endforeach
+
diff --git a/app/application/resources/views/site/projects/documentation/index.blade.php b/app/application/resources/views/site/projects/documentation/index.blade.php
new file mode 100644
index 0000000..9391de0
--- /dev/null
+++ b/app/application/resources/views/site/projects/documentation/index.blade.php
@@ -0,0 +1,13 @@
+@section('meta_title', $websiteTranslations->translate('site.Documentation') . ' - ' . $project->name . ' ' . $version->title)
+@section('h1', $websiteTranslations->translate('site.Documentation'))
+
+
+ @foreach($categories as $category)
+ @continue(! $category->content?->title)
+ {{ $category->content->title }}
+ @endforeach
+ @foreach($documentations as $documentation)
+ @continue(! $documentation->content?->title)
+ {{ $documentation->content->title }}
+ @endforeach
+
diff --git a/app/application/resources/views/site/projects/documentation/no-default-version.blade.php b/app/application/resources/views/site/projects/documentation/no-default-version.blade.php
new file mode 100644
index 0000000..464dbae
--- /dev/null
+++ b/app/application/resources/views/site/projects/documentation/no-default-version.blade.php
@@ -0,0 +1,6 @@
+@section('meta_title', $websiteTranslations->translate('site.Documentation not created'))
+@section('h1', $websiteTranslations->translate('site.Documentation not created'))
+
+
+
+
diff --git a/app/application/resources/views/site/projects/documentation/view.blade.php b/app/application/resources/views/site/projects/documentation/view.blade.php
new file mode 100644
index 0000000..c831e15
--- /dev/null
+++ b/app/application/resources/views/site/projects/documentation/view.blade.php
@@ -0,0 +1,9 @@
+@section('meta_title', $documentation->content?->title . ' - ' . $project->name . ' ' . $version->title)
+@section('h1', $documentation->content?->title)
+
+
+ {!! $documentation->content->content !!}
+ @push('scripts')
+ @include('_prism')
+ @endpush
+
diff --git a/app/application/routes/web-project.php b/app/application/routes/web-project.php
index 330eff2..8dfef3e 100644
--- a/app/application/routes/web-project.php
+++ b/app/application/routes/web-project.php
@@ -6,6 +6,22 @@
Route::middleware([\App\Http\Middleware\IsProject::class, \App\Http\Middleware\IsWebsiteTranslations::class])->group(function () {
Route::get('/language/{language}', [\App\Http\Controllers\Site\ProjectsController::class, 'index'])->name('home-language');
+ Route::prefix('docs')->group(function () {
+ Route::get('/', [\App\Http\Controllers\Site\DocumentationController::class, 'defaultVersion'])->name('documentation');
+ Route::get('language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'defaultVersion'])->name('documentation-language');
+
+ Route::middleware([\App\Http\Middleware\DocumentationVersion::class])->prefix('{version}')->group(function () {
+ Route::get('/', [\App\Http\Controllers\Site\DocumentationController::class, 'index'])->name('documentation.version');
+ Route::get('language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'index'])->name('documentation.version-language');
+
+ Route::get('category/{slug}', [\App\Http\Controllers\Site\DocumentationController::class, 'category'])->name('documentation.category');
+ Route::get('category/{slug}/language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'category'])->name('documentation.category-language');
+
+ Route::get('{slug}', [\App\Http\Controllers\Site\DocumentationController::class, 'view'])->name('documentation.view');
+ Route::get('{slug}/language/{language}', [\App\Http\Controllers\Site\DocumentationController::class, 'view'])->name('documentation.view-language');
+ });
+ });
+
Route::prefix('feedback')->group(function () {
Route::get('/', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback');
Route::get('language/{language}', [\App\Http\Controllers\Site\FeedbackController::class, 'index'])->name('feedback-language');