Merge branch 'issues' into 'develop'
Issues See merge request ldc-web/projets/volena!11
This commit is contained in:
commit
3a1a7e5fce
30 changed files with 1496 additions and 908 deletions
|
|
@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
|||
use App\Http\Requests\Bo\Sections\UpdateSectionRequest;
|
||||
use App\Models\Section;
|
||||
use App\Models\StaticPage;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
@ -88,7 +89,7 @@ class SectionController extends Controller
|
|||
* @param \App\Models\Section $section
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(UpdateSectionRequest $request, Section $section): \Illuminate\Http\RedirectResponse
|
||||
public function update(UpdateSectionRequest $request, Section $section): RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request, $section) {
|
||||
$section->fill(array_replace_recursive(
|
||||
|
|
@ -109,4 +110,38 @@ class SectionController extends Controller
|
|||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Section add PartPiece
|
||||
*
|
||||
* @param \App\Models\Section $section
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function addPartPiece(Section $section): RedirectResponse
|
||||
{
|
||||
$section->addPartPiece();
|
||||
$section->saveOrFail();
|
||||
return response()->redirectToRoute('bo.sections.edit', $section)
|
||||
->with('success', trans('Partie ajouté'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Section remove PartPiece
|
||||
*
|
||||
* @param \App\Models\Section $section
|
||||
* @param integer $index
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function removePartPiece(Section $section, int $index): RedirectResponse
|
||||
{
|
||||
$count = $section->removePartPiece($index);
|
||||
$section->saveOrFail();
|
||||
$response = response()->redirectToRoute('bo.sections.edit', $section);
|
||||
if ($count) {
|
||||
$response->with('error', trans('Partie enlevé'));
|
||||
} else {
|
||||
$response->with('info', trans('Aucune partie enlevé'));
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class RedirectIfAuthenticated
|
|||
return \redirect()->route('fo.home.show');
|
||||
case 'backend':
|
||||
if (!$request->routeIs('bo.*')) {
|
||||
continue;
|
||||
continue 2;
|
||||
}
|
||||
return \redirect()->route('bo.dashboard');
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -133,15 +133,10 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
case InputType::url->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
'required', 'url',
|
||||
$this->compileValidate('url', $input),
|
||||
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
try {
|
||||
ToolboxHelper::assertNoInternalUrls($attribute, $value);
|
||||
ToolboxHelper::assertNoInternalBoUrls($attribute, $value);
|
||||
} catch (ValidationException $e) {
|
||||
$fail(collect($e->errors())->first()[0]);
|
||||
}
|
||||
$this->validateUrl($attribute, $value, $fail);
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
|
@ -184,18 +179,32 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
}
|
||||
}
|
||||
|
||||
$test = \collect($inputTypeValidate)->all();
|
||||
$test = \collect($inputTypeValidate)->sort(function ($valueA, $valueB) {
|
||||
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
|
||||
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
|
||||
return $valueA - $valueB;
|
||||
})->unique()->all();
|
||||
|
||||
return \collect($inputTypeValidate)->unique()->sort(function ($valueA, $valueB) {
|
||||
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
|
||||
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
|
||||
return $valueA - $valueB;
|
||||
})->all();
|
||||
})->values()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate URl
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $url
|
||||
* @param \Closure $fail
|
||||
* @return void
|
||||
*/
|
||||
private function validateUrl(string $attribute, mixed $url, \Closure $fail): void
|
||||
{
|
||||
try {
|
||||
if (!is_null($url)) {
|
||||
$url = is_string($url) ? $url : '';
|
||||
ToolboxHelper::assertNoInternalUrls($attribute, $url);
|
||||
ToolboxHelper::assertNoInternalBoUrls($attribute, $url);
|
||||
}
|
||||
} catch (ValidationException $e) {
|
||||
$fail(collect($e->errors())->first()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use Illuminate\Support\Str;
|
|||
* @property \App\Enums\Sections\PageSectionEnum $section Slider that contains the slide.
|
||||
* @property string $h2_title Title.
|
||||
* @property string $text WYSIWYG - Content.
|
||||
* @property string $data Minicrud content.
|
||||
* @property array $data Minicrud content.
|
||||
* @property-read \Carbon\Carbon $created_at Created date.
|
||||
* @property-read \Carbon\Carbon $updated_at Updated date.
|
||||
*
|
||||
|
|
@ -53,7 +53,7 @@ class Section extends Model
|
|||
*/
|
||||
protected $casts = [
|
||||
'section' => PageSectionEnum::class,
|
||||
'data' => 'object',
|
||||
'data' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -138,7 +138,7 @@ class Section extends Model
|
|||
bool $required = false,
|
||||
string $name = "URL à ouvrir",
|
||||
): array {
|
||||
$data["bloc_link"] = [
|
||||
$data[sprintf('bloc_%s', Str::of($name)->snake()->ascii()->remove(['\'', '-']))] = [
|
||||
'label' => $name,
|
||||
'inputType' => InputType::url->value,
|
||||
'value' => $url,
|
||||
|
|
@ -265,6 +265,160 @@ class Section extends Model
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add part piece
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPartPiece(): void
|
||||
{
|
||||
$pieceTemplateLinesCount = self::countPartPieces();
|
||||
|
||||
$pieceToAdd = self::getAddablePartPieceBlock(
|
||||
$this->section,
|
||||
$this->countPartPieces() + 1
|
||||
);
|
||||
|
||||
$linesToSkip = 1;
|
||||
$data = collect($this->data);
|
||||
$groupedPieces = $data->skip($linesToSkip)->splitIn($data->count() / ($pieceTemplateLinesCount ?: 1))
|
||||
->concat([$pieceToAdd])
|
||||
->sort(function ($aCollect, $bCollect) {
|
||||
if ($this->section->equals(PageSectionEnum::produce_rhd)) {
|
||||
return strcmp(
|
||||
strrev(collect(collect($aCollect)->first())->first()),
|
||||
strrev(collect(collect($bCollect)->first())->first())
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
$groupedPieces->prepend([$data->keys()->first() => $data->first()]);
|
||||
$this->data = $groupedPieces->collapse()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove part piece
|
||||
*
|
||||
* @param integer $index
|
||||
* @return integer Elements count removed.
|
||||
*/
|
||||
public function removePartPiece(int $index): int
|
||||
{
|
||||
$pieceTemplate = self::getPartPieces();
|
||||
$pieceTemplateLinesCount = self::countPartPieces();
|
||||
if (!$pieceTemplateLinesCount) {
|
||||
return 0;
|
||||
}
|
||||
$linesToSkip = 1;
|
||||
$data = collect($this->data);
|
||||
$groupedPieces = $data->skip($linesToSkip)->splitIn($data->count() / count($pieceTemplate));
|
||||
$removedGroup = $groupedPieces->splice($index - 1, 1);
|
||||
$this->data = $groupedPieces->collapse()
|
||||
->prepend($data->first(), $data->keys()->first())
|
||||
->all();
|
||||
return $removedGroup->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count part pieces
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function countPartPieces(): int
|
||||
{
|
||||
$pieceTemplate = self::getPartPieces();
|
||||
$data = collect($this->data);
|
||||
$templateKeys = collect($pieceTemplate)->keys();
|
||||
$dataCount = $data->count();
|
||||
$repetitionCount = 0;
|
||||
for ($i = 1; $i <= $dataCount; $i++) {
|
||||
$indexKeys = $templateKeys->map(fn ($key) => str_replace('__index', strval($i), $key));
|
||||
if ($indexKeys->every(fn ($key) => $data->has($key))) {
|
||||
$repetitionCount++;
|
||||
}
|
||||
}
|
||||
return $repetitionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get part pieces
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getPartPieces(): array
|
||||
{
|
||||
$pieceTemplate = self::getAddablePartPieceBlockTemplate($this->section);
|
||||
return is_null($pieceTemplate) ? [] : $pieceTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addable part piece block
|
||||
*
|
||||
* @param \App\Enums\Sections\PageSectionEnum $sectionEnum
|
||||
* @param integer $index
|
||||
* @return array
|
||||
*/
|
||||
public static function getAddablePartPieceBlock(PageSectionEnum $sectionEnum, int $index = 1): array|null
|
||||
{
|
||||
$data = null;
|
||||
$pieceTemplate = self::getAddablePartPieceBlockTemplate($sectionEnum);
|
||||
if (!is_null($pieceTemplate)) {
|
||||
$data = json_decode(Str::of(json_encode($pieceTemplate))->replace('__index', strval($index)), true);
|
||||
/** @var array $data */
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addable part piece block
|
||||
*
|
||||
* @param \App\Enums\Sections\PageSectionEnum $sectionEnum
|
||||
* @return array
|
||||
*/
|
||||
public static function getAddablePartPieceBlockIndexes(PageSectionEnum $sectionEnum): array|null
|
||||
{
|
||||
$data = self::getAddablePartPieceBlockTemplate($sectionEnum);
|
||||
if (!is_null($data)) {
|
||||
return array_keys($data);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\Enums\Sections\PageSectionEnum $sectionEnum
|
||||
* @return array|null
|
||||
*/
|
||||
private static function getAddablePartPieceBlockTemplate(PageSectionEnum $sectionEnum): array|null
|
||||
{
|
||||
$data = null;
|
||||
switch ($sectionEnum) {
|
||||
case PageSectionEnum::produce_export:
|
||||
$data = array_merge(
|
||||
self::emptyWysiwygBloc(
|
||||
[],
|
||||
"accompagnant l'image __index de la partie",
|
||||
''
|
||||
),
|
||||
self::emptyPictureBloc([], '', '', '', 100, 100, true, "Image __index de la partie"),
|
||||
self::emptyLinkBloc([], '', false, "Lien de l'image__index de la partie")
|
||||
);
|
||||
break;
|
||||
case PageSectionEnum::produce_rhd:
|
||||
$data = array_merge(
|
||||
self::emptyWysiwygBloc(
|
||||
[],
|
||||
"accompagnant l'image __index de la partie 1",
|
||||
''
|
||||
),
|
||||
self::emptyPictureBloc([], '', '', '', 100, 100, true, "Image __index de la partie 1"),
|
||||
self::emptyLinkBloc([], '', false, "Lien de l'image__index de la partie 1")
|
||||
);
|
||||
break;
|
||||
default:
|
||||
} //end switch
|
||||
return $data;
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Section;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
|
|
@ -30,4 +31,32 @@ class SectionPolicy
|
|||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can add part piece
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Section $section
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
*/
|
||||
public function addPartPiece(User $authUser, Section $section): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return Section::getAddablePartPieceBlock($section->section) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can remove part piece
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Section $section
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
*/
|
||||
public function removePartPiece(User $authUser, Section $section): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return Section::getAddablePartPieceBlock($section->section) !== null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,9 @@ trait MiniCrudModel
|
|||
case InputType::string->value:
|
||||
case InputType::text->value:
|
||||
case InputType::url->value:
|
||||
ToolboxHelper::assertNoInternalBoUrls('content', $input['value']);
|
||||
if (!is_null($input['value'])) {
|
||||
ToolboxHelper::assertNoInternalBoUrls('content', $input['value']);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \ErrorException(sprintf(
|
||||
|
|
|
|||
BIN
database/assets/schema.png
Normal file
BIN
database/assets/schema.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Sections\PageSectionEnum;
|
||||
use App\Models\Section;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Collection::macro('insertAfter', function ($target, $value, $new_key = null) {
|
||||
if (is_int($target)) {
|
||||
$this->items = array_merge(array_slice($this->items, 0, $target + 1), [$value], array_slice($this->items, $target + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->items as $key => $old) {
|
||||
$new[$key] = $old;
|
||||
if ($key === $target && is_null($new_key)) {
|
||||
$new[] = $value;
|
||||
} elseif ($key === $target) {
|
||||
$new[$new_key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $new;
|
||||
});
|
||||
|
||||
$export = Section::query()->where('section', PageSectionEnum::produce_export)->first();
|
||||
if ($export) {
|
||||
$partsAmount = 3;
|
||||
$data = collect($export->data);
|
||||
for ($i = 1; $i <= $partsAmount; $i++) {
|
||||
$bloc = collect(Section::emptyLinkBloc(
|
||||
[],
|
||||
'',
|
||||
false,
|
||||
"Lien de l'image {$i} de la partie"
|
||||
))->first();
|
||||
$data->insertAfter(
|
||||
"bloc_image{$i}_de_la_partie",
|
||||
$bloc,
|
||||
"bloc_lien_de_limage{$i}_de_la_partie"
|
||||
);
|
||||
}
|
||||
$export->data = $data->all();
|
||||
$export->saveQuietly();
|
||||
}
|
||||
$rhd = Section::query()->where('section', PageSectionEnum::produce_rhd)->first();
|
||||
if ($rhd) {
|
||||
$getEmptyLinkBlock = fn ($i, $k) => collect(Section::emptyLinkBloc(
|
||||
[],
|
||||
'',
|
||||
false,
|
||||
"Lien de l'image {$i} de la partie {$k}"
|
||||
))->first();
|
||||
|
||||
$data = collect($rhd->data);
|
||||
$data->insertAfter(
|
||||
'bloc_image1_de_la_partie1',
|
||||
$getEmptyLinkBlock(1, 1),
|
||||
'bloc_lien_de_limage1_de_la_partie1'
|
||||
);
|
||||
$data->insertAfter(
|
||||
'bloc_image2_de_la_partie1',
|
||||
$getEmptyLinkBlock(2, 1),
|
||||
'bloc_lien_de_limage2_de_la_partie1'
|
||||
);
|
||||
$data->insertAfter(
|
||||
'bloc_image3_de_la_partie1',
|
||||
$getEmptyLinkBlock(3, 1),
|
||||
'bloc_lien_de_limage3_de_la_partie1'
|
||||
);
|
||||
$data->insertAfter(
|
||||
'bloc_image1_de_la_partie2',
|
||||
$getEmptyLinkBlock(1, 2),
|
||||
'bloc_lien_de_limage1_de_la_partie2'
|
||||
);
|
||||
$rhd->data = $data->all();
|
||||
$rhd->saveQuietly();
|
||||
} //end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$export = Section::query()->where('section', PageSectionEnum::produce_export)->first();
|
||||
if ($export) {
|
||||
$partsAmount = 3;
|
||||
$data = collect($export->data);
|
||||
for ($i = 1; $i <= $partsAmount; $i++) {
|
||||
$data->forget("bloc_lien_de_limage{$i}_de_la_partie");
|
||||
}
|
||||
$export->data = $data->all();
|
||||
$export->saveQuietly();
|
||||
}
|
||||
$rhd = Section::query()->where('section', PageSectionEnum::produce_rhd)->first();
|
||||
if ($rhd) {
|
||||
$data = collect($rhd->data);
|
||||
$data->forget('bloc_lien_de_limage1_de_la_partie1');
|
||||
$data->forget('bloc_lien_de_limage2_de_la_partie1');
|
||||
$data->forget('bloc_lien_de_limage3_de_la_partie1');
|
||||
$data->forget('bloc_lien_de_limage1_de_la_partie2');
|
||||
$rhd->data = $data->all();
|
||||
$rhd->saveQuietly();
|
||||
} //end if
|
||||
}
|
||||
};
|
||||
71
database/migrations/2024_06_21_145915_qa_section.php
Normal file
71
database/migrations/2024_06_21_145915_qa_section.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\Sections\PageSectionEnum;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Section;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Collection::macro('insertAfter', function ($target, $value, $new_key = null) {
|
||||
if (is_int($target)) {
|
||||
$this->items = array_merge(array_slice($this->items, 0, $target + 1), [$value], array_slice($this->items, $target + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->items as $key => $old) {
|
||||
$new[$key] = $old;
|
||||
if ($key === $target && is_null($new_key)) {
|
||||
$new[] = $value;
|
||||
} elseif ($key === $target) {
|
||||
$new[$new_key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items = $new;
|
||||
});
|
||||
|
||||
$quality = Section::query()->where('section', PageSectionEnum::produce_quality)->first();
|
||||
if ($quality) {
|
||||
$data = collect($quality->data);
|
||||
$data->insertAfter(
|
||||
'bloc_text_de_la_partie2_content',
|
||||
collect(Section::emptyPictureBloc([], FileStorageHelper::storeFile(
|
||||
new Section(),
|
||||
new \SplFileInfo(\resource_path(
|
||||
'../database/assets/schema.png'
|
||||
))
|
||||
), fake()->sentence(), fake()->sentence(), 1208, 374))->first(),
|
||||
'bloc_image_de_la_partie2_content'
|
||||
);
|
||||
$quality->data = $data->all();
|
||||
$quality->saveQuietly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$rhd = Section::query()->where('section', PageSectionEnum::produce_rhd)->first();
|
||||
if ($rhd) {
|
||||
$data = collect($rhd->data);
|
||||
$data->forget('bloc_lien_de_limage1_de_la_partie1');
|
||||
$rhd->data = $data->all();
|
||||
$rhd->saveQuietly();
|
||||
} //end if
|
||||
}
|
||||
};
|
||||
|
|
@ -465,10 +465,6 @@ class SectionSeeder extends Seeder
|
|||
'../database/factories/assets/sections/default-picture-100x100.png'
|
||||
))
|
||||
), fake()->sentence(), fake()->sentence(), 200, 200);
|
||||
$data = array_merge(
|
||||
$data,
|
||||
$section->emptyWysiwygBloc([], 'accompagnant l\'image', clean(\fake()->randomHtml()))
|
||||
);
|
||||
$data = array_merge($data, $section->emptyWysiwygBloc([], 'text', clean(\fake()->randomHtml())));
|
||||
return $data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@
|
|||
@include media-breakpoint-up(md) {
|
||||
hyphens: initial;
|
||||
}
|
||||
|
||||
h2,h3,h4,.h2,.h3,.h4 {
|
||||
color: $color-sixt !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -30,4 +30,28 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Popup delete item.
|
||||
*/
|
||||
const elementsLinks = document.getElementsByClassName(
|
||||
"confirmDeleteByLinkTS"
|
||||
) as HTMLCollectionOf<Element>;
|
||||
for (const element of elementsLinks) {
|
||||
element.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
const el = e.target;
|
||||
sweetalert.methods.confirm(
|
||||
"Êtes-vous sûr ?",
|
||||
"Toutes les données vont être perdu",
|
||||
el as HTMLElement,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function (response: SweetAlertResult<any>) {
|
||||
window.location.href = (el as HTMLAnchorElement).href;
|
||||
},
|
||||
{ icon: "warning" }
|
||||
);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ import type VueTagsInput from "@sipec/vue3-tags-input";
|
|||
import { VueTagsInput as VueTagsInputCls } from "@sipec/vue3-tags-input";
|
||||
import slugify from "slugify";
|
||||
import { defineComponent } from "vue";
|
||||
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
|
||||
import route from "./../../modules/route";
|
||||
import trans from "./../../modules/trans";
|
||||
import parseLaravelErrorMessages from "../../modules/parseLaravelErrorMessages";
|
||||
import route from "../../modules/route";
|
||||
import trans from "../../modules/trans";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BelongsToManyDropdownComponent",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,108 +1,103 @@
|
|||
<template>
|
||||
<WordCounter
|
||||
:id="intId"
|
||||
:id="id"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<textarea
|
||||
ref="textarea"
|
||||
:id="intId"
|
||||
:class="`input-text-auto-component ${intClass}`"
|
||||
:name="intName"
|
||||
:aria-describedby="intAriaDescribedby"
|
||||
:placeholder="intPlaceholder"
|
||||
:required="intRequired"
|
||||
@input="onInput"
|
||||
:id="id"
|
||||
:class="`input-text-auto-component ${classPath}`"
|
||||
:name="name"
|
||||
:aria-describedby="ariaDescribedby"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
@input="styleElement(checkInput())"
|
||||
@blur="onBlur"
|
||||
v-model="intValue"
|
||||
v-model="value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import trans from "./../../modules/trans";
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onBeforeMount, onMounted, ref, useAttrs } from "vue";
|
||||
import WordCounter from "./WordCounterComponent.vue";
|
||||
|
||||
const textarea = ref<HTMLTextAreaElement | null>(null);
|
||||
|
||||
export default defineComponent({
|
||||
name: "InputTextAutoComponent",
|
||||
inheritAttrs: false,
|
||||
mixins: [trans],
|
||||
components: {
|
||||
WordCounter,
|
||||
},
|
||||
setup() {
|
||||
return { textarea };
|
||||
},
|
||||
beforeMount() {
|
||||
if (!this.$attrs.json) {
|
||||
throw new Error("this component requires json");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
this.intId = String(data.id);
|
||||
this.intClass = String(data.class);
|
||||
this.intName = String(data.name);
|
||||
this.intPlaceholder = String(data.placeholder);
|
||||
this.intAriaDescribedby = String(data.ariaDescribedby);
|
||||
this.intValue = String(data.value);
|
||||
if (data.required) {
|
||||
this.intRequired = true;
|
||||
}
|
||||
if (this.textarea) {
|
||||
// Default input init
|
||||
this.textarea.setAttribute("style", "height: 1rem;overflow-y: hidden;");
|
||||
}
|
||||
// * Init input
|
||||
this.$nextTick(this.onInput);
|
||||
},
|
||||
data(): {
|
||||
intId: string;
|
||||
intClass: string;
|
||||
intName: string;
|
||||
intPlaceholder: string;
|
||||
intAriaDescribedby: string;
|
||||
intRequired: boolean;
|
||||
intValue: string;
|
||||
} {
|
||||
return {
|
||||
intId: "",
|
||||
intClass: "",
|
||||
intName: "",
|
||||
intPlaceholder: "",
|
||||
intAriaDescribedby: "",
|
||||
intRequired: false,
|
||||
intValue: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onBlur() {
|
||||
const el = textarea.value;
|
||||
if (!el) {
|
||||
throw new Error("undefined textarea element");
|
||||
}
|
||||
// * Remove every new line char
|
||||
el.value = el.value.replace(/[\n\r]/g, "");
|
||||
this.$nextTick(() => {
|
||||
el.style.height = "auto";
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
});
|
||||
},
|
||||
onInput() {
|
||||
const el = textarea.value;
|
||||
if (!el) {
|
||||
throw new Error("undefined textarea element");
|
||||
}
|
||||
el.style.height = "auto";
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
},
|
||||
},
|
||||
defineOptions({
|
||||
name: "InputTextAutoComponent"
|
||||
});
|
||||
|
||||
// * ATTRIBUTES
|
||||
const attrs = useAttrs();
|
||||
|
||||
// * REFS
|
||||
const textarea = ref<HTMLTextAreaElement|null>(null);
|
||||
|
||||
// * DATA
|
||||
/** Hack to set the id for wordCounterComponent. */
|
||||
const id = ref<string>(JSON.parse(String(attrs.json ?? "{}")).id);
|
||||
const classPath = ref<string>("");
|
||||
const name = ref<string>("");
|
||||
const placeholder = ref<string>("");
|
||||
const ariaDescribedby = ref<string>("");
|
||||
const value = ref<string>("");
|
||||
const required = ref<boolean>(true);
|
||||
|
||||
// * COMPUTED
|
||||
const isUsedWithProps = computed<boolean>(() => attrs.json === undefined);
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (isUsedWithProps.value) {
|
||||
throw new Error("this component requires json");
|
||||
}
|
||||
});
|
||||
|
||||
// * LIFECYCLE
|
||||
onMounted(() => {
|
||||
const json = String(attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
if (!isUsedWithProps.value) {
|
||||
id.value = String(data.id);
|
||||
classPath.value = String(data.class);
|
||||
name.value = String(data.name);
|
||||
placeholder.value = String(data.placeholder);
|
||||
ariaDescribedby.value = String(data.ariaDescribedby);
|
||||
value.value = String(data.value);
|
||||
required.value = data.required;
|
||||
textarea.value?.setAttribute("style", "height:1rem;overflow-y:hidden;");
|
||||
}
|
||||
nextTick(() => {
|
||||
styleElement(checkInput());
|
||||
});
|
||||
});
|
||||
|
||||
// * METHODS
|
||||
|
||||
/** When the textarea get the focus. */
|
||||
function onBlur() {
|
||||
const el = checkInput();
|
||||
/** Remove every new line char */
|
||||
el.value = el.value.replace(/[\n\r]/g, "");
|
||||
nextTick(() => {
|
||||
styleElement(el);
|
||||
});
|
||||
}
|
||||
|
||||
/** Check if the element is an HTMLTextAreaElement. */
|
||||
function checkInput(): HTMLTextAreaElement {
|
||||
const el = textarea.value;
|
||||
if (!el) {
|
||||
throw new Error("undefined textarea element");
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
/** Style the textarea element. */
|
||||
function styleElement(el: HTMLTextAreaElement): void {
|
||||
el.style.height = "auto";
|
||||
el.style.height = `${el.scrollHeight}px`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scopped>
|
||||
.input-text-auto-component {
|
||||
resize: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,36 @@
|
|||
<template>
|
||||
<button
|
||||
ref="passwordVisibility"
|
||||
@click.prevent="toggleVisibility()"
|
||||
@keydown="keydown"
|
||||
:class="`btn btn-primary rounded-0 rounded-end password-visibility-${inputId} h-100`"
|
||||
:class="`btn btn-primary rounded-0 rounded-end password-visibility password-visibility-${id} h-100 border-0 p-0 overflow-hidden`"
|
||||
:aria-pressed="visible"
|
||||
>
|
||||
<span
|
||||
class="text-bg-primary reveal d-flex justify-content-center align-items-center h-100"
|
||||
|
||||
:class="{
|
||||
'text-bg-primary reveal d-flex justify-content-center align-items-center h-100': true,
|
||||
'd-none': visible
|
||||
}"
|
||||
type="button"
|
||||
:title="visible ? __('vue.PasswordVisibility.hide') : __('vue.PasswordVisibility.show')"
|
||||
:title="trans.methods.__('vue.PasswordVisibility.hide')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
|
||||
v-if="visible"
|
||||
icon="fas fa-eye-slash"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
|
||||
</span>
|
||||
<span
|
||||
ref="passwordVisibilitySpan"
|
||||
:class="{
|
||||
'text-bg-primary reveal d-flex justify-content-center align-items-center h-100': true,
|
||||
'd-none': !visible
|
||||
}"
|
||||
type="button"
|
||||
:title="trans.methods.__('vue.PasswordVisibility.show')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
v-else
|
||||
icon="fas fa-eye"
|
||||
:aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -28,84 +38,85 @@
|
|||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { defineComponent } from "vue";
|
||||
import bsTooltips from "./../../modules/bsTooltips";
|
||||
import trans from "./../../modules/trans";
|
||||
import { onMounted, ref, useAttrs } from "vue";
|
||||
import { Tooltips } from "../../modules/bsTooltips";
|
||||
import trans from "../../modules/trans";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PasswordVisibility",
|
||||
mixins: [bsTooltips, trans],
|
||||
components: { FontAwesomeIcon },
|
||||
data(): {
|
||||
inputId: string;
|
||||
input: HTMLInputElement;
|
||||
visible: boolean;
|
||||
tooltipList: HTMLButtonElement[];
|
||||
} {
|
||||
return {
|
||||
inputId: "",
|
||||
input: document.createElement("input"),
|
||||
visible: false,
|
||||
tooltipList: [],
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
this.inputId = data.inputId;
|
||||
this.input = document.getElementById(this.inputId) as HTMLInputElement;
|
||||
if (!this.input) {
|
||||
throw new Error(`Cannot find input id '${this.inputId}'`);
|
||||
}
|
||||
if (this.input.tagName !== "INPUT") {
|
||||
throw new Error(`id '${this.inputId}' is not an input`);
|
||||
}
|
||||
if ((this.input as HTMLInputElement).type !== "password") {
|
||||
throw new Error(`id '${this.inputId}' is not an input password`);
|
||||
}
|
||||
this.input = this.input as HTMLInputElement;
|
||||
this.$nextTick(() => {
|
||||
this.initTooltips();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
keydown(e:KeyboardEvent):void {
|
||||
if (
|
||||
e.key === " " ||
|
||||
e.code === "Space" ||
|
||||
e.keyCode === 32 ||
|
||||
e.key === "Enter" ||
|
||||
e.code === "Enter" ||
|
||||
e.keyCode === 13
|
||||
) {
|
||||
this.toggleVisibility();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update password status (visible, hidden).
|
||||
*/
|
||||
toggleVisibility(): void {
|
||||
if (this.input.type === "password") {
|
||||
this.input.type = "text";
|
||||
this.visible = true;
|
||||
} else {
|
||||
this.input.type = "password";
|
||||
this.visible = false;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.initTooltips();
|
||||
});
|
||||
},
|
||||
},
|
||||
defineOptions({
|
||||
name: "PasswordVisibility"
|
||||
});
|
||||
|
||||
// * ATTRIBUTES
|
||||
const attrs = useAttrs();
|
||||
|
||||
// * REFS
|
||||
const passwordVisibility = ref<HTMLButtonElement|null>(null);
|
||||
|
||||
// * DATA
|
||||
const id = ref<string>("");
|
||||
const input = ref<HTMLInputElement>(document.createElement("input"));
|
||||
const visible = ref<boolean>(true);
|
||||
const tooltips = ref<Tooltips | null>(null);
|
||||
|
||||
// * LIFECYCLE
|
||||
onMounted(() => {
|
||||
const json = String(attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
id.value = data.id;
|
||||
input.value = document.getElementById(id.value) as HTMLInputElement;
|
||||
if (!input.value) {
|
||||
throw new Error(`Cannot find input id '${id.value}'`);
|
||||
}
|
||||
if (input.value.tagName !== "INPUT") {
|
||||
throw new Error(`id '${id.value}' is not an input`);
|
||||
}
|
||||
if ((input.value as HTMLInputElement).type !== "password") {
|
||||
throw new Error(`id '${id.value}' is not an input password`);
|
||||
}
|
||||
initTooltips();
|
||||
});
|
||||
|
||||
// * METHODS
|
||||
|
||||
/** Set event on key down. */
|
||||
function keydown(e: KeyboardEvent): void {
|
||||
if (
|
||||
e.key === " " ||
|
||||
e.code === "Space" ||
|
||||
e.keyCode === 32 ||
|
||||
e.key === "Enter" ||
|
||||
e.code === "Enter" ||
|
||||
e.keyCode === 13
|
||||
) {
|
||||
toggleVisibility();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/** Update password status (visible, hidden). */
|
||||
function toggleVisibility(): void {
|
||||
input.value.type = (input.value.type === "password") ? "text" : "password";
|
||||
visible.value = (input.value.type === "password") ? true : false;
|
||||
}
|
||||
|
||||
/** Initialise all tooltips in the component. */
|
||||
function initTooltips(): void {
|
||||
setTimeout(() => {
|
||||
tooltips.value = Tooltips.make({
|
||||
type: "dom",
|
||||
elements: passwordVisibility.value!.querySelectorAll("[data-bs-toggle=\"tooltip\"]")
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.reveal {
|
||||
<style lang="scss" scopped>
|
||||
.password-visibility {
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
.password-visibility .reveal {
|
||||
width: 52px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import trans from "./../../modules/trans";
|
||||
import trans from "../../modules/trans";
|
||||
|
||||
export default defineComponent({
|
||||
name: "WordCounterComponent",
|
||||
|
|
@ -106,8 +106,6 @@ export default defineComponent({
|
|||
<style lang="scss">
|
||||
#word-counter + input[type="text"],
|
||||
.word-counter + input[type="text"],
|
||||
#word-counter + input[type="url"],
|
||||
.word-counter + input[type="url"],
|
||||
#word-counter + textarea,
|
||||
.word-counter + textarea,
|
||||
#word-counter-varchar + textarea,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import axios from "axios";
|
||||
import * as bootstrap from "bootstrap";
|
||||
import bsTooltips from "./../../modules/bsTooltips";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
|
|
@ -18,4 +18,4 @@ axios.defaults.headers.common = {
|
|||
window.axios = axios;
|
||||
window.bootstrap = bootstrap;
|
||||
|
||||
bsTooltips.methods.initTooltips();
|
||||
Tooltips.make();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import axios from "axios";
|
||||
import * as bootstrap from "bootstrap";
|
||||
import bsTooltips from "./../../modules/bsTooltips";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
|
|
@ -18,4 +18,4 @@ axios.defaults.headers.common = {
|
|||
window.axios = axios;
|
||||
window.bootstrap = bootstrap;
|
||||
|
||||
bsTooltips.methods.initTooltips();
|
||||
Tooltips.make();
|
||||
|
|
|
|||
1
resources/ts/index.d.ts
vendored
1
resources/ts/index.d.ts
vendored
|
|
@ -69,6 +69,7 @@ declare global {
|
|||
type LaravelModelList = ModelList;
|
||||
type LaravelPaginator = Paginator;
|
||||
type LaravelPaginatorLink = PaginatorLink;
|
||||
type ValueOf<T> = T[keyof T];
|
||||
|
||||
type Enum = EnumInterface;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,114 @@
|
|||
import type * as bootstrap from "bootstrap";
|
||||
import { Tooltip } from "bootstrap";
|
||||
|
||||
let tooltips: Array<bootstrap.Tooltip> = [];
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
/**
|
||||
* Initialize all Bootstrap tooltips.
|
||||
*/
|
||||
initTooltips() {
|
||||
this.disableBootstrapTooltip();
|
||||
tooltips = [].slice
|
||||
.call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]"))
|
||||
.map(function (tooltipTriggerEl) {
|
||||
return new window.bootstrap.Tooltip(tooltipTriggerEl, {
|
||||
customClass: "large-tooltip",
|
||||
delay: {
|
||||
show: 1250,
|
||||
hide: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
type ConstructorTypeParent = "parent";
|
||||
type ConstructorTypeDom = "dom";
|
||||
|
||||
interface ParentSelectorTooltips {
|
||||
type: ConstructorTypeParent
|
||||
/** The parent element that contains Dom elements for which tooltips shall be created. */
|
||||
parentSelector: string;
|
||||
}
|
||||
|
||||
interface DomProvidedTooltips {
|
||||
type: ConstructorTypeDom;
|
||||
/** Dom elements for which tooltips shall be created. */
|
||||
elements: HTMLElement | NodeListOf<HTMLElement>;
|
||||
}
|
||||
|
||||
interface TooltipsOptions {
|
||||
/** Tooltip options. */
|
||||
options?: Partial<Tooltip.Options>|null|undefined;
|
||||
}
|
||||
|
||||
export type TooltipsConstructor = TooltipsOptions & (ParentSelectorTooltips|DomProvidedTooltips);
|
||||
|
||||
export class Tooltips {
|
||||
/** BS tooltips instances */
|
||||
private tooltips: Array<Tooltip> = [];
|
||||
|
||||
/** The parent element that contains Dom elements for which tooltips shall be created. */
|
||||
private parentSelector: string | null = null;
|
||||
|
||||
/** Dom elements for which tooltips shall be created. */
|
||||
private elements: Array<HTMLElement> = [];
|
||||
|
||||
/** Tooltip default options. */
|
||||
private static defaultOptions: Partial<Tooltip.Options> = {
|
||||
delay: {
|
||||
show: 1250,
|
||||
hide: 0,
|
||||
},
|
||||
/**
|
||||
* Disable all Bootstrap tooltips.
|
||||
*/
|
||||
disableBootstrapTooltip() {
|
||||
tooltips.forEach((tooltip) => {
|
||||
tooltip.disable();
|
||||
tooltip.hide();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Close all Bootstrap tooltips.
|
||||
*/
|
||||
closeBootstrapTooltip() {
|
||||
[].slice
|
||||
.call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]"))
|
||||
.forEach((tooltip: HTMLElement) => {
|
||||
tooltip.blur();
|
||||
window.bootstrap.Tooltip.getInstance(tooltip)?.hide();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/** Tooltip options. */
|
||||
private options: Partial<Tooltip.Options> = {};
|
||||
|
||||
/**
|
||||
* @param parentSelector
|
||||
* @param options Tooltip options.
|
||||
*/
|
||||
constructor(params: TooltipsConstructor) {
|
||||
if (params.type === "parent") {
|
||||
this.parentSelectorConstructor(params);
|
||||
}
|
||||
if (params.type === "dom") {
|
||||
this.domPrividedTooltipsContructor(params);
|
||||
}
|
||||
}
|
||||
|
||||
public static make(params: TooltipsConstructor = { type: "parent", parentSelector: "body"}
|
||||
) {
|
||||
const fullParams = { ...{ type: "parent", options: {} }, ...params };
|
||||
return new Tooltips(fullParams);
|
||||
}
|
||||
|
||||
private parentSelectorConstructor({ parentSelector, options }: TooltipsOptions & ParentSelectorTooltips) {
|
||||
this.parentSelector = parentSelector;
|
||||
this.options = { ...Tooltips.defaultOptions, ...options };
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
private domPrividedTooltipsContructor({ elements, options }: TooltipsOptions & DomProvidedTooltips) {
|
||||
if (elements instanceof HTMLElement) {
|
||||
this.elements = [elements];
|
||||
} else {
|
||||
this.elements = Array.from(elements);
|
||||
}
|
||||
this.options = { ...Tooltips.defaultOptions, ...options };
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all Bootstrap tooltips and reinstanciate them.
|
||||
*/
|
||||
public refreshTooltips(): void {
|
||||
this.closeBootstrapTooltip();
|
||||
this.initTooltips();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all Bootstrap tooltips for ever.
|
||||
*/
|
||||
public closeBootstrapTooltip(): void {
|
||||
this.tooltips.forEach((tooltip) => {
|
||||
tooltip.hide();
|
||||
// * Prevent pending tooltips beeing shown after we asked to hide them
|
||||
tooltip.disable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tooltips
|
||||
*/
|
||||
private initTooltips() {
|
||||
this.closeBootstrapTooltip();
|
||||
/** data-bs-toggle tooltips elements */
|
||||
const elements = this.parentSelector ? [].slice
|
||||
.call(document.querySelectorAll(`${this.parentSelector} [data-bs-toggle="tooltip"]`)) :
|
||||
this.elements;
|
||||
this.tooltips = elements.map((tooltipTriggerEl) => {
|
||||
return new Tooltip(tooltipTriggerEl, this.options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ export default {
|
|||
allErrors: Record<string, Array<string>> = {}
|
||||
): string | null {
|
||||
let errorList = [] as Array<string>;
|
||||
Object.keys(allErrors).filter((value, index) => {
|
||||
Object.keys(allErrors).filter((value) => {
|
||||
if (value == inputName) {
|
||||
errorList = allErrors[index];
|
||||
errorList = allErrors[inputName];
|
||||
}
|
||||
});
|
||||
return errorList.length
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const route = {
|
|||
routeName: string,
|
||||
parameters: Record<
|
||||
string,
|
||||
string | number | Array<string | number>
|
||||
string | number | null | undefined | Array<string | number>
|
||||
> = {},
|
||||
keepActualQueries: boolean = false,
|
||||
keepActualHashes: boolean = false
|
||||
|
|
|
|||
|
|
@ -110,6 +110,25 @@
|
|||
</button>
|
||||
<div class="collapse" id="minicrud">
|
||||
<div class="card card-body rounded-0">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@can('addPartPiece', $sectionModel)
|
||||
<a class="btn btn-primary"
|
||||
href="{{ route('bo.sections.addpartpiece', $sectionModel) }}">{{ __('Ajouter une partie') }}</a>
|
||||
@endcan
|
||||
@can('removePartPiece', $sectionModel)
|
||||
<div class="btn-group">
|
||||
@for ($i = 1; $i <= $sectionModel->countPartPieces(); $i++)
|
||||
<a class="btn btn-danger confirmDeleteByLinkTS"
|
||||
href="{{ route('bo.sections.removepartpiece', [
|
||||
'section' => $sectionModel,
|
||||
'index' => $i,
|
||||
]) }}">{{ __('Enlever partie :index', ['index' => $i]) }}</a>
|
||||
@endfor
|
||||
</div>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@include('modules.minicrud-form-inputs', [
|
||||
'data' => $sectionModel->data,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
alt="{{ __('Volena - Vollailers Engagés pour l\'Avenir') }}" role="img"
|
||||
aria-label="{{ __('Volena - Vollailers Engagés pour l\'Avenir') }}">
|
||||
</a>
|
||||
<button class="btn btn-primary rounded-pill ms-4"data-bs-dismiss="offcanvas" title="{{ __('Fermer le Menu') }}"
|
||||
<button class="btn btn-primary rounded-pill ms-4" data-bs-dismiss="offcanvas" title="{{ __('Fermer le Menu') }}"
|
||||
aria-controls="main-menu">{{ __('Fermer') }}</button>
|
||||
</div>
|
||||
<hr>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
<div class="col-12">
|
||||
<img class="img-fluid" src="{{ asset($data["bloc_banniere_{$partName}"]['value']) }}"
|
||||
<img class="img-fluid rounded-4" src="{{ asset($data["bloc_banniere_{$partName}"]['value']) }}"
|
||||
title="{{ $data["bloc_banniere_{$partName}"]['title'] }}" alt="{{ $data["bloc_banniere_{$partName}"]['alt'] }}">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="col-md-8">
|
||||
@php $k = 0; @endphp
|
||||
@for ($i = 1; $i <= $itemMax; $i++)
|
||||
@if (empty($data["bloc_image{$i}_{$partName}"]))
|
||||
@continue
|
||||
@endif
|
||||
@include('fo.pages.produce.flex-row', [
|
||||
'image_link' => $data["bloc_lien_de_limage{$i}_{$partName}"]['value'] ?? null,
|
||||
'image' => $data["bloc_image{$i}_{$partName}"]['value'],
|
||||
'alt' => $data["bloc_image{$i}_{$partName}"]['alt'],
|
||||
'title' => $data["bloc_image{$i}_{$partName}"]['title'],
|
||||
'text' => $data["bloc_accompagnant_limage{$i}_{$partName}_content"]['value'],
|
||||
'switch' => $i % 2 ? false : true,
|
||||
'switch' => $k++ % 2 ? true : false,
|
||||
])
|
||||
@endfor
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,16 @@
|
|||
'd-flex flex-column flex-sm-row justify-content-center align-items-center my-3',
|
||||
'flex-sm-row-reverse' => $switch,
|
||||
])>
|
||||
<img src="{{ asset($image) }}" title="{{ $title }}" alt="{{ $alt }}" role="img" aria-label="{{ $alt }}">
|
||||
@if (!empty($image_link))
|
||||
<a href="{{ $image_link }}" title="{{ __('Lien de :title', ['title' => $title]) }}" target="_blank">
|
||||
@endisset
|
||||
<img src="{{ asset($image) }}" title="{{ $title }}" alt="{{ $alt }}" role="img" aria-label="{{ $alt }}">
|
||||
@isset($image_link)
|
||||
</a>
|
||||
@endif
|
||||
<div class="d-flex justify-content-start align-items-center mx-3">
|
||||
<div class="text-start">
|
||||
{!! $text !!}
|
||||
<div class="ck-content">{!! clean($text) !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,9 +20,14 @@
|
|||
<section class="anchor-padding container my-4 text-center" id="local">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="h2 fw-bold text-primary">{{ $localSection->h2_title }}</h2>
|
||||
<h2 class="fw-bold text-primary mb-3">{{ $localSection->h2_title }}</h2>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="ck-content">{!! clean($localSection->text) !!}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center align-items-center flex-md-column-reverse">
|
||||
<div class="col-12 my-3">
|
||||
<div class="col-md-8 my-3">
|
||||
@include('fo.pages.produce.flex-row', [
|
||||
'image' => $localSection->data['bloc_image']['value'],
|
||||
'alt' => $localSection->data['bloc_image']['alt'],
|
||||
|
|
@ -39,9 +44,9 @@
|
|||
<section class="border-top-curved-tertiary-light">
|
||||
<div class="bg-tertiary-light overflow-hidden pb-5">
|
||||
<div class="container text-center">
|
||||
<h2 class="h2 fw-bold text-primary">{{ $gmsSection->h2_title }}</h2>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3">
|
||||
<h2 class="fw-bold text-primary mb-3">{{ $gmsSection->h2_title }}</h2>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 mb-3">
|
||||
<div class="ck-content">
|
||||
{!! clean($gmsSection->text) !!}
|
||||
</div>
|
||||
|
|
@ -72,24 +77,24 @@
|
|||
<section class="border-top-curved-upper-tertiary-light">
|
||||
<div class="overflow-hidden bg-white pb-5">
|
||||
<div class="container text-center">
|
||||
<h2 class="h2 fw-bold text-primary">{{ $rhdSection->h2_title }}</h2>
|
||||
<div class="row">
|
||||
<h2 class="fw-bold text-primary mb-3">{{ $rhdSection->h2_title }}</h2>
|
||||
<div class="row justify-content-center">
|
||||
{{-- PART 1 --}}
|
||||
@include('fo.pages.produce.banner-elements', [
|
||||
'partName' => 'de_la_partie1',
|
||||
'data' => $rhdSection->data,
|
||||
'itemMax' => 3,
|
||||
'itemMax' => 10,
|
||||
])
|
||||
<div class="col-12 mb-3">
|
||||
<div class="col-md-8 mb-3">
|
||||
<div class="ck-content">
|
||||
{!! clean($rhdSection->text) !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mb-3">
|
||||
<div class="col-md-8 mb-3">
|
||||
<button class="btn btn-primary rounded-pill">{{ __('LDC restauration') }}</button>
|
||||
</div>
|
||||
{{-- PART 2 --}}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="col-md-8 mb-3">
|
||||
<div class="row w-100 bg-nine rounded-4 mx-auto p-4">
|
||||
<div class="col-4 d-none d-md-flex flex-column justify-content-center align-items-start">
|
||||
<h3 class="h3 text-primary fw-bold text-uppercase m-0">{{ __('le saviez-vous ?') }}</h3>
|
||||
|
|
@ -116,12 +121,12 @@
|
|||
<section class="border-top-curved-primary">
|
||||
<div class="bg-primary overflow-hidden pb-5 text-white">
|
||||
<div class="container text-center">
|
||||
<h2 class="h2 fw-bold">{{ $exportSection->h2_title }}</h2>
|
||||
<div class="row">
|
||||
<h2 class="fw-bold mb-3">{{ $exportSection->h2_title }}</h2>
|
||||
<div class="row justify-content-center">
|
||||
@include('fo.pages.produce.banner-elements', [
|
||||
'partName' => 'de_la_partie',
|
||||
'data' => $exportSection->data,
|
||||
'itemMax' => 3,
|
||||
'itemMax' => 10,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -131,9 +136,9 @@
|
|||
<section class="border-top-curved-upper-primary">
|
||||
<div class="overflow-hidden bg-white pb-5">
|
||||
<div class="container">
|
||||
<div class="row gx-5">
|
||||
<div class="col-12 mb-3 text-center">
|
||||
<h2 class="h2 fw-bold">{{ $qualitySection->h2_title }}</h2>
|
||||
<div class="row justify-content-center gx-5">
|
||||
<div class="col-md-8 mb-3 text-center">
|
||||
<h2 class="fw-bold mb-3">{{ $qualitySection->h2_title }}</h2>
|
||||
<div class="ck-content">{!! clean($qualitySection->text) !!}</div>
|
||||
@if ($qualitySection->data['bloc_button__on_vous_explique_comment_lire_cetteetiquette']['value'])
|
||||
<a class="btn btn-primary rounded-pill mt-3" href="https://www.etiquettebienetreanimal.fr/"
|
||||
|
|
@ -145,36 +150,43 @@
|
|||
'data' => $qualitySection->data,
|
||||
'itemMax' => 1,
|
||||
])
|
||||
<div class="col-12 mb-3 text-center">
|
||||
<div class="col-md-8 mb-3 text-center">
|
||||
<div class="bg-tertiary-light rounded-4 my-3 p-4">
|
||||
<div class="ck-content">{!! clean($qualitySection->data['bloc_text_de_la_partie2_content']['value']) !!}</div>
|
||||
<div class="d-flex justify-content-center align-items-center mt-3">
|
||||
<img class="img-fluid" src="{{ Vite::asset('resources/assets/images/produce/schema.png') }}"
|
||||
title="{{ __('Schéma') }}" alt="{{ __('Plein de formes différentes') }}">
|
||||
<img class="img-fluid" src="{{ $qualitySection->data['bloc_image_de_la_partie2_content']['value'] }}"
|
||||
title="{{ $qualitySection->data['bloc_image_de_la_partie2_content']['title'] }}"
|
||||
alt="{{ $qualitySection->data['bloc_image_de_la_partie2_content']['alt'] }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 my-3">
|
||||
<div class="bg-sixt-light rounded-2 px-3 py-4 text-center">
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4 my-3">
|
||||
<div class="bg-sixt-light rounded-2 h-100 d-flex flex-column justify-content-between px-3 py-4 text-center">
|
||||
<div class="ck-content">
|
||||
{!! $qualitySection->data['bloc_accompagnant_le_bouton_on_vousecoute_content']['value'] !!}
|
||||
</div>
|
||||
@if ($qualitySection->data['bloc_button__on_vousecoute']['value'])
|
||||
<a class="btn btn-primary rounded-pill rounded-2 mt-3" href="{{ route('fo.listen.show') }}">
|
||||
{{ __('On vous écoute') }}
|
||||
</a>
|
||||
<div>
|
||||
<a class="btn btn-primary rounded-pill rounded-2 mt-3" href="{{ route('fo.listen.show') }}">
|
||||
{{ __('On vous écoute') }}
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 my-3">
|
||||
<div class="bg-quint-light rounded-2 px-3 py-4 text-center">
|
||||
<div class="col-md-4 my-3">
|
||||
<div class="bg-quint-light rounded-2 h-100 d-flex flex-column justify-content-between px-3 py-4 text-center">
|
||||
<div class="ck-content">
|
||||
{!! $qualitySection->data['bloc_accompagnant_le_bouton_on_se_devoile_content']['value'] !!}
|
||||
</div>
|
||||
@if ($qualitySection->data['bloc_button__on_se_devoile']['value'])
|
||||
<a class="btn btn-primary rounded-pill rounded-2 mt-3" href="{{ route('fo.unveil.show') }}">
|
||||
{{ __('On se dévoile') }}
|
||||
</a>
|
||||
<div>
|
||||
<a class="btn btn-primary rounded-pill rounded-2 mt-3" href="{{ route('fo.unveil.show') }}">
|
||||
{{ __('On se dévoile') }}
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@
|
|||
'width' => $input['dimensions']['width'],
|
||||
'height' => $input['dimensions']['height'],
|
||||
'model' => $model ?? null,
|
||||
'value' => old("data.{$k}.value", $model->data[$k]['value'] ?? null),
|
||||
'value' => $model->data[$k]['value'],
|
||||
'helper' => __('crud.minicrud.dimensions_must_be', [
|
||||
'width' => $input['dimensions']['width'],
|
||||
'height' => $input['dimensions']['height'],
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ Route::prefix('bo')
|
|||
->only(['index', 'edit', 'update'])
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware(['htmlsanitize:text,data']);
|
||||
Route::get('sections/{section}/addpartpiece', [SectionController::class, 'addPartPiece'])
|
||||
->name('sections.addpartpiece');
|
||||
Route::get('sections/{section}/removepartpiece/{index}', [SectionController::class, 'removePartPiece'])
|
||||
->where('index', '[0-9]+')
|
||||
->name('sections.removepartpiece');
|
||||
|
||||
// * JOB VIDEOS.
|
||||
Route::resource('jobvideos', JobVideoController::class)->except('show')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue