Sprint 3 + sylelint/eslint
41
.eslintrc.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-strongly-recommended",
|
||||
"@vue/eslint-config-typescript"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.vue"],
|
||||
"rules": {
|
||||
"no-undef": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.vue"],
|
||||
"rules": {
|
||||
"indent": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["vue", "@typescript-eslint"],
|
||||
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
|
||||
"rules": {
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double"],
|
||||
"semi": ["error", "always"],
|
||||
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
|
||||
"vue/one-component-per-file": "off"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
|
||||
|
||||
stages:
|
||||
- Security
|
||||
- CodingStandards
|
||||
- Builds
|
||||
|
||||
composer-security:
|
||||
stage: Security
|
||||
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
|
||||
script:
|
||||
- local-php-security-checker --format=json --path=./composer.lock > composer-security-checker-report.json
|
||||
artifacts:
|
||||
|
|
@ -14,9 +14,13 @@ composer-security:
|
|||
paths:
|
||||
- composer-security-checker-report.json
|
||||
allow_failure: true
|
||||
needs: []
|
||||
|
||||
npm-security:
|
||||
stage: Security
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install --location=global audit-ci@^6
|
||||
script:
|
||||
- audit-ci -p npm -o json > npm-security-checker-report.json
|
||||
artifacts:
|
||||
|
|
@ -25,10 +29,13 @@ npm-security:
|
|||
paths:
|
||||
- npm-security-checker-report.json
|
||||
allow_failure: true
|
||||
needs: []
|
||||
|
||||
phpcs:
|
||||
stage: CodingStandards
|
||||
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
|
||||
before_script:
|
||||
- composer config gitlab-token.gitlab.com $CI_DEPLOY_USER $CI_DEPLOY_PASSWORD
|
||||
- composer install
|
||||
cache:
|
||||
paths:
|
||||
|
|
@ -42,4 +49,70 @@ phpcs:
|
|||
- phpcs-report.xml
|
||||
reports:
|
||||
junit: phpcs-report.xml
|
||||
allow_failure: false
|
||||
allow_failure: false
|
||||
needs:
|
||||
- composer-security
|
||||
- npm-security
|
||||
|
||||
eslint:
|
||||
stage: CodingStandards
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
script:
|
||||
- npm run eslint-ci --silent > eslint-report.xml
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'eslint'
|
||||
paths:
|
||||
- eslint-report.xml
|
||||
reports:
|
||||
junit: eslint-report.xml
|
||||
allow_failure: false
|
||||
needs:
|
||||
- composer-security
|
||||
- npm-security
|
||||
|
||||
stylelint:
|
||||
stage: CodingStandards
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
script:
|
||||
- npm run stylelint-ci --silent > stylelint-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'stylelint'
|
||||
paths:
|
||||
- stylelint-report.json
|
||||
allow_failure: false
|
||||
needs:
|
||||
- composer-security
|
||||
- npm-security
|
||||
|
||||
front-build:
|
||||
stage: Builds
|
||||
image: node:17.9.1
|
||||
before_script:
|
||||
- npm install
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
script:
|
||||
- npm run prod > front-build-report.json
|
||||
artifacts:
|
||||
when: on_failure
|
||||
expose_as: 'front-build-report'
|
||||
paths:
|
||||
- front-build-report.json
|
||||
allow_failure: false
|
||||
needs:
|
||||
- phpcs
|
||||
- eslint
|
||||
- stylelint
|
||||
|
|
|
|||
20
.stylelintrc.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "stylelint-config-sass-guidelines",
|
||||
"ignoreFiles": [
|
||||
"**/vendor/*.css",
|
||||
"**/vendor/**/*.css",
|
||||
"**/vendor/*.scss",
|
||||
"**/vendor/**/*.scss",
|
||||
"**/vendor/*.sass",
|
||||
"**/vendor/**/*.sass",
|
||||
"**/vendor/*.less",
|
||||
"**/vendor/**/*.less"
|
||||
],
|
||||
"customSyntax": "postcss-scss",
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"string-quotes": "double",
|
||||
"selector-max-id": null,
|
||||
"color-no-invalid-hex": true
|
||||
}
|
||||
}
|
||||
5
.vscode/settings.json
vendored
|
|
@ -16,8 +16,9 @@
|
|||
"titleBar.activeBackground": "#353517",
|
||||
"titleBar.activeForeground": "#e7e7e7",
|
||||
"titleBar.inactiveBackground": "#35351799",
|
||||
"titleBar.inactiveForeground": "#e7e7e799"
|
||||
"titleBar.inactiveForeground": "#e7e7e799",
|
||||
"commandCenter.border": "#e7e7e799"
|
||||
},
|
||||
"peacock.remoteColor": "#353517",
|
||||
"vue3snippets.enable-compile-vue-file-on-did-save-code": true
|
||||
"vue3snippets.enable-compile-vue-file-on-did-save-code": false
|
||||
}
|
||||
|
|
|
|||
50
app/Enums/Engagements/EngagementType.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Engagements;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* @method static self renewableEnergies()
|
||||
* @method static self localCereals()
|
||||
* @method static self agroforestry()
|
||||
* @method static self cereals()
|
||||
* @method static self animalWelfare()
|
||||
* @method static self beekeeping()
|
||||
*/
|
||||
final class EngagementType extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'renewableEnergies' => 1,
|
||||
'localCereals' => 2,
|
||||
'agroforestry' => 3,
|
||||
'cereals' => 4,
|
||||
'animalWelfare' => 5,
|
||||
'beekeeping' => 6
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Labels
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function labels(): array
|
||||
{
|
||||
return [
|
||||
'renewableEnergies' => __('models.classes.engagements.renewableEnergies'),
|
||||
'localCereals' => __('models.classes.engagements.localCereals'),
|
||||
'agroforestry' => __('models.classes.engagements.agroforestry'),
|
||||
'cereals' => __('models.classes.engagements.cereals'),
|
||||
'animalWelfare' => __('models.classes.engagements.animalWelfare'),
|
||||
'beekeeping' => __('models.classes.engagements.beekeeping')
|
||||
];
|
||||
}
|
||||
}
|
||||
46
app/Enums/InputType.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
|
||||
/**
|
||||
* @method static self boolean()
|
||||
* @method static self image()
|
||||
* @method static self string()
|
||||
* @method static self text()
|
||||
* html autorisé
|
||||
* @method static self url()
|
||||
* @method static self imageArray()
|
||||
* @method static self labelArray()
|
||||
* @method static self dropdownArray()
|
||||
* html autorisé
|
||||
* @method static self miniblocArray()
|
||||
* html autorisé
|
||||
* @method static self videoVimeo()
|
||||
* @method static self number()
|
||||
*/
|
||||
final class InputType extends BaseEnumRoutable
|
||||
{
|
||||
/**
|
||||
* Values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function values(): array
|
||||
{
|
||||
return [
|
||||
'boolean' => 0,
|
||||
'image' => 1,
|
||||
'string' => 2,
|
||||
'text' => 3,
|
||||
'url' => 4,
|
||||
'imageArray' => 5,
|
||||
'labelArray' => 6,
|
||||
'dropdownArray' => 7,
|
||||
'miniblocArray' => 8,
|
||||
'videoVimeo' => 9,
|
||||
'number' => 10
|
||||
];
|
||||
}
|
||||
}
|
||||
43
app/Http/Controllers/Bo/EngagementsController.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Engagements\UpdateEngagementsRequest;
|
||||
use App\Models\Engagement;
|
||||
|
||||
class EngagementsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param Engagement $engagement
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function edit(Engagement $engagement): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view('back.engagements.edit', ['engagement' => $engagement]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UpdateEngagementsRequest $request
|
||||
* @param Engagement $engagement
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(
|
||||
UpdateEngagementsRequest $request,
|
||||
Engagement $engagement
|
||||
): \Illuminate\Http\RedirectResponse {
|
||||
$engagement->fill(array_replace_recursive(
|
||||
\json_decode(\json_encode($engagement->toArray()), true),
|
||||
$request->validated()
|
||||
));
|
||||
$engagement->saveOrFail();
|
||||
return redirect()
|
||||
->route('bo.engagements.edit', $engagement)
|
||||
->with('success', 'Enregistré !');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Engagements;
|
||||
|
||||
use App\Http\Requests\Bo\UpdateMiniCrudRequest;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UpdateEngagementsRequest extends UpdateMiniCrudRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
/**
|
||||
* The model parameter name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $modelName = 'engagement';
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'slug' => Str::slug($this->name)
|
||||
]);
|
||||
$this->mergePicture('banner');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(
|
||||
[
|
||||
'title' => 'required|min:3|max:255',
|
||||
'seo_title' => 'required|min:53|max:60',
|
||||
'seo_description' => 'required|min:50|max:160',
|
||||
'banner_alt' => 'required_with:banner|nullable|string|min:6|max:255',
|
||||
'banner_title' => 'required_with:banner|nullable|string|min:6|max:255'
|
||||
],
|
||||
self::pictureRules('banner', 1920, 340, 1920, 340),
|
||||
parent::rules()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'title' => trans('title'),
|
||||
'seo_title' => trans('title') . ' seo',
|
||||
'seo_description' => trans('description') . ' seo',
|
||||
'banner' => trans('bannière'),
|
||||
'banner_alt' => trans('Texte alternatif de la bannière'),
|
||||
'banner_title' => trans('Titre de la bannière')
|
||||
];
|
||||
}
|
||||
}
|
||||
156
app/Http/Requests/Bo/UpdateMiniCrudRequest.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use App\Enums\InputType;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
abstract class UpdateMiniCrudRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* The model parameter name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $modelName = null;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
// phpcs:enable
|
||||
$rules = [];
|
||||
foreach ($this->{$this->modelName}->data as $iptName => $input) {
|
||||
$input = \json_decode(\json_encode($input), true);
|
||||
switch ($input['inputType']['value']) {
|
||||
case InputType::number()->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate('required|numeric', $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::string()->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate('required|string|min:1', $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::text()->value:
|
||||
$mergeRules = $this->compileValidate('required|string|min:10', $input);
|
||||
$mergeRules = collect(\explode('|', $mergeRules))
|
||||
->map(function ($rule) {
|
||||
if (\strpos($rule, 'max:') !== false) {
|
||||
$max = \intval(str_replace('max:', '', $rule));
|
||||
return function ($attribute, $value, $fail) use ($max) {
|
||||
if (\strlen(\strip_tags($value)) > $max) {
|
||||
$fail("$attribute ne doit pas excéder $max caractères");
|
||||
}
|
||||
};
|
||||
}
|
||||
return $rule;
|
||||
});
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.text" => $mergeRules
|
||||
]);
|
||||
break;
|
||||
case InputType::image()->value:
|
||||
$ipt = $this->data[$iptName];
|
||||
$max = isset($ipt['dimensionsMax']);
|
||||
$dim = collect(explode(
|
||||
' par ',
|
||||
$ipt['dimensionsMax'] ?? $input['dimensions']
|
||||
))->mapWithKeys(function ($v) {
|
||||
$v = explode(' ', $v);
|
||||
return [$v[1] => $v[0]];
|
||||
});
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate(
|
||||
implode('|', [
|
||||
'sometimes',
|
||||
'image',
|
||||
'mimes:png,jpg,jpeg',
|
||||
sprintf(
|
||||
'dimensions:%s=%d,%s=%d',
|
||||
$max ? 'max_height' : 'height',
|
||||
$dim['(hauteur)'],
|
||||
$max ? 'max_width' : 'width',
|
||||
$dim['(largeur)']
|
||||
)
|
||||
]),
|
||||
$input
|
||||
),
|
||||
"data.$iptName.alt" => $this->compileValidate('sometimes|string|min:5', $input),
|
||||
"data.$iptName.title" => $this->compileValidate('sometimes|string|min:5', $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::url()->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
'required', 'url',
|
||||
// @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]);
|
||||
}
|
||||
}
|
||||
]
|
||||
]);
|
||||
break;
|
||||
case InputType::boolean()->value:
|
||||
$all = $this->all();
|
||||
$nested = ToolboxHelper::dotStringToNestedArray(
|
||||
"data.$iptName.value",
|
||||
$this->input("data.$iptName.value") ? true : false
|
||||
);
|
||||
$all = array_merge_recursive_distinct($all, $nested);
|
||||
$this->merge($all);
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => 'boolean'
|
||||
]);
|
||||
break;
|
||||
} //end switch
|
||||
} //end foreach
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string $inputTypeValidate
|
||||
* @param array $input
|
||||
* @return string
|
||||
*/
|
||||
public function compileValidate(string $inputTypeValidate, array $input): string
|
||||
{
|
||||
if (!isset($input['validate']) or !\is_string($input['validate'])) {
|
||||
return $inputTypeValidate;
|
||||
}
|
||||
/** @var array */
|
||||
$inputValidate = explode('|', $input['validate']);
|
||||
$out = explode('|', $inputTypeValidate);
|
||||
foreach ($inputValidate as $validate) {
|
||||
if (!\in_array($validate, $out)) {
|
||||
$out[] = $validate;
|
||||
}
|
||||
}
|
||||
return \implode('|', $out);
|
||||
}
|
||||
}
|
||||
83
app/Models/Engagement.php
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Engagements\EngagementType;
|
||||
use App\Traits\Models\MiniCrudModel;
|
||||
use App\Lib\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Kwaadpepper\Enum\Traits\CastsEnums;
|
||||
|
||||
class Engagement extends Model
|
||||
{
|
||||
use CastsEnums;
|
||||
use MiniCrudModel;
|
||||
|
||||
/**
|
||||
* The primary key for the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'type';
|
||||
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'data' => 'array'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to Enums.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $enumCasts = [
|
||||
'type' => EngagementType::class
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'slug',
|
||||
'seo_title',
|
||||
'seo_description',
|
||||
'banner',
|
||||
'banner_title',
|
||||
'banner_alt',
|
||||
'type',
|
||||
'data'
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
$model->banner = ToolboxHelper::storeImage($model, $model->banner);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
ToolboxHelper::removeOldImage($model, 'banner');
|
||||
$model->banner = ToolboxHelper::storeImage($model, $model->banner);
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
ToolboxHelper::removeImage($model, 'banner');
|
||||
});
|
||||
}
|
||||
}
|
||||
80
app/Traits/Models/MiniCrudModel.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits\Models;
|
||||
|
||||
use App\Enums\InputType;
|
||||
use App\Lib\ToolboxHelper;
|
||||
|
||||
trait MiniCrudModel
|
||||
{
|
||||
/**
|
||||
* Fill the model with an array of attributes.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return void
|
||||
* @throws \Illuminate\Database\Eloquent\MassAssignmentException If fill prop is not allowed.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
|
||||
*/
|
||||
public function fill(array $attributes)
|
||||
{
|
||||
parent::fill($attributes);
|
||||
if (isset($attributes['data'])) {
|
||||
$this->setData($attributes['data']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific prop
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
private function setData(array $data): void
|
||||
{
|
||||
$attrs = $this->attributes;
|
||||
if (isset($attrs['data'])) {
|
||||
unset($attrs['data']);
|
||||
}
|
||||
$data = $this->serializeData($data);
|
||||
$this->setRawAttributes(\array_merge_recursive(
|
||||
$attrs,
|
||||
['data' => $data]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize properties
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
* @throws \ErrorException If input type is no handled.
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
*/
|
||||
private function serializeData(array $data): string
|
||||
{
|
||||
foreach ($data as $o => $input) {
|
||||
switch ($input['inputType']->value ?? $input['inputType']['value']) {
|
||||
case InputType::image()->value:
|
||||
$data[$o]['value'] = ToolboxHelper::storeImage($this, $input['value']);
|
||||
break;
|
||||
case InputType::boolean()->value:
|
||||
case InputType::text()->value:
|
||||
case InputType::number()->value:
|
||||
case InputType::string()->value:
|
||||
case InputType::videoVimeo()->value:
|
||||
break;
|
||||
case InputType::url()->value:
|
||||
ToolboxHelper::assertNoInternalBoUrls('content', $input['value']);
|
||||
break;
|
||||
default:
|
||||
throw new \ErrorException(sprintf(
|
||||
'Unknown input Type %s',
|
||||
$input['inputType']->label ?? $input['inputType']['label']
|
||||
));
|
||||
}
|
||||
} //end foreach
|
||||
return json_encode($data);
|
||||
}
|
||||
}
|
||||
1202
database/migrations/2022_09_05_131055_create_engagements_table.php
Normal file
|
|
@ -19,7 +19,7 @@ class DatabaseSeeder extends Seeder
|
|||
BreederSeeder::class,
|
||||
RangeSeeder::class,
|
||||
RecipeSeeder::class,
|
||||
ProductSeeder::class,
|
||||
ProductSeeder::class
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
{
|
||||
"include": [
|
||||
"./resources/js/**/*"
|
||||
]
|
||||
}
|
||||
{
|
||||
"include": ["./resources/js/**/*"]
|
||||
}
|
||||
|
|
|
|||
3542
package-lock.json
generated
17
package.json
|
|
@ -7,7 +7,11 @@
|
|||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
||||
"hot": "mix watch --hot",
|
||||
"prod": "npm run production",
|
||||
"production": "mix --production"
|
||||
"production": "mix --production",
|
||||
"eslint": "node ./node_modules/.bin/eslint -c .eslintrc.json ./resources",
|
||||
"eslint-ci": "node ./node_modules/.bin/eslint -c .eslintrc.json ./resources -f junit",
|
||||
"stylelint": "npx stylelint '**/*.scss'",
|
||||
"stylelint-ci": "npx stylelint '**/*.scss' -f json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ckpack/vue-color": "^1.1.9",
|
||||
|
|
@ -19,17 +23,28 @@
|
|||
"@types/toastify-js": "^1.11.1",
|
||||
"@types/vue-select": "^3.16.0",
|
||||
"@types/webpack-env": "^1.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||
"@typescript-eslint/parser": "^5.35.1",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"axios": "^0.21",
|
||||
"bootstrap": "^5.1.3",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"eslint-webpack-plugin": "^3.2.0",
|
||||
"cropperjs": "^1.5.12",
|
||||
"laravel-mix": "^6.0.6",
|
||||
"laravel-mix-clean": "^0.1.0",
|
||||
"laravel-mix-eslint": "^0.2.0",
|
||||
"laravel-mix-polyfill": "^3.0.1",
|
||||
"laravel-mix-stylelint": "^1.0.0",
|
||||
"lodash": "^4.17.19",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.52.1",
|
||||
"sass-loader": "^12.6.0",
|
||||
"stylelint": "^14.11.0",
|
||||
"stylelint-config-sass-guidelines": "^9.0.1",
|
||||
"stylelint-webpack-plugin": "^3.3.0",
|
||||
"slugify": "^1.6.5",
|
||||
"sweetalert2": "^11.4.17",
|
||||
"toastify-js": "^1.11.2",
|
||||
|
|
|
|||
BIN
resources/assets/images/engagements/1920x340.jpg
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
resources/assets/images/engagements/342x200.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
resources/assets/images/engagements/412x319.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
resources/assets/images/engagements/486x278.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
resources/assets/images/engagements/500x637.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
resources/assets/images/engagements/525x451.jpg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
resources/assets/images/engagements/680x350.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
import axios from 'axios';
|
||||
import axios from "axios";
|
||||
|
||||
class CKUploadAdaptater {
|
||||
constructor(loader) {
|
||||
|
|
@ -8,11 +8,13 @@ class CKUploadAdaptater {
|
|||
|
||||
// Starts the upload process.
|
||||
upload() {
|
||||
return this.loader.file
|
||||
.then(file => new Promise((resolve, reject) => {
|
||||
this._initRequest();
|
||||
this._sendRequest(file, resolve, reject);
|
||||
}));
|
||||
return this.loader.file.then(
|
||||
(file) =>
|
||||
new Promise((resolve, reject) => {
|
||||
this._initRequest();
|
||||
this._sendRequest(file, resolve, reject);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Aborts the upload process.
|
||||
|
|
@ -25,32 +27,41 @@ class CKUploadAdaptater {
|
|||
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
|
||||
_initRequest() {
|
||||
this.cancel = axios.CancelToken.source();
|
||||
this.route = require('./../modules/route').default.methods.route('crud-policies.upload')
|
||||
// eslint-disable-next-line no-undef
|
||||
this.route = require("./../modules/route").default.methods.route(
|
||||
"crud-policies.upload"
|
||||
);
|
||||
}
|
||||
|
||||
// Prepares the data and sends the request.
|
||||
_sendRequest(file, resolve, reject) {
|
||||
// Prepare the form data.
|
||||
const data = new FormData();
|
||||
data.append('upload', file);
|
||||
let headers = Object.assign({}, window.axios ? window.axios.defaults.headers.common : {})
|
||||
headers['Content-Type'] = 'multipart/form-data'
|
||||
axios.post(this.route, data, {
|
||||
withCredentials: true,
|
||||
headers: headers,
|
||||
onUploadProgress: (evt) => {
|
||||
this.loader.uploadTotal = evt.total;
|
||||
this.loader.uploaded = evt.loaded;
|
||||
}
|
||||
}).then((response) => {
|
||||
resolve({
|
||||
default: response.data.url
|
||||
data.append("upload", file);
|
||||
let headers = Object.assign(
|
||||
{},
|
||||
window.axios ? window.axios.defaults.headers.common : {}
|
||||
);
|
||||
headers["Content-Type"] = "multipart/form-data";
|
||||
axios
|
||||
.post(this.route, data, {
|
||||
withCredentials: true,
|
||||
headers: headers,
|
||||
onUploadProgress: (evt) => {
|
||||
this.loader.uploadTotal = evt.total;
|
||||
this.loader.uploaded = evt.loaded;
|
||||
},
|
||||
})
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
reject('Le fichier à été marbré')
|
||||
})
|
||||
.then((response) => {
|
||||
resolve({
|
||||
default: response.data.url,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
reject("Le fichier à été marbré");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { CKUploadAdaptater }
|
||||
export { CKUploadAdaptater };
|
||||
|
|
|
|||
|
|
@ -41,37 +41,37 @@ export default class LaravelFileManager extends Plugin {
|
|||
}
|
||||
|
||||
if (imageURL.split(".").pop() === "mp4") {
|
||||
editor.model.change((writer) => {
|
||||
const oembed = writer.createElement("oembed", {
|
||||
url: imageURL,
|
||||
});
|
||||
const figure = writer.createElement(
|
||||
"figure",
|
||||
{ class: "media" },
|
||||
[oembed]
|
||||
);
|
||||
// editor.model.change((writer) => {
|
||||
// const oembed = writer.createElement("oembed", {
|
||||
// url: imageURL,
|
||||
// });
|
||||
// const figure = writer.createElement(
|
||||
// "figure",
|
||||
// { class: "media" },
|
||||
// [oembed]
|
||||
// );
|
||||
|
||||
const p = writer.createElement(
|
||||
"p",
|
||||
null,
|
||||
"fofofo"
|
||||
);
|
||||
// const p = writer.createElement(
|
||||
// "p",
|
||||
// null,
|
||||
// "fofofo"
|
||||
// );
|
||||
|
||||
// < video controls allowfullscreen style = "max-width: 90%; margin: 0 auto; display: block;" > ' +
|
||||
// `<source src="${path}" type="video/mp4">` +
|
||||
// `<p>Votre navigateur ne prend pas en charge les vidéos HTML5. Voici <a href="${path}">un lien pour télécharger la vidéo</a>.</p>` +
|
||||
// '</>
|
||||
// // < video controls allowfullscreen style = "max-width: 90%; margin: 0 auto; display: block;" > ' +
|
||||
// // `<source src="${path}" type="video/mp4">` +
|
||||
// // `<p>Votre navigateur ne prend pas en charge les vidéos HTML5. Voici <a href="${path}">un lien pour télécharger la vidéo</a>.</p>` +
|
||||
// // '</>
|
||||
|
||||
// Insert the oembed video in the current selection location.
|
||||
// editor.model.insertContent(p, editor.model.document.selection);
|
||||
// console.log('insert')
|
||||
// const insertPosition = editor.model.document.selection.getFirstPosition();
|
||||
// writer.insertText('foo', insertPosition);
|
||||
// editor.model.insertContent(p, insertPosition);
|
||||
// editor.model.insertContent(figure, insertPosition);
|
||||
// console.log(imageURL)
|
||||
editor.execute("mediaEmbed", imageURL);
|
||||
});
|
||||
// // Insert the oembed video in the current selection location.
|
||||
// // editor.model.insertContent(p, editor.model.document.selection);
|
||||
// // console.log('insert')
|
||||
// // const insertPosition = editor.model.document.selection.getFirstPosition();
|
||||
// // writer.insertText('foo', insertPosition);
|
||||
// // editor.model.insertContent(p, insertPosition);
|
||||
// // editor.model.insertContent(figure, insertPosition);
|
||||
// // console.log(imageURL)
|
||||
// editor.execute("mediaEmbed", imageURL);
|
||||
// });
|
||||
} else {
|
||||
editor.model.change((writer) => {
|
||||
const imageElement = writer.createElement(
|
||||
|
|
|
|||
|
|
@ -9,5 +9,14 @@ return [
|
|||
'products' => 'Products',
|
||||
'recipes' => 'Recipes',
|
||||
'breeders' => 'Breeders',
|
||||
'engagements' => [
|
||||
'title' => 'Engagements',
|
||||
'renewableEnergies' => 'Renewable energy',
|
||||
'localCereals' => 'Sustainable livestock / Local cereals',
|
||||
'agroforestry' => 'Agroforestry',
|
||||
'cereals' => 'Cereals',
|
||||
'animalWelfare' => 'Animal welfare',
|
||||
'beekeeping' => 'Beekeeping',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,5 +9,14 @@ return [
|
|||
'products' => 'Produits',
|
||||
'recipes' => 'Recettes',
|
||||
'breeders' => 'Éleveurs',
|
||||
'engagements' => [
|
||||
'title' => 'Engagements',
|
||||
'renewableEnergies' => 'Énergies renouvelables',
|
||||
'localCereals' => 'Élevage durable / Céréales locales',
|
||||
'agroforestry' => 'Agroforesteries',
|
||||
'cereals' => 'Céréales',
|
||||
'animalWelfare' => 'Bien être animal',
|
||||
'beekeeping' => 'Apiculture',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@import './variables';
|
||||
@import './fonts';
|
||||
@import '~bootstrap';
|
||||
@import "./variables";
|
||||
@import "./fonts";
|
||||
@import "~bootstrap";
|
||||
|
||||
.bg-loue-blue {
|
||||
background-color: $loue-blue !important;
|
||||
|
|
@ -8,20 +8,20 @@
|
|||
|
||||
.text-loue {
|
||||
color: $loue-yellow !important;
|
||||
font-family: 'museo_sans', 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif !important;
|
||||
font-family: "museo_sans", "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.icon-size {
|
||||
%icon-size {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.cross-red {
|
||||
@extend .icon-size;
|
||||
color: crimson;
|
||||
@extend %icon-size;
|
||||
color: #dc143c;
|
||||
}
|
||||
|
||||
.check-green {
|
||||
@extend .icon-size;
|
||||
color: seagreen;
|
||||
@extend %icon-size;
|
||||
color: #2e8b57;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +1,92 @@
|
|||
@font-face {
|
||||
font-family: "museo_sans";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url("~@/fonts/museo_sans/museosans-100-webfont.eot");
|
||||
src: url("~@/fonts/museo_sans/museosans-100-webfont.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
src: url("~@/fonts/museo_sans/museosans-100-webfont.eot?#iefix") format("embedded-opentype"),
|
||||
url("~@/fonts/museo_sans/museosans-100-webfont.woff") format("woff"),
|
||||
url("~@/fonts/museo_sans/museosans-100-webfont.ttf")
|
||||
format("truetype"),
|
||||
format("truetype"),
|
||||
url("~@/fonts/museo_sans/museosans-100-webfont.svg#museo_sans100")
|
||||
format("svg");
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "museo_sans";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("~@/fonts/museo_sans/museosans-300-webfont.eot");
|
||||
src: url("~@/fonts/museo_sans/museosans-300-webfont.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
format("embedded-opentype"),
|
||||
url("~@/fonts/museo_sans/museosans-300-webfont.woff") format("woff"),
|
||||
url("~@/fonts/museo_sans/museosans-300-webfont.ttf")
|
||||
format("truetype"),
|
||||
format("truetype"),
|
||||
url("~@/fonts/museo_sans/museosans-300-webfont.svg#museo_sans300")
|
||||
format("svg");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "museo_sans";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("~@/fonts/museo_sans/museosans-500-webfont.eot");
|
||||
src: url("~@/fonts/museo_sans/museosans-500-webfont.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
format("embedded-opentype"),
|
||||
url("~@/fonts/museo_sans/museosans-500-webfont.woff") format("woff"),
|
||||
url("~@/fonts/museo_sans/museosans-500-webfont.ttf")
|
||||
format("truetype"),
|
||||
format("truetype"),
|
||||
url("~@/fonts/museo_sans/museosans-500-webfont.svg#museo_sans500")
|
||||
format("svg");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "museo_sans";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url("~@/fonts/museo_sans/museosans-700-webfont.eot");
|
||||
src: url("~@/fonts/museo_sans/museosans-700-webfont.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
format("embedded-opentype"),
|
||||
url("~@/fonts/museo_sans/museosans-700-webfont.woff") format("woff"),
|
||||
url("~@/fonts/museo_sans/museosans-700-webfont.ttf")
|
||||
format("truetype"),
|
||||
format("truetype"),
|
||||
url("~@/fonts/museo_sans/museosans-700-webfont.svg#museo_sans700")
|
||||
format("svg");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "museo_sans";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url("~@/fonts/museo_sans/museosans-900-webfont.eot");
|
||||
src: url("~@/fonts/museo_sans/museosans-900-webfont.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
format("embedded-opentype"),
|
||||
url("~@/fonts/museo_sans/museosans-900-webfont.woff") format("woff"),
|
||||
url("~@/fonts/museo_sans/museosans-900-webfont.ttf")
|
||||
format("truetype"),
|
||||
format("truetype"),
|
||||
url("~@/fonts/museo_sans/museosans-900-webfont.svg#museo_sans900")
|
||||
format("svg");
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
format("svg");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "museo";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("~@/fonts/museo/museo300.eot");
|
||||
src: url("~@/fonts/museo/museo300.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
format("embedded-opentype"),
|
||||
url("~@/fonts/museo/museo300.woff") format("woff"),
|
||||
url("~@/fonts/museo/museo300.ttf") format("truetype"),
|
||||
url("~@/fonts/museo/museo300.svg#museo_300") format("svg");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "museo";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("~@/fonts/museo/museo500.eot");
|
||||
src: url("~@/fonts/museo/museo500.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
format("embedded-opentype"),
|
||||
url("~@/fonts/museo/museo500.woff") format("woff"),
|
||||
url("~@/fonts/museo/museo500.ttf") format("truetype"),
|
||||
url("~@/fonts/museo/museo500.svg#museo_500") format("svg");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,16 @@
|
|||
// LIBS
|
||||
@import "./common.scss";
|
||||
@import "./common";
|
||||
|
||||
.icon-size {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.cross-red {
|
||||
@extend .icon-size;
|
||||
color: crimson;
|
||||
}
|
||||
|
||||
.check-green {
|
||||
@extend .icon-size;
|
||||
color: seagreen;
|
||||
}
|
||||
|
||||
.table.fixAction {
|
||||
& th:last-child,
|
||||
& td:last-child {
|
||||
// * Shrink
|
||||
width: 0.1%;
|
||||
white-space: nowrap;
|
||||
.table.fix-action {
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
background-color: $gray-200;
|
||||
color: $gray-900;
|
||||
// * Sticky
|
||||
position: sticky;
|
||||
right: 0;
|
||||
background-color: $gray-200;
|
||||
color: $gray-900;
|
||||
white-space: nowrap;
|
||||
// * Shrink
|
||||
width: 0.1%;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// LIBS
|
||||
@import './common';
|
||||
|
||||
@import "./common";
|
||||
|
||||
// Local
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// LIBS
|
||||
import './common'
|
||||
import './vue'
|
||||
import './confirm'
|
||||
import "./common";
|
||||
import "./vue";
|
||||
import "./confirm";
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ const bootstrap = require("bootstrap");
|
|||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
axios.defaults.headers.common = {
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRF-TOKEN' : document.querySelector('meta[name="csrf-token"]')!.getAttribute('content')
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRF-TOKEN" : document.querySelector("meta[name=\"csrf-token\"]")!.getAttribute("content")
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -18,8 +18,8 @@ window.axios = axios;
|
|||
window.bootstrap = bootstrap;
|
||||
|
||||
// enable bs tooltips
|
||||
var tooltipTriggerList = [].slice.call(
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const tooltipTriggerList = [].slice.call(
|
||||
document.querySelectorAll("[data-bs-toggle=\"tooltip\"]")
|
||||
);
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div ref="wordCounter"></div>
|
||||
<div ref="ckeditor"></div>
|
||||
<div ref="wordCounter" />
|
||||
<div ref="ckeditor" />
|
||||
<input
|
||||
:id="internalId"
|
||||
class="form-control"
|
||||
|
|
@ -9,17 +9,18 @@
|
|||
:name="name"
|
||||
:value="internalText"
|
||||
:required="internalRequired"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { defineComponent} from "vue";
|
||||
import trans from "../../modules/trans";
|
||||
import "./../../../js/vendor/ckeditor/build/ckeditor";
|
||||
import { CKUploadAdaptater } from "../../back/CkUploadAdaptater";
|
||||
|
||||
export default defineComponent({
|
||||
emits: ["input"],
|
||||
mixins: [trans],
|
||||
beforeMount() {
|
||||
if (!this.$attrs.json) {
|
||||
|
|
@ -28,7 +29,7 @@ export default defineComponent({
|
|||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
data = JSON.parse(json);
|
||||
this.name = data.name;
|
||||
this.internalText = data.text ? String(data.text) : "";
|
||||
this.internalId = data.id ? data.id : "";
|
||||
|
|
@ -68,8 +69,8 @@ export default defineComponent({
|
|||
methods: {
|
||||
makeSuperBuild() {
|
||||
const self = this,
|
||||
ckeditor = this.$refs.ckeditor as HTMLElement,
|
||||
wordCounter = this.$refs.wordCounter as HTMLElement;
|
||||
ckeditor = this.$refs.ckeditor as HTMLElement,
|
||||
wordCounter = this.$refs.wordCounter as HTMLElement;
|
||||
// @ts-ignore
|
||||
ClassicEditor.create(ckeditor, {
|
||||
extraPlugins: [
|
||||
|
|
@ -101,22 +102,6 @@ export default defineComponent({
|
|||
"dailymotion",
|
||||
"spotify",
|
||||
],
|
||||
// see https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-media-embed/src/mediaembedediting.js
|
||||
// providers: ['youtube', {
|
||||
// // display local videos in oembed tags
|
||||
// name: 'localVideo',
|
||||
// url: /^(\/(?:content|storage)\/.*)/,
|
||||
// html: match => {
|
||||
// let path = match[ 1 ];
|
||||
|
||||
// return (
|
||||
// '<video controls allowfullscreen style="max-width: 90%; margin: 0 auto; display: block;">' +
|
||||
// `<source src="${ path }" type="video/mp4">` +
|
||||
// `<p>Votre navigateur ne prend pas en charge les vidéos HTML5. Voici <a href="${ path }">un lien pour télécharger la vidéo</a>.</p>` +
|
||||
// '</video>'
|
||||
// );
|
||||
// }
|
||||
// }]
|
||||
},
|
||||
toolbar: {
|
||||
items: [
|
||||
|
|
@ -140,17 +125,11 @@ export default defineComponent({
|
|||
"mediaEmbed",
|
||||
"blockQuote",
|
||||
"|",
|
||||
// 'insertImage', 'mediaEmbed', 'blockQuote','|',
|
||||
"insertTable",
|
||||
"tableColumn",
|
||||
"tableRow",
|
||||
"mergeTableCells",
|
||||
"|",
|
||||
// 'videoStyle:alignLeft', 'videoStyle:alignCenter', 'videoStyle:alignRight', 'videoResize',
|
||||
// '|',
|
||||
// 'videoResize:50',
|
||||
// 'videoResize:75',
|
||||
// 'videoResize:original',
|
||||
"fontColor",
|
||||
"fontFamily",
|
||||
"fontSize",
|
||||
|
|
|
|||
|
|
@ -1,50 +1,65 @@
|
|||
<template>
|
||||
<div
|
||||
:class="`input-group color-picker-component ${_rgbaMode ? '' : 'noTransparent'}`"
|
||||
:class="`input-group color-picker-component ${intRgbaMode ? '' : 'noTransparent'}`"
|
||||
ref="colorpicker"
|
||||
>
|
||||
<div
|
||||
:id="`colorPickerFakeInput${_id}`"
|
||||
:id="`colorPickerFakeInput${intId}`"
|
||||
class="d-inline-block"
|
||||
ref="colorPickerFakeInput"
|
||||
>
|
||||
<div class="form-check form-switch d-inline-block pt-3">
|
||||
<b>
|
||||
<label v-if="_label.length" :for="_id ? _id : ''" class="form-label">
|
||||
{{ _label }}
|
||||
<label
|
||||
v-if="intLabel.length"
|
||||
:for="intId ? intId : ''"
|
||||
class="form-label"
|
||||
>
|
||||
{{ intLabel }}
|
||||
</label>
|
||||
</b>
|
||||
<input
|
||||
:id="_id"
|
||||
:id="intId"
|
||||
type="color"
|
||||
class="form-control form-control-color"
|
||||
:value="_internalHex"
|
||||
:title="_title"
|
||||
:aria-describedby="_ariaDescribedby"
|
||||
:value="intInternalHex"
|
||||
:title="intTitle"
|
||||
:aria-describedby="intAriaDescribedby"
|
||||
data-bs-toggle="tooltip"
|
||||
v-on:click.prevent="togglePicker()"
|
||||
/>
|
||||
<input :name="_name" type="text" class="d-none" :value="_value" />
|
||||
@click.prevent="togglePicker()"
|
||||
>
|
||||
<input
|
||||
:name="intName"
|
||||
type="text"
|
||||
class="d-none"
|
||||
:value="intValue"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
:class="`form-check form-switch pt-3 ${_nullable ? 'd-inline-block' : 'd-none'}`"
|
||||
:class="`form-check form-switch pt-3 ${intNullable ? 'd-inline-block' : 'd-none'}`"
|
||||
>
|
||||
<input
|
||||
ref="nullableInput"
|
||||
:id="`checkbox${_id}`"
|
||||
:id="`checkbox${intId}`"
|
||||
class="form-check-input me-1 mt-4"
|
||||
type="checkbox"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Rendre la couleur transparente ?')"
|
||||
:checked="_value === null ? true : false"
|
||||
v-on:change="updateValue"
|
||||
/>
|
||||
<label class="form-check-label user-select-none mt-3" :for="`checkbox${_id}`">
|
||||
:checked="intValue === null ? true : false"
|
||||
@change="updateValue"
|
||||
>
|
||||
<label
|
||||
class="form-check-label user-select-none mt-3"
|
||||
:for="`checkbox${intId}`"
|
||||
>
|
||||
{{ __("Transparent ?") }}
|
||||
</label>
|
||||
</div>
|
||||
<div :class="`chromePicker ${_displayPicker ? 'd-inline-block' : 'd-none'}`">
|
||||
<chrome-picker ref="picker" v-model="_internalValue" />
|
||||
<div :class="`chromePicker ${intDisplayPicker ? 'd-inline-block' : 'd-none'}`">
|
||||
<chrome-picker
|
||||
ref="picker"
|
||||
v-model="intInternalValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -64,34 +79,34 @@ export default defineComponent({
|
|||
"chrome-picker": Chrome,
|
||||
},
|
||||
mounted() {
|
||||
this.__nullableInput = this.$refs.nullableInput as HTMLInputElement;
|
||||
this.__colorPickerFakeInput = this.$refs.colorPickerFakeInput as HTMLElement;
|
||||
this.__picker = this.$refs.picker as Chrome;
|
||||
this.intNullableInput = this.$refs.nullableInput as HTMLInputElement;
|
||||
this.intColorPickerFakeInput = this.$refs.colorPickerFakeInput as HTMLElement;
|
||||
this.intPicker = this.$refs.picker as Chrome;
|
||||
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
this._id = this.id ? this.id : data.id;
|
||||
this._name = this.name ? this.name : data.name;
|
||||
this._value = this.value ? this.value : data.value;
|
||||
this._title = this.title ? this.title : data.title;
|
||||
this._label = this.label ? this.label : data.label;
|
||||
this._ariaDescribedby = this.ariaDescribedby
|
||||
data = JSON.parse(json);
|
||||
this.intId = this.id ? this.id : data.id;
|
||||
this.intName = this.name ? this.name : data.name;
|
||||
this.intValue = this.value ? this.value : data.value;
|
||||
this.intTitle = this.title ? this.title : data.title;
|
||||
this.intLabel = this.label ? this.label : data.label;
|
||||
this.intAriaDescribedby = this.ariaDescribedby
|
||||
? this.ariaDescribedby
|
||||
: data.ariaDescribedby;
|
||||
this._nullable = this.nullable ? this.nullable : data.nullable ? true : false;
|
||||
this._rgbaMode = this.rgbaMode ? this.rgbaMode : data.rgbaMode ? true : false;
|
||||
this.intNullable = this.nullable ? this.nullable : data.nullable ? true : false;
|
||||
this.intRgbaMode = this.rgbaMode ? this.rgbaMode : data.rgbaMode ? true : false;
|
||||
|
||||
if (!this._nullable) {
|
||||
this._internalValue = this._value
|
||||
? this._value
|
||||
: this._rgbaMode
|
||||
? "rgba(0,0,0,0)"
|
||||
: "#000000";
|
||||
if (!this.intNullable) {
|
||||
this.intInternalValue = this.intValue
|
||||
? this.intValue
|
||||
: this.intRgbaMode
|
||||
? "rgba(0,0,0,0)"
|
||||
: "#000000";
|
||||
} else {
|
||||
this._internalValue = this._value ?? "";
|
||||
this.intInternalValue = this.intValue ?? "";
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this._internalHex = this.__picker.$data.val.hex;
|
||||
this.intInternalHex = this.intPicker.$data.val.hex;
|
||||
});
|
||||
|
||||
this.initTooltips();
|
||||
|
|
@ -103,6 +118,7 @@ export default defineComponent({
|
|||
},
|
||||
name: {
|
||||
type: String as PropType<string | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
value: {
|
||||
type: String as PropType<string | null>,
|
||||
|
|
@ -110,12 +126,15 @@ export default defineComponent({
|
|||
},
|
||||
title: {
|
||||
type: String as PropType<string | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
type: String as PropType<string | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
nullable: {
|
||||
type: Boolean as PropType<boolean | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
rgbaMode: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
|
|
@ -123,91 +142,92 @@ export default defineComponent({
|
|||
},
|
||||
ariaDescribedby: {
|
||||
type: String as PropType<string | undefined>,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(): {
|
||||
_id: string;
|
||||
_name: string;
|
||||
_value: string | null;
|
||||
_title: string;
|
||||
_label: string;
|
||||
_nullable: boolean;
|
||||
_rgbaMode: boolean;
|
||||
_ariaDescribedby: string;
|
||||
intId: string;
|
||||
intName: string;
|
||||
intValue: string | null;
|
||||
intTitle: string;
|
||||
intLabel: string;
|
||||
intNullable: boolean;
|
||||
intRgbaMode: boolean;
|
||||
intAriaDescribedby: string;
|
||||
|
||||
_internalValue:
|
||||
intInternalValue:
|
||||
| string
|
||||
| { hex: string; rgba: { a: string; b: string; g: string; r: string } };
|
||||
_internalHex: string;
|
||||
intInternalHex: string;
|
||||
|
||||
_displayPicker: boolean;
|
||||
intDisplayPicker: boolean;
|
||||
|
||||
__nullableInput: HTMLInputElement | null;
|
||||
__colorPickerFakeInput: HTMLElement | null;
|
||||
__picker: Chrome | null;
|
||||
intNullableInput: HTMLInputElement | null;
|
||||
intColorPickerFakeInput: HTMLElement | null;
|
||||
intPicker: Chrome | null;
|
||||
} {
|
||||
return {
|
||||
_id: "",
|
||||
_name: "",
|
||||
_value: "",
|
||||
_title: "",
|
||||
_label: "",
|
||||
_nullable: false,
|
||||
_rgbaMode: false,
|
||||
_ariaDescribedby: "",
|
||||
intId: "",
|
||||
intName: "",
|
||||
intValue: "",
|
||||
intTitle: "",
|
||||
intLabel: "",
|
||||
intNullable: false,
|
||||
intRgbaMode: false,
|
||||
intAriaDescribedby: "",
|
||||
|
||||
_internalValue: "",
|
||||
_internalHex: "",
|
||||
intInternalValue: "",
|
||||
intInternalHex: "",
|
||||
|
||||
_displayPicker: false,
|
||||
intDisplayPicker: false,
|
||||
|
||||
__nullableInput: null,
|
||||
__colorPickerFakeInput: null,
|
||||
__picker: null,
|
||||
intNullableInput: null,
|
||||
intColorPickerFakeInput: null,
|
||||
intPicker: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
_internalValue() {
|
||||
intInternalValue() {
|
||||
this.updateValue();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateValue() {
|
||||
if (this.__nullableInput?.checked) {
|
||||
if (this.intNullableInput?.checked) {
|
||||
this.hidePicker();
|
||||
this._value = "";
|
||||
this._internalHex = "";
|
||||
this.intValue = "";
|
||||
this.intInternalHex = "";
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this._internalHex = this.__picker.$data.val.hex;
|
||||
this.intInternalHex = this.intPicker.$data.val.hex;
|
||||
});
|
||||
if (typeof this._internalValue === "string") {
|
||||
this._value = this._internalValue;
|
||||
if (typeof this.intInternalValue === "string") {
|
||||
this.intValue = this.intInternalValue;
|
||||
return;
|
||||
}
|
||||
const isNull = this.__nullableInput?.checked ? true : false;
|
||||
if (this._rgbaMode) {
|
||||
const rgba = this._internalValue?.rgba;
|
||||
this._value =
|
||||
const isNull = this.intNullableInput?.checked ? true : false;
|
||||
if (this.intRgbaMode) {
|
||||
const rgba = this.intInternalValue?.rgba;
|
||||
this.intValue =
|
||||
!isNull && rgba
|
||||
? `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
|
||||
: this._nullable
|
||||
? null
|
||||
: "";
|
||||
: this.intNullable
|
||||
? null
|
||||
: "";
|
||||
return;
|
||||
}
|
||||
this._value =
|
||||
!isNull && this._internalValue
|
||||
? this._internalValue.hex
|
||||
: this._nullable
|
||||
? null
|
||||
: "";
|
||||
this.intValue =
|
||||
!isNull && this.intInternalValue
|
||||
? this.intInternalValue.hex
|
||||
: this.intNullable
|
||||
? null
|
||||
: "";
|
||||
},
|
||||
showPicker() {
|
||||
document.addEventListener("click", this.documentClick);
|
||||
this._displayPicker = true;
|
||||
const input = this.__nullableInput;
|
||||
this.intDisplayPicker = true;
|
||||
const input = this.intNullableInput;
|
||||
if (!input) {
|
||||
throw new Error("input missing");
|
||||
}
|
||||
|
|
@ -215,19 +235,19 @@ export default defineComponent({
|
|||
},
|
||||
hidePicker() {
|
||||
document.removeEventListener("click", this.documentClick);
|
||||
this._displayPicker = false;
|
||||
this.intDisplayPicker = false;
|
||||
},
|
||||
togglePicker() {
|
||||
this._displayPicker ? this.hidePicker() : this.showPicker();
|
||||
this.intDisplayPicker ? this.hidePicker() : this.showPicker();
|
||||
},
|
||||
documentClick(e: Event) {
|
||||
const colorpickerComponent = this.__colorPickerFakeInput,
|
||||
target = e.target;
|
||||
const colorpickerComponent = this.intColorPickerFakeInput,
|
||||
target = e.target;
|
||||
if (
|
||||
colorpickerComponent !== target &&
|
||||
colorpickerComponent &&
|
||||
(target as HTMLElement).closest(`#colorPickerFakeInput${this._id}`)?.id !==
|
||||
colorpickerComponent.id
|
||||
(target as HTMLElement).closest(`#colorPickerFakeInput${this.intId}`)?.id !==
|
||||
colorpickerComponent.id
|
||||
) {
|
||||
this.hidePicker();
|
||||
}
|
||||
|
|
@ -235,7 +255,7 @@ export default defineComponent({
|
|||
initTooltips() {
|
||||
// * INIT Bootstrap tooltips
|
||||
let tooltipTriggerList = [].slice.call(
|
||||
document.querySelectorAll('.color-picker-component [data-bs-toggle="tooltip"]')
|
||||
document.querySelectorAll(".color-picker-component [data-bs-toggle=\"tooltip\"]")
|
||||
);
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new Tooltip(tooltipTriggerEl, {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
<template>
|
||||
<div class="taggableDropdown">
|
||||
<div v-if="errorMessages.length">
|
||||
<span class="d-block text-danger" v-for="errorMessage of errorMessages">
|
||||
<span
|
||||
class="d-block text-danger"
|
||||
v-for="errorMessage of errorMessages"
|
||||
:key="errorMessage"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -16,23 +20,32 @@
|
|||
:max-tags="6"
|
||||
:placeholder="__('Ajoutez ici vos étiquettes')"
|
||||
/>
|
||||
<div class="text-center py-2" v-if="waitingListObjects.length">
|
||||
<button class="btn btn-sm btn-dark ms-2" v-on:click.prevent="cancelTags">
|
||||
<div
|
||||
class="text-center py-2"
|
||||
v-if="waitingListObjects.length"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-dark ms-2"
|
||||
@click.prevent="cancelTags"
|
||||
>
|
||||
{{ __("Annuler") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(sTag, index) in tags" :key="index">
|
||||
<template
|
||||
v-for="(sTag, index) in tags"
|
||||
:key="index"
|
||||
>
|
||||
<input
|
||||
:name="`${name}[${String(index)}][value]`"
|
||||
type="hidden"
|
||||
:value="sTag.value"
|
||||
/>
|
||||
>
|
||||
<input
|
||||
:name="`${name}[${String(index)}][label]`"
|
||||
type="hidden"
|
||||
:value="sTag.text"
|
||||
/>
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -58,7 +71,7 @@ export default defineComponent({
|
|||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
data = JSON.parse(json);
|
||||
this.name = data.name;
|
||||
this.tags = this.parseTags(data.value);
|
||||
this.autocompleteItems = this.parseTags(data.tags);
|
||||
|
|
|
|||
|
|
@ -1,119 +1,139 @@
|
|||
<template>
|
||||
<div class="container ImageInput p-0">
|
||||
<div class="row">
|
||||
<div :class="`col-12 ${_preview ? 'col-md-6' : ''} form-group`">
|
||||
<label v-if="_preview" :for="_id" class="col-form-label">
|
||||
<div :class="`col-12 ${intPreview ? 'col-md-6' : ''} form-group`">
|
||||
<label
|
||||
v-if="intPreview"
|
||||
:for="intId"
|
||||
class="col-form-label"
|
||||
>
|
||||
<b>{{ __("Choisir un fichier") }}</b>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<button
|
||||
v-on:click.prevent="chooseAFile"
|
||||
@click.prevent="chooseAFile"
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Modifier l\'image source')"
|
||||
>
|
||||
<i class="fa-solid fa-folder-open"></i>
|
||||
<i class="fa-solid fa-folder-open" />
|
||||
</button>
|
||||
<input
|
||||
:id="_id"
|
||||
:id="intId"
|
||||
ref="actualImage"
|
||||
@change="changedImage"
|
||||
type="file"
|
||||
class="d-none"
|
||||
:name="_name"
|
||||
:name="intName"
|
||||
accept=".jpg,.jpeg,.gif,.png"
|
||||
:aria-describedby="`Help${_id}`"
|
||||
/>
|
||||
:aria-describedby="`Help${intId}`"
|
||||
>
|
||||
<input
|
||||
v-if="typeof _value === 'string'"
|
||||
v-if="typeof intValue === 'string'"
|
||||
class="d-none"
|
||||
:name="_name"
|
||||
:name="intName"
|
||||
type="text"
|
||||
:value="_value"
|
||||
/>
|
||||
:value="intValue"
|
||||
>
|
||||
<input
|
||||
v-on:click.prevent="chooseAFile"
|
||||
@click.prevent="chooseAFile"
|
||||
type="text"
|
||||
:value="_valueFileName"
|
||||
:value="intValueFileName"
|
||||
class="form-control rightAligned"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Modifier l\'image source')"
|
||||
:aria-describedby="`Help${_id}`"
|
||||
:aria-describedby="`Help${intId}`"
|
||||
readonly
|
||||
/>
|
||||
>
|
||||
<button
|
||||
v-if="!_required"
|
||||
v-on:click.prevent="removeFile"
|
||||
v-if="!intRequired"
|
||||
@click.prevent="removeFile"
|
||||
class="btn btn-outline-danger"
|
||||
type="button"
|
||||
>
|
||||
<i class="fas fa-eraser"></i>
|
||||
<i class="fas fa-eraser" />
|
||||
</button>
|
||||
<button
|
||||
v-if="!_preview && _value"
|
||||
v-if="!intPreview && intValue"
|
||||
class="btn btn-warning"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
:data-bs-target="`#ModalPreview${_id}`"
|
||||
:data-bs-target="`#ModalPreview${intId}`"
|
||||
>
|
||||
<span data-bs-toggle="tooltip" :title="__('Visualiser l\'image')"
|
||||
><i class="fa-solid fa-eye"></i
|
||||
></span>
|
||||
<span
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Visualiser l\'image')"
|
||||
><i class="fa-solid fa-eye" /></span>
|
||||
</button>
|
||||
<button
|
||||
v-on:click.prevent="prepareImageEditor($event, Number.parseInt(_id))"
|
||||
:disabled="!_hasImage"
|
||||
:class="`btn ${!_hasModdedImage ? 'btn-primary' : 'btn-success'}`"
|
||||
@click.prevent="prepareImageEditor($event)"
|
||||
:disabled="!intHasImage"
|
||||
:class="`btn ${!intHasModdedImage ? 'btn-primary' : 'btn-success'}`"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
:data-bs-target="`#Modal${_id}`"
|
||||
:data-bs-target="`#Modal${intId}`"
|
||||
>
|
||||
<span data-bs-toggle="tooltip" :title="__('Redimensionner l\'image')">
|
||||
<i class="fa-solid fa-crop"></i>
|
||||
<span
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Redimensionner l\'image')"
|
||||
>
|
||||
<i class="fa-solid fa-crop" />
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
v-if="_hasModdedImage"
|
||||
v-if="intHasModdedImage"
|
||||
class="input-group-text text-success"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Image redimensionée')"
|
||||
>
|
||||
<i class="fa-solid fa-wand-magic"></i>
|
||||
<i class="fa-solid fa-wand-magic" />
|
||||
</span>
|
||||
</div>
|
||||
<small :id="`Help${_id}`" class="form-text text-muted">{{ _helper }}</small>
|
||||
</div>
|
||||
<div v-if="_preview" :class="`col-12 ${_preview ? 'col-md-6' : ''} form-group`">
|
||||
<small
|
||||
v-if="_value"
|
||||
:id="`PreviewHelp${_id}`"
|
||||
:id="`Help${intId}`"
|
||||
class="form-text text-muted"
|
||||
>{{ intHelper }}</small>
|
||||
</div>
|
||||
<div
|
||||
v-if="intPreview"
|
||||
:class="`col-12 ${intPreview ? 'col-md-6' : ''} form-group`"
|
||||
>
|
||||
<small
|
||||
v-if="intValue"
|
||||
:id="`PreviewHelp${intId}`"
|
||||
class="form-text text-muted d-block"
|
||||
>{{ __("Prévisualisation de l'image") }}
|
||||
<span v-if="_originalFile" class="fw-bold">
|
||||
{{ humanFileSize(_originalFile.size) }}
|
||||
{{ `hauteur ${_originalFileDimensions.height}px` }}
|
||||
{{ `largeur ${_originalFileDimensions.width}px` }}
|
||||
>{{ __("Prévisualisation de l'image") }}
|
||||
<span
|
||||
v-if="intOriginalFile"
|
||||
class="fw-bold"
|
||||
>
|
||||
{{ humanFileSize(intOriginalFile.size) }}
|
||||
{{ `hauteur ${intOriginalFileDimensions.height}px` }}
|
||||
{{ `largeur ${intOriginalFileDimensions.width}px` }}
|
||||
</span>
|
||||
</small>
|
||||
<img
|
||||
v-if="_value"
|
||||
v-if="intValue"
|
||||
class="img-fluid"
|
||||
:src="_value"
|
||||
:src="intValue"
|
||||
:alt="__('Image placeholder')"
|
||||
:aria-describedby="`PreviewHelp${_id}`"
|
||||
/>
|
||||
:aria-describedby="`PreviewHelp${intId}`"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:id="`Modal${_id}`"
|
||||
:id="`Modal${intId}`"
|
||||
class="modal"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
data-bs-backdrop="static"
|
||||
data-bs-keyboard="false"
|
||||
>
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div
|
||||
class="modal-dialog modal-xl"
|
||||
role="document"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
|
|
@ -126,7 +146,7 @@
|
|||
:aria-label="__('Fermer la fenêtre')"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Fermer la fenêtre sans valider le contenu')"
|
||||
></button>
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
|
|
@ -137,9 +157,9 @@
|
|||
class="btn btn-warning"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Réinitialiser l\'image')"
|
||||
v-on:click.prevent="reset()"
|
||||
@click.prevent="reset()"
|
||||
>
|
||||
<i class="fa fa-sync-alt"></i>
|
||||
<i class="fa fa-sync-alt" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group me-4 mt-2">
|
||||
|
|
@ -147,17 +167,17 @@
|
|||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Zoom avant')"
|
||||
v-on:click.prevent="zoom(0.1)"
|
||||
@click.prevent="zoom(0.1)"
|
||||
>
|
||||
<i class="fa fa-search-plus"></i>
|
||||
<i class="fa fa-search-plus" />
|
||||
</button>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Zoom arrière')"
|
||||
v-on:click.prevent="zoom(-0.1)"
|
||||
@click.prevent="zoom(-0.1)"
|
||||
>
|
||||
<i class="fa fa-search-minus"></i>
|
||||
<i class="fa fa-search-minus" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group me-4 mt-2">
|
||||
|
|
@ -165,33 +185,33 @@
|
|||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Déplacer vers la gauche')"
|
||||
v-on:click.prevent="move(-10, 0)"
|
||||
@click.prevent="move(-10, 0)"
|
||||
>
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
<i class="fa fa-arrow-left" />
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Déplacer vers la droite')"
|
||||
v-on:click.prevent="move(10, 0)"
|
||||
@click.prevent="move(10, 0)"
|
||||
>
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
<i class="fa fa-arrow-right" />
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Déplacer vers le haut')"
|
||||
v-on:click.prevent="move(0, -10)"
|
||||
@click.prevent="move(0, -10)"
|
||||
>
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
<i class="fa fa-arrow-up" />
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Déplacer vers le bas')"
|
||||
v-on:click.prevent="move(0, 10)"
|
||||
@click.prevent="move(0, 10)"
|
||||
>
|
||||
<i class="fa fa-arrow-down"></i>
|
||||
<i class="fa fa-arrow-down" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="btn-group me-4 mt-2">
|
||||
|
|
@ -199,17 +219,17 @@
|
|||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Mirroir Horizontal')"
|
||||
v-on:click.prevent="scale(true)"
|
||||
@click.prevent="scale(true)"
|
||||
>
|
||||
<i class="fa fa-arrows-alt-h"></i>
|
||||
<i class="fa fa-arrows-alt-h" />
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Mirroir Vertical')"
|
||||
v-on:click.prevent="scale(false)"
|
||||
@click.prevent="scale(false)"
|
||||
>
|
||||
<i class="fa fa-arrows-alt-v"></i>
|
||||
<i class="fa fa-arrows-alt-v" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -224,49 +244,61 @@
|
|||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Rotation sens anti-horaire')"
|
||||
v-on:click.prevent="rotate(-45)"
|
||||
@click.prevent="rotate(-45)"
|
||||
>
|
||||
<i class="fa fa-undo-alt"></i>
|
||||
<i class="fa fa-undo-alt" />
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Rotation sens horaire')"
|
||||
v-on:click.prevent="rotate(45)"
|
||||
@click.prevent="rotate(45)"
|
||||
>
|
||||
<i class="fa fa-redo-alt"></i>
|
||||
<i class="fa fa-redo-alt" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 ms-4 rotateDrag">
|
||||
<label for="range" class="form-label me-4"
|
||||
>{{
|
||||
__(":deg degrès", {
|
||||
deg: String(_rotationText),
|
||||
})
|
||||
}}
|
||||
<label
|
||||
for="range"
|
||||
class="form-label me-4"
|
||||
>{{
|
||||
__(":deg degrès", {
|
||||
deg: String(intRotationText),
|
||||
})
|
||||
}}
|
||||
</label>
|
||||
<input
|
||||
ref="rotationSlider"
|
||||
v-on:input.stop="rotationChange($event as Event)"
|
||||
v-on:change.stop="rotateTo(Number.parseInt(($event.target as HTMLInputElement).value))"
|
||||
@input.stop="rotationChange($event as Event)"
|
||||
@change.stop="rotateTo(Number.parseInt(($event.target as HTMLInputElement).value))"
|
||||
type="range"
|
||||
class="form-range"
|
||||
id="range"
|
||||
min="0"
|
||||
max="360"
|
||||
value="0"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 col-md-6 p-0 mb-3">
|
||||
<img class="imageModification" ref="imgEditor" src="" />
|
||||
<img
|
||||
class="imageModification"
|
||||
ref="imgEditor"
|
||||
src=""
|
||||
>
|
||||
</div>
|
||||
<div ref="previewBox" class="col-12 col-md-6 p-0 bg-light previewIMG">
|
||||
<div :id="`Preview${_id}`" :style="_previewStyle"></div>
|
||||
<div
|
||||
ref="previewBox"
|
||||
class="col-12 col-md-6 p-0 bg-light previewIMG"
|
||||
>
|
||||
<div
|
||||
:id="`Preview${intId}`"
|
||||
:style="intPreviewStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -283,7 +315,7 @@
|
|||
{{ __("Fermer") }}
|
||||
</button>
|
||||
<button
|
||||
v-on:click.prevent="exportToBlob"
|
||||
@click.prevent="exportToBlob"
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="tooltip"
|
||||
|
|
@ -297,15 +329,18 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!_preview && _value"
|
||||
:id="`ModalPreview${_id}`"
|
||||
v-if="!intPreview && intValue"
|
||||
:id="`ModalPreview${intId}`"
|
||||
class="modal"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
data-bs-backdrop="static"
|
||||
data-bs-keyboard="false"
|
||||
>
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div
|
||||
class="modal-dialog modal-xl"
|
||||
role="document"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
|
|
@ -318,21 +353,24 @@
|
|||
:aria-label="__('Fermer la fenêtre')"
|
||||
data-bs-toggle="tooltip"
|
||||
:title="__('Fermer la fenêtre sans valider le contenu')"
|
||||
></button>
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<small :id="`PreviewHelp${_id}`" class="form-text text-muted d-block">{{
|
||||
<small
|
||||
:id="`PreviewHelp${intId}`"
|
||||
class="form-text text-muted d-block"
|
||||
>{{
|
||||
__("Prévisualisation de l'image")
|
||||
}}</small>
|
||||
<img
|
||||
class="img-fluid"
|
||||
:src="_value"
|
||||
:src="intValue"
|
||||
:alt="__('Image placeholder')"
|
||||
:aria-describedby="`PreviewHelp${_id}`"
|
||||
/>
|
||||
:aria-describedby="`PreviewHelp${intId}`"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -344,7 +382,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { defineComponent} from "vue";
|
||||
import Cropper from "cropperjs";
|
||||
import { Tooltip } from "bootstrap";
|
||||
import trans from "../../modules/trans";
|
||||
|
|
@ -359,7 +397,7 @@ export default defineComponent({
|
|||
const json = String(this.$attrs.json ?? "");
|
||||
// * INIT Bootstrap tooltips
|
||||
let tooltipTriggerList = [].slice.call(
|
||||
document.querySelectorAll('.ImageInput [data-bs-toggle="tooltip"]')
|
||||
document.querySelectorAll(".ImageInput [data-bs-toggle=\"tooltip\"]")
|
||||
);
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new Tooltip(tooltipTriggerEl, {
|
||||
|
|
@ -369,45 +407,46 @@ export default defineComponent({
|
|||
if (json.length) {
|
||||
// * Init uppon Laravel Blade
|
||||
const data = JSON.parse(json);
|
||||
this._id = data.id;
|
||||
this._name = data.name;
|
||||
this._helper = data.helper ?? "";
|
||||
this._width = data.width;
|
||||
this._height = data.height;
|
||||
this._value = String(data.value);
|
||||
if (this._value.length) {
|
||||
this._valueFileName = this._value;
|
||||
this._value = this._value.replace(/^\/|^/, "/");
|
||||
this.intId = data.id;
|
||||
this.intName = data.name;
|
||||
this.intHelper = data.helper ?? "";
|
||||
this.intWidth = data.width;
|
||||
this.intHeight = data.height;
|
||||
this.intValue = String(data.value);
|
||||
if (this.intValue.length) {
|
||||
this.intValueFileName = this.intValue;
|
||||
this.intValue = this.intValue.replace(/^\/|^/, "/");
|
||||
}
|
||||
if (data.preview !== undefined) {
|
||||
this._preview = data.preview ? true : false;
|
||||
this.intPreview = data.preview ? true : false;
|
||||
}
|
||||
if (data.required !== undefined) {
|
||||
this._required = data.required ? true : false;
|
||||
this.intRequired = data.required ? true : false;
|
||||
}
|
||||
if (data.browseEvent !== undefined) {
|
||||
this._browseEvent = data.browseEvent ? true : false;
|
||||
this.intBrowseEvent = data.browseEvent ? true : false;
|
||||
}
|
||||
} else {
|
||||
// * Init Uppon Vue component
|
||||
this._id = this.id;
|
||||
this._name = this.name;
|
||||
this._helper = this.helper;
|
||||
this._width = this.width;
|
||||
this._height = this.height;
|
||||
this._value = this.value;
|
||||
if (this._value.length) {
|
||||
this._valueFileName = this._value;
|
||||
this._value = this._value.replace(/^\/|^/, "/");
|
||||
this.intId = this.id;
|
||||
this.intName = this.name;
|
||||
this.intHelper = this.helper;
|
||||
this.intWidth = this.width;
|
||||
this.intHeight = this.height;
|
||||
this.intValue = this.value;
|
||||
if (this.intValue.length) {
|
||||
this.intValueFileName = this.intValue;
|
||||
this.intValue = this.intValue.replace(/^\/|^/, "/");
|
||||
}
|
||||
this._preview = this.preview;
|
||||
this._required = this.required;
|
||||
this._browseEvent = this.browseEvent;
|
||||
this.intPreview = this.preview;
|
||||
this.intRequired = this.required;
|
||||
this.intBrowseEvent = this.browseEvent;
|
||||
}
|
||||
this._hasImage = false;
|
||||
this.intHasImage = false;
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
required: {
|
||||
|
|
@ -444,70 +483,69 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
data(): {
|
||||
_id: string;
|
||||
_value: string;
|
||||
_valueFileName: string;
|
||||
_helper: string;
|
||||
_name: string;
|
||||
_required: boolean;
|
||||
_width: number;
|
||||
_height: number;
|
||||
_preview: boolean;
|
||||
intId: string;
|
||||
intValue: string;
|
||||
intValueFileName: string;
|
||||
intHelper: string;
|
||||
intName: string;
|
||||
intRequired: boolean;
|
||||
intWidth: number;
|
||||
intHeight: number;
|
||||
intPreview: boolean;
|
||||
|
||||
_browseEvent: boolean;
|
||||
intBrowseEvent: boolean;
|
||||
|
||||
_rotationText: number;
|
||||
_rotationValue: number;
|
||||
_scaleX: number;
|
||||
_scaleY: number;
|
||||
_hasImage: boolean;
|
||||
_hasModdedImage: boolean;
|
||||
_cropper: Cropper | null;
|
||||
intRotationText: number;
|
||||
intRotationValue: number;
|
||||
intScaleX: number;
|
||||
intScaleY: number;
|
||||
intHasImage: boolean;
|
||||
intHasModdedImage: boolean;
|
||||
intCropper: Cropper | null;
|
||||
|
||||
_originalFile: File | null;
|
||||
_originalFileDimensions: {
|
||||
intOriginalFile: File | null;
|
||||
intOriginalFileDimensions: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
_originalFileName: string;
|
||||
_previewStyle: string;
|
||||
intOriginalFileName: string;
|
||||
intPreviewStyle: string;
|
||||
} {
|
||||
return {
|
||||
_id: "",
|
||||
_value: "",
|
||||
_valueFileName: this.__("Sélectionnez une image"),
|
||||
_helper: "",
|
||||
_name: "",
|
||||
_required: true,
|
||||
_width: 0,
|
||||
_height: 0,
|
||||
_preview: true,
|
||||
intId: "",
|
||||
intValue: "",
|
||||
intValueFileName: this.__("Sélectionnez une image"),
|
||||
intHelper: "",
|
||||
intName: "",
|
||||
intRequired: true,
|
||||
intWidth: 0,
|
||||
intHeight: 0,
|
||||
intPreview: true,
|
||||
|
||||
_browseEvent: false,
|
||||
intBrowseEvent: false,
|
||||
|
||||
_rotationText: 0,
|
||||
_rotationValue: 0,
|
||||
_scaleX: 1,
|
||||
_scaleY: 1,
|
||||
_hasImage: false,
|
||||
_hasModdedImage: false,
|
||||
_cropper: null,
|
||||
intRotationText: 0,
|
||||
intRotationValue: 0,
|
||||
intScaleX: 1,
|
||||
intScaleY: 1,
|
||||
intHasImage: false,
|
||||
intHasModdedImage: false,
|
||||
intCropper: null,
|
||||
|
||||
_originalFile: null,
|
||||
_originalFileDimensions: {
|
||||
intOriginalFile: null,
|
||||
intOriginalFileDimensions: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
_originalFileName: "",
|
||||
_previewStyle: "",
|
||||
intOriginalFileName: "",
|
||||
intPreviewStyle: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
imageRatio() {
|
||||
return this._width / this._height;
|
||||
return this.intWidth / this.intHeight;
|
||||
},
|
||||
cropperOption(): Cropper.Options {
|
||||
const self = this;
|
||||
return {
|
||||
autoCrop: true,
|
||||
background: true,
|
||||
|
|
@ -524,83 +562,83 @@ export default defineComponent({
|
|||
cropBoxMovable: true,
|
||||
cropBoxResizable: false,
|
||||
toggleDragModeOnDblclick: false,
|
||||
aspectRatio: this._width / this._height,
|
||||
preview: `#Preview${this._id}`,
|
||||
aspectRatio: this.intWidth / this.intHeight,
|
||||
preview: `#Preview${this.intId}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
_rotationValue(value) {
|
||||
if (!(this._cropper instanceof Cropper)) {
|
||||
intRotationValue(value) {
|
||||
if (!(this.intCropper instanceof Cropper)) {
|
||||
throw new Error("cropped not initialized");
|
||||
}
|
||||
this._cropper.rotateTo(value);
|
||||
this.intCropper.rotateTo(value);
|
||||
},
|
||||
_value() {
|
||||
if (this._browseEvent) {
|
||||
this._hasImage = true;
|
||||
intValue() {
|
||||
if (this.intBrowseEvent) {
|
||||
this.intHasImage = true;
|
||||
}
|
||||
},
|
||||
value() {
|
||||
this._value = this.value.replace(/^\/|^/, "/");
|
||||
this._valueFileName = this.value;
|
||||
this.intValue = this.value.replace(/^\/|^/, "/");
|
||||
this.intValueFileName = this.value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
previewStyleCompute() {
|
||||
const previewBox = this.$refs.previewBox as HTMLElement,
|
||||
style = window.getComputedStyle(previewBox),
|
||||
targetWidth = Number.parseFloat(style.width) / 1.2,
|
||||
targetHeight =
|
||||
(this._height * Number.parseFloat(style.width)) / this._width / 1.2;
|
||||
this._previewStyle = `width: ${targetWidth.toFixed(
|
||||
style = window.getComputedStyle(previewBox),
|
||||
targetWidth = Number.parseFloat(style.width) / 1.2,
|
||||
targetHeight =
|
||||
(this.intHeight * Number.parseFloat(style.width)) / this.intWidth / 1.2;
|
||||
this.intPreviewStyle = `width: ${targetWidth.toFixed(
|
||||
3
|
||||
)}px;height:${targetHeight.toFixed(3)}px;`;
|
||||
},
|
||||
reset() {
|
||||
if (!(this._cropper instanceof Cropper)) {
|
||||
if (!(this.intCropper instanceof Cropper)) {
|
||||
return;
|
||||
}
|
||||
this._cropper.reset();
|
||||
this.intCropper.reset();
|
||||
},
|
||||
zoom(v: number) {
|
||||
if (!(this._cropper instanceof Cropper)) {
|
||||
if (!(this.intCropper instanceof Cropper)) {
|
||||
return;
|
||||
}
|
||||
this._cropper.zoom(v);
|
||||
this.intCropper.zoom(v);
|
||||
},
|
||||
move(x: number, y: number) {
|
||||
if (!(this._cropper instanceof Cropper)) {
|
||||
if (!(this.intCropper instanceof Cropper)) {
|
||||
return;
|
||||
}
|
||||
this._cropper.move(x, y);
|
||||
this.intCropper.move(x, y);
|
||||
},
|
||||
scale(horizontal: boolean) {
|
||||
if (!(this._cropper instanceof Cropper)) {
|
||||
if (!(this.intCropper instanceof Cropper)) {
|
||||
return;
|
||||
}
|
||||
if (horizontal) {
|
||||
this._scaleX = -1 / this._scaleX;
|
||||
this._cropper.scaleX(this._scaleX);
|
||||
this.intScaleX = -1 / this.intScaleX;
|
||||
this.intCropper.scaleX(this.intScaleX);
|
||||
} else {
|
||||
this._scaleY = -1 / this._scaleY;
|
||||
this._cropper.scaleY(this._scaleY);
|
||||
this.intScaleY = -1 / this.intScaleY;
|
||||
this.intCropper.scaleY(this.intScaleY);
|
||||
}
|
||||
},
|
||||
rotate(value: number) {
|
||||
const rotationSlider = this.$refs.rotationSlider as HTMLInputElement;
|
||||
let res = this._rotationValue + value;
|
||||
let res = this.intRotationValue + value;
|
||||
res = res < 0 ? 0 : res;
|
||||
res = res > 360 ? 360 : res;
|
||||
this._rotationValue = res;
|
||||
this._rotationText = this._rotationValue;
|
||||
rotationSlider.value = String(this._rotationValue);
|
||||
this.intRotationValue = res;
|
||||
this.intRotationText = this.intRotationValue;
|
||||
rotationSlider.value = String(this.intRotationValue);
|
||||
},
|
||||
rotateTo(value: number) {
|
||||
value = value < 0 ? 0 : value;
|
||||
value = value > 360 ? 360 : value;
|
||||
this._rotationValue = value;
|
||||
this._rotationText = this._rotationValue;
|
||||
this.intRotationValue = value;
|
||||
this.intRotationText = this.intRotationValue;
|
||||
},
|
||||
rotationChange(e: Event) {
|
||||
const el = e.target;
|
||||
|
|
@ -609,23 +647,23 @@ export default defineComponent({
|
|||
}
|
||||
this.rotateTo(Number.parseFloat(el.value));
|
||||
},
|
||||
chooseAFile(e: Event) {
|
||||
chooseAFile() {
|
||||
const actualImage = this.$refs.actualImage as HTMLInputElement;
|
||||
if (this._browseEvent) {
|
||||
this.$emit("browseFile", this._id);
|
||||
if (this.intBrowseEvent) {
|
||||
this.$emit("browseFile", this.intId);
|
||||
return;
|
||||
}
|
||||
actualImage.click();
|
||||
},
|
||||
removeFile(e: Event) {
|
||||
removeFile() {
|
||||
const actualImage = this.$refs.actualImage as HTMLInputElement;
|
||||
actualImage.value = "";
|
||||
this._valueFileName = this.__("Sélectionnez une image");
|
||||
this._value = "";
|
||||
this.intValueFileName = this.__("Sélectionnez une image");
|
||||
this.intValue = "";
|
||||
},
|
||||
changedImage(e: Event) {
|
||||
const self = this,
|
||||
reader = new FileReader();
|
||||
reader = new FileReader();
|
||||
const el = e.target;
|
||||
if (!el || !(el instanceof HTMLInputElement)) {
|
||||
throw new Error("confirmDoneJS can only be executed on an exising form");
|
||||
|
|
@ -633,39 +671,39 @@ export default defineComponent({
|
|||
if (!el.files) {
|
||||
throw new Error();
|
||||
}
|
||||
self._hasImage = true;
|
||||
self.intHasImage = true;
|
||||
reader.addEventListener(
|
||||
"load",
|
||||
() => {
|
||||
// On convertit l'image en une chaîne de caractères base64.
|
||||
self._value = String(reader.result);
|
||||
self.intValue = String(reader.result);
|
||||
},
|
||||
false
|
||||
);
|
||||
if (el.files[0]) {
|
||||
self._originalFile = el.files[0];
|
||||
self.intOriginalFile = el.files[0];
|
||||
reader.readAsDataURL(el.files[0]);
|
||||
reader.onloadend = () => this.fetchDataUrlImageSizes(String(reader.result));
|
||||
this._valueFileName = el.files[0].name;
|
||||
this.intValueFileName = el.files[0].name;
|
||||
}
|
||||
},
|
||||
prepareImageEditor(e: Event, id: number) {
|
||||
prepareImageEditor(e: Event) {
|
||||
const self = this,
|
||||
actualImage = this.$refs.actualImage as HTMLInputElement,
|
||||
imgEditor = this.$refs.imgEditor as HTMLImageElement,
|
||||
createCropper = () => {
|
||||
if (self._cropper) {
|
||||
self._cropper.destroy();
|
||||
}
|
||||
self._cropper = new Cropper(imgEditor, self.cropperOption);
|
||||
};
|
||||
actualImage = this.$refs.actualImage as HTMLInputElement,
|
||||
imgEditor = this.$refs.imgEditor as HTMLImageElement,
|
||||
createCropper = () => {
|
||||
if (self.intCropper) {
|
||||
self.intCropper.destroy();
|
||||
}
|
||||
self.intCropper = new Cropper(imgEditor, self.cropperOption);
|
||||
};
|
||||
|
||||
if (!self.browseEvent) {
|
||||
if (!actualImage || !actualImage.files) {
|
||||
throw new Error("actualImage is not defined");
|
||||
}
|
||||
self._originalFile = actualImage.files[0];
|
||||
self._originalFileName = actualImage.files[0].name;
|
||||
self.intOriginalFile = actualImage.files[0];
|
||||
self.intOriginalFileName = actualImage.files[0].name;
|
||||
imgEditor.src = URL.createObjectURL(actualImage.files[0]);
|
||||
|
||||
self.previewStyleCompute();
|
||||
|
|
@ -677,7 +715,7 @@ export default defineComponent({
|
|||
this.$nextTick(() => {
|
||||
fetch(self.value.replace(/^\/|^/, "/"))
|
||||
.then((response) => {
|
||||
self._originalFileName = response.url.substring(
|
||||
self.intOriginalFileName = response.url.substring(
|
||||
response.url.lastIndexOf("/") + 1
|
||||
);
|
||||
return response.blob();
|
||||
|
|
@ -709,44 +747,44 @@ export default defineComponent({
|
|||
},
|
||||
fetchDataUrlImageSizes(dataUrl: string) {
|
||||
const img = new Image(),
|
||||
self = this;
|
||||
self = this;
|
||||
try {
|
||||
img.onload = () => {
|
||||
self._originalFileDimensions.width = img.width;
|
||||
self._originalFileDimensions.height = img.height;
|
||||
self.intOriginalFileDimensions.width = img.width;
|
||||
self.intOriginalFileDimensions.height = img.height;
|
||||
};
|
||||
img.src = dataUrl;
|
||||
} catch (e) {
|
||||
self._originalFileDimensions.width = img.width;
|
||||
self._originalFileDimensions.height = img.height;
|
||||
self.intOriginalFileDimensions.width = img.width;
|
||||
self.intOriginalFileDimensions.height = img.height;
|
||||
}
|
||||
},
|
||||
exportToBlob() {
|
||||
const self = this,
|
||||
actualImage = this.$refs.actualImage as HTMLInputElement,
|
||||
container = new DataTransfer();
|
||||
if (!(self._cropper instanceof Cropper)) {
|
||||
actualImage = this.$refs.actualImage as HTMLInputElement,
|
||||
container = new DataTransfer();
|
||||
if (!(self.intCropper instanceof Cropper)) {
|
||||
return;
|
||||
}
|
||||
if (!actualImage.files) {
|
||||
throw new Error();
|
||||
}
|
||||
self._hasModdedImage = true;
|
||||
self._value = self._cropper
|
||||
self.intHasModdedImage = true;
|
||||
self.intValue = self.intCropper
|
||||
.getCroppedCanvas({
|
||||
fillColor: "rgba(0, 0, 0, 0)",
|
||||
width: self._width,
|
||||
height: self._height,
|
||||
width: self.intWidth,
|
||||
height: self.intHeight,
|
||||
imageSmoothingEnabled: true,
|
||||
imageSmoothingQuality: "high",
|
||||
})
|
||||
.toDataURL("image/png");
|
||||
this.fetchDataUrlImageSizes(self._value);
|
||||
const file = new File([self.dataURLtoBlob(self._value)], self._originalFileName, {
|
||||
this.fetchDataUrlImageSizes(self.intValue);
|
||||
const file = new File([self.dataURLtoBlob(self.intValue)], self.intOriginalFileName, {
|
||||
type: "image/png",
|
||||
lastModified: new Date().getTime(),
|
||||
});
|
||||
this._originalFile = file;
|
||||
this.intOriginalFile = file;
|
||||
container.items.add(file);
|
||||
actualImage.files = container.files;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
<template>
|
||||
<WordCounter :id="_id" />
|
||||
<WordCounter :id="intId" />
|
||||
<textarea
|
||||
ref="textarea"
|
||||
:id="_id"
|
||||
:class="`inputTextAutoComponent ${_class}`"
|
||||
:name="_name"
|
||||
:aria-describedby="_ariaDescribedby"
|
||||
:placeholder="_placeholder"
|
||||
:required="_required"
|
||||
v-on:input="onInput"
|
||||
:id="intId"
|
||||
:class="`inputTextAutoComponent ${intClass}`"
|
||||
:name="intName"
|
||||
:aria-describedby="intAriaDescribedby"
|
||||
:placeholder="intPlaceholder"
|
||||
:required="intRequired"
|
||||
@input="onInput"
|
||||
:json="'{}'"
|
||||
>{{ _value }}</textarea
|
||||
>
|
||||
v-model="intValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref } from "vue";
|
||||
import { defineComponent, ref } from "vue";
|
||||
import trans from "../../modules/trans";
|
||||
import WordCounter from "./WordCounterComponent.vue";
|
||||
|
||||
|
|
@ -37,42 +37,42 @@ export default defineComponent({
|
|||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
this._id = String(data.id);
|
||||
this._class = String(data.class);
|
||||
this._name = String(data.name);
|
||||
this._placeholder = String(data.placeholder);
|
||||
this._ariaDescribedby = String(data.ariaDescribedby);
|
||||
this._value = String(data.value);
|
||||
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._required = true;
|
||||
this.intRequired = true;
|
||||
}
|
||||
if (this.textarea) {
|
||||
// Default input init
|
||||
this.textarea.setAttribute("style", `height: 1rem;overflow-y: hidden;`);
|
||||
this.textarea.setAttribute("style", "height: 1rem;overflow-y: hidden;");
|
||||
}
|
||||
// * Init input
|
||||
this.$nextTick(this.onInput);
|
||||
},
|
||||
data(): {
|
||||
_id: string;
|
||||
_class: string;
|
||||
_name: string;
|
||||
_placeholder: string;
|
||||
_ariaDescribedby: string;
|
||||
_required: boolean;
|
||||
_value: string;
|
||||
_textarea: HTMLTextAreaElement | null;
|
||||
intId: string;
|
||||
intClass: string;
|
||||
intName: string;
|
||||
intPlaceholder: string;
|
||||
intAriaDescribedby: string;
|
||||
intRequired: boolean;
|
||||
intValue: string;
|
||||
intTextarea: HTMLTextAreaElement | null;
|
||||
} {
|
||||
return {
|
||||
_id: "",
|
||||
_class: "",
|
||||
_name: "",
|
||||
_placeholder: "",
|
||||
_ariaDescribedby: "",
|
||||
_required: false,
|
||||
_value: "",
|
||||
_textarea: null,
|
||||
intId: "",
|
||||
intClass: "",
|
||||
intName: "",
|
||||
intPlaceholder: "",
|
||||
intAriaDescribedby: "",
|
||||
intRequired: false,
|
||||
intValue: "",
|
||||
intTextarea: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -1,43 +1,51 @@
|
|||
<template>
|
||||
<div class="ModelLink form-control">
|
||||
<VueSelect
|
||||
:placeholder="_placeholder"
|
||||
:_loading="_loading"
|
||||
v-model="_value"
|
||||
:placeholder="intPlaceholder"
|
||||
:loading="loading"
|
||||
v-model="intValue"
|
||||
:clearable="true"
|
||||
label="label"
|
||||
:options="_data"
|
||||
v-on:search="search"
|
||||
:disabled="_readonly || _disabled"
|
||||
:options="data"
|
||||
@search="searchAjax"
|
||||
:disabled="intReadonly || intDisabled"
|
||||
:filterable="true"
|
||||
:searchable="true"
|
||||
>
|
||||
<template v-slot:option="{ label }: { label: string }">
|
||||
<template #option="{ label }: { label: string }">
|
||||
{{
|
||||
label.length >= _maxLength
|
||||
? label.substring(0, _maxLength) + ".."
|
||||
: label
|
||||
label.length >= intMaxLength ? label.substring(0, intMaxLength) + ".." : label
|
||||
}}
|
||||
</template>
|
||||
<template v-slot:no-options="{ search, searching }: { search: string, searching: boolean }">
|
||||
<template
|
||||
#no-options="{ search, searching }: { search: string, searching: boolean }"
|
||||
>
|
||||
<template v-if="searching">
|
||||
{{ __("Aucun résultat pour") }}
|
||||
<em>{{ search }}</em> .
|
||||
</template>
|
||||
<em v-else style="opacity: 0.5">{{ __("Chercher un lien du site") }}</em>
|
||||
<em
|
||||
v-else
|
||||
style="opacity: 0.5"
|
||||
>
|
||||
{{ __("Chercher un lien du site") }}
|
||||
</em>
|
||||
</template>
|
||||
<template v-slot:list-footer>
|
||||
<li class="pagination" v-if="_data.length">
|
||||
<template #list-footer>
|
||||
<li
|
||||
class="pagination"
|
||||
v-if="data.length"
|
||||
>
|
||||
<button
|
||||
v-on:click.prevent="_page -= 1"
|
||||
:disabled="!_hasPrevPage"
|
||||
@click.prevent="page -= 1"
|
||||
:disabled="!hasPrevPage"
|
||||
class="btn btn-sm btn-primary mt-2 mb-2 ms-2 me-2"
|
||||
>
|
||||
{{ __("précédent") }}
|
||||
</button>
|
||||
<button
|
||||
v-on:click.prevent="_page += 1"
|
||||
:disabled="!_hasNextPage"
|
||||
@click.prevent="page += 1"
|
||||
:disabled="!hasNextPage"
|
||||
class="btn btn-sm btn-primary mt-2 mb-2"
|
||||
>
|
||||
{{ __("suivant") }}
|
||||
|
|
@ -45,7 +53,12 @@
|
|||
</li>
|
||||
</template>
|
||||
</VueSelect>
|
||||
<input v-if="_isJson" type="hidden" :name="String(_name)" :value="_value?.value" />
|
||||
<input
|
||||
v-if="isJson"
|
||||
type="hidden"
|
||||
:name="String(name)"
|
||||
:value="intValue?.value"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -67,24 +80,25 @@ export default defineComponent({
|
|||
components: {
|
||||
VueSelect,
|
||||
},
|
||||
emits: ["input"],
|
||||
beforeMount() {
|
||||
if (!this.$attrs.json) {
|
||||
throw new Error("this component requires json");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
const json = String(this.$attrs.json ?? "{}");
|
||||
const data = JSON.parse(json);
|
||||
if (Object.keys(data).length) {
|
||||
this._isJson = true;
|
||||
this._name = data.name;
|
||||
this.isJson = true;
|
||||
this.name = data.name;
|
||||
}
|
||||
this._disabled = this.disabled ? this.disabled : data.disabled ? true : false;
|
||||
this._readonly = this.readonly ? this.readonly : data.readonly ? true : false;
|
||||
this._placeholder = this.placeholder ? this.placeholder : data.placeholder;
|
||||
this._maxLength = this.maxLength ? this.maxLength : data.maxLength;
|
||||
this.intDisabled = this.disabled ? this.disabled : data.disabled ? true : false;
|
||||
this.intReadonly = this.readonly ? this.readonly : data.readonly ? true : false;
|
||||
this.intPlaceholder = this.placeholder ? this.placeholder : data.placeholder;
|
||||
this.intMaxLength = this.maxLength ? this.maxLength : data.maxLength;
|
||||
this.parseValue(this.value ? this.value : data.value);
|
||||
this.search();
|
||||
this.searchAjax();
|
||||
},
|
||||
props: {
|
||||
disabled: {
|
||||
|
|
@ -109,81 +123,81 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
data(): {
|
||||
_isJson: boolean;
|
||||
_name: string | null;
|
||||
_disabled: boolean;
|
||||
_readonly: boolean;
|
||||
_placeholder: string;
|
||||
_maxLength: number;
|
||||
_value: SelectModel | null;
|
||||
_loading: boolean;
|
||||
_searchText: string;
|
||||
_data: SelectModel[];
|
||||
_page: number;
|
||||
_perPage: number;
|
||||
_hasNextPage: boolean;
|
||||
_hasPrevPage: boolean;
|
||||
isJson: boolean;
|
||||
name: string | null;
|
||||
intDisabled: boolean;
|
||||
intReadonly: boolean;
|
||||
intPlaceholder: string;
|
||||
intMaxLength: number;
|
||||
intValue: SelectModel | null;
|
||||
loading: boolean;
|
||||
searchText: string;
|
||||
data: SelectModel[];
|
||||
page: number;
|
||||
perPage: number;
|
||||
hasNextPage: boolean;
|
||||
hasPrevPage: boolean;
|
||||
} {
|
||||
return {
|
||||
_isJson: false,
|
||||
_name: null,
|
||||
_disabled: false,
|
||||
_readonly: false,
|
||||
_placeholder: "",
|
||||
_value: null,
|
||||
_loading: false,
|
||||
_searchText: "",
|
||||
_data: [],
|
||||
_page: 1,
|
||||
_perPage: 15,
|
||||
_hasNextPage: true,
|
||||
_hasPrevPage: false,
|
||||
_maxLength: 65,
|
||||
isJson: false,
|
||||
name: null,
|
||||
intDisabled: false,
|
||||
intReadonly: false,
|
||||
intPlaceholder: "",
|
||||
intValue: null,
|
||||
loading: false,
|
||||
searchText: "",
|
||||
data: [],
|
||||
page: 1,
|
||||
perPage: 15,
|
||||
hasNextPage: true,
|
||||
hasPrevPage: false,
|
||||
intMaxLength: 65,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
_page() {
|
||||
if (this._page < 1) {
|
||||
this._page = 1;
|
||||
page() {
|
||||
if (this.page < 1) {
|
||||
this.page = 1;
|
||||
}
|
||||
if (this._page > 1) {
|
||||
this._hasPrevPage = true;
|
||||
if (this.page > 1) {
|
||||
this.hasPrevPage = true;
|
||||
} else {
|
||||
this._hasPrevPage = false;
|
||||
this.hasPrevPage = false;
|
||||
}
|
||||
this.search();
|
||||
this.searchAjax();
|
||||
},
|
||||
_data() {
|
||||
if (this._data.length == this._perPage) {
|
||||
this._hasNextPage = true;
|
||||
data() {
|
||||
if (this.data.length == this.perPage) {
|
||||
this.hasNextPage = true;
|
||||
} else {
|
||||
this._hasNextPage = false;
|
||||
this.hasNextPage = false;
|
||||
}
|
||||
},
|
||||
value() {
|
||||
this.parseValue(this.value);
|
||||
},
|
||||
_value() {
|
||||
this.$emit("input", this._value ? this._value.value : "");
|
||||
intValue() {
|
||||
this.$emit("input", this.intValue ? this.intValue.value : "");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
search(search = null) {
|
||||
searchAjax(search = null) {
|
||||
if (search !== false && search !== null) {
|
||||
this._searchText = search;
|
||||
this.searchText = search;
|
||||
}
|
||||
if (!this._searchText) {
|
||||
if (!this.searchText) {
|
||||
return;
|
||||
}
|
||||
let routeUrl = route.methods.route("bo.ranges.search-uri");
|
||||
if (!routeUrl) {
|
||||
throw new Error(`undefined route bo.ranges.search-uri`);
|
||||
throw new Error("undefined route bo.ranges.search-uri");
|
||||
}
|
||||
const url = new URL(routeUrl);
|
||||
routeUrl = `${url.origin}${url.pathname}?`;
|
||||
routeUrl += "page=" + encodeURIComponent(this._page);
|
||||
routeUrl += "&search=" + encodeURIComponent(this._searchText);
|
||||
this._loading = true;
|
||||
routeUrl += "page=" + encodeURIComponent(this.page);
|
||||
routeUrl += "&search=" + encodeURIComponent(this.searchText);
|
||||
this.loading = true;
|
||||
axios
|
||||
.get(routeUrl)
|
||||
.then((response) => {
|
||||
|
|
@ -193,20 +207,20 @@ export default defineComponent({
|
|||
console.error(error);
|
||||
})
|
||||
.then(() => {
|
||||
this._loading = false;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
parseValue(value: string | null) {
|
||||
if (!value) {
|
||||
this._value = null;
|
||||
this.intValue = null;
|
||||
return;
|
||||
}
|
||||
let routeUrl = route.methods.route("bo.ranges.model-title");
|
||||
if (!routeUrl) {
|
||||
throw new Error(`undefined route bo.ranges.model-title`);
|
||||
throw new Error("undefined route bo.ranges.model-title");
|
||||
}
|
||||
const url = new URL(routeUrl),
|
||||
model = value.replace(/^#/, "").replace(/#$/, "").split("#");
|
||||
model = value.replace(/^#/, "").replace(/#$/, "").split("#");
|
||||
|
||||
routeUrl = `${url.origin}${url.pathname}?`;
|
||||
|
||||
|
|
@ -215,11 +229,11 @@ export default defineComponent({
|
|||
axios.get(routeUrl).then((response) => {
|
||||
let label = String(response.data as string);
|
||||
label =
|
||||
label.length >= this._maxLength
|
||||
? label.substring(0, this._maxLength) + ".."
|
||||
: label;
|
||||
label.length >= this.intMaxLength
|
||||
? label.substring(0, this.intMaxLength) + ".."
|
||||
: label;
|
||||
if (label) {
|
||||
this._value = this.parseResource({
|
||||
this.intValue = this.parseResource({
|
||||
class: model[0],
|
||||
label: label,
|
||||
value: model[1],
|
||||
|
|
@ -250,9 +264,9 @@ export default defineComponent({
|
|||
for (const model of data) {
|
||||
out.push(this.parseResource(model));
|
||||
}
|
||||
// pass _searchText if is valid url and nothing else is found
|
||||
// pass searchText if is valid url and nothing else is found
|
||||
if (out.length) {
|
||||
this._data = out;
|
||||
this.data = out;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -264,6 +278,7 @@ export default defineComponent({
|
|||
|
||||
.ModelLink {
|
||||
padding: 0 !important;
|
||||
|
||||
.vs__dropdown-toggle {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
<template>
|
||||
<div class="taggableDropdown">
|
||||
<div v-if="errorMessages.length">
|
||||
<span class="d-block text-danger" v-for="errorMessage of errorMessages">
|
||||
<span
|
||||
class="d-block text-danger"
|
||||
v-for="errorMessage of errorMessages"
|
||||
:key="errorMessage"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -16,18 +20,38 @@
|
|||
:max-tags="6"
|
||||
:placeholder="__('Ajoutez ici vos étiquettes')"
|
||||
/>
|
||||
<div class="text-center py-2" v-if="waitingListObjects.length">
|
||||
<button class="btn btn-sm btn-primary ms-2" v-on:click.prevent="addTags">
|
||||
<div
|
||||
class="text-center py-2"
|
||||
v-if="waitingListObjects.length"
|
||||
>
|
||||
<button
|
||||
class="btn btn-sm btn-primary ms-2"
|
||||
@click.prevent="addTags"
|
||||
>
|
||||
{{ __("Créer et ajouter à la liste") }}
|
||||
</button>
|
||||
<button class="btn btn-sm btn-dark ms-2" v-on:click.prevent="cancelTags">
|
||||
<button
|
||||
class="btn btn-sm btn-dark ms-2"
|
||||
@click.prevent="cancelTags"
|
||||
>
|
||||
{{ __("Annuler") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(sTag, index) in tags" :key="index">
|
||||
<input :name="`${name}[${String(index)}][id]`" type="hidden" :value="sTag.id" />
|
||||
<input :name="`${name}[${String(index)}][name]`" type="hidden" :value="sTag.text" />
|
||||
<template
|
||||
v-for="(sTag, index) in tags"
|
||||
:key="index"
|
||||
>
|
||||
<input
|
||||
:name="`${name}[${String(index)}][id]`"
|
||||
type="hidden"
|
||||
:value="sTag.id"
|
||||
>
|
||||
<input
|
||||
:name="`${name}[${String(index)}][name]`"
|
||||
type="hidden"
|
||||
:value="sTag.text"
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -53,7 +77,7 @@ export default defineComponent({
|
|||
},
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
data = JSON.parse(json);
|
||||
this.name = data.name;
|
||||
this.tags = this.parseTags(data.value);
|
||||
this.autocompleteItems = this.parseTags(data.tags);
|
||||
|
|
@ -115,8 +139,8 @@ export default defineComponent({
|
|||
this.waitingListObjects.push(obj);
|
||||
},
|
||||
ajaxSaveTag(obj: VueTagsInput.IAddArgs): void {
|
||||
const self = this,
|
||||
storeTagRoute = route.methods.route("bo.tags.jsonStore");
|
||||
const self = this;
|
||||
const storeTagRoute = route.methods.route("bo.tags.jsonStore");
|
||||
if (!storeTagRoute) {
|
||||
throw new Error("Undefined route bo.tags.jsonStore");
|
||||
}
|
||||
|
|
@ -124,7 +148,7 @@ export default defineComponent({
|
|||
.post(storeTagRoute, { name: self.tag })
|
||||
.then((reponse) => {
|
||||
const parsedTag = self.parseTags([reponse.data])[0] as CustomTag,
|
||||
tag = obj.tag as CustomTag;
|
||||
tag = obj.tag as CustomTag;
|
||||
self.autocompleteItems.push(parsedTag);
|
||||
tag.id = parsedTag.id;
|
||||
tag.slug = parsedTag.slug;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
<template>
|
||||
<div class="wordCounter">
|
||||
<span class="words"
|
||||
><u>{{ __("Mots") }}</u
|
||||
> : <b>{{ _words }}</b></span
|
||||
>
|
||||
<span class="chars"
|
||||
><u>{{ __("Caractères") }}</u
|
||||
> : <b>{{ _chars }}</b></span
|
||||
>
|
||||
<span class="words">
|
||||
<u>{{ __("Mots") }}</u>
|
||||
:
|
||||
<b>{{ intWords }}</b>
|
||||
</span>
|
||||
<span class="chars">
|
||||
<u>{{ __("Caractères") }}</u>
|
||||
:
|
||||
<b>{{ intChars }}</b>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -18,11 +20,11 @@ import trans from "../../modules/trans";
|
|||
export default defineComponent({
|
||||
mixins: [trans],
|
||||
mounted() {
|
||||
const json = String(this.$attrs.json ?? "{}"),
|
||||
data = JSON.parse(json);
|
||||
this._id = String(this.id);
|
||||
const json = String(this.$attrs.json ?? "{}");
|
||||
const data = JSON.parse(json);
|
||||
this.intId = String(this.id);
|
||||
if (data.id) {
|
||||
this._id = data.id;
|
||||
this.intId = data.id;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
|
@ -32,66 +34,66 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
data(): {
|
||||
_id: string;
|
||||
_words: number;
|
||||
_chars: number;
|
||||
_input: HTMLInputElement | HTMLTextAreaElement | null;
|
||||
intId: string;
|
||||
intWords: number;
|
||||
intChars: number;
|
||||
intInput: HTMLInputElement | HTMLTextAreaElement | null;
|
||||
} {
|
||||
return {
|
||||
_id: "",
|
||||
_words: 0,
|
||||
_chars: 0,
|
||||
_input: null,
|
||||
intId: "",
|
||||
intWords: 0,
|
||||
intChars: 0,
|
||||
intInput: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
id() {
|
||||
this._id = this.id;
|
||||
this.intId = this.id;
|
||||
},
|
||||
_id() {
|
||||
if (!this._id) {
|
||||
intId() {
|
||||
if (!this.intId) {
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
const input = document.getElementById(this._id);
|
||||
const input = document.getElementById(this.intId);
|
||||
if (
|
||||
!input ||
|
||||
!(input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement)
|
||||
) {
|
||||
throw new Error(`${this._id} is not a input or textarea`);
|
||||
throw new Error(`${this.intId} is not a input or textarea`);
|
||||
}
|
||||
this._input = input;
|
||||
this.intInput = input;
|
||||
});
|
||||
},
|
||||
_input() {
|
||||
intInput() {
|
||||
if (
|
||||
!this._input ||
|
||||
!this.intInput ||
|
||||
!(
|
||||
this._input instanceof HTMLInputElement ||
|
||||
this._input instanceof HTMLTextAreaElement
|
||||
this.intInput instanceof HTMLInputElement ||
|
||||
this.intInput instanceof HTMLTextAreaElement
|
||||
)
|
||||
) {
|
||||
throw new Error(`${this._id} is not a input or textarea`);
|
||||
throw new Error(`${this.intId} is not a input or textarea`);
|
||||
}
|
||||
this._input.addEventListener("input", (e) => {
|
||||
this.intInput.addEventListener("input", (e) => {
|
||||
const el = e.target;
|
||||
if (
|
||||
!el ||
|
||||
!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)
|
||||
) {
|
||||
throw new Error(`${this._id} is not a input or textarea`);
|
||||
throw new Error(`${this.intId} is not a input or textarea`);
|
||||
}
|
||||
this.updateValues(el.value);
|
||||
});
|
||||
this.updateValues(this._input.value);
|
||||
this.updateValues(this.intInput.value);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateValues(string: string) {
|
||||
let chars, words;
|
||||
[words, chars] = this.calculateValues(string);
|
||||
this._words = words;
|
||||
this._chars = chars;
|
||||
this.intWords = words;
|
||||
this.intChars = chars;
|
||||
},
|
||||
calculateValues(string: string) {
|
||||
const arr = string.split(" ");
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
}
|
||||
elements = document.getElementsByClassName("confirmDoneJS");
|
||||
for (let element of elements) {
|
||||
for (const element of elements) {
|
||||
element.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
const el = e.target;
|
||||
|
|
@ -47,7 +47,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
}
|
||||
elements = document.getElementsByClassName("confirmSubmitJS");
|
||||
for (let element of elements) {
|
||||
for (const element of elements) {
|
||||
element.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
const el = e.target;
|
||||
|
|
@ -69,7 +69,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
}
|
||||
elements = document.getElementsByClassName("confirmJS");
|
||||
for (let element of elements) {
|
||||
for (const element of elements) {
|
||||
element.addEventListener(
|
||||
"click",
|
||||
function (e) {
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ export default {
|
|||
options: SweetAlertOptions = {}
|
||||
) {
|
||||
if (!self) {
|
||||
throw Error('self is needed to set "this" on callback');
|
||||
throw Error("self is needed to set 'this' on callback");
|
||||
}
|
||||
if (!after) {
|
||||
throw Error("callback is needed when using confirm");
|
||||
}
|
||||
let icon = options.icon ?? "warning";
|
||||
const icon = options.icon ?? "warning";
|
||||
Swal.fire({
|
||||
title: title,
|
||||
text: text,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const route = {
|
|||
const routes = route.flattenProperties(window.__SYSTEM._routes);
|
||||
const routeUrl =
|
||||
Object.keys(routes).indexOf(routeName) !== -1
|
||||
? routes[routeName]
|
||||
? routes[routeName] as string
|
||||
: null;
|
||||
if (!routeUrl) {
|
||||
return routeUrl;
|
||||
|
|
@ -31,8 +31,8 @@ const route = {
|
|||
delete parameters[paramName];
|
||||
}
|
||||
|
||||
for (let paramName in parameters) {
|
||||
let paramValue = parameters[paramName];
|
||||
for (const paramName in parameters) {
|
||||
const paramValue = parameters[paramName];
|
||||
if (search.indexOf(paramName + "=") >= 0) {
|
||||
const prefix = search.substring(
|
||||
0,
|
||||
|
|
@ -57,14 +57,18 @@ const route = {
|
|||
},
|
||||
},
|
||||
flattenProperties(
|
||||
obj: Record<PropertyKey, any>,
|
||||
obj: Record<PropertyKey, unknown>,
|
||||
parent: string | null = null,
|
||||
res: Record<PropertyKey, any> = {}
|
||||
): Record<PropertyKey, any> {
|
||||
res: Record<PropertyKey, unknown> = {}
|
||||
): Record<PropertyKey, unknown> {
|
||||
for (const key of Object.keys(obj)) {
|
||||
const propName = parent ? parent + "." + key : key;
|
||||
if (typeof obj[key] === "object") {
|
||||
route.flattenProperties(obj[key], propName, res);
|
||||
if (obj[key] instanceof Object) {
|
||||
route.flattenProperties(
|
||||
obj[key] as Record<PropertyKey, unknown>,
|
||||
propName,
|
||||
res
|
||||
);
|
||||
} else {
|
||||
res[propName] = obj[key];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
.replace(/^-/, "")
|
||||
.replace("-component", "");
|
||||
|
||||
let el = document.getElementById(htmlKey),
|
||||
const el = document.getElementById(htmlKey),
|
||||
elGroup = document.getElementsByClassName(htmlKey);
|
||||
// * Init using ID
|
||||
if (el) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
@extends('layouts.back')
|
||||
@section('title', __('Connexion'))
|
||||
@section('description', __('Formulaire d\'authentification au Back-Office'))
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
|
|
|
|||
30
resources/views/back/engagements/edit.blade.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
@extends('layouts.back')
|
||||
|
||||
@section('title', $engagement->title)
|
||||
|
||||
@section('breadcrumb', request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="{{ route('bo.engagements.update', $engagement) }}" method="POST" enctype="multipart/form-data">
|
||||
@method('PUT')
|
||||
@csrf
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
@include('back.engagements.form')
|
||||
@include('back.modules.minicrud-form-inputs', ['model' => $engagement])
|
||||
<div class="form-group text-center mt-3">
|
||||
<button type="submit" class="btn btn-primary"
|
||||
title="{{ __('Modifier la page :model en base', [
|
||||
'model' => $engagement->title
|
||||
]) }}" data-bs-toggle="tooltip">
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
93
resources/views/back/engagements/form.blade.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<div class="form-group">
|
||||
<label for="engagementTitle" class="col-form-label"><b>{{ __('Titre') }}*</b></label>
|
||||
<div class="col-12 form-group">
|
||||
@php
|
||||
$data = [
|
||||
'name' => 'title',
|
||||
'text' => old('title', $engagement->title ?? ''),
|
||||
];
|
||||
@endphp
|
||||
<div class="ck-editor-bo" data-json='@json($data)'></div>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'title',
|
||||
'helperId' => 'engagementTitleHelp',
|
||||
'helper' => __('Doit être un titre court (max 35 caractères)')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="w-25 mx-auto">
|
||||
|
||||
<div class="row ">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="engagementSeoTitle" class="col-form-label"><b>{{ __('Titre seo') }}*</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'engagementSeoTitle'])'></div>
|
||||
<input name="seo_title" type="text" value="{{ old('seo_title', $engagement->seo_title ?? '') }}" class="form-control"
|
||||
id="engagementSeoTitle" aria-describedby="engagementSeoTitleHelp" placeholder="{{ __('Un titre en 70 caractères maximum') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'seo_title', 'helperId' => 'engagementSeoTitleHelp' , 'helper' => __('Doit décrire précisément la page, recommandation pour un positionnement optimal dans les moteurs de recherche : entre 53 et 60 caractères, balise <title> ')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="engagementSeoDescription" class="col-form-label"><b>{{ __('Description seo') }}*</b></label>
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'engagementSeoDescription',
|
||||
'class' => 'form-control',
|
||||
'name' => 'seo_description',
|
||||
'placeholder' => __('Une description du contenu de la page pour les moteurs de recherche'),
|
||||
'ariaDescribedby' => 'engagementSeoDescriptionHelp',
|
||||
'value' => old('engagementSeoDescription', $engagement->seo_description),
|
||||
'required' => true,
|
||||
'value' => old('seo_description', $engagement->seo_description ?? '')
|
||||
];
|
||||
@endphp
|
||||
<div id="input-text-auto" data-json='@json($data)'></div>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'seo_description',
|
||||
'helperId' => 'engagementSeoDescriptionHelp',
|
||||
'helper' => __(' Doit décrire précisément le contenu de la page, requiert entre 50 et 160 caractères, balise <meta> description')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="w-25 mx-auto">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="engagementBannerAlt" class="col-form-label"><b>{{ __('Texte alternatif de la bannière') }}*</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'engagementBannerAlt'])'></div>
|
||||
<input name="banner_alt" type="text" value="{{ old('banner_alt', $engagement->banner_alt ?? '') }}" class="form-control"
|
||||
id="engagementBannerAlt" aria-describedby="engagementBannerAltHelp" placeholder="{{ __('Une image de petits chatons') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'banner_alt',
|
||||
'helperId' => 'engagementBannerAltHelp',
|
||||
'helper' => __('Doit décrire votre bannière pour les moteurs de recherches et les non voyants (5 à 6 mots)')
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="engagementBannerTitle" class="col-form-label"><b>{{ __('Titre de la bannière') }}*</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => 'engagementBannerTitle'])'></div>
|
||||
<input name="banner_title" type="text" value="{{ old('banner_title', $engagement->banner_title ?? '') }}" class="form-control"
|
||||
id="engagementBannerTitle" aria-describedby="engagementBannerTitleHelp" placeholder="{{ __('Une description de l\'article pour les moteurs de recherche') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'banner_title',
|
||||
'helperId' => 'engagementBannerTitleHelp' ,
|
||||
'helper' => __('Utile pour les utilisateurs, s\'affiche en passant la souris sur la bannière')
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@php
|
||||
$data = [
|
||||
'id' => 'engagementBanner',
|
||||
'name' => 'banner',
|
||||
'helper' => __('L\'image qui va servir de bannière, doit être de 1920px (largeur) par 340px (hauteur)'),
|
||||
'value' => $engagement->banner ?? '',
|
||||
];
|
||||
@endphp
|
||||
@include('back.modules.input-error', ['inputName' => 'banner'])
|
||||
<div id="image-input" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -2,28 +2,59 @@
|
|||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.news.*')) active @endif" href="{{ route('bo.news.index') }}" title="{{ __('Administrer les :model', [
|
||||
'model' => Str::singular(__('models.classes.news'))
|
||||
'model' => __('models.classes.news')
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.news') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.tags.*')) active @endif" href="{{ route('bo.tags.index') }}" title="{{ __('Administrer les :model', [
|
||||
'model' => Str::singular(__('models.classes.tags'))
|
||||
'model' => __('models.classes.tags')
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.tags') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.sliders.*')) active @endif" href="{{ route('bo.sliders.index') }}" title="{{ __('Administrer les :model', [
|
||||
'model' => Str::singular(__('models.classes.sliders'))
|
||||
'model' => __('models.classes.sliders')
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.sliders') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.ranges.*')) active @endif" href="{{ route('bo.ranges.index') }}" title="{{ __('Administrer les :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges'))
|
||||
'model' => __('models.classes.ranges')
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.ranges') }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @if(request()->routeIs('bo.products.*')) active @endif" href="{{ route('bo.products.index') }}" title="{{ __('Administrer les :model', [
|
||||
'model' => Str::singular(__('models.classes.products'))
|
||||
'model' => __('models.classes.products')
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('models.classes.products') }}</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle @if(request()->routeIs('bo.engagements.*')) active @endif" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ __('models.classes.engagements.title') }}
|
||||
</a>
|
||||
<div class="dropdown-menu p-0">
|
||||
<a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'energies-renouvelables') }}" title="{{ __('Administrer la page :model', [
|
||||
'model' => __('models.classes.engagements.renewableEnergies')]) }}" data-bs-toggle="tooltip" data-bs-placement="right">
|
||||
{{ __('models.classes.engagements.renewableEnergies') }}
|
||||
</a>
|
||||
<a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'elevage-durable-cereales-locales') }}" title="{{ __('Administrer la page :model', [
|
||||
'model' => __('models.classes.engagements.localCereals')]) }}" data-bs-toggle="tooltip" data-bs-placement="right">
|
||||
{{ __('models.classes.engagements.localCereals') }}
|
||||
</a>
|
||||
<a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'agroforesterie') }}" title="{{ __('Administrer la page :model', [
|
||||
'model' => __('models.classes.engagements.agroforestry')]) }}" data-bs-toggle="tooltip" data-bs-placement="right">
|
||||
{{ __('models.classes.engagements.agroforestry') }}
|
||||
</a>
|
||||
<a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'cereales') }}" title="{{ __('Administrer la page :model', [
|
||||
'model' => __('models.classes.engagements.cereals')]) }}" data-bs-toggle="tooltip" data-bs-placement="right">
|
||||
{{ __('models.classes.engagements.cereals') }}
|
||||
</a>
|
||||
<a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'bien-etre-animal') }}" title="{{ __('Administrer la page :model', [
|
||||
'model' => __('models.classes.engagements.animalWelfare')]) }}" data-bs-toggle="tooltip" data-bs-placement="right">
|
||||
{{ __('models.classes.engagements.animalWelfare') }}
|
||||
</a>
|
||||
<a class="nav-link dropdown-item" href="{{ route('bo.engagements.edit', 'apiculture') }}" title="{{ __('Administrer la page :model', [
|
||||
'model' => __('models.classes.engagements.beekeeping')]) }}" data-bs-toggle="tooltip" data-bs-placement="right">
|
||||
{{ __('models.classes.engagements.beekeeping') }}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@endauth
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@if($errors->has($inputName))
|
||||
<div class="col-12">
|
||||
<small class="text-danger">{{ implode(',', $errors->get($inputName)) }}</small>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<small class="text-danger">{{ implode(',', $errors->get($inputName)) }}</small>
|
||||
</div>
|
||||
@endif
|
||||
@if(isset($helper))
|
||||
<small @if(isset($helperId)) id="{{ $helperId }}" @endif class="form-text text-muted">{{ $helper }}</small>
|
||||
<small @if(isset($helperId)) id="{{ $helperId }}" @endif class="form-text text-muted">{{ $helper }}</small>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -1,83 +1,153 @@
|
|||
{{-- MINI CRUD EDIT --}}
|
||||
<div id="minicrud">
|
||||
@foreach($model->data as $k => $input)
|
||||
@if(!$loop->first)
|
||||
<hr class="w-25">
|
||||
@endif
|
||||
@php
|
||||
$input = json_decode(json_encode($input), true);
|
||||
@endphp
|
||||
@switch($input['inputType']['value'])
|
||||
@case(InputType::url()->value)
|
||||
@case(InputType::string()->value)
|
||||
<div class="form-group">
|
||||
<label for="{{ $k }}"><small>{{ $input['label'] }}*</small></label>
|
||||
<input id="{{ $k }}" type="text" class="form-control" name="data[{{$k}}][value]" placeholder="{{ $input['label'] }}" value="{{ old("data.$k.value") ?? $input['value'] ?? '' }}" required>
|
||||
</div>
|
||||
@foreach ($model->data as $k => $input)
|
||||
<hr class="w-25 mx-auto">
|
||||
@php
|
||||
$input = json_decode(json_encode($input), true);
|
||||
@endphp
|
||||
@switch($input['inputType']['value'])
|
||||
@case(App\Enums\InputType::url()->value)
|
||||
|
||||
@case(App\Enums\InputType::string()->value)
|
||||
<div class="form-group">
|
||||
<label for="{{$k}}"><b>{{ $input['label'] }}*</b></label>
|
||||
<div class="word-counter" data-json='@json(['id' => $k])'></div>
|
||||
<input id="{{$k}}" type="text" class="form-control" name="data[{{$k}}][value]"
|
||||
placeholder="{{ $input['label'] }}" value="{{ old("data.$k.value") ?? ($input['value'] ?? '') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'data[{{$k}}][value]',
|
||||
'helperId' => '{{$k}}StringHelp',
|
||||
'helper' => $input['helper'],
|
||||
])
|
||||
</div>
|
||||
@break
|
||||
@case(InputType::number()->value)
|
||||
<div class="form-group">
|
||||
<label for="{{ $k }}"><small>{{ $input['label'] }}*</small></label>
|
||||
<input id="{{ $k }}" type="number" step="0.01" class="form-control" name="data[{{$k}}][value]" placeholder="{{ $input['label'] }}" value="{{ old("data.$k.value") ?? $input['value'] ?? '' }}" required>
|
||||
</div>
|
||||
|
||||
@case(App\Enums\InputType::number()->value)
|
||||
<div class="form-group">
|
||||
<label for="{{$k}}"><b>{{ $input['label'] }}*</b></label>
|
||||
<input id="{{$k}}" type="number" step="0.01" class="form-control"
|
||||
name="data[{{ $k }}][value]" placeholder="{{ $input['label'] }}"
|
||||
value="{{ old("data.$k.value") ?? ($input['value'] ?? '') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'data[{{$k}}][value]',
|
||||
'helperId' => '{{$k}}NumberHelp',
|
||||
'helper' => $input['helper'],
|
||||
])
|
||||
</div>
|
||||
@break
|
||||
@case(InputType::text()->value)
|
||||
<div class="form-group vueComponent">
|
||||
<label class="col-form-label">{{ $input['label'] }}*</label>
|
||||
<custom-ckeditor name="data[{{$k}}][text]" dbtext="{{ old("data.$k.text") ?? $input['text'] ?? '' }}"></custom-ckeditor>
|
||||
</div>
|
||||
|
||||
@case(App\Enums\InputType::text()->value)
|
||||
<div class="form-group vueComponent">
|
||||
<label class="col-form-label"><b>{{ $input['label'] }}*</b></label>
|
||||
@php
|
||||
$editor = [
|
||||
'csrf' => csrf_token(),
|
||||
'name' => "data[$k][text]",
|
||||
'text' => old("data.$k.text") ?? ($input['text'] ?? ''),
|
||||
];
|
||||
@endphp
|
||||
<div class="ck-editor-bo" data-json='@json($editor)'></div>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'data[{{$k}}][text]',
|
||||
'helperId' => '{{$k}}TextHelp',
|
||||
'helper' => $input['helper'],
|
||||
])
|
||||
</div>
|
||||
@break
|
||||
@case(InputType::boolean()->value)
|
||||
<div class="form-group">
|
||||
<label class="user-select-none">
|
||||
<input type="checkbox" name="data[{{$k}}][value]" value="1" @if (( old("data.$k.value") ?? $input['value'] ?? 0 ))checked @endif>
|
||||
{{ $input['label'] }}*
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@case(App\Enums\InputType::boolean()->value)
|
||||
<div class="form-group">
|
||||
<label class="user-select-none">
|
||||
<input type="checkbox" name="data[{{$k}}][value]" value="1"
|
||||
@if (old("data.$k.value") ?? ($input['value'] ?? 0)) checked @endif>
|
||||
{{ $input['label'] }}*
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'data[{{$k}}][value]',
|
||||
'helperId' => '{{$k}}BooleanHelp',
|
||||
'helper' => $input['helper'],
|
||||
])
|
||||
</label>
|
||||
</div>
|
||||
@break
|
||||
@case(InputType::image()->value)
|
||||
<div class="form-group">
|
||||
@if(isset($input['label']))
|
||||
<h3>{{ $input['label'] }}</h3>
|
||||
@endif
|
||||
<span id="{{ $k }}imgLABEL">{{ __('Visuel * de type JPEG/PNG/SVG - 2Mo max.')}} - {{ isset($input['dimensions']) ? __('Dimensions :dimensions', ['dimensions' => $input['dimensions']]) : __('Dimensions Maximales :max', ['max' => $input['dimensionsMax']]) }} px</span>
|
||||
<div class="input-group mb-3">
|
||||
<div class="custom-file">
|
||||
<input id="{{ $k }}img" type="file" class="custom-file-input" name="data[{{$k}}][value]" accept="image/png,image/jpeg,.svg" aria-describedby="{{ $k }}imgLABEL">
|
||||
<label class="custom-file-label" for="{{ $k }}img">{{ __('Choisir un fichier') }}</label>
|
||||
|
||||
@case(App\Enums\InputType::image()->value)
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="{{$k}}alt" class="col-form-label">
|
||||
<b>{{ __('Texte alternatif de l\'image') }}</b>
|
||||
</label>
|
||||
<div class="word-counter" data-json='@json(['id' => $k.'alt'])'></div>
|
||||
<input name="data[{{$k}}][alt]" type="text"
|
||||
value="{{ old("data.$k.alt") ?? $input['alt'] ?? '' }}"
|
||||
class="form-control" id="{{$k}}alt"
|
||||
aria-describedby="{{$k}}altHelp"
|
||||
placeholder="{{ __('Texte alternatif à l\'image') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'data[{{$k}}][alt]',
|
||||
'helperId' => '{{$k}}altHelp',
|
||||
'helper' => __(
|
||||
'Doit décrire votre image pour les moteurs de recherches et les non voyants (5 à 6 mots)'
|
||||
),
|
||||
])
|
||||
</div>
|
||||
<div class="col-12 col-md-6 form-group">
|
||||
<label for="{{$k}}title" class="col-form-label">
|
||||
<b>{{ __('Titre de l\'image') }}</b>
|
||||
</label>
|
||||
<div class="word-counter" data-json='@json(['id' => $k.'title'])'></div>
|
||||
<input name="data[{{$k}}][title]" type="text"
|
||||
value="{{ old("data.$k.title") ?? $input['title'] ?? '' }}"
|
||||
class="form-control" id="{{$k}}title"
|
||||
aria-describedby="{{$k}}titleHelp"
|
||||
placeholder="{{ __('Titre de l\'image') }}" required>
|
||||
@include('back.modules.input-error', [
|
||||
'inputName' => 'data[{{$k}}][title]',
|
||||
'helperId' => '{{$k}}titleHelp',
|
||||
'helper' => __(
|
||||
'Utile pour les utilisateurs, s\'affiche en passant la souris sur l\'image'
|
||||
),
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@php
|
||||
$data = [
|
||||
'id' => $k . 'picture',
|
||||
'name' => 'data[' . $k . '][value]',
|
||||
'helper' => __('L\'image qui va servir de vignette à l\'article, doit être de ') . $input['dimensions'],
|
||||
'value' => $input['value'] ?? '',
|
||||
];
|
||||
@endphp
|
||||
@include('back.modules.input-error', ['inputName' => 'data[' . $k . '][value]'])
|
||||
<div class="image-input" data-json='@json($data)'></div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<label for="{{ $k }}alt"><small>{{ $input['label'] }} - {{ __('Texte alternatif') }}</small></label>
|
||||
<input id="{{ $k }}alt" type="text" class="form-control" name="data[{{$k}}][alt]" placeholder="{{ __('Texte alternatif à l\'image') }}" value="{{ old("data.$k.alt") ?? $input['alt'] ?? '' }}">
|
||||
<br>
|
||||
@if(isset($input['value']) and strlen($input['value']))
|
||||
<img src="{{ $input['value'] }}" alt="image" style="max-height:200px;max-width:100%;object-fit:contain;margin:0 auto;display:block;">
|
||||
@endif
|
||||
</div>
|
||||
@break
|
||||
@case(InputType::imageArray()->value)
|
||||
@case(InputType::labelArray()->value)
|
||||
@default
|
||||
@php throw new Exception('Input type not handled'); @endphp
|
||||
@endswitch
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let minicrud = document.getElementById('minicrud').closest('form');
|
||||
if(minicrud) {
|
||||
minicrud.addEventListener('submit', function(e) {
|
||||
document.querySelectorAll('input[type="file"]').forEach(function(input) {
|
||||
if (input.dataset['old-value'] && !input.value) {
|
||||
input.setAttribute('type', 'text');
|
||||
input.value = input.getAttribute('old-value');
|
||||
@case(App\Enums\InputType::imageArray()->value)
|
||||
|
||||
@case(App\Enums\InputType::labelArray()->value)
|
||||
|
||||
@default
|
||||
@php throw new Exception('Input type not handled'); @endphp
|
||||
@endswitch
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script nonce="{{ $nonce }}">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let minicrud = document.getElementById('minicrud').closest('form');
|
||||
if (minicrud) {
|
||||
minicrud.addEventListener('submit', function(e) {
|
||||
document.querySelectorAll('input[type="file"]').forEach(function(input) {
|
||||
if (input.dataset['old-value'] && !input.value) {
|
||||
input.setAttribute('type', 'text');
|
||||
input.value = input.getAttribute('old-value');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fixAction">
|
||||
<table class="table fix-action">
|
||||
<thead>
|
||||
@php
|
||||
$cols = ['title' => __('Titre'), 'tags' => __('Tags'), 'published' => __('Publication'), 'updated_at' => __('Mis à jour le')];
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fixAction">
|
||||
<table class="table fix-action">
|
||||
<thead>
|
||||
@php
|
||||
$cols = ['title' => __('Titre'), 'description' => __('Description'), 'status' => __('Publication'), 'updated_at' => __('Mis à jour le')];
|
||||
|
|
|
|||
|
|
@ -1,123 +1,123 @@
|
|||
@extends('layouts.back')
|
||||
@extends('layouts.back')
|
||||
|
||||
@section('title', sprintf('%s - %s', __('models.classes.ranges'), Str::ucfirst(__('liste'))))
|
||||
@section('title', sprintf('%s - %s', __('models.classes.ranges'), Str::ucfirst(__('liste'))))
|
||||
|
||||
@section('breadcrumb',
|
||||
request()->route()->getName())
|
||||
@section('breadcrumb',
|
||||
request()->route()->getName())
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('back.breadcrumb.breadcrumb-body')
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h2">{{ __('models.classes.ranges') }} {{ Str::ucfirst(__('liste')) }}</h1>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="input-group justify-content-end">
|
||||
<a href="{{ route('bo.ranges.create') }}" class="btn btn-primary"
|
||||
title="{{ __('Ajouter une :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip"><i class="fa-solid fa-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('back.modules.searchBar')
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $ranges->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fixAction">
|
||||
<thead>
|
||||
@php
|
||||
$cols = ['title' => __('Titre'), 'category' => __('Categorie'), 'description' => __('Description'), 'status' => __('Publication'), 'updated_at' => __('Mis à jour le')];
|
||||
@endphp
|
||||
@include('back.modules.tableColSorter', [
|
||||
'cols' => $cols,
|
||||
'mobileHide' => ['status'],
|
||||
])
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($ranges->count())
|
||||
@foreach ($ranges as $rangeModel)
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ $rangeModel->title }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ $rangeModel->category->label }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ Str::limit(strip_tags($rangeModel->description)) }}</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<form action="{{ route('bo.ranges.change-published', $rangeModel->id) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm"
|
||||
title="{{ __(':action :model', [
|
||||
'action' => $rangeModel->status ? __('Dépublier') : __('Publier'),
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip">
|
||||
<i
|
||||
class="fas @if ($rangeModel->status) fa-check check-green @else fa-times cross-red @endif"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ $rangeModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
{{-- Public View Page --}}
|
||||
@if ($rangeModel->status)
|
||||
<a class="btn btn-sm btn-warning" href="" target="_blank"
|
||||
title="{{ __('Visualiser :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
|
||||
@endif
|
||||
<a class="btn btn-sm btn-primary"
|
||||
href="{{ route('bo.ranges.edit', $rangeModel->id) }}"
|
||||
title="{{ __('Modifier :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">
|
||||
<span
|
||||
class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.ranges')]) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $ranges->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@include('back.breadcrumb.breadcrumb-body')
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h2">{{ __('models.classes.ranges') }} {{ Str::ucfirst(__('liste')) }}</h1>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="input-group justify-content-end">
|
||||
<a href="{{ route('bo.ranges.create') }}" class="btn btn-primary"
|
||||
title="{{ __('Ajouter une :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip"><i class="fa-solid fa-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@include('back.modules.searchBar')
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $ranges->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fix-action">
|
||||
<thead>
|
||||
@php
|
||||
$cols = ['title' => __('Titre'), 'category' => __('Categorie'), 'description' => __('Description'), 'status' => __('Publication'), 'updated_at' => __('Mis à jour le')];
|
||||
@endphp
|
||||
@include('back.modules.tableColSorter', [
|
||||
'cols' => $cols,
|
||||
'mobileHide' => ['status'],
|
||||
])
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($ranges->count())
|
||||
@foreach ($ranges as $rangeModel)
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ $rangeModel->title }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ $rangeModel->category->label }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ Str::limit(strip_tags($rangeModel->description)) }}</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<form action="{{ route('bo.ranges.change-published', $rangeModel->id) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm"
|
||||
title="{{ __(':action :model', [
|
||||
'action' => $rangeModel->status ? __('Dépublier') : __('Publier'),
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip">
|
||||
<i
|
||||
class="fas @if ($rangeModel->status) fa-check check-green @else fa-times cross-red @endif"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ $rangeModel->updated_at->formatLocalized('%d/%m/%Y à %I:%M') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
{{-- Public View Page --}}
|
||||
@if ($rangeModel->status)
|
||||
<a class="btn btn-sm btn-warning" href="" target="_blank"
|
||||
title="{{ __('Visualiser :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip"><i class="fa-solid fa-eye"></i></a>
|
||||
@endif
|
||||
<a class="btn btn-sm btn-primary"
|
||||
href="{{ route('bo.ranges.edit', $rangeModel->id) }}"
|
||||
title="{{ __('Modifier :model', [
|
||||
'model' => Str::singular(__('models.classes.ranges')),
|
||||
]) }}"
|
||||
data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
@else
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">
|
||||
<span
|
||||
class="h3">{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.ranges')]) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-6 p-0 p-md-3">
|
||||
{{ $ranges->withQueryString()->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fixAction">
|
||||
<table class="table fix-action">
|
||||
<thead>
|
||||
@php
|
||||
$rst = !is_null(request()->rst);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
@csrf
|
||||
@method('PUT')
|
||||
@include('back.tags.form', compact('tag'))
|
||||
<button type="submit" class="btn btn-primary" title="{{ __('Enregister :model en base', [
|
||||
<button type="submit" class="btn btn-primary" title="{{ __('Enregister :model en base', [
|
||||
'model' => Str::singular(__('models.classes.tags'))
|
||||
]) }}" data-bs-toggle="tooltip">{{ __('Enregister') }}</button>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="table-responsive">
|
||||
<table class="table fixAction">
|
||||
<table class="table fix-action">
|
||||
<thead>
|
||||
@php
|
||||
$cols = ['name' => __('Nom')];
|
||||
|
|
@ -56,7 +56,11 @@
|
|||
]) }}" data-bs-toggle="tooltip"><i class="fa-solid fa-pencil"></i></a>
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger"><i class="fa-solid fa-trash"></i></button>
|
||||
<button type="submit" class="btn btn-sm btn-danger"
|
||||
title="{{ __('Supprimer :model', ['model' => Str::singular(__('models.classes.tags'))]) }}"
|
||||
data-bs-toggle="tooltip">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@
|
|||
@endif
|
||||
@else
|
||||
<li class="nav-item dropdown">
|
||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
|
||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
|
||||
{{ Auth::user()->name }}
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||
<form id="logout-form" action="{{ route('bo.logout') }}" method="POST" class="text-center">
|
||||
<form id="logout-form" action="{{ route('bo.logout') }}" method="POST" class="text-center">
|
||||
@csrf
|
||||
<button class="btn btn-link p-0 text-decoration-none text-muted text-capitalize" type="submit">{{ __('Déconnexion') }}</button>
|
||||
<button class="btn btn-link p-0 text-decoration-none text-muted text-capitalize" title="{{ __('Se déconnecter') }}" data-bs-toggle="tooltip"type="submit">{{ __('Déconnexion') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\Bo\EngagementsController;
|
||||
use App\Http\Controllers\Bo\HomeController;
|
||||
use App\Http\Controllers\Bo\NewsController;
|
||||
use App\Http\Controllers\Bo\ProductController;
|
||||
|
|
@ -56,5 +57,14 @@ Route::prefix('bo')->name('bo.')->group(function () {
|
|||
->except(['show'])
|
||||
->withoutMiddleware('htmlsanitize')
|
||||
->middleware('htmlsanitize:content');
|
||||
|
||||
// * ENGAGEMENTS
|
||||
Route::prefix('engagements')
|
||||
->name('engagements.')
|
||||
->group(function () {
|
||||
Route::get('/{engagement:slug}/edit', [EngagementsController::class, 'edit'])->name('edit');
|
||||
Route::match(['post', 'put'], '/{engagement}', [EngagementsController::class, 'update'])
|
||||
->name('update');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,26 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"es5", "es6", "dom", "dom.iterable"
|
||||
],
|
||||
"lib": ["es5", "es6", "es2021", "dom", "dom.iterable"],
|
||||
"module": "esnext",
|
||||
// "isolatedModules": false,
|
||||
// "experimentalDecorators": true,
|
||||
// "strictNullChecks": true,
|
||||
// "removeComments": true,
|
||||
// "allowSyntheticDefaultImports": true,
|
||||
|
||||
// this enables stricter inference for data properties on `this`
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": ["resources/ts/@types", "node_modules/@types"],
|
||||
"typeRoots": ["resources/ts/@types", "node_modules/@types"]
|
||||
},
|
||||
"include": [
|
||||
"resources/**/*.js",
|
||||
"resources/**/*.ts",
|
||||
"resources/**/*.vue"
|
||||
],
|
||||
"include": ["resources/**/*.js", "resources/**/*.ts", "resources/**/*.vue"],
|
||||
"exclude": ["node_modules"],
|
||||
"compileOnSave": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ const mix = require('laravel-mix')
|
|||
|
||||
require('laravel-mix-polyfill')
|
||||
require('laravel-mix-clean')
|
||||
require("laravel-mix-eslint");
|
||||
require("laravel-mix-stylelint");
|
||||
|
||||
const assetDir = 'assets'
|
||||
const polyfill = {
|
||||
|
|
@ -68,27 +70,43 @@ mix.clean({
|
|||
})
|
||||
|
||||
// * BO
|
||||
mix.ts('resources/ts/back.ts', 'public/js/back.js', { transpileOnly: true })
|
||||
mix.ts("resources/ts/back.ts", "public/js/back.js", { transpileOnly: true })
|
||||
.vue({
|
||||
runtimeOnly: true,
|
||||
extractStyles: true,
|
||||
globalStyles: false
|
||||
globalStyles: false,
|
||||
})
|
||||
.eslint({
|
||||
fix: true,
|
||||
extensions: ["js", "ts"],
|
||||
})
|
||||
.sass("resources/scss/back.scss", "public/css/back.css")
|
||||
.stylelint({
|
||||
configFile: ".stylelintrc.json",
|
||||
files: ["**/*.scss"],
|
||||
})
|
||||
.sass('resources/scss/back.scss', 'public/css/back.css')
|
||||
.polyfill(polyfill)
|
||||
.version()
|
||||
.version();
|
||||
// .sourceMaps(false)
|
||||
|
||||
// * FO
|
||||
mix.ts('resources/ts/front.ts', 'public/js/front.js', { transpileOnly: true })
|
||||
.sass('resources/scss/front.scss', 'public/css/front.css')
|
||||
mix.ts("resources/ts/front.ts", "public/js/front.js", { transpileOnly: true })
|
||||
.eslint({
|
||||
fix: true,
|
||||
extensions: ["js", "ts"],
|
||||
})
|
||||
.sass("resources/scss/front.scss", "public/css/front.css")
|
||||
.stylelint({
|
||||
configFile: ".stylelintrc.json",
|
||||
files: ["**/*.scss"],
|
||||
})
|
||||
.vue({
|
||||
runtimeOnly: true,
|
||||
extractStyles: true,
|
||||
globalStyles: false
|
||||
globalStyles: false,
|
||||
})
|
||||
.polyfill(polyfill)
|
||||
.version()
|
||||
.version();
|
||||
// .sourceMaps(false)
|
||||
|
||||
// Copy Blade Images Assets
|
||||
|
|
|
|||