Compare commits
2 Commits
156d8a9f68
...
21026cdf52
Author | SHA1 | Date | |
---|---|---|---|
21026cdf52 | |||
45504791c0 |
15
app/application/app/Dto/Builder/Documentation.php
Normal file
15
app/application/app/Dto/Builder/Documentation.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Builder;
|
||||||
|
|
||||||
|
final readonly class Documentation
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ?bool $isPublic = null,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function isPublic(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
}
|
15
app/application/app/Dto/Builder/DocumentationCategory.php
Normal file
15
app/application/app/Dto/Builder/DocumentationCategory.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Builder;
|
||||||
|
|
||||||
|
final readonly class DocumentationCategory
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ?bool $isPublic = null,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function isPublic(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
}
|
15
app/application/app/Dto/Builder/DocumentationVersion.php
Normal file
15
app/application/app/Dto/Builder/DocumentationVersion.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Builder;
|
||||||
|
|
||||||
|
final readonly class DocumentationVersion
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ?bool $isPublic = null,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function isPublic(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\Documentation;
|
||||||
|
|
||||||
|
use App\Dto\Builder\Documentation;
|
||||||
|
use App\Dto\Service\Pages;
|
||||||
|
|
||||||
|
final readonly class Index extends Pages
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Documentation $documentation,
|
||||||
|
int $page
|
||||||
|
) {
|
||||||
|
parent::__construct($page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDocumentation(): Documentation
|
||||||
|
{
|
||||||
|
return $this->documentation;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\Documentation;
|
||||||
|
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
final readonly class StoreUpdate extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $slug,
|
||||||
|
private bool $isPublic,
|
||||||
|
private int $sort,
|
||||||
|
private Contents $contents,
|
||||||
|
private ?int $categoryId,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getSlug(): string
|
||||||
|
{
|
||||||
|
return $this->slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPublic(): bool
|
||||||
|
{
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCategoryId(): ?int
|
||||||
|
{
|
||||||
|
return $this->categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSort(): int
|
||||||
|
{
|
||||||
|
return $this->sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContents(): Contents
|
||||||
|
{
|
||||||
|
return $this->contents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationCategory;
|
||||||
|
|
||||||
|
use App\Dto\Builder\DocumentationCategory;
|
||||||
|
use App\Dto\Service\Pages;
|
||||||
|
|
||||||
|
final readonly class Index extends Pages
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private DocumentationCategory $documentationCategory,
|
||||||
|
int $page
|
||||||
|
) {
|
||||||
|
parent::__construct($page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDocumentationCategory(): DocumentationCategory
|
||||||
|
{
|
||||||
|
return $this->documentationCategory;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationCategory;
|
||||||
|
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategoryContent\Contents;
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
|
||||||
|
final readonly class StoreUpdate extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $slug,
|
||||||
|
private bool $isPublic,
|
||||||
|
private int $sort,
|
||||||
|
private Contents $contents,
|
||||||
|
private ?int $parentId,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getSlug(): string
|
||||||
|
{
|
||||||
|
return $this->slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPublic(): bool
|
||||||
|
{
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSort(): int
|
||||||
|
{
|
||||||
|
return $this->sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParentId(): ?int
|
||||||
|
{
|
||||||
|
return $this->parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContents(): Contents
|
||||||
|
{
|
||||||
|
return $this->contents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationCategoryContent;
|
||||||
|
|
||||||
|
final readonly class Content
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private int $languageId,
|
||||||
|
private string $title,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getLanguageId(): int
|
||||||
|
{
|
||||||
|
return $this->languageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationCategoryContent;
|
||||||
|
|
||||||
|
final class Contents
|
||||||
|
{
|
||||||
|
private array $contents = [];
|
||||||
|
|
||||||
|
public function addContent(Content $content): void
|
||||||
|
{
|
||||||
|
$this->contents[$content->getLanguageId()] = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(int $languageId): ?Content
|
||||||
|
{
|
||||||
|
return $this->contents[$languageId] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContents(): array
|
||||||
|
{
|
||||||
|
return $this->contents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationContent;
|
||||||
|
|
||||||
|
final readonly class Content
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private int $languageId,
|
||||||
|
private string $title,
|
||||||
|
private string $content,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getLanguageId(): int
|
||||||
|
{
|
||||||
|
return $this->languageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationContent;
|
||||||
|
|
||||||
|
final class Contents
|
||||||
|
{
|
||||||
|
private array $contents = [];
|
||||||
|
|
||||||
|
public function addContent(Content $content): void
|
||||||
|
{
|
||||||
|
$this->contents[$content->getLanguageId()] = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(int $languageId): ?Content
|
||||||
|
{
|
||||||
|
return $this->contents[$languageId] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContents(): array
|
||||||
|
{
|
||||||
|
return $this->contents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationVersion;
|
||||||
|
|
||||||
|
use App\Dto\Builder\DocumentationVersion;
|
||||||
|
use App\Dto\Service\Pages;
|
||||||
|
|
||||||
|
final readonly class Index extends Pages
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private DocumentationVersion $documentationVersionBuildDto,
|
||||||
|
int $page
|
||||||
|
) {
|
||||||
|
parent::__construct($page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDocumentationVersionBuildDto(): DocumentationVersion
|
||||||
|
{
|
||||||
|
return $this->documentationVersionBuildDto;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Service\Admin\Project\DocumentationVersion;
|
||||||
|
|
||||||
|
use App\Dto\Service\Dto;
|
||||||
|
use App\Enums\DocumentationVersionStatus;
|
||||||
|
|
||||||
|
final readonly class StoreUpdate extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $title,
|
||||||
|
private string $slug,
|
||||||
|
private bool $isPublic,
|
||||||
|
private DocumentationVersionStatus $status,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSlug(): string
|
||||||
|
{
|
||||||
|
return $this->slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPublic(): bool
|
||||||
|
{
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatus(): DocumentationVersionStatus
|
||||||
|
{
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
}
|
36
app/application/app/Enums/DocumentationVersionStatus.php
Normal file
36
app/application/app/Enums/DocumentationVersionStatus.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
enum DocumentationVersionStatus: int
|
||||||
|
{
|
||||||
|
case NotSupported = 0;
|
||||||
|
case Supported = 50;
|
||||||
|
case CurrentVersion = 100;
|
||||||
|
case FutureVersion = 150;
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return __('version-status.' . $this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toArray(): array
|
||||||
|
{
|
||||||
|
$items = [];
|
||||||
|
foreach (self::cases() as $item) {
|
||||||
|
$items[] = [
|
||||||
|
'name' => $item->name,
|
||||||
|
'value' => $item->value,
|
||||||
|
'title' => $item->getTitle(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toCollection(): Collection
|
||||||
|
{
|
||||||
|
return collect(self::toArray());
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,8 @@ enum Permission: string
|
|||||||
case ProjectLink = 'project-link';
|
case ProjectLink = 'project-link';
|
||||||
case ProjectTranslation = 'project-translation';
|
case ProjectTranslation = 'project-translation';
|
||||||
case ProjectFeedback = 'project-feedback';
|
case ProjectFeedback = 'project-feedback';
|
||||||
|
case Documentation = 'documentation';
|
||||||
|
case DocumentationCategory = 'documentation-category';
|
||||||
|
|
||||||
public function getPermissions(): array
|
public function getPermissions(): array
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions\Services\DocumentationCategory;
|
||||||
|
|
||||||
|
final class ParentException extends \Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions\Services\DocumentationCategoryContent;
|
||||||
|
|
||||||
|
final class ContentSaveException extends \Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions\Services\DocumentationContent;
|
||||||
|
|
||||||
|
final class ContentSaveException extends \Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
19
app/application/app/Exceptions/Services/ServiceException.php
Normal file
19
app/application/app/Exceptions/Services/ServiceException.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions\Services;
|
||||||
|
|
||||||
|
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||||
|
|
||||||
|
final class ServiceException extends \Exception
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ServiceResultErrorContract $serviceResultError
|
||||||
|
) {
|
||||||
|
parent::__construct($this->serviceResultError->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getServiceResultError(): ServiceResultErrorContract
|
||||||
|
{
|
||||||
|
return $this->serviceResultError;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin\Projects;
|
||||||
|
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Http\Controllers\Admin\Controller;
|
||||||
|
use App\Http\Requests\Admin\Projects\DocumentationCategories\IndexRequest;
|
||||||
|
use App\Http\Requests\Admin\Projects\DocumentationCategories\StoreUpdateRequest;
|
||||||
|
use App\Services\Admin\Project\DocumentationCategoryService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class DocumentationCategoriesController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DocumentationCategoryService $documentationCategoryService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(int $projectId, int $versionId, IndexRequest $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$data = $request->getDto();
|
||||||
|
$querySettingsDto = new QuerySettingsDto(
|
||||||
|
limit: 20,
|
||||||
|
page: $data->getPage(),
|
||||||
|
queryWith: []
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->documentationCategoryService->index($projectId, $versionId, $data->getDocumentationCategory(), $querySettingsDto, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentation-categories/index', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(int $projectId, int $versionId, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationCategoryService->create($projectId, $versionId, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentation-categories/create', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $projectId, int $versionId, int $id, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationCategoryService->edit($projectId, $versionId, $id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentation-categories/edit', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(int $projectId, int $versionId, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationCategoryService->store($projectId, $versionId, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.categories.edit', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'version' => $versionId,
|
||||||
|
'category' => $result->getModel()->id,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $projectId, int $versionId, int $id, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationCategoryService->update($projectId, $versionId, $id, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.categories.edit', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'version' => $versionId,
|
||||||
|
'category' => $result->getModel()->id,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $projectId, int $versionId, int $id, Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationCategoryService->destroy($projectId, $versionId, $id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.categories.index', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'version' => $versionId,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin\Projects;
|
||||||
|
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Http\Controllers\Admin\Controller;
|
||||||
|
use App\Http\Requests\Admin\Projects\DocumentVersions\IndexRequest;
|
||||||
|
use App\Http\Requests\Admin\Projects\DocumentVersions\StoreUpdateRequest;
|
||||||
|
use App\Services\Admin\Project\DocumentationVersionService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class DocumentationVersionController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DocumentationVersionService $documentationVersionService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(int $projectId, IndexRequest $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$data = $request->getDto();
|
||||||
|
$querySettingsDto = new QuerySettingsDto(
|
||||||
|
limit: 20,
|
||||||
|
page: $data->getPage(),
|
||||||
|
queryWith: []
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->documentationVersionService->index($projectId, $data->getDocumentationVersionBuildDto(), $querySettingsDto, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentation-versions/index', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(int $projectId, int $id, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationVersionService->show($projectId, $id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentation-versions/show', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(int $projectId, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationVersionService->create($projectId, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentation-versions/create', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $projectId, int $id, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationVersionService->edit($projectId, $id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentation-versions/edit', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(int $projectId, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationVersionService->store($projectId, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.edit', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'documentation_version' => $result->getModel()->id,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $projectId, int $id, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationVersionService->update($projectId, $id, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.edit', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'documentation_version' => $result->getModel()->id,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $projectId, int $id, Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationVersionService->destroy($projectId, $id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.index', ['project' => $projectId])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin\Projects;
|
||||||
|
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Http\Controllers\Admin\Controller;
|
||||||
|
use App\Http\Requests\Admin\Projects\Documentations\IndexRequest;
|
||||||
|
use App\Http\Requests\Admin\Projects\Documentations\StoreUpdateRequest;
|
||||||
|
use App\Services\Admin\Project\DocumentationService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
final class DocumentationsController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DocumentationService $documentationService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(int $projectId, int $versionId, IndexRequest $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$data = $request->getDto();
|
||||||
|
$querySettingsDto = new QuerySettingsDto(
|
||||||
|
limit: 20,
|
||||||
|
page: $data->getPage(),
|
||||||
|
queryWith: []
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->documentationService->index($projectId, $versionId, $data->getDocumentation(), $querySettingsDto, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentations/index', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(int $projectId, int $versionId, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationService->create($projectId, $versionId, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentations/create', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $projectId, int $versionId, int $id, Request $request): View
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationService->edit($projectId, $versionId, $id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
$this->errors($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/projects/documentations/edit', $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(int $projectId, int $versionId, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationService->store($projectId, $versionId, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.documentations.edit', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'version' => $versionId,
|
||||||
|
'documentation' => $result->getModel()->id,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $projectId, int $versionId, int $id, StoreUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->getDto();
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationService->update($projectId, $versionId, $id, $data, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.documentations.edit', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'version' => $versionId,
|
||||||
|
'documentation' => $result->getModel()->id,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $projectId, int $versionId, int $id, Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$result = $this->documentationService->destroy($projectId, $versionId, $id, $user);
|
||||||
|
if ($result->isError()) {
|
||||||
|
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.projects.documentation-versions.documentations.index', [
|
||||||
|
'project' => $projectId,
|
||||||
|
'version' => $versionId,
|
||||||
|
])->withSuccess($result->getMessage());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Projects\DocumentVersions;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Builder\DocumentationVersion;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationVersion\Index;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'page' => ['nullable', 'numeric', 'min:1']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): Index
|
||||||
|
{
|
||||||
|
return new Index(
|
||||||
|
documentationVersionBuildDto: new DocumentationVersion(),
|
||||||
|
page: (int) $this->input('page', 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Projects\DocumentVersions;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationVersion\StoreUpdate;
|
||||||
|
use App\Enums\DocumentationVersionStatus;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rules\Enum;
|
||||||
|
|
||||||
|
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => ['required', 'string', 'max:255'],
|
||||||
|
'slug' => ['required', 'string', 'max:70', 'regex:/^[a-z0-9._-]+$/'],
|
||||||
|
'is_public' => ['required', 'boolean'],
|
||||||
|
'status' => ['required', new Enum(DocumentationVersionStatus::class)],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): StoreUpdate
|
||||||
|
{
|
||||||
|
return new StoreUpdate(
|
||||||
|
title: $this->input('title'),
|
||||||
|
slug: $this->input('slug'),
|
||||||
|
isPublic: (bool) $this->input('is_public', false),
|
||||||
|
status: DocumentationVersionStatus::from((int) $this->input('status')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Projects\DocumentationCategories;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Builder\DocumentationCategory;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategory\Index;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'page' => ['nullable', 'numeric', 'min:1']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): Index
|
||||||
|
{
|
||||||
|
return new Index(
|
||||||
|
documentationCategory: new DocumentationCategory(),
|
||||||
|
page: (int) $this->input('page', 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Projects\DocumentationCategories;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategory\StoreUpdate;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategoryContent\Content;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategoryContent\Contents;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'slug' => ['required', 'string', 'max:200', 'regex:/^[a-z0-9._-]+$/'],
|
||||||
|
'is_public' => ['required', 'boolean'],
|
||||||
|
'sort' => ['required', 'integer', 'min:-1000', 'max:1000'],
|
||||||
|
'parent_id' => ['nullable', 'integer', 'exists:documentation_categories,id'],
|
||||||
|
'content.*.title' => ['required', 'string', 'max:255'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): StoreUpdate
|
||||||
|
{
|
||||||
|
$parentId = $this->input('parent_id', null);
|
||||||
|
if (!\is_null($parentId)) {
|
||||||
|
$parentId = (int) $parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StoreUpdate(
|
||||||
|
slug: $this->input('slug'),
|
||||||
|
isPublic: (bool) $this->input('is_public', false),
|
||||||
|
sort: (int) $this->input('sort'),
|
||||||
|
contents: $this->getContents(),
|
||||||
|
parentId: $parentId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getContents(): Contents
|
||||||
|
{
|
||||||
|
$contents = new Contents();
|
||||||
|
foreach ($this->input('content', []) as $languageId => $content) {
|
||||||
|
$contents->addContent(new Content(
|
||||||
|
languageId: (int) $languageId,
|
||||||
|
title: $content['title'],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Projects\Documentations;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Builder\Documentation;
|
||||||
|
use App\Dto\Service\Admin\Project\Documentation\Index;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'page' => ['nullable', 'numeric', 'min:1']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): Index
|
||||||
|
{
|
||||||
|
return new Index(
|
||||||
|
documentation: new Documentation(),
|
||||||
|
page: (int) $this->input('page', 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Projects\Documentations;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationContent\Content;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'slug' => ['required', 'string', 'max:200', 'regex:/^[a-z0-9._-]+$/'],
|
||||||
|
'is_public' => ['required', 'boolean'],
|
||||||
|
'sort' => ['required', 'integer', 'min:-1000', 'max:1000'],
|
||||||
|
'category_id' => ['nullable', 'integer', 'exists:documentation_categories,id'],
|
||||||
|
'content.*.title' => ['required', 'string', 'max:255'],
|
||||||
|
'content.*.content' => ['nullable', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): StoreUpdate
|
||||||
|
{
|
||||||
|
$categoryId = $this->input('category_id', null);
|
||||||
|
if (!\is_null($categoryId)) {
|
||||||
|
$categoryId = (int) $categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StoreUpdate(
|
||||||
|
slug: $this->input('slug'),
|
||||||
|
isPublic: (bool) $this->input('is_public', false),
|
||||||
|
sort: (int) $this->input('sort'),
|
||||||
|
contents: $this->getContents(),
|
||||||
|
categoryId: $categoryId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getContents(): Contents
|
||||||
|
{
|
||||||
|
$contents = new Contents();
|
||||||
|
foreach ($this->input('content', []) as $languageId => $content) {
|
||||||
|
$contents->addContent(new Content(
|
||||||
|
languageId: (int) $languageId,
|
||||||
|
title: $content['title'],
|
||||||
|
content: $content['content'] ?? '',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
}
|
65
app/application/app/Models/Documentation.php
Normal file
65
app/application/app/Models/Documentation.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
final class Documentation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'documentation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The model's default values for attributes.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'sort' => 100,
|
||||||
|
'is_public' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'is_public',
|
||||||
|
'category_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'is_public' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function category(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(DocumentationCategory::class, 'category_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contents(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(DocumentationContent::class, 'documentation_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function content(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(DocumentationContent::class, 'documentation_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
75
app/application/app/Models/DocumentationCategory.php
Normal file
75
app/application/app/Models/DocumentationCategory.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
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;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
|
||||||
|
|
||||||
|
#[ScopedBy([SortScope::class])]
|
||||||
|
final class DocumentationCategory extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes, HasRecursiveRelationships;
|
||||||
|
|
||||||
|
protected $table = 'documentation_categories';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The model's default values for attributes.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'sort' => 100,
|
||||||
|
'is_public' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'slug',
|
||||||
|
'sort',
|
||||||
|
'parent_id',
|
||||||
|
'is_public',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'is_public' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parent(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(DocumentationCategory::class, 'parent_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contents(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(DocumentationCategoryContent::class, 'category_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function content(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(DocumentationCategoryContent::class, 'category_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function version(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(DocumentationVersion::class, 'version_id');
|
||||||
|
}
|
||||||
|
}
|
24
app/application/app/Models/DocumentationCategoryContent.php
Normal file
24
app/application/app/Models/DocumentationCategoryContent.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
final class DocumentationCategoryContent extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'documentation_category_content';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'language_id',
|
||||||
|
];
|
||||||
|
}
|
25
app/application/app/Models/DocumentationContent.php
Normal file
25
app/application/app/Models/DocumentationContent.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
final class DocumentationContent extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'documentation_content';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'content',
|
||||||
|
'language_id',
|
||||||
|
];
|
||||||
|
}
|
67
app/application/app/Models/DocumentationVersion.php
Normal file
67
app/application/app/Models/DocumentationVersion.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\DocumentationVersionStatus;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
final class DocumentationVersion extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'documentation_versions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The model's default values for attributes.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'is_public' => true,
|
||||||
|
'status' => DocumentationVersionStatus::CurrentVersion,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'slug',
|
||||||
|
'is_public',
|
||||||
|
'status',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'is_public' => 'boolean',
|
||||||
|
'status' => DocumentationVersionStatus::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function project(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documentations(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Documentation::class, 'version_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function categories(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(DocumentationCategory::class, 'version_id');
|
||||||
|
}
|
||||||
|
}
|
@ -62,4 +62,9 @@ public function feedbacks(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(ProjectFeedback::class);
|
return $this->hasMany(ProjectFeedback::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function documentationVersions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(DocumentationVersion::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
34
app/application/app/Policies/DocumentationCategoryPolicy.php
Normal file
34
app/application/app/Policies/DocumentationCategoryPolicy.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\DocumentationCategory;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
final readonly class DocumentationCategoryPolicy extends Policy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation-category.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, DocumentationCategory $category): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation-category.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation-category.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, DocumentationCategory $category): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation.update');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, DocumentationCategory $category): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation-category.delete');
|
||||||
|
}
|
||||||
|
}
|
34
app/application/app/Policies/DocumentationPolicy.php
Normal file
34
app/application/app/Policies/DocumentationPolicy.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Documentation;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
final readonly class DocumentationPolicy extends Policy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Documentation $documentation): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Documentation $documentation): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation.update');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Documentation $documentation): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('documentation.delete');
|
||||||
|
}
|
||||||
|
}
|
39
app/application/app/Policies/DocumentationVersionPolicy.php
Normal file
39
app/application/app/Policies/DocumentationVersionPolicy.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\DocumentationVersion;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
final readonly class DocumentationVersionPolicy extends Policy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
// Not a mistake or typo. Shared rights with Documentation.
|
||||||
|
return $user->hasPermission('documentation.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, DocumentationVersion $documentationVersion): bool
|
||||||
|
{
|
||||||
|
// Not a mistake or typo. Shared rights with Documentation.
|
||||||
|
return $user->hasPermission('documentation.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
// Not a mistake or typo. Shared rights with Documentation.
|
||||||
|
return $user->hasPermission('documentation.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, DocumentationVersion $documentationVersion): bool
|
||||||
|
{
|
||||||
|
// Not a mistake or typo. Shared rights with Documentation.
|
||||||
|
return $user->hasPermission('documentation.update');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, DocumentationVersion $documentationVersion): bool
|
||||||
|
{
|
||||||
|
// Not a mistake or typo. Shared rights with Documentation.
|
||||||
|
return $user->hasPermission('documentation.delete');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use App\Models\DocumentationCategory;
|
||||||
|
use App\Models\ProjectLanguage;
|
||||||
|
use App\Services\DocumentationCategory\BuilderCommand;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use App\Dto\Builder\DocumentationCategory as DocumentationCategoryBuilderDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class DocumentationCategoryRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||||
|
private BuilderCommand $builderCommand
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getCategories(int $versionId, DocumentationCategoryBuilderDto $documentationCategoryBuilderDto, array $with = []): Search
|
||||||
|
{
|
||||||
|
$query = $this->builderCommand->execute(
|
||||||
|
query: DocumentationCategory::query()->where('version_id', $versionId)->with($with),
|
||||||
|
documentationCategoryBuilderDto: $documentationCategoryBuilderDto
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCategoryById(int $id): ?DocumentationCategory
|
||||||
|
{
|
||||||
|
return DocumentationCategory::query()->where('id', $id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCategoryByCode(string $code): ?DocumentationCategory
|
||||||
|
{
|
||||||
|
return DocumentationCategory::query()->where('code', $code)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool
|
||||||
|
{
|
||||||
|
return DocumentationCategory::query()
|
||||||
|
->where('version_id', $versionId)
|
||||||
|
->where('slug', Str::lower($slug))
|
||||||
|
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||||
|
$query->where('id', '!=', $exceptId);
|
||||||
|
})
|
||||||
|
->withTrashed()
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getForSelect(?ProjectLanguage $defaultLanguage, ?DocumentationCategory $exceptCategory = null, array $withExcepts = []): array
|
||||||
|
{
|
||||||
|
$with = [
|
||||||
|
'content' => function (HasOne $hasOne) use ($defaultLanguage) {
|
||||||
|
$hasOne->when($defaultLanguage, function (Builder $query, ProjectLanguage $defaultLanguage) {
|
||||||
|
$query->where('language_id', $defaultLanguage->id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
$categories = DocumentationCategory::query()
|
||||||
|
->with($with)
|
||||||
|
->when($exceptCategory, function (Builder $query, DocumentationCategory $exceptCategory) {
|
||||||
|
$query->whereNotIn(
|
||||||
|
'id',
|
||||||
|
$exceptCategory->descendantsAndSelf()->pluck('id')->toArray()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
->when($withExcepts, function (Builder $query) use ($withExcepts) {
|
||||||
|
$query->withTrashed()->whereNull('deleted_at')->orWhereIn('id', $withExcepts);
|
||||||
|
})->get();
|
||||||
|
|
||||||
|
return $categories->map(function (DocumentationCategory $documentationCategory) use ($defaultLanguage) {
|
||||||
|
return [
|
||||||
|
'id' => $documentationCategory->id,
|
||||||
|
'title' => $documentationCategory->content?->title ?? $documentationCategory->slug,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->pluck('title', 'id')
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
51
app/application/app/Repositories/DocumentationRepository.php
Normal file
51
app/application/app/Repositories/DocumentationRepository.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Dto\Builder\Documentation as DocumentationBuilderDto;
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use App\Models\Documentation;
|
||||||
|
use App\Services\Documentation\BuilderCommand;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class DocumentationRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||||
|
private BuilderCommand $builderCommand
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getDocumentations(int $versionId, DocumentationBuilderDto $documentationBuilderDto, array $with = []): Search
|
||||||
|
{
|
||||||
|
$query = $this->builderCommand->execute(
|
||||||
|
query: Documentation::query()->where('version_id', $versionId)->with($with),
|
||||||
|
documentationBuilderDto: $documentationBuilderDto
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDocumentationById(int $id): ?Documentation
|
||||||
|
{
|
||||||
|
return Documentation::query()->where('id', $id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDocumentationByCode(string $code): ?Documentation
|
||||||
|
{
|
||||||
|
return Documentation::query()->where('code', $code)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExistsSlug(int $versionId, string $slug, ?int $exceptId = null): bool
|
||||||
|
{
|
||||||
|
return Documentation::query()
|
||||||
|
->where('version_id', $versionId)
|
||||||
|
->where('slug', Str::lower($slug))
|
||||||
|
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||||
|
$query->where('id', '!=', $exceptId);
|
||||||
|
})
|
||||||
|
->withTrashed()
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use App\Models\DocumentationVersion;
|
||||||
|
use App\Services\DocumentationVersion\BuilderCommand;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use App\Dto\Builder\DocumentationVersion as DocumentationVersionBuilderDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class DocumentationVersionRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||||
|
private BuilderCommand $builderCommand
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getVersions(int $projectId, DocumentationVersionBuilderDto $documentationVersionBuilderDto, array $with = []): Search
|
||||||
|
{
|
||||||
|
$query = $this->builderCommand->execute(
|
||||||
|
query: DocumentationVersion::query()->where('project_id', $projectId)->with($with),
|
||||||
|
documentationVersionBuilderDto: $documentationVersionBuilderDto
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersionById(int $id): ?DocumentationVersion
|
||||||
|
{
|
||||||
|
return DocumentationVersion::query()->where('id', $id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersionByCode(string $code): ?DocumentationVersion
|
||||||
|
{
|
||||||
|
return DocumentationVersion::query()->where('code', $code)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExistsSlug(int $projectId, string $slug, ?int $exceptId = null): bool
|
||||||
|
{
|
||||||
|
return DocumentationVersion::query()
|
||||||
|
->where('project_id', $projectId)
|
||||||
|
->where('slug', Str::lower($slug))
|
||||||
|
->when($exceptId, function (Builder $query, int $exceptId) {
|
||||||
|
$query->where('id', '!=', $exceptId);
|
||||||
|
})
|
||||||
|
->withTrashed()
|
||||||
|
->exists();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,257 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Admin\Project;
|
||||||
|
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Dto\Builder\DocumentationCategory as DocumentationCategoryBuilderDto;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategory\StoreUpdate;
|
||||||
|
use App\Exceptions\Services\DocumentationCategory\ParentException;
|
||||||
|
use App\Exceptions\Services\ServiceException;
|
||||||
|
use App\Models\DocumentationCategory;
|
||||||
|
use App\Models\DocumentationCategoryContent;
|
||||||
|
use App\Models\ProjectLanguage;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Repositories\DocumentationCategoryRepository;
|
||||||
|
use App\Repositories\DocumentationVersionRepository;
|
||||||
|
use App\ServiceResults\ServiceResultArray;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use App\Services\DocumentationCategory\DocumentationCategoryCommandHandler;
|
||||||
|
use App\Services\DocumentationCategoryContent\ModelSyncCommand;
|
||||||
|
use App\Services\Service;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
final class DocumentationCategoryService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DocumentationVersionRepository $documentationVersionRepository,
|
||||||
|
private readonly DocumentationCategoryRepository $documentationCategoryRepository,
|
||||||
|
private readonly DocumentationCategoryCommandHandler $categoryCommandHandler,
|
||||||
|
private readonly ModelSyncCommand $contentSaveCommand,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(int $projectId, int $versionId, DocumentationCategoryBuilderDto $documentationCategoryBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('viewAny', DocumentationCategory::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultLanguage = $project->languages()->where('is_default', 1)->first();
|
||||||
|
$with = [
|
||||||
|
'content' => function (HasOne $hasOne) use ($defaultLanguage) {
|
||||||
|
/** @var ?ProjectLanguage $defaultLanguage */
|
||||||
|
$hasOne->when($defaultLanguage, function (Builder $query, ProjectLanguage $defaultLanguage) {
|
||||||
|
$query->where('language_id', $defaultLanguage->id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
||||||
|
$with = array_merge($with, $querySettingsDto->getQueryWith());
|
||||||
|
|
||||||
|
$categories = $this->documentationCategoryRepository->getCategories(
|
||||||
|
$version->id,
|
||||||
|
$documentationCategoryBuilderDto,
|
||||||
|
$with
|
||||||
|
)->pagination(
|
||||||
|
$querySettingsDto->getLimit(),
|
||||||
|
$querySettingsDto->getPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'project' => $project,
|
||||||
|
'categories' => $categories,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('create', DocumentationCategory::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultLanguage = $project->languages->where('is_default', 1)->first();
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'project' => $project,
|
||||||
|
'category' => new DocumentationCategory(),
|
||||||
|
'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $projectId, int $versionId, int $categoryId, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$category = $this->documentationCategoryRepository->getCategoryById($categoryId);
|
||||||
|
if (\is_null($category)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $category)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$withCategories = [];
|
||||||
|
if ($category->parent_id) {
|
||||||
|
$withCategories[] = $category->parent_id;
|
||||||
|
}
|
||||||
|
$defaultLanguage = $project->languages->where('is_default', 1)->first();
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'project' => $project,
|
||||||
|
'category' => $category,
|
||||||
|
'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage, $category, $withCategories),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(int $projectId, int $versionId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', DocumentationCategory::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$category = DB::transaction(function () use ($data, $version, $user, $versionId, $project) {
|
||||||
|
if ($this->documentationCategoryRepository->isExistsSlug($versionId, $data->getSlug()) !== false) {
|
||||||
|
$error = $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.slug')]),
|
||||||
|
['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])]
|
||||||
|
);
|
||||||
|
throw new ServiceException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dataCategory = $this->getDataCategory($data);
|
||||||
|
$category = $this->categoryCommandHandler->handleStore($version, $dataCategory);
|
||||||
|
$this->contentSaveCommand->execute($project, $category, $data->getContents());
|
||||||
|
|
||||||
|
return $category;
|
||||||
|
});
|
||||||
|
} catch (ServiceException $e) {
|
||||||
|
return $e->getServiceResultError();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($category, __('Category successfully created'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $projectId, int $versionId, int $categoryId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$category = $this->documentationCategoryRepository->getCategoryById($categoryId);
|
||||||
|
if (\is_null($category)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $category)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($data->getParentId() === $category->id) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.parent', ['attribute' => __('validation.attributes.parent_id')]),
|
||||||
|
['parent_id' => __('validation.parent', ['attribute' => __('validation.attributes.parent_id')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$category = DB::transaction(function () use ($data, $category, $versionId, $project) {
|
||||||
|
if ($this->documentationCategoryRepository->isExistsSlug($versionId, $data->getSlug(), $category->id) !== false) {
|
||||||
|
$error = $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.slug')]),
|
||||||
|
['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])]
|
||||||
|
);
|
||||||
|
throw new ServiceException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dataCategory = $this->getDataCategory($data);
|
||||||
|
$category = $this->categoryCommandHandler->handleUpdate($category, $dataCategory);
|
||||||
|
$this->contentSaveCommand->execute($project, $category, $data->getContents());
|
||||||
|
|
||||||
|
return $category;
|
||||||
|
});
|
||||||
|
} catch (ServiceException $e) {
|
||||||
|
return $e->getServiceResultError();
|
||||||
|
} catch (ParentException $e) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.parent_cycle_detected', ['attribute' => __('validation.attributes.parent_id')]),
|
||||||
|
['parent_id' => __('validation.parent_cycle_detected', ['attribute' => __('validation.attributes.parent_id')])]
|
||||||
|
);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($category, __('Category updated successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $projectId, int $versionId, int $categoryId, User $user): ServiceResultError|ServiceResultSuccess
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$category = $this->documentationCategoryRepository->getCategoryById($categoryId);
|
||||||
|
if (\is_null($category)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('delete', $category)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($category) {
|
||||||
|
$this->categoryCommandHandler->handleDestroy($category);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok(__('Category successfully deleted'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDataCategory(StoreUpdate $data): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'slug' => $data->getSlug(),
|
||||||
|
'is_public' => $data->isPublic(),
|
||||||
|
'sort' => $data->getSort(),
|
||||||
|
'parent_id' => $data->getParentId(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,239 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Admin\Project;
|
||||||
|
|
||||||
|
use App\Dto\Builder\Documentation as DocumentationBuilderDto;
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Dto\Service\Admin\Project\Documentation\StoreUpdate;
|
||||||
|
use App\Models\Documentation;
|
||||||
|
use App\Models\ProjectLanguage;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Repositories\DocumentationCategoryRepository;
|
||||||
|
use App\Repositories\DocumentationRepository;
|
||||||
|
use App\Repositories\DocumentationVersionRepository;
|
||||||
|
use App\Repositories\ProjectRepository;
|
||||||
|
use App\ServiceResults\ServiceResultArray;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use App\Services\Documentation\DocumentationCommandHandler;
|
||||||
|
use App\Services\DocumentationContent\ModelSyncCommand;
|
||||||
|
use App\Services\Service;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
final class DocumentationService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DocumentationVersionRepository $documentationVersionRepository,
|
||||||
|
private readonly DocumentationCategoryRepository $documentationCategoryRepository,
|
||||||
|
private readonly DocumentationRepository $documentationRepository,
|
||||||
|
private readonly DocumentationCommandHandler $documentationCommandHandler,
|
||||||
|
private readonly ModelSyncCommand $contentSaveCommand,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(int $projectId, int $versionId, DocumentationBuilderDto $documentationBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('viewAny', Documentation::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultLanguage = $project->languages()->where('is_default', 1)->first();
|
||||||
|
$with = [
|
||||||
|
'content' => function (HasOne $hasOne) use ($defaultLanguage) {
|
||||||
|
/** @var ?ProjectLanguage $defaultLanguage */
|
||||||
|
$hasOne->when($defaultLanguage, function (Builder $query, ProjectLanguage $defaultLanguage) {
|
||||||
|
$query->where('language_id', $defaultLanguage->id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
||||||
|
$with = array_merge($with, $querySettingsDto->getQueryWith());
|
||||||
|
|
||||||
|
$documentations = $this->documentationRepository->getDocumentations(
|
||||||
|
$version->id,
|
||||||
|
$documentationBuilderDto,
|
||||||
|
$with
|
||||||
|
)->pagination(
|
||||||
|
$querySettingsDto->getLimit(),
|
||||||
|
$querySettingsDto->getPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'project' => $project,
|
||||||
|
'documentations' => $documentations,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('create', Documentation::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultLanguage = $project->languages->where('is_default', 1)->first();
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'project' => $project,
|
||||||
|
'documentation' => new Documentation(),
|
||||||
|
'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $projectId, int $versionId, int $documentationId, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$documentation = $this->documentationRepository->getDocumentationById($documentationId);
|
||||||
|
if (\is_null($documentation)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $documentation)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$withCategories = [];
|
||||||
|
if ($documentation->category_id) {
|
||||||
|
$withCategories[] = $documentation->category_id;
|
||||||
|
}
|
||||||
|
$defaultLanguage = $project->languages->where('is_default', 1)->first();
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'project' => $project,
|
||||||
|
'documentation' => $documentation,
|
||||||
|
'categories' => $this->documentationCategoryRepository->getForSelect($defaultLanguage, null, $withCategories),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(int $projectId, int $versionId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', Documentation::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->documentationRepository->isExistsSlug($versionId, $data->getSlug()) !== false) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.slug')]),
|
||||||
|
['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$documentation = DB::transaction(function () use ($data, $version, $user, $project) {
|
||||||
|
$dataDocumentation = $this->getDataDocumentation($data);
|
||||||
|
$documentation = $this->documentationCommandHandler->handleStore($version, $dataDocumentation);
|
||||||
|
$this->contentSaveCommand->execute($project, $documentation, $data->getContents());
|
||||||
|
|
||||||
|
return $documentation;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($documentation, __('Documentation created successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $projectId, int $versionId, int $documentationId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$documentation = $this->documentationRepository->getDocumentationById($documentationId);
|
||||||
|
if (\is_null($documentation)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $documentation)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->documentationRepository->isExistsSlug($versionId, $data->getSlug(), $documentation->id) !== false) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.slug')]),
|
||||||
|
['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$documentation = DB::transaction(function () use ($data, $documentation, $project) {
|
||||||
|
$dataDocumentation = $this->getDataDocumentation($data);
|
||||||
|
$documentation = $this->documentationCommandHandler->handleUpdate($documentation, $dataDocumentation);
|
||||||
|
$this->contentSaveCommand->execute($project, $documentation, $data->getContents());
|
||||||
|
|
||||||
|
return $documentation;
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($documentation, __('Documentation updated successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $projectId, int $versionId, int $documentationId, User $user): ServiceResultError|ServiceResultSuccess
|
||||||
|
{
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
$project = $version?->project;
|
||||||
|
if (\is_null($version) || $project?->id !== $projectId) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$documentation = $this->documentationRepository->getDocumentationById($documentationId);
|
||||||
|
if (\is_null($documentation)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('delete', $documentation)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($documentation) {
|
||||||
|
$this->documentationCommandHandler->handleDestroy($documentation);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok(__('Documentation successfully removed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDataDocumentation(StoreUpdate $data): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'slug' => $data->getSlug(),
|
||||||
|
'is_public' => $data->isPublic(),
|
||||||
|
'sort' => $data->getSort(),
|
||||||
|
'category_id' => $data->getCategoryId(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,222 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Admin\Project;
|
||||||
|
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Dto\Builder\DocumentationVersion as DocumentationVersionBuilderDto;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationVersion\StoreUpdate;
|
||||||
|
use App\Enums\DocumentationVersionStatus;
|
||||||
|
use App\Models\DocumentationVersion;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Repositories\DocumentationVersionRepository;
|
||||||
|
use App\Repositories\ProjectRepository;
|
||||||
|
use App\ServiceResults\ServiceResultArray;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use App\Services\DocumentationVersion\DocumentationVersionCommandHandler;
|
||||||
|
use App\Services\Service;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
final class DocumentationVersionService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProjectRepository $projectRepository,
|
||||||
|
private readonly DocumentationVersionRepository $documentationVersionRepository,
|
||||||
|
private readonly DocumentationVersionCommandHandler $documentationVersionCommandHandler,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(int $projectId, DocumentationVersionBuilderDto $documentationVersionBuilderDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if (\is_null($project)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('viewAny', DocumentationVersion::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$versions = $this->documentationVersionRepository->getVersions(
|
||||||
|
$project->id,
|
||||||
|
$documentationVersionBuilderDto,
|
||||||
|
$querySettingsDto->getQueryWith()
|
||||||
|
)->pagination(
|
||||||
|
$querySettingsDto->getLimit(),
|
||||||
|
$querySettingsDto->getPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'versions' => $versions,
|
||||||
|
'project' => $project,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if (\is_null($project)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
if (\is_null($version)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $version)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'project' => $project,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(int $projectId, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if (\is_null($project)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('create', DocumentationVersion::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'version' => new DocumentationVersion(),
|
||||||
|
'statuses' => DocumentationVersionStatus::toCollection()->pluck( 'title', 'value')->toArray(),
|
||||||
|
'project' => $project,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $projectId, int $versionId, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if (\is_null($project)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
if (\is_null($version)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $version)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'version' => $version,
|
||||||
|
'statuses' => DocumentationVersionStatus::toCollection()->pluck( 'title', 'value')->toArray(),
|
||||||
|
'project' => $project,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(int $projectId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', DocumentationVersion::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if (\is_null($project)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->documentationVersionRepository->isExistsSlug($projectId, $data->getSlug()) !== false) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.slug')]),
|
||||||
|
['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$version = DB::transaction(function () use ($data, $project, $user) {
|
||||||
|
$dataVersion = $this->getDataVersion($data);
|
||||||
|
return $this->documentationVersionCommandHandler->handleStore($project, $dataVersion);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($version, __('Documentation version created successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $projectId, int $versionId, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if (\is_null($project)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
if (\is_null($version)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('update', $version)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->documentationVersionRepository->isExistsSlug($projectId, $data->getSlug(), $version->id) !== false) {
|
||||||
|
return $this->errValidate(
|
||||||
|
__('validation.unique', ['attribute' => __('validation.attributes.slug')]),
|
||||||
|
['slug' => __('validation.unique', ['attribute' => __('validation.attributes.slug')])]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$version = DB::transaction(function () use ($data, $version) {
|
||||||
|
$dataVersion = $this->getDataVersion($data);
|
||||||
|
return $this->documentationVersionCommandHandler->handleUpdate($version, $dataVersion);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($version, __('Documentation version updated successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $projectId, int $versionId, User $user): ServiceResultError|ServiceResultSuccess
|
||||||
|
{
|
||||||
|
$project = $this->projectRepository->getProjectById($projectId);
|
||||||
|
if (\is_null($project)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $this->documentationVersionRepository->getVersionById($versionId);
|
||||||
|
if (\is_null($version)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('delete', $version)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($version) {
|
||||||
|
$this->documentationVersionCommandHandler->handleDestroy($version);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok(__('Documentation version successfully removed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDataVersion(StoreUpdate $data): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => $data->getTitle(),
|
||||||
|
'slug' => $data->getSlug(),
|
||||||
|
'is_public' => $data->isPublic(),
|
||||||
|
'status' => $data->getStatus(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Documentation;
|
||||||
|
|
||||||
|
use App\Dto\Builder\Documentation as DocumentationBuilderDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
final readonly class BuilderCommand
|
||||||
|
{
|
||||||
|
public function execute(Relation | Builder $query, DocumentationBuilderDto $documentationBuilderDto): Relation | Builder
|
||||||
|
{
|
||||||
|
if ($documentationBuilderDto->isPublic() !== null) {
|
||||||
|
$query->where('is_public', $documentationBuilderDto->isPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Documentation;
|
||||||
|
|
||||||
|
use App\Models\Documentation;
|
||||||
|
use App\Models\DocumentationVersion;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class DocumentationCommandHandler
|
||||||
|
{
|
||||||
|
public function handleStore(DocumentationVersion $version, array $data): Documentation
|
||||||
|
{
|
||||||
|
$data['slug'] = Str::lower($data['slug']);
|
||||||
|
return $version->documentations()->create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdate(Documentation $documentation, array $data): Documentation
|
||||||
|
{
|
||||||
|
if (isset($data['slug'])) {
|
||||||
|
$data['slug'] = Str::lower($data['slug']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$documentation->update($data);
|
||||||
|
$documentation->touch();
|
||||||
|
|
||||||
|
return $documentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleDestroy(Documentation $documentation): void
|
||||||
|
{
|
||||||
|
$documentation->update([
|
||||||
|
'slug' => $documentation->slug . '#delete:' . $documentation->id,
|
||||||
|
]);
|
||||||
|
$documentation->delete();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\DocumentationCategory;
|
||||||
|
|
||||||
|
use App\Dto\Builder\DocumentationCategory as DocumentationCategoryBuilderDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
final readonly class BuilderCommand
|
||||||
|
{
|
||||||
|
public function execute(Relation | Builder $query, DocumentationCategoryBuilderDto $documentationCategoryBuilderDto): Relation | Builder
|
||||||
|
{
|
||||||
|
if ($documentationCategoryBuilderDto->isPublic() !== null) {
|
||||||
|
$query->where('is_public', $documentationCategoryBuilderDto->isPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\DocumentationCategory;
|
||||||
|
|
||||||
|
use App\Exceptions\Services\DocumentationCategory\ParentException;
|
||||||
|
use App\Models\DocumentationCategory;
|
||||||
|
use App\Models\DocumentationVersion;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class DocumentationCategoryCommandHandler
|
||||||
|
{
|
||||||
|
public function handleStore(DocumentationVersion $version, array $data): DocumentationCategory
|
||||||
|
{
|
||||||
|
$data['slug'] = Str::lower($data['slug']);
|
||||||
|
return $version->categories()->create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdate(DocumentationCategory $category, array $data): DocumentationCategory
|
||||||
|
{
|
||||||
|
if (!empty($data['parent_id'])) {
|
||||||
|
$category->parent_id = $data['parent_id'];
|
||||||
|
if ($category->ancestors()->withTrashed()->where('id', $category->id)->exists()) {
|
||||||
|
throw new ParentException('Category ID occurs in the parent chain (category: ' . $category->id . ', parent_id: ' . $category->parent_id . ').');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data['slug'])) {
|
||||||
|
$data['slug'] = Str::lower($data['slug']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$category->update($data);
|
||||||
|
$category->touch();
|
||||||
|
|
||||||
|
return $category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleDestroy(DocumentationCategory $category): void
|
||||||
|
{
|
||||||
|
$category->update([
|
||||||
|
'slug' => $category->slug . '#delete:' . $category->id,
|
||||||
|
]);
|
||||||
|
$category->delete();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\DocumentationCategoryContent;
|
||||||
|
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategoryContent\Content;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationCategoryContent\Contents;
|
||||||
|
use App\Exceptions\Services\DocumentationCategoryContent\ContentSaveException;
|
||||||
|
use App\Models\DocumentationCategory;
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
final readonly class ModelSyncCommand
|
||||||
|
{
|
||||||
|
public function execute(Project $project, DocumentationCategory $category, Contents $contents): void
|
||||||
|
{
|
||||||
|
$languages = $project->languages;
|
||||||
|
$categoryContents = $category->contents;
|
||||||
|
|
||||||
|
$newContents = [];
|
||||||
|
foreach ($contents->getContents() as $content) {
|
||||||
|
/** @var Content $content */
|
||||||
|
$language = $languages->firstWhere('id', $content->getLanguageId());
|
||||||
|
if (!$language) {
|
||||||
|
throw new ContentSaveException('Language not found: ' . $content->getLanguageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = $categoryContents->firstWhere('language_id', $language->id);
|
||||||
|
$data = $this->getData($content);
|
||||||
|
if (\is_null($model)) {
|
||||||
|
$newContents[] = array_merge(['language_id' => $content->getLanguageId()], $data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$model->update($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($newContents)) {
|
||||||
|
$category->contents()->createMany($newContents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getData(Content $content): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => $content->getTitle(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\DocumentationContent;
|
||||||
|
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationContent\Content;
|
||||||
|
use App\Dto\Service\Admin\Project\DocumentationContent\Contents;
|
||||||
|
use App\Exceptions\Services\DocumentationContent\ContentSaveException;
|
||||||
|
use App\Models\Documentation;
|
||||||
|
use App\Models\Project;
|
||||||
|
|
||||||
|
final readonly class ModelSyncCommand
|
||||||
|
{
|
||||||
|
public function execute(Project $project, Documentation $documentation, Contents $contents): void
|
||||||
|
{
|
||||||
|
$languages = $project->languages;
|
||||||
|
$documentationContents = $documentation->contents;
|
||||||
|
|
||||||
|
$newContents = [];
|
||||||
|
foreach ($contents->getContents() as $content) {
|
||||||
|
/** @var Content $content */
|
||||||
|
$language = $languages->firstWhere('id', $content->getLanguageId());
|
||||||
|
if (!$language) {
|
||||||
|
throw new ContentSaveException('Language not found: ' . $content->getLanguageId());
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = $documentationContents->firstWhere('language_id', $language->id);
|
||||||
|
$data = $this->getData($content);
|
||||||
|
if (\is_null($model)) {
|
||||||
|
$newContents[] = array_merge(['language_id' => $content->getLanguageId()], $data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$model->update($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($newContents)) {
|
||||||
|
$documentation->contents()->createMany($newContents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getData(Content $content): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => $content->getTitle(),
|
||||||
|
'content' => $content->getContent(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\DocumentationVersion;
|
||||||
|
|
||||||
|
use App\Dto\Builder\DocumentationVersion as DocumentationVersionBuilderDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
final readonly class BuilderCommand
|
||||||
|
{
|
||||||
|
public function execute(Relation | Builder $query, DocumentationVersionBuilderDto $documentationVersionBuilderDto): Relation | Builder
|
||||||
|
{
|
||||||
|
if ($documentationVersionBuilderDto->isPublic() !== null) {
|
||||||
|
$query->where('is_public', $documentationVersionBuilderDto->isPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\DocumentationVersion;
|
||||||
|
|
||||||
|
use App\Models\DocumentationVersion;
|
||||||
|
use App\Models\Project;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class DocumentationVersionCommandHandler
|
||||||
|
{
|
||||||
|
public function handleStore(Project $project, array $data): DocumentationVersion
|
||||||
|
{
|
||||||
|
$data['slug'] = Str::lower($data['slug']);
|
||||||
|
|
||||||
|
return $project->documentationVersions()->create($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdate(DocumentationVersion $version, array $data): DocumentationVersion
|
||||||
|
{
|
||||||
|
if (isset($data['slug'])) {
|
||||||
|
$data['slug'] = Str::lower($data['slug']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$version->update($data);
|
||||||
|
$version->touch();
|
||||||
|
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleDestroy(DocumentationVersion $version): void
|
||||||
|
{
|
||||||
|
$version->update([
|
||||||
|
'slug' => $version->slug . '#delete:' . $version->id,
|
||||||
|
]);
|
||||||
|
$version->delete();
|
||||||
|
}
|
||||||
|
}
|
@ -27,12 +27,12 @@ private function getTitle(): string
|
|||||||
|
|
||||||
private function getCheckboxValue(): string
|
private function getCheckboxValue(): string
|
||||||
{
|
{
|
||||||
return (string) old($this->getRequestName(), $this->checkboxValue);
|
return (string) $this->checkboxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getUserValue(): string
|
private function getUserValue(): string
|
||||||
{
|
{
|
||||||
return (string) $this->userValue;
|
return (string) old($this->getRequestName(), $this->userValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getNotCheckedValue(): ?string
|
private function getNotCheckedValue(): ?string
|
||||||
|
@ -13,6 +13,7 @@ public function __construct(
|
|||||||
private readonly string $type = 'text',
|
private readonly string $type = 'text',
|
||||||
private readonly ?string $value = '',
|
private readonly ?string $value = '',
|
||||||
private readonly ?string $example = null,
|
private readonly ?string $example = null,
|
||||||
|
private readonly ?string $allowedCharacters = null,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
protected function getName(): string
|
protected function getName(): string
|
||||||
@ -40,6 +41,11 @@ private function getExample(): ?string
|
|||||||
return $this->example;
|
return $this->example;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAllowedCharacters(): ?string
|
||||||
|
{
|
||||||
|
return $this->allowedCharacters;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
@ -52,6 +58,7 @@ public function render(): View
|
|||||||
'type' => $this->getType(),
|
'type' => $this->getType(),
|
||||||
'value' => $this->getValue(),
|
'value' => $this->getValue(),
|
||||||
'example' => $this->getExample(),
|
'example' => $this->getExample(),
|
||||||
|
'allowedCharacters' => $this->getAllowedCharacters(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
"intervention/image-laravel": "^1.2",
|
"intervention/image-laravel": "^1.2",
|
||||||
"kor-elf/captcha-rule-for-laravel": "^1.0",
|
"kor-elf/captcha-rule-for-laravel": "^1.0",
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^11.0",
|
||||||
"laravel/tinker": "^2.9"
|
"laravel/tinker": "^2.9",
|
||||||
|
"staudenmeir/laravel-adjacency-list": "^1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"barryvdh/laravel-debugbar": "^3.13",
|
"barryvdh/laravel-debugbar": "^3.13",
|
||||||
|
170
app/application/composer.lock
generated
170
app/application/composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "5e3226c3090c81571c830e2c383433ab",
|
"content-hash": "f7873c3a33599ee1ce0bbce99af9db22",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@ -3381,6 +3381,174 @@
|
|||||||
],
|
],
|
||||||
"time": "2023-11-08T05:53:05+00:00"
|
"time": "2023-11-08T05:53:05+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "staudenmeir/eloquent-has-many-deep-contracts",
|
||||||
|
"version": "v1.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts.git",
|
||||||
|
"reference": "bcbe1a921caad7201b324e297eddb696d4bd8647"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep-contracts/zipball/bcbe1a921caad7201b324e297eddb696d4bd8647",
|
||||||
|
"reference": "bcbe1a921caad7201b324e297eddb696d4bd8647",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/database": "^11.0",
|
||||||
|
"php": "^8.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Staudenmeir\\EloquentHasManyDeepContracts\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jonas Staudenmeir",
|
||||||
|
"email": "mail@jonas-staudenmeir.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Contracts for staudenmeir/eloquent-has-many-deep",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/issues",
|
||||||
|
"source": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/tree/v1.2"
|
||||||
|
},
|
||||||
|
"time": "2024-01-18T01:20:44+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "staudenmeir/laravel-adjacency-list",
|
||||||
|
"version": "v1.22",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/staudenmeir/laravel-adjacency-list.git",
|
||||||
|
"reference": "0ec695e5d4094434f4a7adb956ebd23e228970ea"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/staudenmeir/laravel-adjacency-list/zipball/0ec695e5d4094434f4a7adb956ebd23e228970ea",
|
||||||
|
"reference": "0ec695e5d4094434f4a7adb956ebd23e228970ea",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/database": "^11.0",
|
||||||
|
"php": "^8.2",
|
||||||
|
"staudenmeir/eloquent-has-many-deep-contracts": "^1.2",
|
||||||
|
"staudenmeir/laravel-cte": "^1.11"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"barryvdh/laravel-ide-helper": "^3.0",
|
||||||
|
"larastan/larastan": "^2.0",
|
||||||
|
"mockery/mockery": "^1.5.1",
|
||||||
|
"orchestra/testbench": "^9.0",
|
||||||
|
"phpunit/phpunit": "^10.5",
|
||||||
|
"singlestoredb/singlestoredb-laravel": "^1.5.4",
|
||||||
|
"staudenmeir/eloquent-has-many-deep": "^1.20"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"barryvdh/laravel-ide-helper": "Provide type hints for attributes and relations."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Staudenmeir\\LaravelAdjacencyList\\IdeHelperServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Staudenmeir\\LaravelAdjacencyList\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jonas Staudenmeir",
|
||||||
|
"email": "mail@jonas-staudenmeir.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Recursive Laravel Eloquent relationships with CTEs",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/staudenmeir/laravel-adjacency-list/issues",
|
||||||
|
"source": "https://github.com/staudenmeir/laravel-adjacency-list/tree/v1.22"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://paypal.me/JonasStaudenmeir",
|
||||||
|
"type": "custom"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-04-19T12:11:45+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "staudenmeir/laravel-cte",
|
||||||
|
"version": "v1.11",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/staudenmeir/laravel-cte.git",
|
||||||
|
"reference": "1e7063021febf9db7e47d8ea602f582b00d55da6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/staudenmeir/laravel-cte/zipball/1e7063021febf9db7e47d8ea602f582b00d55da6",
|
||||||
|
"reference": "1e7063021febf9db7e47d8ea602f582b00d55da6",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/database": "^11.0",
|
||||||
|
"php": "^8.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^9.0",
|
||||||
|
"phpstan/phpstan": "^1.10",
|
||||||
|
"phpunit/phpunit": "^10.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Staudenmeir\\LaravelCte\\DatabaseServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Staudenmeir\\LaravelCte\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jonas Staudenmeir",
|
||||||
|
"email": "mail@jonas-staudenmeir.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel queries with common table expressions",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/staudenmeir/laravel-cte/issues",
|
||||||
|
"source": "https://github.com/staudenmeir/laravel-cte/tree/v1.11"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://paypal.me/JonasStaudenmeir",
|
||||||
|
"type": "custom"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-03-09T10:12:45+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/clock",
|
"name": "symfony/clock",
|
||||||
"version": "v7.0.5",
|
"version": "v7.0.5",
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('documentation_versions', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('title', 255);
|
||||||
|
$table->string('slug', 100);
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('project_id')->index();
|
||||||
|
$table->foreign('project_id')->references('id')->on('projects');
|
||||||
|
|
||||||
|
$table->boolean('is_public')->default(0);
|
||||||
|
$table->unsignedInteger('status')->index();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes()->index();
|
||||||
|
|
||||||
|
$table->unique(['project_id', 'slug']);
|
||||||
|
$table->index(['is_public', 'deleted_at']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('documentation_categories', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('slug');
|
||||||
|
$table->boolean('is_public')->default(0);
|
||||||
|
$table->integer('sort')->index();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('version_id')->index();
|
||||||
|
$table->foreign('version_id')->references('id')->on('documentation_versions');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('parent_id')->nullable()->index();
|
||||||
|
$table->foreign('parent_id')->references('id')->on('documentation_categories');
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes()->index();
|
||||||
|
|
||||||
|
$table->index(['slug', 'version_id', 'deleted_at']);
|
||||||
|
$table->index(['slug', 'version_id', 'is_public', 'deleted_at'], 'slug_version_id_is_public_deleted_at_index');
|
||||||
|
|
||||||
|
$table->index(['parent_id', 'deleted_at']);
|
||||||
|
$table->index(['parent_id', 'is_public', 'deleted_at']);
|
||||||
|
|
||||||
|
$table->unique(['version_id', 'slug']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('documentation_category_content', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('category_id')->index();
|
||||||
|
$table->foreign('category_id')->references('id')->on('documentation_categories');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('language_id')->index();
|
||||||
|
$table->foreign('language_id')->references('id')->on('project_languages');
|
||||||
|
|
||||||
|
$table->string('title', 255);
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
|
||||||
|
$table->index(['category_id', 'language_id', 'deleted_at'], 'category_language_deleted_index');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('documentation', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('slug');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('version_id')->index();
|
||||||
|
$table->foreign('version_id')->references('id')->on('documentation_versions');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('category_id')->nullable()->index();
|
||||||
|
$table->foreign('category_id')->references('id')->on('documentation_categories');
|
||||||
|
|
||||||
|
$table->boolean('is_public')->default(0)->index();
|
||||||
|
$table->integer('sort')->index();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes()->index();
|
||||||
|
|
||||||
|
$table->index(['slug', 'version_id', 'deleted_at']);
|
||||||
|
$table->index(['slug', 'version_id', 'is_public', 'deleted_at']);
|
||||||
|
|
||||||
|
$table->index(['category_id', 'deleted_at']);
|
||||||
|
$table->index(['category_id', 'is_public', 'deleted_at']);
|
||||||
|
|
||||||
|
$table->unique(['version_id', 'slug']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('documentation_content', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('documentation_id')->index();
|
||||||
|
$table->foreign('documentation_id')->references('id')->on('documentation');
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('language_id')->index();
|
||||||
|
$table->foreign('language_id')->references('id')->on('project_languages');
|
||||||
|
|
||||||
|
$table->string('title', 255);
|
||||||
|
$table->longText('content')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
|
||||||
|
$table->index(['documentation_id', 'language_id', 'deleted_at'], 'content_documentation_language_deleted_index');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('documentation_content');
|
||||||
|
Schema::dropIfExists('documentation');
|
||||||
|
Schema::dropIfExists('documentation_category_content');
|
||||||
|
Schema::dropIfExists('documentation_categories');
|
||||||
|
Schema::dropIfExists('documentation_versions');
|
||||||
|
}
|
||||||
|
};
|
@ -256,5 +256,16 @@
|
|||||||
"The link has been deleted": "The link has been deleted",
|
"The link has been deleted": "The link has been deleted",
|
||||||
"Language not found": "Language not found",
|
"Language not found": "Language not found",
|
||||||
"Project not found": "Project not found",
|
"Project not found": "Project not found",
|
||||||
"Translations successfully updated": "Translations successfully updated"
|
"Translations successfully updated": "Translations successfully updated",
|
||||||
|
"allowed characters:": "allowed characters:",
|
||||||
|
"Documentation version created successfully": "Documentation version created successfully",
|
||||||
|
"Documentation version updated successfully": "Documentation version updated successfully",
|
||||||
|
"Documentation version successfully removed": "Documentation version successfully removed",
|
||||||
|
"List": "List",
|
||||||
|
"Documentation created successfully": "Documentation created successfully",
|
||||||
|
"Documentation updated successfully": "Documentation updated successfully",
|
||||||
|
"Documentation successfully removed": "Documentation successfully removed",
|
||||||
|
"Category successfully created": "Category successfully created",
|
||||||
|
"Category updated successfully": "Category updated successfully",
|
||||||
|
"Category successfully deleted": "Category successfully deleted"
|
||||||
}
|
}
|
||||||
|
@ -13,4 +13,7 @@
|
|||||||
'Links project' => 'Links from the project',
|
'Links project' => 'Links from the project',
|
||||||
'Translations' => 'Translations',
|
'Translations' => 'Translations',
|
||||||
'Feedback' => 'Feedback',
|
'Feedback' => 'Feedback',
|
||||||
|
'Documentation version' => 'Documentation version',
|
||||||
|
'Documentation' => 'Documentation',
|
||||||
|
'Categories' => 'Categories',
|
||||||
];
|
];
|
||||||
|
@ -17,4 +17,6 @@
|
|||||||
'ProjectLink' => 'Links from the project',
|
'ProjectLink' => 'Links from the project',
|
||||||
'ProjectTranslation' => 'Translations',
|
'ProjectTranslation' => 'Translations',
|
||||||
'ProjectFeedback' => 'Feedback',
|
'ProjectFeedback' => 'Feedback',
|
||||||
|
'Documentation' => 'Documentation',
|
||||||
|
'DocumentationCategory' => 'Documentation category',
|
||||||
];
|
];
|
||||||
|
@ -151,6 +151,8 @@
|
|||||||
'no_type' => 'The :attribute can only use: :type.',
|
'no_type' => 'The :attribute can only use: :type.',
|
||||||
'captcha' => 'Failed to pass human verification.',
|
'captcha' => 'Failed to pass human verification.',
|
||||||
'http_host' => 'The :attribute must be a valid domain.',
|
'http_host' => 'The :attribute must be a valid domain.',
|
||||||
|
'parent' => 'A parent cannot refer to itself.',
|
||||||
|
'parent_cycle_detected' => 'Category ID occurs in the parent chain.',
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'address' => 'address',
|
'address' => 'address',
|
||||||
'affiliate_url' => 'affiliate URL',
|
'affiliate_url' => 'affiliate URL',
|
||||||
@ -301,5 +303,9 @@
|
|||||||
'translations' => 'translations',
|
'translations' => 'translations',
|
||||||
'translations.*.code' => 'translations code',
|
'translations.*.code' => 'translations code',
|
||||||
'translations.*.text' => 'translations',
|
'translations.*.text' => 'translations',
|
||||||
|
'parent_id' => 'parent',
|
||||||
|
'content.*.title' => 'title',
|
||||||
|
'content.*.content' => 'content',
|
||||||
|
'category_id' => 'category',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
7
app/application/lang/en/version-status.php
Normal file
7
app/application/lang/en/version-status.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'NotSupported' => 'Not Supported',
|
||||||
|
'Supported' => 'Supported',
|
||||||
|
'CurrentVersion' => 'Current version',
|
||||||
|
'FutureVersion' => 'Future version',
|
||||||
|
];
|
@ -256,5 +256,16 @@
|
|||||||
"The link has been deleted": "Ссылка удалена",
|
"The link has been deleted": "Ссылка удалена",
|
||||||
"Language not found": "Язык не найден",
|
"Language not found": "Язык не найден",
|
||||||
"Project not found": "Проект не найден",
|
"Project not found": "Проект не найден",
|
||||||
"Translations successfully updated": "Переводы успешно обновлены"
|
"Translations successfully updated": "Переводы успешно обновлены",
|
||||||
|
"allowed characters:": "разрешенные символы:",
|
||||||
|
"Documentation version created successfully": "Версия документации успешно создана",
|
||||||
|
"Documentation version updated successfully": "Версия документации успешно обновлена",
|
||||||
|
"Documentation version successfully removed": "Версия документации успешно удалена",
|
||||||
|
"List": "Список",
|
||||||
|
"Documentation created successfully": "Документация успешно создана",
|
||||||
|
"Documentation updated successfully": "Документация успешно обновлена",
|
||||||
|
"Documentation successfully removed": "Документация успешно удалена",
|
||||||
|
"Category successfully created": "Категория успешно создана",
|
||||||
|
"Category updated successfully": "Категория успешно обновлена",
|
||||||
|
"Category successfully deleted": "Категория успешно удалена"
|
||||||
}
|
}
|
||||||
|
@ -13,4 +13,7 @@
|
|||||||
'Links project' => 'Ссылки от проекта',
|
'Links project' => 'Ссылки от проекта',
|
||||||
'Translations' => 'Переводы',
|
'Translations' => 'Переводы',
|
||||||
'Feedback' => 'Обратная связь',
|
'Feedback' => 'Обратная связь',
|
||||||
|
'Documentation version' => 'Версия документации',
|
||||||
|
'Documentation' => 'Документация',
|
||||||
|
'Categories' => 'Категории',
|
||||||
];
|
];
|
||||||
|
@ -17,4 +17,6 @@
|
|||||||
'ProjectLink' => 'Ссылки от проекта',
|
'ProjectLink' => 'Ссылки от проекта',
|
||||||
'ProjectTranslation' => 'Переводы',
|
'ProjectTranslation' => 'Переводы',
|
||||||
'ProjectFeedback' => 'Обратная связь',
|
'ProjectFeedback' => 'Обратная связь',
|
||||||
|
'Documentation' => 'Документация',
|
||||||
|
'DocumentationCategory' => 'Категория документации',
|
||||||
];
|
];
|
||||||
|
@ -151,6 +151,8 @@
|
|||||||
'no_type' => 'Значение поля :attribute может использовать только: :type.',
|
'no_type' => 'Значение поля :attribute может использовать только: :type.',
|
||||||
'captcha' => 'Не удалось пройти проверку человеком.',
|
'captcha' => 'Не удалось пройти проверку человеком.',
|
||||||
'http_host' => 'Значение поля :attribute не является доменом или имеет некорректный формат.',
|
'http_host' => 'Значение поля :attribute не является доменом или имеет некорректный формат.',
|
||||||
|
'parent' => 'Родитель не может ссылаться на себя.',
|
||||||
|
'parent_cycle_detected' => 'ID категории встречается в родительской цепочке.',
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'address' => 'адрес',
|
'address' => 'адрес',
|
||||||
'affiliate_url' => 'Партнёрская ссылка',
|
'affiliate_url' => 'Партнёрская ссылка',
|
||||||
@ -301,5 +303,9 @@
|
|||||||
'translations' => 'переводы',
|
'translations' => 'переводы',
|
||||||
'translations.*.code' => 'код перевода',
|
'translations.*.code' => 'код перевода',
|
||||||
'translations.*.text' => 'переводы',
|
'translations.*.text' => 'переводы',
|
||||||
|
'parent_id' => 'родитель',
|
||||||
|
'content.*.title' => 'заголовок',
|
||||||
|
'content.*.content' => 'контент',
|
||||||
|
'category_id' => 'категория',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
7
app/application/lang/ru/version-status.php
Normal file
7
app/application/lang/ru/version-status.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'NotSupported' => 'Не поддерживается',
|
||||||
|
'Supported' => 'Поддерживается',
|
||||||
|
'CurrentVersion' => 'Текущая версия',
|
||||||
|
'FutureVersion' => 'Будущая версия',
|
||||||
|
];
|
9
app/application/package-lock.json
generated
9
app/application/package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"notyf": "^3.10.0",
|
"notyf": "^3.10.0",
|
||||||
"nouislider": "^15.2.0",
|
"nouislider": "^15.2.0",
|
||||||
"onscreen": "^1.4.0",
|
"onscreen": "^1.4.0",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
"sass": "^1.47.0",
|
"sass": "^1.47.0",
|
||||||
"simplebar": "^5.3.4",
|
"simplebar": "^5.3.4",
|
||||||
"smooth-scroll": "^16.1.3",
|
"smooth-scroll": "^16.1.3",
|
||||||
@ -1766,6 +1767,14 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prismjs": {
|
||||||
|
"version": "1.29.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||||
|
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"notyf": "^3.10.0",
|
"notyf": "^3.10.0",
|
||||||
"nouislider": "^15.2.0",
|
"nouislider": "^15.2.0",
|
||||||
"onscreen": "^1.4.0",
|
"onscreen": "^1.4.0",
|
||||||
|
"prismjs": "^1.29.0",
|
||||||
"sass": "^1.47.0",
|
"sass": "^1.47.0",
|
||||||
"simplebar": "^5.3.4",
|
"simplebar": "^5.3.4",
|
||||||
"smooth-scroll": "^16.1.3",
|
"smooth-scroll": "^16.1.3",
|
||||||
|
32
app/application/resources/prism/app.js
Normal file
32
app/application/resources/prism/app.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import Prism from 'prismjs';
|
||||||
|
|
||||||
|
import "prismjs/components/prism-core.js";
|
||||||
|
import "prismjs/components/prism-clike.js";
|
||||||
|
import "prismjs/components/prism-markup.js";
|
||||||
|
import "prismjs/components/prism-markup-templating.js"
|
||||||
|
import "prismjs/components/prism-go.js";
|
||||||
|
import "prismjs/components/prism-go-module.js";
|
||||||
|
import "prismjs/components/prism-java.js";
|
||||||
|
import "prismjs/components/prism-javascript.js";
|
||||||
|
import "prismjs/components/prism-css.js";
|
||||||
|
import "prismjs/components/prism-ruby.js";
|
||||||
|
import "prismjs/components/prism-python.js";
|
||||||
|
import "prismjs/components/prism-c.js";
|
||||||
|
import "prismjs/components/prism-csharp.js";
|
||||||
|
import "prismjs/components/prism-cpp.js";
|
||||||
|
import "prismjs/components/prism-nginx.js";
|
||||||
|
import "prismjs/components/prism-docker.js";
|
||||||
|
import "prismjs/components/prism-diff.js";
|
||||||
|
import "prismjs/components/prism-php.js";
|
||||||
|
|
||||||
|
|
||||||
|
import "prismjs/plugins/toolbar/prism-toolbar.css";
|
||||||
|
import "prismjs/plugins/toolbar/prism-toolbar.js";
|
||||||
|
import "prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard.js";
|
||||||
|
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
|
||||||
|
import "prismjs/plugins/line-numbers/prism-line-numbers.js";
|
||||||
|
import "prismjs/plugins/show-language/prism-show-language.js"
|
||||||
|
import "prismjs/plugins/treeview/prism-treeview.css"
|
||||||
|
import "prismjs/plugins/treeview/prism-treeview.js"
|
||||||
|
|
||||||
|
import "prismjs/themes/prism.css";
|
1
app/application/resources/views/_prism.blade.php
Normal file
1
app/application/resources/views/_prism.blade.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
@vite('resources/prism/app.js')
|
@ -0,0 +1,33 @@
|
|||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
function clickEnableAddEventListener(element, index, array) {
|
||||||
|
let parent = element.closest('.{{ $classParent }}'),
|
||||||
|
contents = parent.querySelectorAll('.{{ $classContent }}'),
|
||||||
|
disabled = true;
|
||||||
|
|
||||||
|
if (element.checked) {
|
||||||
|
disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.forEach(function (content) {
|
||||||
|
content.disabled = disabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
element.addEventListener('click', (e) => {
|
||||||
|
let disabled = true;
|
||||||
|
|
||||||
|
if (element.checked) {
|
||||||
|
disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.forEach(function (content) {
|
||||||
|
content.disabled = disabled;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let clickEnable = document.querySelectorAll('.{{ $classCheckbox }}');
|
||||||
|
if (clickEnable.length > 0) {
|
||||||
|
clickEnable.forEach(clickEnableAddEventListener);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
@ -0,0 +1,34 @@
|
|||||||
|
@csrf
|
||||||
|
<x-volt.forms.checkbox :title="__('validation.attributes.is_public')" name="is_public" checkboxValue="1" notCheckedValue="0" :userValue="(string) $category->is_public" />
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.slug')" allowed-characters="a-z0-9.-_" name="slug" type="text" :value="$category->slug" required autofocus />
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.sort')" name="sort" type="number" :value="$category->sort" required />
|
||||||
|
<x-volt.forms.select :title="__('validation.attributes.parent_id')" name="parent_id" :list="$categories" :value="(string) $category->parent_id">
|
||||||
|
<option value=""></option>
|
||||||
|
</x-volt.forms.select>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class="nav nav-tabs mb-4" id="nav-language-tab" role="tablist">
|
||||||
|
@foreach($project->languages as $index => $language)
|
||||||
|
<a class="nav-item nav-link @if($index === 0) active @endif" id="language-{{ $language->id }}-tab" data-bs-toggle="tab" href="#language-{{ $language->id }}" role="tab" aria-controls="language-{{ $language->id }}" aria-selected="{{ $index ? 'false' : 'true' }}">{{ $language->title }}</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="tab-content" id="nav-language-tabContent">
|
||||||
|
@foreach($project->languages as $index => $language)
|
||||||
|
@php
|
||||||
|
$content = $category->contents->firstWhere('language_id', $language->id);
|
||||||
|
@endphp
|
||||||
|
<div class="tab-pane fade @if($index === 0) show active @endif" id="language-{{ $language->id }}" role="tabpanel" aria-labelledby="language-{{ $language->id }}-tab">
|
||||||
|
<x-volt.forms.checkbox :title="__('Edit')" :name="'content-enable-' . $language->id" :user-value="($index === 0) ? 1 : 0" class="content-enable" checkbox-value="1" notCheckedValue="0"/>
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.title')" :name="'content[' . $language->id . '][title]'" type="text" class="language-content" :disabled="$index !== 0" :value="$content?->title ?? ''" required />
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@canany(['create', 'update'], $category)
|
||||||
|
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
|
||||||
|
@endcanany
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
@include('admin._scripts._click-content-enable', ['classParent' => 'tab-pane', 'classCheckbox' => 'content-enable', 'classContent' => 'language-content'])
|
||||||
|
@endpush
|
@ -0,0 +1,25 @@
|
|||||||
|
<div class="mb-4">
|
||||||
|
@can('create', \App\Models\DocumentationCategory::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.categories.create', ['project' => $project->id, 'version' => $version->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||||
|
{{ __('Create') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
@can('viewAny', \App\Models\DocumentationCategory::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.categories.index', ['project' => $project->id, 'version' => $version->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('List') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
@can('viewAny', \App\Models\Documentation::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.documentations.index', ['project' => $project->id, 'version' => $version->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('admin-sections.Documentation') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Categories'))
|
||||||
|
@section('h1')
|
||||||
|
{{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }}
|
||||||
|
@endsection
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentation-categories._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Categories') }}</h3>
|
||||||
|
<form method="post" action="{{ route('admin.projects.documentation-versions.categories.store', ['project' => $project->id, 'version' => $version->id]) }}">
|
||||||
|
@include('admin.projects.documentation-categories._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,20 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Categories'))
|
||||||
|
@section('h1')
|
||||||
|
{{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }}
|
||||||
|
@endsection
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentation-categories._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Categories') }}</h3>
|
||||||
|
<form method="post" action="{{ route('admin.projects.documentation-versions.categories.update', ['project' => $project->id, 'version' => $version->id, 'category' => $category->id]) }}">
|
||||||
|
@method('PUT')
|
||||||
|
@include('admin.projects.documentation-categories._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,67 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Categories'))
|
||||||
|
@section('h1')
|
||||||
|
{{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }}
|
||||||
|
@endsection
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentation-categories._top')
|
||||||
|
<div class="card border-0 shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Categories') }}</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-centered table-nowrap mb-0 rounded">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.title') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.slug') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.is_public') }}</th>
|
||||||
|
<th class="border-0 rounded-end" style="width: 150px"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($categories as $category)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ $category->content?->title }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $category->slug }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($category->is_public)
|
||||||
|
{{ __('Yes') }}
|
||||||
|
@else
|
||||||
|
{{ __('No') }}
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.categories.edit', ['project' => $project->id, 'version' => $version->id, 'category' => $category->id]) }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Edit') }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
@can('delete', $category)
|
||||||
|
<form method="post" class="d-inline-block" action="{{ route('admin.projects.documentation-versions.categories.destroy', ['project' => $project->id, 'version' => $version->id, 'category' => $category->id]) }}">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-danger click-confirm" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Delete') }}">
|
||||||
|
<svg data-slot="icon" fill="currentColor" width="20" height="20" class="align-text-center" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endcan
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="card-footer border-0">
|
||||||
|
{{ $categories->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@push('scripts')
|
||||||
|
@include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')])
|
||||||
|
@endpush
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,10 @@
|
|||||||
|
@csrf
|
||||||
|
<x-volt.forms.checkbox :title="__('validation.attributes.is_public')" name="is_public" checkboxValue="1" notCheckedValue="0" :userValue="(string) $version->is_public" />
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.title')" name="title" type="text" :value="$version->title" required autofocus />
|
||||||
|
|
||||||
|
<x-volt.forms.select :title="__('validation.attributes.status')" name="status" :list="$statuses" :value="(string) $version->status?->value" required></x-volt.forms.select>
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.slug')" allowed-characters="a-z0-9.-_" name="slug" type="text" :value="$version->slug" required />
|
||||||
|
|
||||||
|
@canany(['create', 'update'], $version)
|
||||||
|
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
|
||||||
|
@endcanany
|
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
@can('create', \App\Models\DocumentationVersion::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.create', ['project' => $project->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||||
|
{{ __('Create') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
@can('viewAny', \App\Models\DocumentationVersion::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.index', ['project' => $project->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('List') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,17 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Documentation version'))
|
||||||
|
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentation-versions._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Documentation version') }}</h3>
|
||||||
|
<form method="post" action="{{ route('admin.projects.documentation-versions.store', ['project' => $project->id]) }}">
|
||||||
|
@include('admin.projects.documentation-versions._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,18 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Documentation version'))
|
||||||
|
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentation-versions._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Documentation version') }}</h3>
|
||||||
|
<form method="post" action="{{ route('admin.projects.documentation-versions.update', ['project' => $project->id, 'documentation_version' => $version->id]) }}">
|
||||||
|
@method('PUT')
|
||||||
|
@include('admin.projects.documentation-versions._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,74 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Documentation version'))
|
||||||
|
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentation-versions._top')
|
||||||
|
<div class="card border-0 shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Documentation version') }}</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-centered table-nowrap mb-0 rounded">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.title') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.slug') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.status') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.is_public') }}</th>
|
||||||
|
<th class="border-0 rounded-end" style="width: 150px"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($versions as $version)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.show', ['project' => $project->id, 'documentation_version' => $version->id]) }}" class="fw-bold">
|
||||||
|
<svg data-slot="icon" width="16" height="16" fill="currentColor" class="align-text-top" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ $version->title }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $version->slug }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $version->status->getTitle() }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($version->is_public)
|
||||||
|
{{ __('Yes') }}
|
||||||
|
@else
|
||||||
|
{{ __('No') }}
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.edit', ['project' => $project->id, 'documentation_version' => $version->id]) }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Edit') }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
@can('delete', $version)
|
||||||
|
<form method="post" class="d-inline-block" action="{{ route('admin.projects.documentation-versions.destroy', ['project' => $project->id, 'documentation_version' => $version->id]) }}">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-danger click-confirm" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Delete') }}">
|
||||||
|
<svg data-slot="icon" fill="currentColor" width="20" height="20" class="align-text-center" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endcan
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="card-footer border-0">
|
||||||
|
{{ $versions->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@push('scripts')
|
||||||
|
@include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')])
|
||||||
|
@endpush
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,45 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Documentation version'))
|
||||||
|
@section('h1', __('admin-sections.Project') . ': ' . $project->name)
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentation-versions._top')
|
||||||
|
<div class="card border-0 shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Documentation version') }}: {{ $version->title }}</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-centered table-nowrap mb-0 rounded">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th class="border-0">{{ __('admin-sections.Sections') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@can('viewAny', \App\Models\Documentation::class)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.documentations.index', ['project' => $project->id, 'version' => $version->id]) }}" class="fw-bold">
|
||||||
|
<svg width="16" height="16" class="align-text-top" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('admin-sections.Documentation') }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endcan
|
||||||
|
@can('viewAny', \App\Models\DocumentationCategory::class)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.categories.index', ['project' => $project->id, 'version' => $version->id]) }}" class="fw-bold">
|
||||||
|
<svg width="16" height="16" class="align-text-top" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('admin-sections.Categories') }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endcan
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,35 @@
|
|||||||
|
@csrf
|
||||||
|
<x-volt.forms.checkbox :title="__('validation.attributes.is_public')" name="is_public" checkboxValue="1" notCheckedValue="0" :userValue="(string) $documentation->is_public" />
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.slug')" allowed-characters="a-z0-9.-_" name="slug" type="text" :value="$documentation->slug" required autofocus />
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.sort')" name="sort" type="number" :value="$documentation->sort" required />
|
||||||
|
<x-volt.forms.select :title="__('validation.attributes.category_id')" name="category_id" :list="$categories" :value="(string) $documentation->category?->id">
|
||||||
|
<option value=""></option>
|
||||||
|
</x-volt.forms.select>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class="nav nav-tabs mb-4" id="nav-language-tab" role="tablist">
|
||||||
|
@foreach($project->languages as $index => $language)
|
||||||
|
<a class="nav-item nav-link @if($index === 0) active @endif" id="language-{{ $language->id }}-tab" data-bs-toggle="tab" href="#language-{{ $language->id }}" role="tab" aria-controls="language-{{ $language->id }}" aria-selected="{{ $index ? 'false' : 'true' }}">{{ $language->title }}</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="tab-content" id="nav-language-tabContent">
|
||||||
|
@foreach($project->languages as $index => $language)
|
||||||
|
@php
|
||||||
|
$content = $documentation->contents->firstWhere('language_id', $language->id);
|
||||||
|
@endphp
|
||||||
|
<div class="tab-pane fade @if($index === 0) show active @endif" id="language-{{ $language->id }}" role="tabpanel" aria-labelledby="language-{{ $language->id }}-tab">
|
||||||
|
<x-volt.forms.checkbox :title="__('Edit')" :name="'content-enable-' . $language->id" :user-value="($index === 0) ? 1 : 0" class="content-enable" checkbox-value="1" notCheckedValue="0"/>
|
||||||
|
<x-volt.forms.input :title="__('validation.attributes.title')" :name="'content[' . $language->id . '][title]'" type="text" class="language-content" :disabled="$index !== 0" :value="$content?->title ?? ''" required />
|
||||||
|
<x-volt.forms.textarea-wysiwyg :title="__('validation.attributes.content')" :name="'content[' . $language->id . '][content]'" class="language-content" :value="$content?->content" :disabled="$index !== 0" />
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@canany(['create', 'update'], $documentation)
|
||||||
|
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
|
||||||
|
@endcanany
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
@include('admin._scripts._click-content-enable', ['classParent' => 'tab-pane', 'classCheckbox' => 'content-enable', 'classContent' => 'language-content'])
|
||||||
|
@endpush
|
@ -0,0 +1,25 @@
|
|||||||
|
<div class="mb-4">
|
||||||
|
@can('create', \App\Models\Documentation::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.documentations.create', ['project' => $project->id, 'version' => $version->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||||
|
{{ __('Create') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
@can('viewAny', \App\Models\Documentation::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.documentations.index', ['project' => $project->id, 'version' => $version->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('List') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
@can('viewAny', \App\Models\DocumentationCategory::class)
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.categories.index', ['project' => $project->id, 'version' => $version->id]) }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0 1 12 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('admin-sections.Categories') }}
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Documentation'))
|
||||||
|
@section('h1')
|
||||||
|
{{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }}
|
||||||
|
@endsection
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentations._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Documentation') }}</h3>
|
||||||
|
<form method="post" action="{{ route('admin.projects.documentation-versions.documentations.store', ['project' => $project->id, 'version' => $version->id]) }}">
|
||||||
|
@include('admin.projects.documentations._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,20 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Documentation'))
|
||||||
|
@section('h1')
|
||||||
|
{{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }}
|
||||||
|
@endsection
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentations._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Documentation') }}</h3>
|
||||||
|
<form method="post" action="{{ route('admin.projects.documentation-versions.documentations.update', ['project' => $project->id, 'version' => $version->id, 'documentation' => $documentation->id]) }}">
|
||||||
|
@method('PUT')
|
||||||
|
@include('admin.projects.documentations._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-admin.layout>
|
@ -0,0 +1,67 @@
|
|||||||
|
@section('meta_title', __('admin-sections.Documentation'))
|
||||||
|
@section('h1')
|
||||||
|
{{ __('admin-sections.Project') . ': ' . $project->name . ' (' . $version->title . ')' }}
|
||||||
|
@endsection
|
||||||
|
<x-admin.layout>
|
||||||
|
@include('admin.projects.documentations._top')
|
||||||
|
<div class="card border-0 shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 id="category" class="mb-4">{{ __('admin-sections.Documentation') }}</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-centered table-nowrap mb-0 rounded">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.title') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.slug') }}</th>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.is_public') }}</th>
|
||||||
|
<th class="border-0 rounded-end" style="width: 150px"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($documentations as $documentation)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ $documentation->content?->title }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $documentation->slug }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($documentation->is_public)
|
||||||
|
{{ __('Yes') }}
|
||||||
|
@else
|
||||||
|
{{ __('No') }}
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.documentations.edit', ['project' => $project->id, 'version' => $version->id, 'documentation' => $documentation->id]) }}" class="btn btn-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Edit') }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
@can('delete', $documentation)
|
||||||
|
<form method="post" class="d-inline-block" action="{{ route('admin.projects.documentation-versions.documentations.destroy', ['project' => $project->id, 'version' => $version->id, 'documentation' => $documentation->id]) }}">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-danger click-confirm" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Delete') }}">
|
||||||
|
<svg data-slot="icon" fill="currentColor" width="20" height="20" class="align-text-center" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endcan
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="card-footer border-0">
|
||||||
|
{{ $documentations->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@push('scripts')
|
||||||
|
@include('admin._scripts._click-confirm', ['alert' => __('Do you want to delete?')])
|
||||||
|
@endpush
|
||||||
|
</x-admin.layout>
|
@ -12,6 +12,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@can('viewAny', \App\Models\DocumentationVersion::class)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.projects.documentation-versions.index', ['project' => $project->id]) }}" class="fw-bold">
|
||||||
|
<svg width="16" height="16" class="align-text-top" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ __('admin-sections.Documentation') }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endcan
|
||||||
@can('viewAny', \App\Models\ProjectContent::class)
|
@can('viewAny', \App\Models\ProjectContent::class)
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
@if(!is_null($notCheckedValue))
|
@if(!is_null($notCheckedValue))
|
||||||
<input type="hidden" name="{{ $name }}" value="{{ $notCheckedValue }}">
|
<input type="hidden" name="{{ $name }}" value="{{ $notCheckedValue }}">
|
||||||
@endif
|
@endif
|
||||||
<input class="form-check-input @error($requestName) is-invalid @enderror" name="{{ $name }}" type="checkbox" value="{{ $checkboxValue }}" @checked($checkboxValue === $userValue) id="form-checkbox-{{ $requestName }}">
|
<input class="form-check-input {{ $attributes->get('class') }} @error($requestName) is-invalid @enderror" name="{{ $name }}" type="checkbox" value="{{ $checkboxValue }}" @checked($checkboxValue === $userValue) id="form-checkbox-{{ $requestName }}" {{ $attributes }}>
|
||||||
<label class="form-check-label" for="form-checkbox-{{ $requestName }}">
|
<label class="form-check-label" for="form-checkbox-{{ $requestName }}">
|
||||||
{{ $title }}
|
{{ $title }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
@if(!empty($title) || !empty($example))
|
@if(!empty($title) || !empty($example) || !empty($allowedCharacters))
|
||||||
<label for="form-input-{{ $requestName }}">{{ $title }}
|
<label for="form-input-{{ $requestName }}">{{ $title }}
|
||||||
|
@if(!empty($allowedCharacters))
|
||||||
|
<span class="label__example">({{ __('allowed characters:') }} <strong>{{ $allowedCharacters }}</strong>)</span>
|
||||||
|
@endif
|
||||||
@if(!empty($example))
|
@if(!empty($example))
|
||||||
<span class="label__example">({{ __('example:') }} {!! $example !!})</span>
|
<span class="label__example">({{ __('example:') }} {!! $example !!})</span>
|
||||||
@endif
|
@endif
|
||||||
</label>
|
</label>
|
||||||
@endif
|
@endif
|
||||||
<input id="form-input-{{ $requestName }}" class="form-control @error($requestName) is-invalid @enderror" name="{{ $name }}" type="{{ $type }}" @if($type !== 'password') value="{{ $value }}" @endif {{ $attributes }}>
|
<input id="form-input-{{ $requestName }}" class="form-control {{ $attributes->get('class') }} @error($requestName) is-invalid @enderror" name="{{ $name }}" type="{{ $type }}" @if($type !== 'password') value="{{ $value }}" @endif {{ $attributes }}>
|
||||||
@error($requestName)
|
@error($requestName)
|
||||||
<span class="invalid-feedback">{{ $message }}</span>
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
@enderror
|
@enderror
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="form-textarea-wysiwyg-{{ $requestName }}">{{ $title }}</label>
|
<label for="form-textarea-wysiwyg-{{ $requestName }}">{{ $title }}</label>
|
||||||
<textarea class="form-control textarea-tinymce @error($requestName) is-invalid @enderror" name="{{ $name }}" id="form-textarea-wysiwyg-{{ $requestName }}" rows="3">{{ $value }}</textarea>
|
<textarea class="form-control {{ $attributes->get('class') }} textarea-tinymce @error($requestName) is-invalid @enderror" name="{{ $name }}" id="form-textarea-wysiwyg-{{ $requestName }}" rows="3">{{ $value }}</textarea>
|
||||||
@error($requestName)
|
@error($requestName)
|
||||||
<span class="invalid-feedback">{{ $message }}</span>
|
<span class="invalid-feedback">{{ $message }}</span>
|
||||||
@enderror
|
@enderror
|
||||||
@ -18,7 +18,26 @@
|
|||||||
plugins: 'advlist code emoticons link lists table codesample',
|
plugins: 'advlist code emoticons link lists table codesample',
|
||||||
toolbar: 'bold italic | bullist numlist | link emoticons codesample',
|
toolbar: 'bold italic | bullist numlist | link emoticons codesample',
|
||||||
referrer_policy: 'origin',
|
referrer_policy: 'origin',
|
||||||
|
codesample_global_prismjs: true,
|
||||||
|
codesample_languages: [
|
||||||
|
{text: 'HTML/XML', value: 'markup'},
|
||||||
|
{text: 'JavaScript', value: 'javascript'},
|
||||||
|
{text: 'CSS', value: 'css'},
|
||||||
|
{text: 'PHP', value: 'php'},
|
||||||
|
{text: 'Ruby', value: 'ruby'},
|
||||||
|
{text: 'Python', value: 'python'},
|
||||||
|
{text: 'Java', value: 'java'},
|
||||||
|
{text: 'C', value: 'c'},
|
||||||
|
{text: 'C#', value: 'csharp'},
|
||||||
|
{text: 'C++', value: 'cpp'},
|
||||||
|
{text: 'Go', value: 'go'},
|
||||||
|
{text: 'Nginx', value: 'nginx'},
|
||||||
|
{text: 'Docker', value: 'docker'},
|
||||||
|
{text: "Treeview", value: "treeview"},
|
||||||
|
{text: "Diff", value: "diff"},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@include('_prism')
|
||||||
@endpushonce
|
@endpushonce
|
||||||
|
@ -2,10 +2,14 @@
|
|||||||
@section('h1', $content->title)
|
@section('h1', $content->title)
|
||||||
|
|
||||||
<x-site.layout :project="$project" :websiteTranslations="$websiteTranslations">
|
<x-site.layout :project="$project" :websiteTranslations="$websiteTranslations">
|
||||||
{!! $content->description !!}
|
<div class="line-numbers">{!! $content->description !!}</div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
@foreach($links as $link)
|
@foreach($links as $link)
|
||||||
<p><a href="{{ $link->link }}" rel=“nofollow” target="_blank">{{ $link->title }}</a></p>
|
<p><a href="{{ $link->link }}" rel=“nofollow” target="_blank">{{ $link->title }}</a></p>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
@include('_prism')
|
||||||
|
@endpush
|
||||||
</x-site.layout>
|
</x-site.layout>
|
||||||
|
@ -33,6 +33,11 @@
|
|||||||
|
|
||||||
Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'project'])->name('feedbacks.index');
|
Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'project'])->name('feedbacks.index');
|
||||||
|
|
||||||
|
Route::resource('documentation-versions', \App\Http\Controllers\Admin\Projects\DocumentationVersionController::class)->where(['documentation_version' => '[0-9]+']);
|
||||||
|
Route::prefix('documentation-versions/{version}')->as('documentation-versions.')->group(function () {
|
||||||
|
Route::resource('documentations', \App\Http\Controllers\Admin\Projects\DocumentationsController::class)->except(['show'])->where(['documentation' => '[0-9]+']);
|
||||||
|
Route::resource('categories', \App\Http\Controllers\Admin\Projects\DocumentationCategoriesController::class)->except(['show'])->where(['category' => '[0-9]+']);
|
||||||
|
})->where(['version' => '[0-9]+']);
|
||||||
})->where(['project' => '[0-9]+']);
|
})->where(['project' => '[0-9]+']);
|
||||||
|
|
||||||
Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'index'])->name('feedbacks.index');
|
Route::get('feedbacks', [\App\Http\Controllers\Admin\Projects\FeedbacksController::class, 'index'])->name('feedbacks.index');
|
||||||
|
@ -13,6 +13,8 @@ export default defineConfig({
|
|||||||
|
|
||||||
'resources/site/scss/app.scss',
|
'resources/site/scss/app.scss',
|
||||||
'resources/site/js/app.js',
|
'resources/site/js/app.js',
|
||||||
|
|
||||||
|
'resources/prism/app.js',
|
||||||
],
|
],
|
||||||
refresh: true,
|
refresh: true,
|
||||||
}),
|
}),
|
||||||
|
Loading…
Reference in New Issue
Block a user