add text_color and published fields on the menu configuration
This commit is contained in:
parent
0fb382c0a6
commit
4f952a89ca
25 changed files with 737 additions and 276 deletions
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace App\Http\Requests\Bo\GeneralSettings;
|
||||
|
||||
use App\Enums\Settings\MenuNodeType;
|
||||
use App\Enums\Settings\SettingsType;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
|
||||
class UpdateGeneralSettingRequest extends FormRequest
|
||||
{
|
||||
|
|
@ -41,7 +44,10 @@ class UpdateGeneralSettingRequest extends FormRequest
|
|||
return;
|
||||
}
|
||||
$this->merge([
|
||||
'value' => json_decode($this->input('jsonValue', '[]'))
|
||||
'value' => collect(json_decode($this->input('jsonValue', '[]'), true))->map(function ($element) {
|
||||
$element['published'] = (array_key_exists('published', $element)) ? $element['published'] : false;
|
||||
return $element;
|
||||
})->toArray()
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -65,11 +71,44 @@ class UpdateGeneralSettingRequest extends FormRequest
|
|||
$this->pictureRules('picture', 200, 100, 200, 100, false),
|
||||
);
|
||||
} else {
|
||||
/** @var array<array-key, mixed> $routesName Get all front routes name */
|
||||
$routesName = collect(Route::getRoutes())->map(function ($route) {
|
||||
if (preg_match('/fo.([a-zA-Z])+.([a-zA-Z])+/', $route->getName())) {
|
||||
return $route->getName();
|
||||
}
|
||||
})->filter()->toArray();
|
||||
|
||||
/** @var int $count Count all node published */
|
||||
$count = collect($this->value)->filter(function ($element) {
|
||||
if ($element['published']) {
|
||||
return $element;
|
||||
}
|
||||
})->count();
|
||||
|
||||
return [
|
||||
'jsonValue' => 'required|json',
|
||||
'value' => 'required'
|
||||
'jsonValue' => 'required|json',
|
||||
'value' => [
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
'present', 'array', function ($attribute, array $value, $fail) use ($count) {
|
||||
if ($count > 8) {
|
||||
$fail(\trans('Maximum 8 liens doivent être présents dans le menu'));
|
||||
}
|
||||
}
|
||||
],
|
||||
'value.*.type' => ['required', new Enum(MenuNodeType::class)],
|
||||
'value.*.published' => 'required|boolean',
|
||||
'value.*.title' => 'required|string|between:3,100',
|
||||
'value.*.link.url' => 'nullable|string|url:https',
|
||||
'value.*.link.routeName' => [
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
'nullable', 'string', function ($attribute, string $value, $fail) use ($routesName) {
|
||||
if (!in_array($value, $routesName)) {
|
||||
$fail(\trans('Doît être un lien front existant'));
|
||||
}
|
||||
}
|
||||
],
|
||||
];
|
||||
}
|
||||
} //end if
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -80,7 +119,31 @@ class UpdateGeneralSettingRequest extends FormRequest
|
|||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'value' => trans('validation.custom.value')
|
||||
'text' => trans('validation.attributes.text'),
|
||||
'picture' => trans('validation.attributes.image'),
|
||||
'picture_alt' => trans('validation.custom.attribute_alt'),
|
||||
'picture_title' => trans('validation.custom.attribute_title'),
|
||||
'value' => trans('validation.custom.value'),
|
||||
'value.*.type' => trans(':field :internoeud', [
|
||||
'field' => trans('validation.custom.type'),
|
||||
'inter' => trans('validation.custom.of_the.male'),
|
||||
]),
|
||||
'value.*.published' => trans(':field :internoeud', [
|
||||
'field' => trans('validation.custom.published'),
|
||||
'inter' => trans('validation.custom.of_the.male'),
|
||||
]),
|
||||
'value.*.title' => trans(':field :internoeud', [
|
||||
'field' => trans('validation.attributes.title'),
|
||||
'inter' => trans('validation.custom.of_the.male'),
|
||||
]),
|
||||
'value.*.link.url' => trans(':field :internoeud', [
|
||||
'field' => trans('validation.custom.url'),
|
||||
'inter' => trans('validation.custom.of_the.male'),
|
||||
]),
|
||||
'value.*.link.routeName' => trans(':field :internoeud', [
|
||||
'field' => trans('validation.custom.route_name'),
|
||||
'inter' => trans('validation.custom.of_the.male'),
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Http\Requests\Bo\MenuNodeData;
|
|||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use LVR\Colour\Hex;
|
||||
|
||||
class UpdateMenuNodeDataRequest extends FormRequest
|
||||
{
|
||||
|
|
@ -30,6 +31,9 @@ class UpdateMenuNodeDataRequest extends FormRequest
|
|||
if ($this->has('text')) {
|
||||
$this->merge(['text' => str($this->input('text'))->value()]);
|
||||
}
|
||||
if ($this->has('text_color')) {
|
||||
$this->merge(['text_color' => str($this->input('text_color'))->value()]);
|
||||
}
|
||||
if ($this->has('link_target')) {
|
||||
$this->merge(['link_target' => $this->boolean('link_target')]);
|
||||
}
|
||||
|
|
@ -58,6 +62,7 @@ class UpdateMenuNodeDataRequest extends FormRequest
|
|||
'sub_label' => 'sometimes|required|string|min:4|max:255',
|
||||
'title' => 'sometimes|required|string|min:4|max:255',
|
||||
'text' => 'sometimes|required|string|max:255',
|
||||
'text_color' => ['sometimes', 'required', new Hex()],
|
||||
'link_url' => 'sometimes|required|string|max:255',
|
||||
'link_title' => 'sometimes|required|string|between:3,255',
|
||||
'link_target' => 'sometimes|present|boolean',
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ use App\Enums\Settings\MenuNodeType;
|
|||
use App\Lib\Link;
|
||||
|
||||
/**
|
||||
* @property-read string $type The node menu type.
|
||||
* @property-read string $title The node menu title.
|
||||
* @property-read \App\Lib\Link $link The node menu link.
|
||||
* @property-read string $type The node menu type.
|
||||
* @property-read string $title The node menu title.
|
||||
* @property-read \App\Lib\Link $link The node menu link.
|
||||
* @property-read boolean $published The node menu publish status.
|
||||
* @package App\Lib\Definitions
|
||||
*/
|
||||
class MenuNode extends \stdClass
|
||||
|
|
@ -22,7 +23,7 @@ class MenuNode extends \stdClass
|
|||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
if (in_array($name, ['type', 'title', 'link'])) {
|
||||
if (in_array($name, ['type', 'title', 'link', 'published'])) {
|
||||
return $this->{$name};
|
||||
}
|
||||
throw new \TypeError("undefined property {$name}");
|
||||
|
|
@ -31,13 +32,14 @@ class MenuNode extends \stdClass
|
|||
/**
|
||||
* Make and instance
|
||||
*
|
||||
* @param integer $type The node menu type.
|
||||
* @param string $title The node menu title.
|
||||
* @param array $link The node menu link.
|
||||
* @param integer $type The node menu type.
|
||||
* @param boolean $published The node menu published.
|
||||
* @param string $title The node menu title.
|
||||
* @param array $link The node menu link.
|
||||
* @return \App\Lib\Definitions\MenuNode
|
||||
* @throws \RuntimeException If $type or $data is wrong.
|
||||
*/
|
||||
public static function make(int $type, string $title, array $link): self
|
||||
public static function make(int $type, bool $published, string $title, array $link): self
|
||||
{
|
||||
self::assertTypeIsCorrect($type);
|
||||
|
||||
|
|
@ -45,6 +47,8 @@ class MenuNode extends \stdClass
|
|||
// @phpstan-ignore-next-line
|
||||
$menuNode->type = MenuNodeType::make($type);
|
||||
// @phpstan-ignore-next-line
|
||||
$menuNode->published = $published;
|
||||
// @phpstan-ignore-next-line
|
||||
$menuNode->title = $title;
|
||||
// @phpstan-ignore-next-line
|
||||
$menuNode->link = new Link(...$link);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Casts\HtmlColor;
|
||||
use App\Enums\Settings\MenuNodeType;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Traits\Models\IsCachedAsCollection;
|
||||
|
|
@ -13,6 +14,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @property string $sub_label (Left part) Link label.
|
||||
* @property string $title (Right part) Sub title.
|
||||
* @property string $text (Right part) Sub text.
|
||||
* @property string $text_color (Right part) Sub text color.
|
||||
* @property string $link_url (Right part) Link url.
|
||||
* @property string $link_title (Right part) Link title.
|
||||
* @property boolean $link_target (Right part) Link target.
|
||||
|
|
@ -62,6 +64,7 @@ class MenuNodeData extends Model
|
|||
'sub_label',
|
||||
'title',
|
||||
'text',
|
||||
'text_color',
|
||||
'link_url',
|
||||
'link_title',
|
||||
'link_target',
|
||||
|
|
@ -82,6 +85,7 @@ class MenuNodeData extends Model
|
|||
*/
|
||||
protected $casts = [
|
||||
'type' => MenuNodeType::class,
|
||||
'text_color' => HtmlColor::class,
|
||||
'link_target' => 'boolean',
|
||||
'link_status' => 'boolean',
|
||||
'picture_status' => 'boolean',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class MenuNodeDataFactory extends Factory
|
|||
'sub_label' => fake()->sentence(),
|
||||
'title' => fake()->sentence(),
|
||||
'text' => fake()->sentence(),
|
||||
'text_color' => fake()->unique()->safeHexColor(),
|
||||
'link_url' => fake()->url(),
|
||||
'link_title' => fake()->sentence(),
|
||||
'link_target' => fake()->boolean(),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Database\Seeders\Mandatory\GeneralSettingSeeder;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
|
|
@ -20,10 +18,6 @@ return new class extends Migration
|
|||
$table->json('value')->comment('The actual setting value');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Artisan::call('db:seed', [
|
||||
'class' => GeneralSettingSeeder::class
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Database\Seeders\Mandatory\MenuNodeDataSeeder;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
|
|
@ -39,9 +37,6 @@ return new class extends Migration
|
|||
|
||||
$table->timestamps();
|
||||
});
|
||||
Artisan::call('db:seed', [
|
||||
'class' => MenuNodeDataSeeder::class
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
DB::statement("ALTER TABLE `general_settings` CHANGE `type` `type` TINYINT UNSIGNED NOT NULL COMMENT 'General settings enum type (SettingsType).'");
|
||||
DB::statement("ALTER TABLE `general_settings` CHANGE `value` `value` LONGTEXT NOT NULL COMMENT 'General settings value (json).'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement("ALTER TABLE `general_settings` CHANGE `type` `type` TINYINT UNSIGNED NOT NULL COMMENT 'Enum SettingsType'");
|
||||
DB::statement("ALTER TABLE `general_settings` CHANGE `value` `value` LONGTEXT NOT NULL COMMENT 'The actual setting value'");
|
||||
// phpcs:enable Generic.Files.LineLength.TooLong
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('menu_node_data', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('text_color')->comment('(Right part) Sub text color')->after('text');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('menu_node_data', function (Blueprint $table) {
|
||||
$table->dropColumn('text_color');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Database\Seeders;
|
||||
|
||||
use Database\Seeders\Mandatory\GeneralSettingSeeder;
|
||||
use Database\Seeders\Mandatory\MenuNodeDataSeeder;
|
||||
use Database\Seeders\Mandatory\StaticPageSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
|
|
@ -17,7 +18,8 @@ class MandatorySeeder extends Seeder
|
|||
{
|
||||
$this->call([
|
||||
StaticPageSeeder::class,
|
||||
GeneralSettingSeeder::class
|
||||
GeneralSettingSeeder::class,
|
||||
MenuNodeDataSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ return [
|
|||
'publication' => 'publication',
|
||||
'publish_at' => 'publish at',
|
||||
'published' => 'published',
|
||||
'route_name' => 'name of the route',
|
||||
'seo_description' => 'seo description',
|
||||
'seo_title' => 'seo title',
|
||||
'slug' => 'slug',
|
||||
|
|
@ -262,6 +263,7 @@ return [
|
|||
'subtitle' => 'subtitle',
|
||||
'sustitle' => 'sustitle',
|
||||
'target' => 'target',
|
||||
'type' => 'type',
|
||||
'unpublish_at' => 'unpublish at',
|
||||
'unpublished' => 'unpublished',
|
||||
'url' => 'url',
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ return [
|
|||
'picture_title_label' => 'Attribut du titre de l\'image',
|
||||
'picture_alt_helper' => 'Doit décrire votre image pour les moteurs de recherche et les aveugles (5 à 6 mots)',
|
||||
'picture_alt_label' => 'Attribut Alt de l\'image',
|
||||
'text_warning' => 'Relisez votre contenu avant de le publier',
|
||||
'text_warning' => 'Relisez votre contenu avant de le publier.',
|
||||
],
|
||||
|
||||
'pagination' => [
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ return [
|
|||
'publication' => 'publication',
|
||||
'publish_at' => 'date de publication',
|
||||
'published' => 'publié',
|
||||
'route_name' => 'nom de la route',
|
||||
'seo_description' => 'description SEO',
|
||||
'seo_title' => 'titre SEO',
|
||||
'slug' => 'slug',
|
||||
|
|
@ -263,6 +264,7 @@ return [
|
|||
'subtitle' => 'sous-titre',
|
||||
'sustitle' => 'sous-titre',
|
||||
'target' => 'cible',
|
||||
'type' => 'type',
|
||||
'unpublish_at' => 'date de fin de publication',
|
||||
'unpublished' => 'non publié',
|
||||
'url' => 'lien',
|
||||
|
|
|
|||
|
|
@ -1,55 +1,67 @@
|
|||
<template>
|
||||
<fieldset
|
||||
class="container-fluid main-menu-component my-3"
|
||||
ref="mainMenu"
|
||||
class="container-fluid main-menu-component"
|
||||
:disabled="loading"
|
||||
>
|
||||
<div class="row justify-content-start mb-3">
|
||||
<div class="col">
|
||||
<span class="h5">{{ trans.methods.__('Réorganisez le menu principal comme vous le souhaitez ou ajoutez un lien.') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ORDERABLE NODES -->
|
||||
<div class="row justify-content-start">
|
||||
<div class="col col-xl-4 my-3">
|
||||
<fieldset
|
||||
class="border p-4"
|
||||
:disabled="nodeToEdit !== null"
|
||||
>
|
||||
<legend>
|
||||
<span class="fs-5">{{ trans.methods.__('Ordonancement du menu') }}</span>
|
||||
<!-- ORDERABLE NODES -->
|
||||
<div class="col-12 col-xl-4 p-0">
|
||||
<fieldset :disabled="nodeToEdit !== null">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<p class="fw-bold m-0">
|
||||
{{ trans.methods.__('Ordonancement du menu') }}
|
||||
<span
|
||||
tabindex="0"
|
||||
:title="trans.methods.__('Réorganisez le menu principal comme vous le souhaitez ou ajoutez un lien.')"
|
||||
data-bs-toggle="tooltip"
|
||||
><FontAwesomeIcon icon="fa-solid fa-circle-info fa-xs" /></span>
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-sm btn-primary float-end mt-1"
|
||||
:title="trans.methods.__('Ajouter un lien au menu')"
|
||||
data-bs-toggle="tooltip"
|
||||
@click.prevent="addLinkNode"
|
||||
>
|
||||
<FontAwesomeIcon icon="fa fa-plus" />
|
||||
</button>
|
||||
</legend>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<Draggable
|
||||
:list="nodeList"
|
||||
item-key="type"
|
||||
@change="orderChanged"
|
||||
>
|
||||
<!-- LIST ITEM -->
|
||||
<template #item="{ element, index } : {element: MenuNodeInterface, index: number}">
|
||||
<div class="list-group-item d-flex flux-column justify-content-between">
|
||||
<span class="h6">
|
||||
<FontAwesomeIcon
|
||||
icon="fa-solid fa-align-justify"
|
||||
class="handle text-secondary"
|
||||
/>
|
||||
<span> </span>
|
||||
<strong>{{ element.title }}</strong>
|
||||
</span>
|
||||
|
||||
<div class="d-flex justify-content-center align-items-center h6 m-0">
|
||||
<span class="me-3">
|
||||
<FontAwesomeIcon
|
||||
icon="fa-solid fa-align-justify"
|
||||
class="handle text-secondary"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="element.published"
|
||||
class="me-3"
|
||||
:title="trans.methods.__('Ce noeud est publié')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon="fa fa-circle-check"
|
||||
class="text-success"
|
||||
/>
|
||||
</span>
|
||||
{{ element.title }}
|
||||
</div>
|
||||
<!-- ACTIONS -->
|
||||
<div class="btn-group">
|
||||
<button
|
||||
v-if="element.type !== MenuNodeTypeEnum.siteLogo"
|
||||
class="btn btn-sm btn-primary"
|
||||
@click.prevent="editNode(index)"
|
||||
:title="trans.methods.__('Modifier le noeud')"
|
||||
data-bs-toggle="tooltip"
|
||||
>
|
||||
<FontAwesomeIcon icon="fa fa-pencil" />
|
||||
</button>
|
||||
|
|
@ -67,27 +79,28 @@
|
|||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-12 col-xl-8 main-menu-node-editor-component my-3 overflow-y-scroll">
|
||||
<!-- NODE EDITOR -->
|
||||
<MainMenuNodeEditor
|
||||
<div class="col-12 col-xl-8 main-menu-node-editor-component p-0 pt-3 ps-xl-3 pt-xl-0">
|
||||
<div
|
||||
v-if="nodeToEdit !== null"
|
||||
:menu-node="nodeToEdit"
|
||||
:enum="getEnum(nodeToEdit.type)"
|
||||
@close-editor="closeEditor"
|
||||
@updated-node="(menuNode) => nodeIndexToEdit !== null ? saveEditedNode(nodeIndexToEdit, menuNode) : null"
|
||||
/>
|
||||
|
||||
<!-- NODE DATA EDITOR -->
|
||||
<MainMenuNodeDataEditor
|
||||
v-if="nodeDataToEdit !== null && nodeDataToEdit.type !== MenuNodeTypeEnum.link"
|
||||
:menu-node-data="getNodeData(nodeDataToEdit.type)"
|
||||
:errors="menuNodeDataErrors"
|
||||
@updated-node-data="(prop, value) => nodeDataToEdit !== null ? saveMenuNodeData(nodeDataToEdit.type, prop, value) : null"
|
||||
/>
|
||||
class="border p-3"
|
||||
>
|
||||
<!-- NODE EDITOR -->
|
||||
<MainMenuNodeEditor
|
||||
v-if="nodeToEdit !== null"
|
||||
:menu-node="nodeToEdit"
|
||||
:enum="getEnum(nodeToEdit.type)"
|
||||
@close-editor="closeEditor"
|
||||
@updated-node="(menuNode) => nodeIndexToEdit !== null ? saveEditedNode(nodeIndexToEdit, menuNode) : null"
|
||||
/>
|
||||
<!-- NODE DATA EDITOR -->
|
||||
<MainMenuNodeDataEditor
|
||||
v-if="nodeDataToEdit !== null && nodeDataToEdit.type !== MenuNodeTypeEnum.link"
|
||||
:menu-node-data="getNodeData(nodeDataToEdit.type)"
|
||||
:errors="menuNodeDataErrors"
|
||||
@updated-node-data="(prop, value) => nodeDataToEdit !== null ? saveMenuNodeData(nodeDataToEdit.type, prop, value) : null"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INPUTS FOR SUBMISSION -->
|
||||
<input
|
||||
type="hidden"
|
||||
|
|
@ -113,12 +126,16 @@ import type { MenuNodeInterface } from "./../../types/models/MenuNode";
|
|||
import type { MenuNodeDataInterface } from "./../../types/models/MenuNodeData";
|
||||
import MainMenuNodeDataEditor from "./MainMenuNodeDataEditor.vue";
|
||||
import MainMenuNodeEditor from "./MainMenuNodeEditor.vue";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
|
||||
defineOptions({
|
||||
name: "MainMenu",
|
||||
inheritAttrs: true,
|
||||
});
|
||||
|
||||
// * REFS
|
||||
const mainMenu = ref<HTMLDivElement|null>(null);
|
||||
|
||||
// * ATTRIBUTES
|
||||
const attrs = useAttrs();
|
||||
|
||||
|
|
@ -132,8 +149,7 @@ let nodeIndexToEdit = ref<number|null>(null);
|
|||
let nodeToEdit = ref<MenuNodeInterface|null>(null);
|
||||
let nodeDataToEdit = ref<MenuNodeDataInterface|null>(null);
|
||||
let menuNodeDataErrors = reactive<LaravelValidationErrors>({});
|
||||
|
||||
// * COMPUTED
|
||||
const tooltips = ref<Tooltips|null>(null);
|
||||
|
||||
// * LIFECYCLE
|
||||
onMounted(() => {
|
||||
|
|
@ -141,51 +157,61 @@ onMounted(() => {
|
|||
data = JSON.parse(json);
|
||||
nodeList.value = data.nodeList ?? [];
|
||||
nodeTypeEnumList = data.nodeTypeEnumList ?? [];
|
||||
fetchMenuNodeDataList().then((dataList) => { menuNodeDataList.value = dataList; });
|
||||
fetchMenuNodeDataList().then((dataList) => {
|
||||
menuNodeDataList.value = dataList;
|
||||
initTooltips();
|
||||
});
|
||||
});
|
||||
|
||||
// * METHODS
|
||||
function orderChanged(): void {
|
||||
console.log("sdsd");
|
||||
}
|
||||
|
||||
/** Add a new link node to the menu */
|
||||
function addLinkNode(): void {
|
||||
const newNode = {
|
||||
type: MenuNodeTypeEnum.link,
|
||||
title: trans.methods.__("Lien"),
|
||||
published: false,
|
||||
link: {
|
||||
routeName: null,
|
||||
url: "https://example.net"
|
||||
}
|
||||
};
|
||||
console.log("push nodeList", newNode);
|
||||
nodeList.value.push(newNode);
|
||||
}
|
||||
|
||||
/** Remove a specific link node by index */
|
||||
function removeLinkNode(index: number): void {
|
||||
nodeList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
function editNode(index: number):void {
|
||||
/** Edit a specific link node by index */
|
||||
function editNode(index: number): void {
|
||||
nodeIndexToEdit.value = index;
|
||||
nodeToEdit.value = nodeList.value[index];
|
||||
nodeDataToEdit.value = menuNodeDataList.value.find((nodeData) => nodeData.type === nodeToEdit.value?.type) ?? null;
|
||||
}
|
||||
function saveEditedNode(index: number, menuNode: MenuNodeInterface):void {
|
||||
|
||||
/** Save a specific link node by index */
|
||||
function saveEditedNode(index: number, menuNode: MenuNodeInterface): void {
|
||||
nodeList.value[index] = menuNode;
|
||||
initTooltips();
|
||||
}
|
||||
|
||||
/** Close the editor part */
|
||||
function closeEditor(): void {
|
||||
nodeIndexToEdit.value = null;
|
||||
nodeToEdit.value = null;
|
||||
nodeDataToEdit.value = null;
|
||||
}
|
||||
function refreshEditor(): void
|
||||
{
|
||||
|
||||
/** Refresh the editor part */
|
||||
function refreshEditor(): void {
|
||||
if (nodeIndexToEdit.value !== null) {
|
||||
editNode(nodeIndexToEdit.value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the node data */
|
||||
function getNodeData(type: MenuNodeTypeEnum): MenuNodeDataInterface
|
||||
{
|
||||
const NodeData = menuNodeDataList.value.find((nodeData) => nodeData.type === type);
|
||||
|
|
@ -246,36 +272,6 @@ async function saveMenuNodeData(
|
|||
fetchMenuNodeDataList().then((dataList) => { menuNodeDataList.value = dataList; refreshEditor(); });
|
||||
resolve(true);
|
||||
};
|
||||
const catchAction = (error: AxiosError<{errors: LaravelValidationErrors|{}}>) => {
|
||||
let message = "Une erreur est survenue merci de ressayer plus tard";
|
||||
if (error!.response!.status === 422) {
|
||||
// ts:ignore
|
||||
menuNodeDataErrors = error.response?.data?.errors ?? {};
|
||||
toast.methods.errorNotify(trans.methods.__("La valeur saisie est incorrecte"));
|
||||
return;
|
||||
}
|
||||
if (error!.response!.status === 401) {
|
||||
// ts:ignore
|
||||
toast.methods.errorNotify(trans.methods.__("Vous devez vous reconnecter"));
|
||||
console.warn(trans.methods.__("Vous devez vous reconnecter"));
|
||||
return;
|
||||
}
|
||||
if (error!.response!.status === 419) {
|
||||
// ts:ignore
|
||||
toast.methods.errorNotify(trans.methods.__("Vous devez rafraichir la page"));
|
||||
return;
|
||||
}
|
||||
if (error!.response) {
|
||||
console.error(error.response.data);
|
||||
} else if (error.request) {
|
||||
console.error(error.request);
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error("Error", error.message);
|
||||
}
|
||||
toast.methods.errorNotify(message);
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
if(menuNodeData instanceof File) {
|
||||
const formData = new FormData();
|
||||
|
|
@ -286,18 +282,53 @@ async function saveMenuNodeData(
|
|||
headers: {
|
||||
"content-type": "multipart/form-data"
|
||||
}
|
||||
}).then(succeedAction).catch(catchAction)
|
||||
}).then(succeedAction).catch(ajaxErrorHandler(resolve))
|
||||
.finally(() => { loading.value = false; });
|
||||
return;
|
||||
}
|
||||
|
||||
axios.patch<boolean>(menuNodeDataListRoute, {
|
||||
[prop]: menuNodeData
|
||||
}).then(succeedAction).catch(catchAction)
|
||||
}).then(succeedAction).catch(ajaxErrorHandler(resolve))
|
||||
.finally(() => { loading.value = false; });
|
||||
});
|
||||
}
|
||||
|
||||
/** Catch ajax errors */
|
||||
function ajaxErrorHandler(resolve: (value: boolean | PromiseLike<boolean>) => void) {
|
||||
return (error: AxiosError<{errors: LaravelValidationErrors|{}}>) => {
|
||||
let message = "Une erreur est survenue merci de ressayer plus tard";
|
||||
if (error!.response!.status === 422) {
|
||||
// ts:ignore
|
||||
menuNodeDataErrors = error.response?.data?.errors ?? {};
|
||||
toast.methods.errorNotify(trans.methods.__("La valeur saisie est incorrecte"));
|
||||
return;
|
||||
}
|
||||
if (error!.response!.status === 401) {
|
||||
// ts:ignore
|
||||
toast.methods.errorNotify(trans.methods.__("Vous devez vous reconnecter"));
|
||||
console.warn(trans.methods.__("Vous devez vous reconnecter"));
|
||||
return;
|
||||
}
|
||||
if (error!.response!.status === 419) {
|
||||
// ts:ignore
|
||||
toast.methods.errorNotify(trans.methods.__("Vous devez rafraichir la page"));
|
||||
return;
|
||||
}
|
||||
if (error!.response) {
|
||||
console.error(error.response.data);
|
||||
} else if (error.request) {
|
||||
console.error(error.request);
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error("Error", error.message);
|
||||
}
|
||||
toast.methods.errorNotify(message);
|
||||
resolve(false);
|
||||
};
|
||||
}
|
||||
|
||||
/** Get the enum value */
|
||||
function getEnum(tsEnum: MenuNodeTypeEnum): Enum {
|
||||
const enumValue: Enum|undefined = Object.values(nodeTypeEnumList).find((value) => value.value === tsEnum);
|
||||
if (!enumValue) {
|
||||
|
|
@ -305,6 +336,16 @@ function getEnum(tsEnum: MenuNodeTypeEnum): Enum {
|
|||
}
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
/** Initialise all tooltips in the component */
|
||||
function initTooltips(): void {
|
||||
setTimeout(() => {
|
||||
tooltips.value = Tooltips.make({
|
||||
type: "dom",
|
||||
elements: mainMenu.value!.querySelectorAll("[data-bs-toggle=\"tooltip\"]")
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<template>
|
||||
<fieldset class="main-menu-node-data-editor-component container-fluid p-4 border mb-3" >
|
||||
<hr>
|
||||
<fieldset class="main-menu-node-data-editor-component container-fluid">
|
||||
<legend>
|
||||
<span class="fs-5">{{ trans.methods.__("Modification de données d'un noeud") }}</span>
|
||||
</legend>
|
||||
|
||||
<span class="d-block fs-5">{{ trans.methods.__('Partie gauche') }}</span>
|
||||
|
||||
<span class="d-block fw-bold">{{ trans.methods.__('Partie gauche') }}</span>
|
||||
<!-- SUB LABEL -->
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
|
|
@ -14,7 +13,11 @@
|
|||
>
|
||||
{{ trans.methods.__("Label du sous-menu") }}
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="col">
|
||||
<WordCounter
|
||||
id="title-sub-label"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="title-sub-label"
|
||||
type="text"
|
||||
|
|
@ -22,14 +25,27 @@
|
|||
:value="menuNodeData.sub_label"
|
||||
@blur="($event) => saveNodeDataInput('sub_label', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.sub_label" v-for="(message) of errors.sub_label" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('validation.between.string', {
|
||||
attribute: "label du sous-menu",
|
||||
min: "3",
|
||||
max: "255",
|
||||
}) }}
|
||||
</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.sub_label"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.sub_label"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SEPARATION -->
|
||||
<hr>
|
||||
|
||||
<span class="d-block fs-5">{{ trans.methods.__('Partie droite') }}</span>
|
||||
|
||||
<span class="d-block fw-bold">{{ trans.methods.__('Partie droite') }}</span>
|
||||
<!-- TITLE -->
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
|
|
@ -38,7 +54,11 @@
|
|||
>
|
||||
{{ trans.methods.__("Titre") }}
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="col">
|
||||
<WordCounter
|
||||
id="title-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="title-text"
|
||||
type="text"
|
||||
|
|
@ -46,38 +66,103 @@
|
|||
:value="menuNodeData.title"
|
||||
@blur="($event) => saveNodeDataInput('title', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.title" v-for="(message) of errors.title" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('validation.between.string', {
|
||||
attribute: "titre",
|
||||
min: "3",
|
||||
max: "255",
|
||||
}) }}
|
||||
</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.title"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.title"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TEXT -->
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
for="text-text"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Text") }}
|
||||
{{ trans.methods.__("Texte") }}
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="col">
|
||||
<WordCounter
|
||||
id="text-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<textarea
|
||||
id="text-text"
|
||||
class="form-control"
|
||||
@blur="($event) => $nextTick(() => saveNodeDataInput('text', ($event.target as HTMLTextAreaElement).value))"
|
||||
:value="menuNodeData.text"></textarea>
|
||||
<small v-if="errors.text" v-for="(message) of errors.text" class="text-danger">"{{ message }}"</small>
|
||||
:value="menuNodeData.text"
|
||||
/>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('validation.between.string', {
|
||||
attribute: "texte",
|
||||
min: "3",
|
||||
max: "255",
|
||||
}) + " " + trans.methods.__('crud.minicrud.text_warning') }}
|
||||
</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.text"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.text"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- COLOR -->
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
for="text_color"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Couleur du texte") }}
|
||||
</label>
|
||||
<div class="col">
|
||||
<ColorPickerComponent
|
||||
name="text_color"
|
||||
:value="menuNodeData.text_color"
|
||||
@update-color="($event) => $nextTick(() => saveNodeDataInput('text_color', ($event as HTMLInputElement).value))"
|
||||
/>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('Choisissez une couleur qui se distingue bien de l\'image.') }}
|
||||
</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.text_color"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.text_color"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LINK -->
|
||||
<div class="mb-3 row">
|
||||
<!-- LINK URL -->
|
||||
<label
|
||||
for="link-text"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Link url") }}
|
||||
{{ trans.methods.__("URL du lien") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="col-sm-10 p-2">
|
||||
<WordCounter
|
||||
id="link-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="link-text"
|
||||
type="text"
|
||||
|
|
@ -85,16 +170,35 @@
|
|||
:value="menuNodeData.link_url"
|
||||
@blur="($event) => saveNodeDataInput('link_url', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.link_url" v-for="(message) of errors.link_url" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('validation.between.string', {
|
||||
attribute: "titre du lien",
|
||||
min: "3",
|
||||
max: "255",
|
||||
}) + " " + trans.methods.__('Doît être une url valide.') }}
|
||||
</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.link_url"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.link_url"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<!-- LINK TITLE -->
|
||||
<label
|
||||
for="link-title-text"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Link title") }}
|
||||
{{ trans.methods.__("Titre du lien") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="col-sm-10 p-2">
|
||||
<WordCounter
|
||||
id="link-title-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="link-title-text"
|
||||
type="text"
|
||||
|
|
@ -102,17 +206,32 @@
|
|||
:value="menuNodeData.link_title"
|
||||
@blur="($event) => saveNodeDataInput('link_title', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.link_title" v-for="(message) of errors.link_title" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('validation.between.string', {
|
||||
attribute: "titre du lien",
|
||||
min: "3",
|
||||
max: "255",
|
||||
}) }}
|
||||
</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.link_title"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.link_title"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<!-- LINK TARGET -->
|
||||
<label
|
||||
for="link-target-checkbox"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Link target") }}
|
||||
{{ trans.methods.__("Cible du lien") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="form-check form-switch">
|
||||
<div class="form-check form-switch py-0">
|
||||
<input
|
||||
name="link_target"
|
||||
class="form-check-input pt-3"
|
||||
|
|
@ -122,18 +241,27 @@
|
|||
:checked="menuNodeData.link_target"
|
||||
@change="($event) => saveNodeDataInput('link_target', ($event.target as HTMLInputElement).checked)"
|
||||
>
|
||||
<small v-if="errors.link_target" v-for="(message) of errors.link_target" class="text-danger">"{{ message }}"</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.link_target"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.link_target"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('Ouverture dans un nouvel onglet. Deux réponses possibles : oui ou non.') }}</small>
|
||||
</div>
|
||||
<!-- LINK STATUS -->
|
||||
<label
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
for="link-status-checkbox"
|
||||
>
|
||||
{{ trans.methods.__("Link status") }}
|
||||
{{ trans.methods.__("Statut du lien") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="form-check form-switch">
|
||||
<div class="form-check form-switch py-0">
|
||||
<input
|
||||
name="link_status"
|
||||
class="form-check-input pt-3"
|
||||
|
|
@ -143,45 +271,67 @@
|
|||
:checked="menuNodeData.link_status"
|
||||
@change="($event) => saveNodeDataInput('link_status', ($event.target as HTMLInputElement).checked)"
|
||||
>
|
||||
<small v-if="errors.link_status" v-for="(message) of errors.link_status" class="text-danger">"{{ message }}"</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.link_status"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.link_status"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('Visibilité du lien. Deux réponses possibles : visible ou caché.') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- --- -->
|
||||
<hr class="col-8 mx-auto">
|
||||
|
||||
<!-- PICTURE -->
|
||||
<div class="mb-3 row">
|
||||
<!-- PICTURE -->
|
||||
<label
|
||||
for="picture-input"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Picture") }}
|
||||
{{ trans.methods.__("Image") }}
|
||||
</label>
|
||||
<ImageInputComponent
|
||||
id="picture-input"
|
||||
:required="true"
|
||||
:show-labels="true"
|
||||
name="picture"
|
||||
helper="picture"
|
||||
:width="180"
|
||||
:height="228"
|
||||
:value="menuNodeData.picture"
|
||||
:preview="false"
|
||||
:browse-event="false"
|
||||
@image-file="(file) => file ? saveNodeDataInput('picture', file) : null"
|
||||
/>
|
||||
<small v-if="errors.picture" v-for="(message) of errors.picture" class="text-danger">"{{ message }}"</small>
|
||||
<div class="col-sm-10 p-2">
|
||||
<ImageInputComponent
|
||||
id="picture-input"
|
||||
:required="true"
|
||||
:show-labels="true"
|
||||
name="picture"
|
||||
:helper="trans.methods.__('crud.minicrud.dimensions_must_be', {
|
||||
widthpx: '180px',
|
||||
heightpx: '228px',
|
||||
})"
|
||||
:width="180"
|
||||
:height="228"
|
||||
:value="menuNodeData.picture"
|
||||
:preview="false"
|
||||
:browse-event="false"
|
||||
@image-file="(file) => file ? saveNodeDataInput('picture', file) : null"
|
||||
/>
|
||||
</div>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.picture"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.picture"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
<!-- PICTURE ALT -->
|
||||
<label
|
||||
for="picture-alt-text"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Picture alt") }}
|
||||
{{ trans.methods.__("Text alternatif de l'image") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="col-sm-10 p-2">
|
||||
<WordCounter
|
||||
id="picture-alt-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="picture-alt-text"
|
||||
type="text"
|
||||
|
|
@ -189,16 +339,29 @@
|
|||
:value="menuNodeData.picture_alt"
|
||||
@blur="($event) => saveNodeDataInput('picture_alt', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.picture_alt" v-for="(message) of errors.picture_alt" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('crud.minicrud.picture_alt_helper') }}</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.picture_alt"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.picture_alt"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<!-- PICTURE TITLE -->
|
||||
<label
|
||||
for="picture-title-text"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Picture title") }}
|
||||
{{ trans.methods.__("Titre de l'image") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="col-sm-10 p-2">
|
||||
<WordCounter
|
||||
id="picture-title-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="picture-title-text"
|
||||
type="text"
|
||||
|
|
@ -206,17 +369,26 @@
|
|||
:value="menuNodeData.picture_title"
|
||||
@blur="($event) => saveNodeDataInput('picture_title', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.picture_title" v-for="(message) of errors.picture_title" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('crud.minicrud.picture_title_helper') }}</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.picture_title"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.picture_title"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<!-- PICTURE STATUS -->
|
||||
<label
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
class="col-sm-2 col-form-label"
|
||||
for="picture-status-checkbox"
|
||||
>
|
||||
{{ trans.methods.__("Picture status") }}
|
||||
{{ trans.methods.__("Statut de l'image") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="form-check form-switch">
|
||||
<div class="form-check form-switch py-0">
|
||||
<input
|
||||
name="picture_status"
|
||||
class="form-check-input pt-3"
|
||||
|
|
@ -226,45 +398,67 @@
|
|||
:checked="menuNodeData.picture_status"
|
||||
@change="($event) => saveNodeDataInput('picture_status', ($event.target as HTMLInputElement).checked)"
|
||||
>
|
||||
<small v-if="errors.picture_status" v-for="(message) of errors.picture_status" class="text-danger">"{{ message }}"</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.picture_status"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.picture_status"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('Visibilité de l\'image. Deux réponses possibles : visible ou cachée.') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- --- -->
|
||||
<hr class="col-8 mx-auto">
|
||||
|
||||
<!-- BACKGROUND PICTURE-->
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
for="background-picture-input"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
>
|
||||
{{ trans.methods.__("Background Picture") }}
|
||||
{{ trans.methods.__("Image de fond") }}
|
||||
</label>
|
||||
<!-- BACKGROUND PICTURE -->
|
||||
<ImageInputComponent
|
||||
id="background-picture-input"
|
||||
:required="true"
|
||||
:show-labels="true"
|
||||
name="background_picture"
|
||||
helper="background_picture"
|
||||
:width="500"
|
||||
:height="231"
|
||||
:value="menuNodeData.background_picture"
|
||||
:preview="false"
|
||||
:browse-event="false"
|
||||
@image-file="(file) => file ? saveNodeDataInput('background_picture', file) : null"
|
||||
/>
|
||||
<small v-if="errors.background_picture" v-for="(message) of errors.background_picture" class="text-danger">"{{ message }}"</small>
|
||||
<div class="col-sm-10 p-2">
|
||||
<ImageInputComponent
|
||||
id="background-picture-input"
|
||||
:required="true"
|
||||
:show-labels="true"
|
||||
name="background_picture"
|
||||
:helper="trans.methods.__('crud.minicrud.dimensions_must_be', {
|
||||
widthpx: '500px',
|
||||
heightpx: '231px',
|
||||
})"
|
||||
:width="500"
|
||||
:height="231"
|
||||
:value="menuNodeData.background_picture"
|
||||
:preview="false"
|
||||
:browse-event="false"
|
||||
@image-file="(file) => file ? saveNodeDataInput('background_picture', file) : null"
|
||||
/>
|
||||
</div>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.background_picture"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.background_picture"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
<!-- BACKGROUND PICTURE ALT -->
|
||||
<label
|
||||
for="background-picture-alt-text"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
>
|
||||
{{ trans.methods.__("Background Picture alt") }}
|
||||
{{ trans.methods.__("Text alternatif de l'image de fond") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="col-sm-10 p-2">
|
||||
<WordCounter
|
||||
id="background-picture-alt-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="background-picture-alt-text"
|
||||
type="text"
|
||||
|
|
@ -272,16 +466,29 @@
|
|||
:value="menuNodeData.background_picture_alt"
|
||||
@blur="($event) => saveNodeDataInput('background_picture_alt', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.background_picture_alt" v-for="(message) of errors.background_picture_alt" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('crud.minicrud.picture_alt_helper') }}</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.background_picture_alt"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.background_picture_alt"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
<!-- BACKGROUND PICTURE TITLE -->
|
||||
<label
|
||||
for="background-picture-title-text"
|
||||
class="col-sm-2 col-form-label p-2"
|
||||
>
|
||||
{{ trans.methods.__("Background picture title") }}
|
||||
{{ trans.methods.__("Titre de l'image de fond") }}
|
||||
</label>
|
||||
<div class="col-sm-4 p-2">
|
||||
<div class="col-sm-10 p-2">
|
||||
<WordCounter
|
||||
id="background-picture-title-text"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="background-picture-title-text"
|
||||
type="text"
|
||||
|
|
@ -289,18 +496,29 @@
|
|||
:value="menuNodeData.background_picture_title"
|
||||
@blur="($event) => saveNodeDataInput('background_picture_title', ($event.target as HTMLInputElement).value)"
|
||||
>
|
||||
<small v-if="errors.background_picture_title" v-for="(message) of errors.background_picture_title" class="text-danger">"{{ message }}"</small>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('crud.minicrud.picture_title_helper') }}</small>
|
||||
<template
|
||||
v-for="(message, messageIndex) of errors.background_picture_title"
|
||||
:key="messageIndex"
|
||||
>
|
||||
<small
|
||||
v-if="errors.background_picture_title"
|
||||
class="text-danger"
|
||||
>"{{ message }}"</small>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineOptions, onMounted, reactive, watch, type PropType } from "vue";
|
||||
import { defineOptions, reactive, watch, type PropType } from "vue";
|
||||
import type { IMenuNodeDataInputInterface } from "../../interfaces/IMenuNodeDataInputInterface";
|
||||
import trans from "../../modules/trans";
|
||||
import type { MenuNodeDataInterface } from "../../types/models/MenuNodeData";
|
||||
import ImageInputComponent from "./ImageInputComponent.vue";
|
||||
import ColorPickerComponent from "./ColorPickerComponent.vue";
|
||||
import WordCounter from "./WordCounterComponent.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "MainMenuNodeDataEditor"
|
||||
|
|
@ -311,9 +529,6 @@ const emits = defineEmits<{
|
|||
updatedNodeData: [prop: string, value: ValueOf<IMenuNodeDataInputInterface> ],
|
||||
}>();
|
||||
|
||||
// * ATTRIBUTES
|
||||
// const attrs = useAttrs();
|
||||
|
||||
// * PROPS
|
||||
const props = defineProps({
|
||||
menuNodeData: {
|
||||
|
|
@ -328,21 +543,14 @@ const props = defineProps({
|
|||
|
||||
// * DATA
|
||||
let menuNodeData = reactive<MenuNodeDataInterface>(Object.assign({}, props.menuNodeData));
|
||||
// const urlIsValid = ref<boolean>(true);
|
||||
|
||||
// * LIFECYCLE
|
||||
onMounted(() => {});
|
||||
// * WATCHERS
|
||||
watch(props, () => {
|
||||
menuNodeData = Object.assign({}, props.menuNodeData);
|
||||
})
|
||||
});
|
||||
|
||||
// * METHODS
|
||||
function saveNodeDataInput(inputName: string, inputValue: ValueOf<IMenuNodeDataInputInterface>): void {
|
||||
emits("updatedNodeData", inputName, inputValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main-menu-node-data-editor-component {
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<!-- NODE PROPS EDITOR -->
|
||||
<fieldset class="main-menu-node-editor-component container-fluid p-4 border mb-3">
|
||||
<fieldset
|
||||
ref="mainMenuNodeEditor"
|
||||
class="main-menu-node-editor-component container-fluid mb-3"
|
||||
>
|
||||
<legend>
|
||||
<span class="fs-5">{{ trans.methods.__("Modification d'un noeud") }}</span>
|
||||
<button
|
||||
class="btn btn-sm btn-dark float-end mt-2"
|
||||
:title="trans.methods.__('Fermer la modification')"
|
||||
data-bs-toggle="tooltip"
|
||||
@click.prevent="closeEditor"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
|
|
@ -13,17 +17,17 @@
|
|||
/>
|
||||
</button>
|
||||
</legend>
|
||||
|
||||
<!-- NODE TYPE -->
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
for="type"
|
||||
for="type-node"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Type de Noeud") }}
|
||||
{{ trans.methods.__("Type de noeud") }}
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="col-sm-4 p-2">
|
||||
<input
|
||||
id="type-node"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:value="props.enum.label"
|
||||
|
|
@ -31,27 +35,59 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menu Name -->
|
||||
<div
|
||||
class="mb-3 row"
|
||||
>
|
||||
<!-- MENU PUBLISHED -->
|
||||
<div class="mb-3 row">
|
||||
<!-- LINK TARGET -->
|
||||
<label
|
||||
for="type"
|
||||
class="col-sm-6 col-form-label"
|
||||
for="published-checkbox"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Titre du noeud principal affiché dans le menu") }}
|
||||
{{ trans.methods.__("Visibilité du noeud") }}
|
||||
</label>
|
||||
<div class="col input-group">
|
||||
<div class="col p-2">
|
||||
<div class="form-check form-switch py-0">
|
||||
<input
|
||||
name="published"
|
||||
class="form-check-input pt-3"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="published-checkbox"
|
||||
:checked="menuNode.published"
|
||||
@blur="menuNode.published = ($event.target as HTMLInputElement).checked"
|
||||
>
|
||||
</div>
|
||||
<small class="form-text text-body-secondary">{{ trans.methods.__('Deux réponses possibles : oui ou non (maximum 8 liens publiés).') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MENU NAME -->
|
||||
<div class="mb-3 row">
|
||||
<label
|
||||
for="title-node"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Titre du noeud principal") }}
|
||||
</label>
|
||||
<div class="col-sm-10 p-2">
|
||||
<WordCounter
|
||||
id="title-node"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
id="title-node"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:value="menuNode.title"
|
||||
@blur="menuNode.title = ($event.target as HTMLInputElement).value"
|
||||
>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('validation.between.string', {
|
||||
attribute: "titre du noeud principal",
|
||||
min: "3",
|
||||
max: "255",
|
||||
}) }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ROUTE NAME -->
|
||||
<div
|
||||
v-if="props.enum.value !== MenuNodeTypeEnum.link"
|
||||
|
|
@ -61,9 +97,9 @@
|
|||
for="type"
|
||||
class="col-sm-2 col-form-label"
|
||||
>
|
||||
{{ trans.methods.__("Route") }}
|
||||
{{ trans.methods.__("Lien du noeud") }}
|
||||
</label>
|
||||
<div class="col input-group">
|
||||
<div class="col input-group p-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
|
|
@ -89,14 +125,24 @@
|
|||
>
|
||||
{{ trans.methods.__("Url") }}
|
||||
</label>
|
||||
<div class="col input-group">
|
||||
<input
|
||||
ref="urlInput"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:value="String(menuNode.link.url)"
|
||||
@blur="saveUrl(($event.target as HTMLInputElement | null)!.value)"
|
||||
>
|
||||
<div class="col p-2">
|
||||
<div>
|
||||
<WordCounter
|
||||
id="node-url"
|
||||
class="ck-word-count"
|
||||
/>
|
||||
<input
|
||||
ref="urlInput"
|
||||
id="node-url"
|
||||
type="text"
|
||||
class="form-control"
|
||||
:value="String(menuNode.link.url)"
|
||||
@blur="saveUrl(($event.target as HTMLInputElement | null)!.value)"
|
||||
>
|
||||
</div>
|
||||
<small class="form-text text-body-secondary">
|
||||
{{ trans.methods.__('Doît être une url valide.') }}
|
||||
</small>
|
||||
</div>
|
||||
<small
|
||||
v-if="!urlIsValid"
|
||||
|
|
@ -114,6 +160,8 @@ import trans from "../../modules/trans";
|
|||
import type { MenuNodeInterface } from "../../types/models/MenuNode";
|
||||
import { MenuNodeTypeEnum } from "../enums";
|
||||
import MainMenuNodeDataEditor from "./MainMenuNodeDataEditor.vue";
|
||||
import WordCounter from "./WordCounterComponent.vue";
|
||||
import { Tooltips } from "./../../modules/bsTooltips";
|
||||
|
||||
defineOptions({
|
||||
name: "MainMenuNodeEditor",
|
||||
|
|
@ -126,9 +174,6 @@ const emits = defineEmits<{
|
|||
updatedNode: [menuNode: MenuNodeInterface],
|
||||
}>();
|
||||
|
||||
// * ATTRIBUTES
|
||||
// const attrs = useAttrs();
|
||||
|
||||
// * PROPS
|
||||
const props = defineProps({
|
||||
menuNode: {
|
||||
|
|
@ -141,33 +186,52 @@ const props = defineProps({
|
|||
}
|
||||
});
|
||||
|
||||
// * REFS
|
||||
const mainMenuNodeEditor = ref<HTMLInputElement|null>(null);
|
||||
const urlInput = ref<HTMLInputElement|null>(null);
|
||||
|
||||
// * DATA
|
||||
const menuNode = ref<MenuNodeInterface>(Object.assign({}, props.menuNode));
|
||||
const urlIsValid = ref<boolean>(true);
|
||||
const tooltips = ref<Tooltips|null>(null);
|
||||
|
||||
// * LIFECYCLE
|
||||
onMounted(() => {
|
||||
urlIsValid.value = isUrlValid(String(menuNode.value.link.url));
|
||||
initTooltips();
|
||||
});
|
||||
|
||||
// * WATCHERS
|
||||
watch(menuNode, (newValue) => {
|
||||
emits("updatedNode", newValue);
|
||||
}, { deep: true });
|
||||
|
||||
// * REFS
|
||||
const urlInput = ref<HTMLInputElement|null>(null);
|
||||
|
||||
// * METHODS
|
||||
|
||||
function saveUrl(newUrl: string): void {
|
||||
urlIsValid.value = isUrlValid(newUrl);
|
||||
menuNode.value.link.url = urlInput.value!.value;
|
||||
}
|
||||
|
||||
function closeEditor(): void {
|
||||
tooltips.value?.closeBootstrapTooltip();
|
||||
emits("closeEditor");
|
||||
}
|
||||
|
||||
function isUrlValid(url: string):boolean {
|
||||
const regex = RegExp("(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+@]*)*(\\?[;&a-z\\d%_.~+=-@]*)?(\\#[-a-z\\d_@]*)?$", "i");
|
||||
return url.match(regex)?.[0] !== undefined;
|
||||
}
|
||||
|
||||
/** Initialise all tooltips in the component */
|
||||
function initTooltips(): void {
|
||||
setTimeout(() => {
|
||||
tooltips.value = Tooltips.make({
|
||||
type: "dom",
|
||||
elements: mainMenuNodeEditor.value!.querySelectorAll("[data-bs-toggle=\"tooltip\"]")
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
1
resources/ts/types/models/MenuNode.d.ts
vendored
1
resources/ts/types/models/MenuNode.d.ts
vendored
|
|
@ -4,6 +4,7 @@ import type { LinkInterface } from "resources/ts/types/models/Link";
|
|||
|
||||
export interface MenuNodeInterface {
|
||||
type: MenuNodeTypeEnum,
|
||||
published: boolean,
|
||||
title: string,
|
||||
link: LinkInterface
|
||||
}
|
||||
|
|
|
|||
1
resources/ts/types/models/MenuNodeData.d.ts
vendored
1
resources/ts/types/models/MenuNodeData.d.ts
vendored
|
|
@ -6,6 +6,7 @@ export interface MenuNodeDataInterface {
|
|||
sub_label: string,
|
||||
title: string,
|
||||
text: string,
|
||||
text_color: string,
|
||||
link_url: string
|
||||
link_title: string
|
||||
link_target: boolean
|
||||
|
|
|
|||
|
|
@ -1,30 +1,36 @@
|
|||
@foreach ($menuNodeList as $index => $menuNode)
|
||||
@switch($menuNode->type)
|
||||
@case(\App\Enums\Settings\MenuNodeType::siteLogo)
|
||||
@empty($noLogo)
|
||||
<li class="nav-item menu-logo no-wrap mx-auto">
|
||||
<img src="{{ Vite::asset('resources/assets/images/legaulois-logo.png') }}" alt="{{ config('app.name') }}" role="img">
|
||||
</li>
|
||||
@endempty
|
||||
@break
|
||||
@if ($menuNode->published)
|
||||
@switch($menuNode->type)
|
||||
@case(\App\Enums\Settings\MenuNodeType::siteLogo)
|
||||
@empty($noLogo)
|
||||
<li class="nav-item menu-logo no-wrap mx-auto">
|
||||
<a href="{{ route('fo.home.show') }}" title="Accéder à la page d'accueil">
|
||||
<img src="{{ Vite::asset('resources/assets/images/legaulois-logo.png') }}" alt="{{ config('app.name') }}"
|
||||
title="{{ __(':appname, Volaille 100% Française', ['appname' => config('app.name')]) }}"
|
||||
role="img">
|
||||
</a>
|
||||
</li>
|
||||
@endempty
|
||||
@break
|
||||
|
||||
@case(\App\Enums\Settings\MenuNodeType::link)
|
||||
@empty($logoOnly)
|
||||
<li class="nav-item">
|
||||
@include('fo.layouts.main-menu.menu-nodes.generic.simple-link', [
|
||||
'menuNode' => $menuNode,
|
||||
])
|
||||
</li>
|
||||
@endempty
|
||||
@break
|
||||
@case(\App\Enums\Settings\MenuNodeType::link)
|
||||
@empty($logoOnly)
|
||||
<li class="nav-item">
|
||||
@include('fo.layouts.main-menu.menu-nodes.generic.simple-link', [
|
||||
'menuNode' => $menuNode,
|
||||
])
|
||||
</li>
|
||||
@endempty
|
||||
@break
|
||||
|
||||
@default
|
||||
@empty($logoOnly)
|
||||
<li class="nav-item">
|
||||
@include("fo.layouts.main-menu.menu-nodes.{$menuNode->type->name()}", [
|
||||
'menuNode' => $menuNode,
|
||||
])
|
||||
</li>
|
||||
@endempty
|
||||
@endswitch
|
||||
@default
|
||||
@empty($logoOnly)
|
||||
<li class="nav-item">
|
||||
@include("fo.layouts.main-menu.menu-nodes.{$menuNode->type->name()}", [
|
||||
'menuNode' => $menuNode,
|
||||
])
|
||||
</li>
|
||||
@endempty
|
||||
@endswitch
|
||||
@endif
|
||||
@endforeach
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
@include('fo.layouts.main-menu.partials.collapse-right', [
|
||||
'title' => str($menuNodeData->title)->upper(),
|
||||
'text' => str($menuNodeData->text)->ucFirst(),
|
||||
'textColor' => $menuNodeData->text_color,
|
||||
'btnLabel' => str($menuNodeData->link_title)->upper(),
|
||||
'btnLink' => $menuNodeData->link_url,
|
||||
'btnLinkTarget' => $menuNodeData->link_target,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
@include('fo.layouts.main-menu.partials.collapse-right', [
|
||||
'title' => str($menuNodeData->title)->upper(),
|
||||
'text' => str($menuNodeData->text)->ucFirst(),
|
||||
'textColor' => $menuNodeData->text_color,
|
||||
'btnLabel' => str($menuNodeData->link_title)->upper(),
|
||||
'btnLink' => $menuNodeData->link_url,
|
||||
'btnLinkTarget' => $menuNodeData->link_target,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
@include('fo.layouts.main-menu.partials.collapse-right', [
|
||||
'title' => str($menuNodeData->title)->upper(),
|
||||
'text' => str($menuNodeData->text)->ucFirst(),
|
||||
'textColor' => $menuNodeData->text_color,
|
||||
'btnLabel' => str($menuNodeData->link_title)->upper(),
|
||||
'btnLink' => $menuNodeData->link_url,
|
||||
'btnLinkTarget' => $menuNodeData->link_target,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
@include('fo.layouts.main-menu.partials.collapse-right', [
|
||||
'title' => str($menuNodeData->title)->upper(),
|
||||
'text' => str($menuNodeData->text)->ucFirst(),
|
||||
'textColor' => $menuNodeData->text_color,
|
||||
'btnLabel' => str($menuNodeData->link_title)->upper(),
|
||||
'btnLink' => $menuNodeData->link_url,
|
||||
'btnLinkTarget' => $menuNodeData->link_target,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
@include('fo.layouts.main-menu.partials.collapse-right', [
|
||||
'title' => str($menuNodeData->title)->upper(),
|
||||
'text' => str($menuNodeData->text)->ucFirst(),
|
||||
'textColor' => $menuNodeData->text_color,
|
||||
'btnLabel' => str($menuNodeData->link_title)->upper(),
|
||||
'btnLink' => $menuNodeData->link_url,
|
||||
'btnLinkTarget' => $menuNodeData->link_target,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
alt="{{ $imgAlt }}">
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-8 d-flex flex-column justify-content-center align-items-start">
|
||||
<div class="col-8 d-flex flex-column justify-content-center align-items-start" style="color:{{ $textColor }};">
|
||||
<p class="fs-4 f-bourton-base mb-1">{{ $title }}</p>
|
||||
<p>{{ $text }}</p>
|
||||
@if ($showLink)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue