diff --git a/.env.example b/.env.example index 7dc51e1..8f09ece 100644 --- a/.env.example +++ b/.env.example @@ -32,6 +32,7 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" +MAIL_LIST_SERVICEWEB=informatique.internet@ldc.fr AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= diff --git a/.gitignore b/.gitignore index 16d7679..10c360c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ /vendor .env .env.backup +.env.dev +.env.prod .phpunit.result.cache Homestead.json Homestead.yaml @@ -15,5 +17,3 @@ package-lock.json composer.lock .idea/ .vscode/ -/public/css -/public/js diff --git a/IDEAS.md b/IDEAS.md new file mode 100644 index 0000000..a0cddf1 --- /dev/null +++ b/IDEAS.md @@ -0,0 +1,6 @@ +- Permettre à un user de suivre un projet +- ~~permettre d'insérer des @user dans le texte avec lien vers user~~ + ~~pas de liens vers user mais mise en form~~ +- ~~parse de @user + envoie de mail si un seul user est trouvé~~ +- permettre de modifier son mot de passe ? (je crois que ça n'as pas été mis exprès) +- ~~parser les liens http~~ diff --git a/app/Console/Commands/CreateUser.php b/app/Console/Commands/CreateUser.php index 404d0c9..16c73ea 100644 --- a/app/Console/Commands/CreateUser.php +++ b/app/Console/Commands/CreateUser.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Enums\Role; use App\Models\Project; use App\Models\User; use Illuminate\Console\Command; @@ -41,34 +42,27 @@ class CreateUser extends Command */ public function handle() { - $admin = false; - $name = $this->ask('What is the name?'); $email = $this->ask('What is the email?'); $pwd = $this->secret('What is the password?'); + $user = new User(); + $user->name = $name; + $user->email = $email; if ($this->confirm('Is the user an admin?')) { - $admin = true; + $user->role = Role::admin(); } else { - $project = null; + $user->role = Role::client(); try { $project = Project::where( 'id', $this->ask("What's the id of the project (s)he's working on?") )->firstOrFail(); + $user->projects()->sync([$project->id]); } catch (ModelNotFoundException $e) { $this->error('The project does not exists'); return -1; } } - - $user = new User(); - $user->name = $name; - $user->email = $email; - if ($admin) { - $user->role = 1; - } else { - $user->project_id = $project->id; - } $user->password = Hash::make($pwd); $user->save(); diff --git a/app/Enums/CrudAction.php b/app/Enums/CrudAction.php new file mode 100644 index 0000000..a059ded --- /dev/null +++ b/app/Enums/CrudAction.php @@ -0,0 +1,49 @@ + 0, + 'view' => 1, + 'create' => 2, + 'update' => 3, + 'delete' => 4, + ]; + } + + protected static function labels(): array + { + return [ + 'viewAny' => trans('Liste'), + 'view' => trans('Voir'), + 'create' => trans('Créer'), + 'update' => trans('Éditer'), + 'delete' => trans('Supprimer'), + ]; + } +} diff --git a/app/Enums/CrudType.php b/app/Enums/CrudType.php new file mode 100644 index 0000000..b5f8301 --- /dev/null +++ b/app/Enums/CrudType.php @@ -0,0 +1,56 @@ + 0, + 'int' => 1, + 'string' => 2, + 'text' => 3, + 'json' => 4, + 'email' => 5, + 'password' => 6, + 'float' => 7, + 'enum' => 8, + 'belongsTo' => 9, + 'belongsToMany' => 10 + ]; + } + + protected static function labels(): array + { + return [ + 'boolean' => trans('Booléen'), + 'int' => trans('Nombre'), + 'string' => trans('Chaîne de charactères simple'), + 'text' => trans('Texte'), + 'json' => trans('Json'), + 'email' => trans('Chaîne de charactères email'), + 'password' => trans('Mot de passe'), + 'float' => trans('Nombre à virgule flotante'), + 'enum' => trans('Énumération'), + 'belongsTo' => trans('Clé étrangère'), + 'belongsToMany' => trans('Relation Belongs To Many') + ]; + } +} diff --git a/app/Enums/Role.php b/app/Enums/Role.php new file mode 100644 index 0000000..4bd4d9b --- /dev/null +++ b/app/Enums/Role.php @@ -0,0 +1,29 @@ + 1, + 'client' => 2 + ]; + } + + protected static function labels(): array + { + return [ + 'admin' => trans('Administrateur'), + 'client' => trans('Client') + ]; + } +} diff --git a/app/Enums/TicketStatus.php b/app/Enums/TicketStatus.php index 2a1077d..b0fe989 100644 --- a/app/Enums/TicketStatus.php +++ b/app/Enums/TicketStatus.php @@ -6,10 +6,16 @@ use Kwaadpepper\Enum\BaseEnum; /** * @method static self new() + * @method static self estimateTodo() + * @method static self estimateValidated() + * @method static self toPlan() + * @method static self planned() * @method static self inprogress() + * @method static self waiting() * @method static self processed() - * @method static self archived() + * @method static self recette() * @method static self production() + * @method static self archived() */ class TicketStatus extends BaseEnum { @@ -17,10 +23,16 @@ class TicketStatus extends BaseEnum { return [ 'new' => 1, - 'inprogress' => 2, - 'processed' => 3, - 'archived' => 4, - 'production' => 5 + 'estimateTodo' => 2, + 'estimateValidated' => 3, + 'toPlan' => 4, + 'planned' => 5, + 'inprogress' => 7, + 'waiting' => 9, + 'processed' => 10, + 'recette' => 12, + 'production' => 13, + 'archived' => 15, ]; } @@ -28,10 +40,16 @@ class TicketStatus extends BaseEnum { return [ 'new' => trans('Nouveau'), + 'estimateTodo' => trans('Devis à faire'), + 'estimateValidated' => trans('Devis validé'), + 'toPlan' => trans('À planifier'), + 'planned' => trans('Planifié'), 'inprogress' => trans('En cours'), + 'waiting' => trans('En attente'), 'processed' => trans('Traité'), - 'archived' => trans('Archivé'), + 'recette' => trans('En recettage'), 'production' => trans('En production'), + 'archived' => trans('Archivé'), ]; } } diff --git a/app/Exceptions/CrudException.php b/app/Exceptions/CrudException.php new file mode 100644 index 0000000..cdf73a9 --- /dev/null +++ b/app/Exceptions/CrudException.php @@ -0,0 +1,9 @@ +url)); + } + try { + if (config('app.debug') === false) { + if ($this->shouldntReport($e)) { + return; + } + Mail::send([ + 'html' => 'emails.exception', + 'text' => 'emails.exception-plain' + ], [ + 'exception' => $e, + 'appName' => config('app.name') + ], function ($m) { + $m->to( + config('mail.contactsList.serviceweb'), + sprintf('%s Exception Handler', config('app.name')) + )->subject('Need help !, an error occured in your application, please check Logs ASAP'); + }); + } + // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } catch (RuntimeException $e) { + // do nothing, mail send error, let laravel only handle this + } finally { + parent::report($e); + } + } + /** * Register the exception handling callbacks for the application. * diff --git a/app/Http/Controllers/CrudController.php b/app/Http/Controllers/CrudController.php new file mode 100644 index 0000000..238af0e --- /dev/null +++ b/app/Http/Controllers/CrudController.php @@ -0,0 +1,241 @@ +authorizeResource(static::$modelClass); + + $modelClass = static::$modelClass; + if (is_null($modelClass) or !class_exists($modelClass)) { + throw new CrudException(sprintf( + '%s needs the modelClass prop to be a class, %s %s given', + __CLASS__, + gettype($modelClass), + $modelClass + )); + } + if (!is_subclass_of($modelClass, Model::class)) { + throw new CrudException(sprintf( + '%s needs the model prop to be a class the extends %s', + __CLASS__, + Model::class + )); + } + if (!request()->ajax() and !app()->runningInConsole()) { + self::shareCrudModelsClassesToView(); + $this->shareModelClassToView(); + $this->shareModelTableToView(); + $this->shareModelPropsToView(); + } + } + + public function index(CrudIndexRequest $request) + { + /** @var \Illuminate\Database\Eloquent\Builder */ + $models = static::$modelClass::query(); + + // Search on crud Label column + if ($request->search) { + $model = static::$modelClass; + $col = (new $model())->crudLabelColumn; + $models = $models->where($col, 'LIKE', "%$request->search%"); + } + + // Sort columns + $tableName = (new static::$modelClass())->getTable(); + if ($request->rst) { + Session::remove("crud.$tableName.sort_col"); + Session::remove("crud.$tableName.sort_way"); + } + if ($request->sort_col) { + Session::put("crud.$tableName.sort_col", $request->sort_col); + Session::save(); + } + if ($request->sort_way) { + Session::put("crud.$tableName.sort_way", $request->sort_way); + Session::save(); + } + + if ( + Session::has("crud.$tableName.sort_col") and + Session::has("crud.$tableName.sort_way") and + Schema::hasColumn($tableName, Session::get("crud.$tableName.sort_col")) + ) { + $models = $models->orderBy( + Session::get("crud.$tableName.sort_col"), + Session::get("crud.$tableName.sort_way") + ); + } + + $models = $this->indexQuery($models); + /** @var \Illuminate\Pagination\AbstractPaginator */ + $models = $models->paginate(config('crud.paginate', 15)); + if ($request->ajax()) { + return response()->json(CrudResource::collection($models)); + } else { + $models = $models->withQueryString(); + return view('crud.index', compact('models')); + } + } + + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + public function show(Model $model) + { + return view('crud.show', compact('model')); + } + + public function create() + { + return view('crud.create'); + } + + public function store(StoreCrudModel $request) + { + $modelClass = static::$modelClass; + $model = new $modelClass(); + $model->fill($request->validated()); + $this->storeModel($model); + $model->saveOrFail(); + $model->saveRelations($request); + $this->storedModel($model); + return \redirect()->route(sprintf('%s.edit', $model->getTable()), $model) + ->with('success', trans(':model à bien été enregisté.', ['model' => $model->getModelName()])); + } + + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + public function edit(Model $model) + { + return view('crud.edit', compact('model')); + } + + public function update(UpdateCrudModel $request, Model $model) + { + $model->fill($request->validated()); + $this->updateModel($model); + $model->saveOrFail(); + $model->saveRelations($request); + $this->updatedModel($model); + return \redirect()->route(sprintf('%s.edit', $model->getTable()), $model) + ->with('success', trans(':model à bien été enregisté.', ['model' => $model->getModelName()])); + } + + public function destroy(Model $model) + { + if ($restrict = $model->gotHasManyRelationWithRestrictOnDelete()) { + return redirect()->back() + ->with('error', trans( + ':model n\'as pas pu être supprimé car il est encore associé avec des :associated', + [ + 'model' => $model->getModelName(), + 'associated' => Str::plural($restrict) + ] + )); + } + if ($model->delete()) { + return \redirect()->route(sprintf('%s.index', $model->getTable())) + ->with('warning', trans(':model à bien été supprimé.', ['model' => $model->getModelName()])); + } else { + return redirect()->back() + ->with('error', trans(':model n\'as pas pu être supprimé', ['model' => $model->getModelName()])); + } + } + + public function getModelClass() + { + return static::$modelClass; + } + + /** + * Get the current model class + * + * @return void + */ + public static function shareCrudModelsClassesToView(): void + { + $models = []; + foreach (File::allFiles(\app_path('Http/Controllers')) as $file) { + $class = sprintf('App\Http\Controllers\%s', \substr($file->getFilename(), 0, -4)); + if (is_subclass_of($class, CrudController::class) and $class::$modelClass) { + $models[] = $class::$modelClass; + } + } + View::share('modelsClasses', $models); + } + + /** + * Get the current model class + * + * @return void + */ + private function shareModelClassToView(): void + { + View::share('modelClass', static::$modelClass); + } + + /** + * Get the current model table + * + * @return void + */ + private function shareModelTableToView(): void + { + /** @var \Illuminate\Database\Eloquent\Model $model */ + $model = static::$modelClass; + + View::share('modelTable', (new $model())->getTable()); + } + + /** + * Get the current model properties + * + * @return void + */ + private function shareModelPropsToView(): void + { + $model = static::$modelClass; + View::share('props', (new $model())->getEditableProperties()); + } +} diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index ef0910d..ab43dd2 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -2,59 +2,11 @@ namespace App\Http\Controllers; -use App\Http\Requests\StoreProject; -use App\Http\Requests\UpdateProject; use App\Models\Project; -use App\Models\Ticket; -class ProjectController extends Controller +class ProjectController extends CrudController { - public function __construct() - { - $this->authorizeResource(Project::class, 'project'); - } - public function index() - { - $projects = Project::all(); - return view('project.index', compact('projects')); - } - - public function create() - { - return view('project.create'); - } - - public function store(StoreProject $request) - { - $project = new Project($request->validated()); - $project->saveOrFail(); - - return redirect()->route('project.index'); - } - - public function edit(Project $project) - { - return view('project.edit', [ - 'project' => $project - ]); - } - - public function update(UpdateProject $request, Project $project) - { - $project->fill($request->validated()); - $project->saveOrFail(); - - return redirect()->back()->with('success', 'Modifications enregistrées !'); - } - - public function destroy(Project $project) - { - if (Ticket::where('project_id', $project->id)->count()) { - return redirect()->back() - ->withErrors("Ce projet ne peut être supprimer, des tickets lui sont encore liés."); - } - $project->delete(); - return redirect()->route('project.index'); - } + /** @var Project */ + protected static $modelClass = Project::class; } diff --git a/app/Http/Controllers/TicketController.php b/app/Http/Controllers/TicketController.php index ce362c4..6d5dfb2 100644 --- a/app/Http/Controllers/TicketController.php +++ b/app/Http/Controllers/TicketController.php @@ -2,24 +2,30 @@ namespace App\Http\Controllers; +use App\Enums\Role; use App\Enums\TicketStatus; use App\Enums\TicketType; use App\Events\NotifySubscriptions; -use App\Events\TicketStatusChanged; use App\Http\Requests\ChangeTicketStatus; use App\Http\Requests\CreateMessage; use App\Http\Requests\SortTickets; use App\Http\Requests\StoreTicket; use App\Http\Requests\UpdateTicket; +use App\Jobs\SendTicketMentionMail; use App\Mail\NewTicket; +use App\Mail\TicketMention; use App\Models\Message; use App\Models\Project; use App\Models\Ticket; use App\Models\User; use Exception; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Session; use Illuminate\Support\Str; class TicketController extends Controller @@ -31,40 +37,99 @@ class TicketController extends Controller public function index(SortTickets $request) { - $tickets = Ticket::query(); - - if ($request->status) { - $tickets = $tickets->where('status', TicketStatus::archived()); - } else { - $tickets = $tickets->where('status', '<>', TicketStatus::archived()); + /** @var \App\Models\User */ + $user = auth()->user(); + $tickets = Ticket::with('project'); + if (!$user->role->equals(Role::admin())) { + $tickets = $tickets->whereHas('project', function ($q) use ($user) { + $q->whereHas('users', function ($q) use ($user) { + $q->whereId($user->id); + }); + }); } - - if ($request->sort_col and $request->sort_way) { - switch ($request->sort_col) { - case 'id': - $tickets = $tickets->orderBy('id', $request->sort_way); - break; - case 'project': - $tickets = $tickets->orderBy('project_id', $request->sort_way); - break; - case 'created_at': - $tickets = $tickets->orderBy('created_at', $request->sort_way); - break; - case 'created_by': - $tickets = $tickets->orderBy('created_by', $request->sort_way); - break; - case 'type': - $tickets = $tickets->orderBy('type', $request->sort_way); - break; - case 'status': - $tickets = $tickets->orderBy('status', $request->sort_way); - break; - } + $tickets = $tickets->where('status', $request->status ? '=' : '<>', TicketStatus::archived()); + if ($request->search) { + $tickets = $this->searchTicket($tickets, $request); } - + $tickets = $this->filterTickets($tickets, $request); $tickets = $tickets->paginate(); - return view('ticket.index', compact('tickets')); + $projects = Project::select('id', 'name')->orderBy('name')->get(); + + return view('ticket.index', compact('tickets', 'projects')); + } + + private function filterTickets(Builder $tickets, Request $request): Builder + { + if ($request->rst) { + Session::remove('tickets.pr'); + Session::remove('tickets.sort_col'); + Session::remove('tickets.sort_way'); + } + if ($request->prs) { + Session::remove('tickets.pr'); + } + if ($request->sort_col) { + Session::put('tickets.sort_col', $request->sort_col); + } + if ($request->sort_way) { + Session::put('tickets.sort_way', $request->sort_way); + } + if ($request->pr) { + Session::put('tickets.pr', $request->pr); + } + Session::save(); + + if (Session::has('tickets.pr')) { + $tickets = $tickets->where('project_id', Session::get('tickets.pr')); + } + + if ( + Session::has('tickets.sort_col') and + Session::has('tickets.sort_way') and + in_array(Session::get('tickets.sort_col'), [ + 'id', 'project', 'created_at', 'created_by', + 'type', 'status' + ]) + ) { + $tickets = $tickets->orderBy( + Session::get('tickets.sort_col'), + Session::get('tickets.sort_way') + ); + } + return $tickets; + } + + private function searchTicket(Builder $tickets, Request $request): Builder + { + $words = explode(' ', $request->search); + return $tickets->where(function ($q) use ($words) { + foreach ($words as $word) { + if (!strlen($word)) { + continue; + } + $q = $q->where(function ($q) use ($word) { + $s = collect(TicketStatus::toArray())->filter(function (TicketStatus $t) use ($word) { + return strpos(strtolower($t->label), $word) !== false; + })->map(function (TicketStatus $ticket) { + return $ticket->value; + }); + $t = collect(TicketType::toArray())->filter(function (TicketType $t) use ($word) { + return strpos(strtolower($t->label), $word) !== false; + })->map(function (TicketType $ticket) { + return $ticket->value; + }); + $q->where('text', 'LIKE', "%$word%") + ->orWhereIn('status', $s) + ->orWhereIn('type', $t) + ->orWhere('file', 'LIKE', "%$word%") + ->orWhere('file2', 'LIKE', "%$word%") + ->orWhereHas('user', function ($q) use ($word) { + $q->where('name', 'LIKE', "%$word%"); + }); + }); + } + }); } public function show(Ticket $ticket) @@ -76,7 +141,13 @@ class TicketController extends Controller public function create() { - $projects = Project::all(); + $user = Auth::user(); + if ($user->role === 1) { + $projects = Project::orderBy('name')->get(); + } else { + $projects = $user->projects; + } + if (!count($projects)) { return redirect()->route('project.create') ->with('warning', 'Vous devez tout d\'abord créer un projet pour y ajouter un ticket'); @@ -97,8 +168,15 @@ class TicketController extends Controller $ticket->file2 = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket2); } + if ($request->type === TicketType::evolution()) { + $ticket->status = TicketStatus::estimateTodo(); + } + $ticket->user()->associate(auth()->user()); - $ticket->save(); + $ticket->saveOrFail(); + /** @var \App\Models\User */ + $user = auth()->user(); + $user->subscriptions()->toggle($ticket->id); if (config('mail.MAIL_ADMIN')) { Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket)); @@ -106,7 +184,7 @@ class TicketController extends Controller throw new Exception('Missing MAIL_ADMIN in .env'); } - return redirect()->route('home')->with('success', 'Ticket enregistré !'); + return redirect()->route('ticket.show', $ticket)->with('success', 'Ticket enregistré !'); } // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass @@ -154,7 +232,18 @@ class TicketController extends Controller $message = new Message($request->validated()); $message->user()->associate(auth()->user()); $message->ticket()->associate($ticket); - $message->save(); + $message->saveOrFail(); + + /** @var \App\Models\User */ + $user = auth()->user(); + + if ($request->subscribe) { + if (!$user->subscriptions()->find($ticket->id)) { + $user->subscriptions()->attach($ticket->id); + } + } elseif ($user->subscriptions()->find($ticket->id)) { + $user->subscriptions()->detach($ticket->id); + } if ($request->file_message) { $projectSlug = Str::slug($ticket->project->name); @@ -174,6 +263,25 @@ class TicketController extends Controller ]) )); + // find @user + send mail si un seul user est trouvé + $matches = []; + \preg_match_all('/@\w*/', $message->message, $matches); + $matches = collect($matches[0] ?? [])->unique(); + foreach ($matches as $pseudo_usertag) { + try { + $user = User::where('name', 'LIKE', '%' . ltrim($pseudo_usertag, '@') . '%') + ->whereDoesntHave('subscriptions', function ($q) use ($ticket) { + $q->where('id', $ticket->id); + })->firstOrFail(); + SendTicketMentionMail::dispatchAfterResponse( + $user, + new TicketMention($ticket, $message->message) + ); + } catch (ModelNotFoundException $e) { + continue; + } + } + return back(); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..8e43ef0 --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,12 @@ + [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, diff --git a/app/Http/Middleware/IsAdmin.php b/app/Http/Middleware/IsAdmin.php index 5203e95..0e9a7de 100644 --- a/app/Http/Middleware/IsAdmin.php +++ b/app/Http/Middleware/IsAdmin.php @@ -2,6 +2,7 @@ namespace App\Http\Middleware; +use App\Enums\Role; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -17,8 +18,9 @@ class IsAdmin */ public function handle(Request $request, Closure $next) { - if (Auth::user()->role === 1) + if (Auth::user()->role->equals(Role::admin())) { return $next($request); + } return redirect()->route('home'); } } diff --git a/app/Http/Requests/ChangeTicketStatus.php b/app/Http/Requests/ChangeTicketStatus.php index 4d681d0..8386c9d 100644 --- a/app/Http/Requests/ChangeTicketStatus.php +++ b/app/Http/Requests/ChangeTicketStatus.php @@ -2,11 +2,23 @@ namespace App\Http\Requests; +use App\Enums\Role; use App\Enums\TicketStatus; use Illuminate\Foundation\Http\FormRequest; class ChangeTicketStatus extends FormRequest { + /** + * Determine if the user is authorized to make this request. + * + * @return bool + */ + public function authorize() + { + return auth()->check() and + auth()->user()->role->equals(Role::admin()); + } + /** * Get the validation rules that apply to the request. * diff --git a/app/Http/Requests/CreateMessage.php b/app/Http/Requests/CreateMessage.php index 00a6903..cd9f9cd 100644 --- a/app/Http/Requests/CreateMessage.php +++ b/app/Http/Requests/CreateMessage.php @@ -2,10 +2,25 @@ namespace App\Http\Requests; +use App\Enums\Role; use Illuminate\Foundation\Http\FormRequest; class CreateMessage extends FormRequest { + /** + * Determine if the user is authorized to make this request. + * + * @return bool + */ + public function authorize() + { + $this->merge([ + 'subscribe' => $this->subscribe ? true : false + ]); + return auth()->check() and (auth()->user()->role->equals(Role::admin()) or + auth()->user()->projects->contains(request()->route()->ticket->project_id)); + } + /** * Get the validation rules that apply to the request. * @@ -14,7 +29,8 @@ class CreateMessage extends FormRequest public function rules() { return [ - 'message' => 'required|min:20', + 'subscribe' => 'required|boolean', + 'message' => 'required|min:' . (auth()->user()->role->equals(Role::admin()) ? '5' : '20'), 'file_message' => 'sometimes|mimes:jpeg,jpg,png,pdf|max:2048' ]; } diff --git a/app/Http/Requests/CrudIndexRequest.php b/app/Http/Requests/CrudIndexRequest.php new file mode 100644 index 0000000..c745aa3 --- /dev/null +++ b/app/Http/Requests/CrudIndexRequest.php @@ -0,0 +1,30 @@ +check(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'search' => 'nullable|string' + ]; + } +} diff --git a/app/Http/Requests/CrudRequest.php b/app/Http/Requests/CrudRequest.php new file mode 100644 index 0000000..f2d06f8 --- /dev/null +++ b/app/Http/Requests/CrudRequest.php @@ -0,0 +1,79 @@ +getCurrentModel()::getEditableProperties(); + $propsRules = []; + foreach ($this->getCurrentModel()::getRules() as $k => $rules) { + $propsRules[$k] = []; + $propIsAllowedOnUpdateAction = in_array($action, $props[$k]['actions']); + foreach ($rules as $rule) { + // dont copy required if prop is not allowed on update + if (!$propIsAllowedOnUpdateAction and is_string($rule) and $rule == 'required') { + continue; + } + if (!in_array($rule, $propsRules[$k])) { + $propsRules[$k][] = $rule; + } + // handle password + if ( + $props[$k]['type']->equals(CrudType::password()) and + !$this->{$k} or (is_string($this->{$k}) and !\strlen($this->{$k})) + ) { + $this->offsetUnset($k); + } + // handle boolean + if ($props[$k]['type']->equals(CrudType::boolean())) { + $this->merge([$k => $this->{$k} ? 1 : 0]); + } + // handle belongsToMany + if ($props[$k]['type']->equals(CrudType::belongsToMany())) { + $propsRules["$k.*"][0] = 'exists:' . + (new $props[$k]['belongsToMany']())->getTable() . ',id'; + } + } + } + return $propsRules; + } + /** + * Returns the first parameter that is a crud model + * + * @return string \Illuminate\Database\Eloquent\Model class name + * @throws \App\Exceptions\CrudException if the request does not have a crud model parameter + */ + protected function getCurrentModel(): string + { + if ( + !is_object($this->route()->controller) or + !$this->route()->controller instanceof CrudController + ) { + throw new CrudException(sprintf( + 'Request `%s` needs to be bound on route that use a CrudController', + get_class($this) + )); + } + return $this->route()->controller->getModelClass(); + } +} diff --git a/app/Http/Requests/SortTickets.php b/app/Http/Requests/SortTickets.php index 3112e19..d60352f 100644 --- a/app/Http/Requests/SortTickets.php +++ b/app/Http/Requests/SortTickets.php @@ -25,6 +25,7 @@ class SortTickets extends FormRequest public function rules() { return [ + 'search' => 'nullable|string', 'sort_way' => 'sometimes|in:asc,desc', 'status' => [ 'sometimes', @@ -37,6 +38,7 @@ class SortTickets extends FormRequest } } ], + 'pr' => 'sometimes|int|exists:projects,id' ]; } } diff --git a/app/Http/Requests/StoreCrudModel.php b/app/Http/Requests/StoreCrudModel.php new file mode 100644 index 0000000..fe8857e --- /dev/null +++ b/app/Http/Requests/StoreCrudModel.php @@ -0,0 +1,35 @@ +getCurrentModel(); + /** @var \App\Models\User|null $user */ + $user = auth()->user(); + $authorized = auth()->check() and $user->can('create', $model); + if ($authorized) { + $this->rules = $this->getRulesForAction(CrudAction::create()); + } + return $authorized; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return $this->rules; + } +} diff --git a/app/Http/Requests/StoreProject.php b/app/Http/Requests/StoreProject.php index b607417..162b683 100644 --- a/app/Http/Requests/StoreProject.php +++ b/app/Http/Requests/StoreProject.php @@ -2,6 +2,7 @@ namespace App\Http\Requests; +use App\Enums\Role; use Illuminate\Foundation\Http\FormRequest; class StoreProject extends FormRequest @@ -13,7 +14,8 @@ class StoreProject extends FormRequest */ public function authorize() { - return auth()->check(); + return auth()->check() and + auth()->user()->role->equals(Role::admin()); } /** diff --git a/app/Http/Requests/StoreTicket.php b/app/Http/Requests/StoreTicket.php index 78370a9..4f73e40 100644 --- a/app/Http/Requests/StoreTicket.php +++ b/app/Http/Requests/StoreTicket.php @@ -15,12 +15,6 @@ class StoreTicket extends FormRequest */ public function authorize() { - if (auth()->user()->project_id) { - $this->merge([ - 'project_id' => auth()->user()->project_id, - 'status' => TicketStatus::new()->value, - ]); - } return auth()->check(); } diff --git a/app/Http/Requests/UpdateCrudModel.php b/app/Http/Requests/UpdateCrudModel.php new file mode 100644 index 0000000..91edc6a --- /dev/null +++ b/app/Http/Requests/UpdateCrudModel.php @@ -0,0 +1,35 @@ +getCurrentModel(); + /** @var \App\Models\User|null $user */ + $user = auth()->user(); + $authorized = auth()->check() and $user->can('update', $model); + if ($authorized) { + $this->rules = $this->getRulesForAction(CrudAction::update()); + } + return $authorized; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return $this->rules; + } +} diff --git a/app/Http/Requests/UpdateProject.php b/app/Http/Requests/UpdateProject.php index 307790d..2cc5353 100644 --- a/app/Http/Requests/UpdateProject.php +++ b/app/Http/Requests/UpdateProject.php @@ -2,7 +2,6 @@ namespace App\Http\Requests; - class UpdateProject extends StoreProject { } diff --git a/app/Http/Requests/UpdateTicket.php b/app/Http/Requests/UpdateTicket.php index 167da51..203416a 100644 --- a/app/Http/Requests/UpdateTicket.php +++ b/app/Http/Requests/UpdateTicket.php @@ -2,20 +2,13 @@ namespace App\Http\Requests; +use App\Enums\Role; use App\Enums\TicketStatus; class UpdateTicket extends StoreTicket { public function authorize() { - if (auth()->user()->project_id) { - $this->merge([ - 'project_id' => auth()->user()->project_id, - 'status' => TicketStatus::new()->value, - ]); - } - return auth()->check() or - auth()->user()->role === 1 or - request()->route()->ticket->created_by === auth()->user()->id; + return auth()->check() and (auth()->user()->role->equals(Role::admin())); } } diff --git a/app/Http/Resources/CrudResource.php b/app/Http/Resources/CrudResource.php new file mode 100644 index 0000000..dd2853b --- /dev/null +++ b/app/Http/Resources/CrudResource.php @@ -0,0 +1,22 @@ + $this->crudLabel, + 'value' => $this->crudValue + ]; + } +} diff --git a/app/Http/Resources/UserLabeledResource.php b/app/Http/Resources/UserLabeledResource.php new file mode 100644 index 0000000..09bfed0 --- /dev/null +++ b/app/Http/Resources/UserLabeledResource.php @@ -0,0 +1,28 @@ +collection->mapWithKeys(function (User $user, $key) { + return [ + $key => [ + 'label' => sprintf('%d %s', $user->id, $user->name), + 'key' => $user->id + ] + ]; + }); + } +} diff --git a/app/Jobs/SendTicketMentionMail.php b/app/Jobs/SendTicketMentionMail.php new file mode 100644 index 0000000..3bbc35c --- /dev/null +++ b/app/Jobs/SendTicketMentionMail.php @@ -0,0 +1,45 @@ +user = $user; + $this->mail = $mail; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + Mail::to($this->user->email)->send($this->mail); + Log::debug(sprintf('Mention mail sent to %s', $this->user->email)); + } +} diff --git a/app/Lib/PolicySystem/RootPolicy.php b/app/Lib/PolicySystem/RootPolicy.php new file mode 100644 index 0000000..121d5d4 --- /dev/null +++ b/app/Lib/PolicySystem/RootPolicy.php @@ -0,0 +1,106 @@ +policy = \strtolower(\str_replace('Policy', '', \end($cls))); + } + + protected function actionPolicy(string $method, string $policy, User $user, Model $model = null) + { + $this->setPolicies($user, $model); + $ctx = [$policy, $user, $model]; + + if (!$this->hasPolicy($policy)) { + $m = __('Unhandled policy ":policy"', ['policy' => $policy]); + Log::critical($m, $ctx); + return $this->deny($m); + } + + if (!is_array($this->policies[$policy])) { + $m = __('Policy ":policy" has to be array', ['policy' => $policy]); + Log::critical($m, $ctx); + return $this->deny($m); + } + + // Policy method exists + if (!array_key_exists($method, $this->policies[$policy])) { + $m = __('Unhandled policy ":policy" for action ":action"', ['policy' => $policy, 'action' => $method]); + Log::critical($m, $ctx); + return $this->deny($m); + } + + // Call policy method + if (Sr::handle($this->policies[$policy][$method], $user, $model)) { + return $this->allow(); + } else { + $m = __('Policy ":policy" for action ":action" denied :onmodel', [ + 'policy' => $policy, + 'action' => $method, + 'onmodel' => $model ? get_class($model) : '' + ]); + if (config('app.policyDebug')) { + Log::debug(sprintf("%s user => %s model => %s", $m, $user, $model), $ctx); + } + return $this->deny($m); + } + + $m = __('Unhandled policy case on resource ":method"', ['method' => $method]); + Log::critical($m, $ctx); + return $this->deny(); + } + + /** + ** Do the magic calling Root policy from any policy + */ + public function __call($method, $args) + { + return call_user_func_array([$this, 'actionPolicy'], array_merge([$method, $this->policy], $args)); + } + + private function hasPolicy(string $policy) + { + return array_key_exists($policy, $this->policies); + } + + /** + * Allow action for the user + * + * @param string|null $message + * @return \Illuminate\Auth\Access\Response + */ + protected function allow(?string $message = null) + { + return Response::allow($message); + } + + /** + * Deny action for the user + * + * @param string|null $message + * @param mixed $code + * @return \Illuminate\Auth\Access\Response + */ + protected function deny(?string $message = null, $code = null) + { + if (!$message) { + $message = __('This action is not allowed to you for this resource.'); + } + return Response::deny($message, $code); + } +} diff --git a/app/Lib/PolicySystem/StaticRules.php b/app/Lib/PolicySystem/StaticRules.php new file mode 100644 index 0000000..034b05e --- /dev/null +++ b/app/Lib/PolicySystem/StaticRules.php @@ -0,0 +1,86 @@ +[^\s@]+@([^\s@.,]+\.)+[^\s@.,]{2,})|(?P@\w*)|(?P(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/', + function ($matches) { + $match = collect($matches)->first() ?? ''; + if (filter_var($match, FILTER_VALIDATE_EMAIL)) { + return strlen($match) ? "$match" : $match; + } elseif (isset($matches['user']) and strlen($matches['user'])) { + return sprintf( + '%s', + $matches['user'] + ); + } else { + $url = $match; + if (\strpos($url, 'http') === false and \strpos($url, 'https') === false) { + $url = sprintf('http://%s', $url); + } + $fsligthbox = self::isImageFile($url) ? 'data-fslightbox="gallery"' : ''; + return strlen($match) ? "$match" : $match; + } + }, + $text + ); + return nl2br($text); + } + + public static function isImageFile(string $url): bool + { + $m = []; + return \preg_match('/(\.png|\.jpg|\.jpeg|\.gif)$/', $url, $m) === 1; + } +} diff --git a/app/Listeners/SendNotificationMail.php b/app/Listeners/SendNotificationMail.php index 178df4a..fc9531e 100644 --- a/app/Listeners/SendNotificationMail.php +++ b/app/Listeners/SendNotificationMail.php @@ -21,7 +21,7 @@ class SendNotificationMail public function handle(NotifySubscriptions $event) { foreach ($event->ticket->subscriptions as $sub) { - if ($sub->email !== Auth::user()->email) { + if (Auth::user() and $sub->email !== Auth::user()->email) { Mail::to($sub->email)->send(new TicketUpdate($event->ticket, $event->corpsMaj)); } } diff --git a/app/Mail/NewTicket.php b/app/Mail/NewTicket.php index 00934ff..6e17361 100644 --- a/app/Mail/NewTicket.php +++ b/app/Mail/NewTicket.php @@ -31,6 +31,6 @@ class NewTicket extends Mailable public function build() { $ticket = $this->ticket; - return $this->subject("[SupportWeb] Nouveau ticket n°" . $ticket->id)->view('mails.new-ticket', compact('ticket')); + return $this->subject("[SupportWeb] Nouveau ticket n°" . $ticket->id)->view('emails.new-ticket', compact('ticket')); } } diff --git a/app/Mail/TicketMention.php b/app/Mail/TicketMention.php new file mode 100644 index 0000000..9eb0b25 --- /dev/null +++ b/app/Mail/TicketMention.php @@ -0,0 +1,45 @@ +ticket = $ticket; + $this->corpsMaj = $corpsMaj; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + $ticket = $this->ticket; + $corpsMaj = Utils::plainToHtml($this->corpsMaj); + return $this->subject("[SupportWeb] Vous avez été mentionné sur le ticket n° $ticket->id") + ->view('emails.ticket-mention', compact('ticket', 'corpsMaj')); + } +} diff --git a/app/Mail/TicketUpdate.php b/app/Mail/TicketUpdate.php index 17673de..13b89a7 100644 --- a/app/Mail/TicketUpdate.php +++ b/app/Mail/TicketUpdate.php @@ -2,6 +2,8 @@ namespace App\Mail; +use App\Lib\Utils; +use App\Models\Ticket; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; @@ -9,16 +11,21 @@ use Illuminate\Queue\SerializesModels; class TicketUpdate extends Mailable { - use Queueable, SerializesModels; + use Queueable; + use SerializesModels; - private $ticket, $corpsMaj; + /** @var Ticket */ + private $ticket; + + /** @var string */ + private $corpsMaj; /** * Create a new message instance. * * @return void */ - public function __construct($ticket, $corpsMaj) + public function __construct(Ticket $ticket, string $corpsMaj) { $this->ticket = $ticket; $this->corpsMaj = $corpsMaj; @@ -32,7 +39,7 @@ class TicketUpdate extends Mailable public function build() { $ticket = $this->ticket; - $corpsMaj = $this->corpsMaj; - return $this->subject("[SupportWeb] Le ticket n°" . $ticket->id . " à été mis à jour")->view('mails.ticket-update', compact('ticket', 'corpsMaj')); + $corpsMaj = Utils::plainToHtml($this->corpsMaj); + return $this->subject("[SupportWeb] Le ticket n°" . $ticket->id . " à été mis à jour")->view('emails.ticket-update', compact('ticket', 'corpsMaj')); } } diff --git a/app/Models/Project.php b/app/Models/Project.php index f1498f8..28a236a 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Enums\CrudAction; +use App\Enums\CrudType; +use App\Enums\Role; +use App\Traits\IsCrudModel; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\File; @@ -10,9 +14,52 @@ use Illuminate\Support\Str; class Project extends Model { use HasFactory; + use IsCrudModel; + + protected $crudLabelColumn = 'name'; + + protected $crudValueColumn = 'id'; protected $fillable = ['name']; + public function __construct($attributes = []) + { + self::$editableProperties = [ + 'name' => [ + 'label' => trans('Nom'), + 'type' => CrudType::string(), + 'validate' => [ + 'string', + 'max:255' + ], + 'required' => true, + 'nullable' => false, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update(), + ] + ], + 'users' => [ + 'label' => trans('Utilisateurs'), + 'type' => CrudType::belongsToMany(), + 'belongsToMany' => User::class, + 'belongsToManyLabel' => 'name', + 'readonly' => function () { + return auth()->user()->role->equals(Role::admin()) ? false : true; + }, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update(), + ] + ] + ]; + parent::__construct($attributes); + } + protected static function booted() { static::created(function (Project $project) { @@ -39,6 +86,11 @@ class Project extends Model }); } + public function users() + { + return $this->belongsToMany(User::class); + } + public function tickets() { return $this->hasMany(Ticket::class, 'project_id'); diff --git a/app/Models/ProjectUser.php b/app/Models/ProjectUser.php new file mode 100644 index 0000000..5d2558e --- /dev/null +++ b/app/Models/ProjectUser.php @@ -0,0 +1,11 @@ +type->equals(TicketType::evolution())) { + $ticket->status = TicketStatus::estimateTodo(); + } + }); static::updating(function (Ticket $ticket) { $oldStatus = Ticket::where('id', $ticket->id)->first()->status; if (!$ticket->status->equals($oldStatus)) { diff --git a/app/Models/User.php b/app/Models/User.php index 1bb0266..76df3d3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,14 +2,29 @@ namespace App\Models; +use App\Enums\CrudAction; +use App\Enums\CrudType; +use App\Enums\Role; +use App\Traits\IsCrudModel; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Kwaadpepper\Enum\Traits\CastsEnums; class User extends Authenticatable { use HasFactory; use Notifiable; + use CastsEnums; + use IsCrudModel; + + protected $enumCasts = [ + 'role' => Role::class + ]; + + protected $crudLabelColumn = 'name'; + + protected $crudValueColumn = 'id'; /** * The attributes that are mass assignable. @@ -41,6 +56,72 @@ class User extends Authenticatable 'email_verified_at' => 'datetime', ]; + public function __construct($attributes = []) + { + self::$editableProperties = [ + 'name' => [ + 'label' => trans('Nom'), + 'type' => CrudType::string(), + 'validate' => [ + 'string', + 'max:255' + ], + 'required' => true, + 'nullable' => false, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update(), + ] + ], + 'email' => [ + 'label' => trans('Email'), + 'type' => CrudType::email(), + 'required' => true, + 'nullable' => false, + 'validate' => [ + 'max:255' + ], + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update(), + ] + ], + 'role' => [ + 'label' => trans('Role'), + 'type' => CrudType::enum(), + 'enum' => Role::class, + 'required' => true, + 'nullable' => false, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update(), + ] + ], + 'projects' => [ + 'label' => trans('Projets'), + 'type' => CrudType::belongsToMany(), + 'belongsToMany' => Project::class, + 'belongsToManyLabel' => 'name', + 'readonly' => function () { + return auth()->user()->role->equals(Role::admin()) ? false : true; + }, + 'actions' => [ + CrudAction::viewAny(), + CrudAction::view(), + CrudAction::create(), + CrudAction::update(), + ] + ] + ]; + parent::__construct($attributes); + } + public function tickets() { return $this->hasMany(Ticket::class, 'created_by'); @@ -56,8 +137,8 @@ class User extends Authenticatable return $this->belongsToMany(Ticket::class, 'subscriptions'); } - public function project() + public function projects() { - return $this->belongsTo(Project::class); + return $this->belongsToMany(Project::class); } } diff --git a/app/Policies/AppPolicies.php b/app/Policies/AppPolicies.php new file mode 100644 index 0000000..849952d --- /dev/null +++ b/app/Policies/AppPolicies.php @@ -0,0 +1,43 @@ +policies = [ + 'project' => [ + 'viewAny' => URR::make('userIsAdmin', $user), + 'view' => URR::make('userIsAdmin', $user), + 'update' => URR::make('userIsAdmin', $user), + 'create' => URR::make('userIsAdmin', $user), + 'delete' => URR::make('userIsAdmin', $user) + ], + 'user' => [ + 'viewAny' => URR::make('userIsAdmin', $user), + 'view' => URR::make('userIsAdmin', $user), + 'update' => URR::make('userIsAdmin', $user), + 'create' => URR::make('userIsAdmin', $user), + 'delete' => URR::make('userIsAdmin', $user) + ], + ]; + } +} diff --git a/app/Policies/ProjectPolicy.php b/app/Policies/ProjectPolicy.php index 9513d25..4ef9498 100644 --- a/app/Policies/ProjectPolicy.php +++ b/app/Policies/ProjectPolicy.php @@ -2,98 +2,11 @@ namespace App\Policies; -use App\Models\Project; -use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; -class ProjectPolicy +class ProjectPolicy extends AppPolicies { use HandlesAuthorization; - /** - * Determine whether the user can view any models. - * - * @param \App\Models\User $user - * @return mixed - */ - public function viewAny(User $user) - { - return $user->role === 1; - } - - /** - * Determine whether the user can view the model. - * - * @param \App\Models\User $user - * @param \App\Models\Project $project - * @return mixed - */ - //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed - public function view(User $user, Project $project) - { - return $user->role === 1; - } - - /** - * Determine whether the user can create models. - * - * @param \App\Models\User $user - * @return mixed - */ - public function create(User $user) - { - return $user->role === 1; - } - - /** - * Determine whether the user can update the model. - * - * @param \App\Models\User $user - * @param \App\Models\Project $project - * @return mixed - */ - //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed - public function update(User $user, Project $project) - { - return $user->role === 1; - } - - /** - * Determine whether the user can delete the model. - * - * @param \App\Models\User $user - * @param \App\Models\Project $project - * @return mixed - */ - //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed - public function delete(User $user, Project $project) - { - return $user->role === 1; - } - - /** - * Determine whether the user can restore the model. - * - * @param \App\Models\User $user - * @param \App\Models\Project $project - * @return mixed - */ - //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed - public function restore(User $user, Project $project) - { - return $user->role === 1; - } - - /** - * Determine whether the user can permanently delete the model. - * - * @param \App\Models\User $user - * @param \App\Models\Project $project - * @return mixed - */ - //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed - public function forceDelete(User $user, Project $project) - { - return $user->role === 1; - } + // Handle delete has tickets } diff --git a/app/Policies/Rules/UserRoleRules.php b/app/Policies/Rules/UserRoleRules.php new file mode 100644 index 0000000..afbfd55 --- /dev/null +++ b/app/Policies/Rules/UserRoleRules.php @@ -0,0 +1,20 @@ +role->equals(Role::admin()); + } +} diff --git a/app/Policies/Rules/UserStaticRules.php b/app/Policies/Rules/UserStaticRules.php new file mode 100644 index 0000000..9258d8c --- /dev/null +++ b/app/Policies/Rules/UserStaticRules.php @@ -0,0 +1,33 @@ +id == $userToCheck->id) != 0; + } + + protected static function usersAreNotTheSame(User $user, User $userToCheck) + { + return !self::usersAreTheSame($user, $userToCheck); + } + + protected static function everyOne() + { + return true; + } + + protected static function noOne() + { + return false; + } +} diff --git a/app/Policies/TicketPolicy.php b/app/Policies/TicketPolicy.php index de00932..c6b3e90 100644 --- a/app/Policies/TicketPolicy.php +++ b/app/Policies/TicketPolicy.php @@ -2,6 +2,7 @@ namespace App\Policies; +use App\Enums\Role; use App\Enums\TicketStatus; use App\Models\Ticket; use App\Models\User; @@ -59,7 +60,7 @@ class TicketPolicy */ public function update(User $user, Ticket $ticket) { - return Auth::user()->role === 1 or $ticket->created_by == $user->id; + return Auth::user()->role->equals(Role::admin()) or $user->projects->contains($ticket->project_id); } /** @@ -84,7 +85,7 @@ class TicketPolicy */ public function restore(User $user, Ticket $ticket) { - return Auth::user()->role === 1 or $ticket->created_by == $user->id; + return Auth::user()->role->equals(Role::admin()) or $user->projects->contains($ticket->project_id); } /** @@ -102,7 +103,7 @@ class TicketPolicy public function changeStatus(User $user) { - return $user->role === 1; + return $user->role->equals(Role::admin()); } // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed @@ -114,7 +115,16 @@ class TicketPolicy // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed public function createMessage(User $user, Ticket $ticket) { - if ($user->role !== 1 and $ticket->created_by !== $user->id) { + //can create if + //role == 1 + // OR project_id IN user->projects + + if ( + !( + $user->role->equals(Role::admin()) + or $user->projects->contains($ticket->project_id) ) + ) + { return Response::deny(trans('Vous ne pouvez modifier un ticket qui ne vous appartient pas.')); } return $ticket->status->equals(TicketStatus::archived()) ? diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php new file mode 100644 index 0000000..23d56de --- /dev/null +++ b/app/Policies/UserPolicy.php @@ -0,0 +1,10 @@ +count()); + View::share('ticketsInProgress', Ticket::whereNotIn( + 'status', + [ + TicketStatus::new(), + TicketStatus::estimateTodo(), + TicketStatus::estimateValidated(), + TicketStatus::toPlan(), + TicketStatus::planned(), + TicketStatus::production(), + TicketStatus::archived() + ] + )->count()); } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 3a82fd0..074e26b 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -15,10 +15,7 @@ class AuthServiceProvider extends ServiceProvider * * @var array */ - protected $policies = [ - Project::class => ProjectPolicy::class, - Ticket::class => TicketPolicy::class - ]; + protected $policies = []; /** * Register any authentication / authorization services. diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 3bd3c81..cd8fc15 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use App\Models\Project; +use App\Models\User; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Http\Request; @@ -47,6 +49,8 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace) ->group(base_path('routes/web.php')); }); + Route::model('user', User::class); + Route::model('project', Project::class); } /** diff --git a/app/Providers/TranslationServiceProvider.php b/app/Providers/TranslationServiceProvider.php new file mode 100644 index 0000000..a91772e --- /dev/null +++ b/app/Providers/TranslationServiceProvider.php @@ -0,0 +1,61 @@ +collectLocalesStrings() as $locale) { // suported locales + Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) { + $translations = [ + 'php' => $this->phpTranslations($locale), + 'json' => $this->jsonTranslations($locale), + ]; + return $translations; + }); + } + } + + private function collectLocalesStrings() + { + return collect(File::allFiles(resource_path('lang/')))->flatMap(function ($file) { + if ($file->getRelativePath()) { + return [$file->getRelativePath() => '']; + } + })->keys(); + } + + private function phpTranslations($locale) + { + $path = resource_path("lang/$locale"); + + return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) { + $key = ($translation = $file->getBasename('.php')); + + return [$key => trans($translation, [], $locale)]; + }); + } + + private function jsonTranslations($locale) + { + $path = resource_path("lang/$locale.json"); + + if (is_string($path) && is_readable($path)) { + return json_decode(file_get_contents($path), true); + } + + return []; + } +} diff --git a/app/Traits/IsCrudModel.php b/app/Traits/IsCrudModel.php new file mode 100644 index 0000000..3dcf992 --- /dev/null +++ b/app/Traits/IsCrudModel.php @@ -0,0 +1,516 @@ + null, + 'placeholder' => '', + 'type' => null, + 'enum' => null, + 'nullable' => false, + 'readonly' => false, + 'disabled' => false, + 'required' => false, + 'validate' => [], + 'rules' => [], + 'actions' => [], + 'belongsTo' => null, + 'belongsToMany' => null, + 'getAttribute' => null, + 'setAttribute' => null + ]; + + // OVERLOAD MODEL FUNCTION + public function __get($key) + { + if ( + isset(static::$editableProperties[$key]['getAttribute']) and + is_callable(static::$editableProperties[$key]['getAttribute']) + ) { + return static::$editableProperties[$key]['getAttribute']($this); + } + return parent::__get($key); + } + + // OVERLOAD MODEL FUNCTION + public function __set($key, $value) + { + if ( + isset($this::$editableProperties[$key]['setAttribute']) and + is_callable($this::$editableProperties[$key]['setAttribute']) + ) { + return $this::$editableProperties[$key]['setAttribute']($this, $value); + } + return parent::__set($key, $value); + } + + // OVERLOAD MODEL FUNCTION + public function fill(array $attributes) + { + parent::fill($attributes); + foreach ($attributes as $k => $attribute) { + if ( + isset(static::$editableProperties[$k]['setAttribute']) and + is_callable(static::$editableProperties[$k]['setAttribute']) + ) { + $this->{$k} = $attribute; + } + } + } + + public function saveRelations(CrudRequest $request) + { + $validated = $request->validated(); + $props = self::getEditableProperties(); + foreach ($props as $k => $prop) { + if ($prop['belongsToMany'] and \is_array($validated[$k])) { + $this->{$k}()->sync($validated[$k]); + } + } + } + + /** + * Has this model Has many relations with delete restrict ? + * + * @return bool|string + */ + public function gotHasManyRelationWithRestrictOnDelete() + { + $sm = DB::getDoctrineSchemaManager(); + foreach (self::getRelationships() as $relation => $type) { + if ($type[0] === 'HasMany') { + $table = (new $type[1]())->getTable(); + /** @var \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey */ + foreach ($sm->listTableForeignKeys($table) as $foreignKey) { + // /vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php onEvent() + if ($foreignKey->onDelete() === 'CASCADE') { + // allow only CASCADE action, otherwise it would break the app + continue; + } + return class_basename($type[1]); + } + } + } + return false; + } + + /** + * Get the crud label + */ + public function getCrudLabelAttribute() + { + return $this->{$this->crudLabelColumn}; + } + + /** + * Get the crud value + */ + public function getCrudValueAttribute() + { + if (is_null($this->crudValueColumn)) { + throw new CrudException(sprintf( + '[\'belongsTo\'] model `%s` must have `crudValueColumn` property', + get_class($this), + CrudAction::class + )); + } + return $this->{$this->crudValueColumn}; + } + + /** + * Get the crud label column + */ + public function getCrudLabelColumnAttribute() + { + if (is_null($this->crudLabelColumn)) { + throw new CrudException(sprintf( + '[\'belongsTo\'] model `%s` must have `crudLabelColumn` property', + get_class($this), + CrudAction::class + )); + } + return $this->crudLabelColumn; + } + + /** + * Get the crud value column + */ + public function getCrudValueColumnAttribute() + { + return $this->crudValueColumn; + } + + /** + * Get the model validation rules + * + * @return array + */ + public static function getRules(): array + { + return collect(static::$editableProperties)->mapWithKeys(function ($item, $key) { + $item = \array_merge(self::$schemaCrud, $item); + $defaultRules = []; + if (!$item['nullable']) { + $defaultRules[] = 'required'; + } elseif ($item['required']) { + $defaultRules[] = 'required'; + } elseif ($item['readonly']) { + $defaultRules[] = 'prohibited'; + } + switch ($item['type']) { + case CrudType::boolean(): + $defaultRules[] = 'boolean'; + break; + case CrudType::email(): + $defaultRules[] = 'email:rfc,dns'; + break; + case CrudType::belongsTo(): + $defaultRules[] = 'integer'; + $defaultRules[] = 'min:0'; + break; + case CrudType::int(): + $defaultRules[] = 'integer'; + break; + case CrudType::belongsToMany(): + $defaultRules[] = 'array'; + break; + case CrudType::password(): + // phpcs:ignore Generic.Files.LineLength.TooLong + $defaultRules[] = "regex:/^(?=.*[A-Z])(?=.*[²&~\"#'\{\(\[\-|`_\\^@\)\°=+\}¨^\$£¤%?,;.:\/!§*<>])(?=.*[0-9].*)(?=.*[a-z]).{8,}$/"; + break; + case CrudType::text(): + case CrudType::string(): + default: + $defaultRules[] = 'string'; + break; + } + return [$key => array_merge($defaultRules, $item['validate'] ?? self::$schemaCrud['validate'])]; + })->all(); + } + + /** + * Get the model Name + * + * @return string + */ + public function getModelName(): string + { + return class_basename(get_class($this)); + } + + /** + * Get the Editable Properties + * + * @return array + */ + public static function getEditableProperties(): array + { + static::assertEditablePropertiesIsCorrect(); + return static::$editableProperties; + } + + /** + * Assert self::$editableProperties is in a correct format + * + * 'string' => CrudType::value() + * + * @return void + * @throws Exception + */ + private static function assertEditablePropertiesIsCorrect(): void + { + if (empty(static::$editableProperties)) { + throw new CrudException('$editableProperties must be defined in ' . __CLASS__); + } + foreach (static::$editableProperties as $name => &$prop) { + if (!is_string($name)) { + throw new CrudException(sprintf( + 'editableProperties array value must be an array given: %s', + print_r($prop, true) // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found + )); + } + if (!is_array($prop)) { + throw new CrudException(sprintf( + 'editableProperties array value must be an array given: %s', + print_r($prop, true) // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found + )); + } + $prop = array_merge_recursive_distinct(self::$schemaCrud, $prop); + + // Auto label + $prop['label'] = $prop['label'] ?? ucfirst($name); + self::assertLabelIscorrect($prop); + self::assertPlaceholderIscorrect($prop); + self::assertTypeIscorrect($prop); + switch ($prop['type']) { + // Email auto rule + case CrudType::email(): + $prop['validate'] = is_array($prop['validate']) ? $prop['validate'] : []; + if (!array_search('email:rfc,dns', $prop['validate'])) { + $prop['validate'][] = 'email:rfc,dns'; + } + break; + case CrudType::json(): + $prop['validate'] = is_array($prop['validate']) ? $prop['validate'] : []; + if (!array_search('json', $prop['validate'])) { + $prop['validate'][] = 'json'; + } + break; + } + self::assertNullableIscorrect($prop); + self::assertReadonlyIscorrect($prop); + self::assertDisabledIscorrect($prop); + self::assertValidateIscorrect($prop); + self::assertRulesArecorrect($prop); + self::assertActionsArecorrect($prop); + self::assertBelongsToIsCorrect($prop); + self::assertEnumIscorrect($prop); + self::assertGetAttributeIscorrect($prop); + self::assertSetAttributeIscorrect($prop); + self::assertBelongsToManyIsCorrect($prop); + } + } + + private static function assertLabelIscorrect(array $prop): void + { + if (!isset($prop['label']) or !is_string($prop['label'])) { + throw new CrudException('$editableProperties[\'label\'] array value must be a string'); + } + } + + private static function assertPlaceholderIscorrect(array $prop): void + { + if (!isset($prop['placeholder']) or !is_string($prop['placeholder'])) { + throw new CrudException('$editableProperties[\'placeholder\'] array value must be a string'); + } + } + + private static function assertTypeIscorrect(array $prop): void + { + if (!isset($prop['type']) or !is_object($prop['type']) or get_class($prop['type']) !== CrudType::class) { + throw new CrudException('$editableProperties[\'type\'] value must be a CrudType enum'); + } + } + + private static function assertNullableIscorrect(array $prop): void + { + if (!isset($prop['nullable']) or !is_bool($prop['nullable'])) { + throw new CrudException('$editableProperties[\'nullable\'] array value must be a boolean'); + } + } + + private static function assertReadonlyIscorrect(array $prop): void + { + if ( + !isset($prop['readonly']) or + (!is_bool($prop['readonly']) and !\is_callable($prop['readonly'])) + ) { + throw new CrudException('$editableProperties[\'readonly\'] array value must be a boolean or a function returnning a boolean'); + } + } + + private static function assertDisabledIscorrect(array $prop): void + { + if (!isset($prop['disabled']) or !is_bool($prop['disabled'])) { + throw new CrudException('$editableProperties[\'disabled\'] array value must be a boolean'); + } + } + + private static function assertRequiredIscorrect(array $prop): void + { + if (!isset($prop['required']) or !is_bool($prop['required'])) { + throw new CrudException('$editableProperties[\'required\'] array value must be a boolean'); + } + } + + private static function assertValidateIscorrect(array $prop): void + { + if (!isset($prop['validate']) or !\is_array($prop['validate'])) { + throw new CrudException('$editableProperties[\'validate\'] array value must be an array'); + } + } + + private static function assertGetAttributeIscorrect(array $prop): void + { + if (!is_null($prop['getAttribute']) and !is_callable($prop['getAttribute'])) { + throw new CrudException('$editableProperties[\'getAttribute\'] array value must be callable'); + } + } + + private static function assertSetAttributeIscorrect(array $prop): void + { + if (!is_null($prop['setAttribute']) and !is_callable($prop['setAttribute'])) { + throw new CrudException('$editableProperties[\'setAttribute\'] array value must be callable'); + } + } + + private static function assertRulesArecorrect(array $prop): void + { + $allowedRules = [ + 'min' => function ($v) { + return \is_numeric($v); + }, + 'max' => function ($v) { + return \is_numeric($v); + }, + 'step' => function ($v) { + return \is_numeric($v); + }, + ]; + if (!isset($prop['rules']) or !\is_array($prop['rules'])) { + throw new CrudException('$editableProperties[\'rules\'] array value must be an array'); + } + if (count(array_diff_key($prop['rules'], $allowedRules))) { + throw new CrudException(sprintf( + '$editableProperties[\'rules\'] array contains unallowed rule, allowed are : %s', + implode(',', array_keys($allowedRules)) + )); + } + foreach ($allowedRules as $k => $f) { + if (array_key_exists($k, $prop['rules']) and !$f($prop['rules'][$k])) { + throw new CrudException(sprintf( + '$editableProperties[\'rules\'] rule `%s` does not validate with value `%s`', + $k, + $prop['rules'][$k] + )); + } + } + } + + private static function assertActionsArecorrect(array $prop): void + { + if (!isset($prop['actions']) or !\is_array($prop['actions'])) { + throw new CrudException('$editableProperties[\'actions\'] array value must be an array'); + } + foreach ($prop['actions'] as $action) { + if (!is_object($action) or get_class($action) !== CrudAction::class) { + throw new CrudException(sprintf( + '$editableProperties[\'actions\'] array value must contain `%s` objects', + CrudAction::class + )); + } + } + } + + private static function assertEnumIscorrect(array $prop): void + { + if ( + $prop['type']->equals(CrudType::enum()) and (is_null($prop['enum']) or + !\is_subclass_of($prop['enum'], BaseEnum::class)) + ) { + throw new CrudException(sprintf( + '$editableProperties[\'enum\'] array value must be an enum `%s`', + BaseEnum::class + )); + } + } + + private static function assertBelongsToIsCorrect(array $prop): void + { + if (is_null($prop['belongsTo'])) { + return; + } + if ( + $prop['type']->equals(CrudType::belongsTo()) and + (!\is_string($prop['belongsTo']) or + !\class_exists($prop['belongsTo']) or + is_a($prop['belongsTo'], Model::class, true) or + !in_array(IsCrudModel::class, class_uses_recursive($prop['belongsTo']))) + ) { + throw new CrudException(sprintf( + '$editableProperties[\'belongsTo\'] array value must be a CrudModel given: `%s`', + $prop['belongsTo'] + )); + } + } + + private static function assertBelongsToManyIsCorrect(array $prop): void + { + if (is_null($prop['belongsToMany'])) { + return; + } + if ( + $prop['type']->equals(CrudType::belongsToMany()) and + (!\is_string($prop['belongsToMany']) or + !\class_exists($prop['belongsToMany']) or + is_a($prop['belongsToMany'], Model::class, true) or + !in_array(IsCrudModel::class, class_uses_recursive($prop['belongsToMany']))) + ) { + throw new CrudException(sprintf( + '$editableProperties[\'belongsToMany\'] array value must be a CrudModel given: `%s`', + $prop['belongsToMany'] + )); + } + if ( + $prop['type']->equals(CrudType::belongsToMany()) and + (!\is_string($prop['belongsToManyLabel'])) + ) { + throw new CrudException(sprintf( + '$editableProperties[\'belongsToManyLabel\'] must be string using belongsToMany, given: `%s`', + $prop['belongsToMany'] + )); + } + } + + /** + * Get eloquent relationships + * + * @url https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships + * + * @return array + */ + private static function getRelationships() + { + $instance = new static(); + + // Get public methods declared without parameters and non inherited + $class = get_class($instance); + $allMethods = (new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC); + $methods = array_filter( + $allMethods, + function ($method) use ($class) { + return $method->class === $class + && !$method->getParameters() // relationships have no parameters + && $method->getName() !== 'getRelationships' // prevent infinite recursion + && $method->getName() !== 'gotHasManyRelationWithRestrictOnDelete'; // prevent infinite recursion + } + ); + + DB::beginTransaction(); + + $relations = []; + foreach ($methods as $method) { + try { + $methodName = $method->getName(); + $methodReturn = $instance->$methodName(); + if (!$methodReturn instanceof Relation) { + continue; + } + } catch (\Throwable $th) { + continue; + } + + $type = (new \ReflectionClass($methodReturn))->getShortName(); + $model = get_class($methodReturn->getRelated()); + $relations[$methodName] = [$type, $model]; + } + + DB::rollBack(); + + return $relations; + } +} diff --git a/composer.json b/composer.json index 3f90a66..2e5e731 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "license": "MIT", "require": { "php": "^7.3|^8.0", + "doctrine/dbal": "^3.1", "fideloper/proxy": "^4.4", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", diff --git a/config/app.php b/config/app.php index c697c9a..bba609a5 100644 --- a/config/app.php +++ b/config/app.php @@ -174,6 +174,7 @@ return [ // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Providers\TranslationServiceProvider::class ], @@ -227,7 +228,11 @@ return [ 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, 'TicketType' => App\Enums\TicketType::class, - 'TicketStatus' => App\Enums\TicketStatus::class + 'TicketStatus' => App\Enums\TicketStatus::class, + 'Utils' => App\Lib\Utils::class, + 'CrudAction' => App\Enums\CrudAction::class, + 'CrudType' => App\Enums\CrudType::class, + 'Role' => App\Enums\Role::class, ], ]; diff --git a/config/mail.php b/config/mail.php index 6649a64..8df1fc5 100644 --- a/config/mail.php +++ b/config/mail.php @@ -91,6 +91,10 @@ return [ 'name' => env('MAIL_FROM_NAME', 'Example'), ], + 'contactsList' => [ + 'serviceweb' => env('MAIL_LIST_SERVICEWEB', 'informatique.internet@ldc.fr') + ], + /* |-------------------------------------------------------------------------- | Markdown Mail Settings diff --git a/database/migrations/2021_04_21_132132_update_tickets_status_with_enum_status.php b/database/migrations/2021_04_21_132132_update_tickets_status_with_enum_status.php new file mode 100644 index 0000000..d677530 --- /dev/null +++ b/database/migrations/2021_04_21_132132_update_tickets_status_with_enum_status.php @@ -0,0 +1,33 @@ +get() as $ticket) { + $ticket->status = TicketStatus::archived(); + $ticket->save(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status2.php b/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status2.php new file mode 100644 index 0000000..0143414 --- /dev/null +++ b/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status2.php @@ -0,0 +1,41 @@ +get() as $ticket) { + $ticket->status = TicketStatus::production(); + $ticket->save(); + } + foreach (Ticket::where('status', 4)->get() as $ticket) { + $ticket->status = TicketStatus::recette(); + $ticket->save(); + } + foreach (Ticket::where('status', 3)->get() as $ticket) { + $ticket->status = TicketStatus::processed(); + $ticket->save(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status3.php b/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status3.php new file mode 100644 index 0000000..16ce2f8 --- /dev/null +++ b/database/migrations/2021_04_21_132140_update_tickets_status_with_enum_status3.php @@ -0,0 +1,54 @@ +get() as $ticket) { + $ticket->status = TicketStatus::archived(); + $ticket->saveQuietly(); + } + foreach (Ticket::where('status', 6)->get() as $ticket) { + $ticket->status = TicketStatus::production(); + $ticket->saveQuietly(); + } + foreach (Ticket::where('status', 5)->get() as $ticket) { + $ticket->status = TicketStatus::recette(); + $ticket->saveQuietly(); + } + foreach (Ticket::where('status', 4)->get() as $ticket) { + $ticket->status = TicketStatus::processed(); + $ticket->saveQuietly(); + } + foreach (Ticket::where('status', 3)->get() as $ticket) { + $ticket->status = TicketStatus::waiting(); + $ticket->saveQuietly(); + } + foreach (Ticket::where('status', 2)->get() as $ticket) { + $ticket->status = TicketStatus::inprogress(); + $ticket->saveQuietly(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/database/migrations/2021_06_17_173912_create_project_user_table.php b/database/migrations/2021_06_17_173912_create_project_user_table.php new file mode 100644 index 0000000..88aa981 --- /dev/null +++ b/database/migrations/2021_06_17_173912_create_project_user_table.php @@ -0,0 +1,64 @@ +foreignId('project_id')->constrained('projects'); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->unique(['project_id', 'user_id']); + }); + /** @var \App\Models\User */ + foreach (User::all() as $user) { + if ($user->project_id) { + $uP = new ProjectUser(); + $uP->project_id = $user->project_id; + $uP->user_id = $user->id; + $uP->saveOrFail(); + } + } + Schema::table('users', function (Blueprint $table) { + $table->dropForeign(['project_id']); + $table->dropColumn('project_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->foreignId('project_id')->nullable() + ->after('role')->constrained('projects'); + }); + /** @var \App\Models\ProjectUser */ + foreach (ProjectUser::all() as $uP) { + try { + /** @var \App\Models\User */ + $user = User::whereId($uP->user_id)->firstOrFail(); + $user->project_id = $uP->project_id; + $user->saveOrFail(); + //phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } catch (ModelNotFoundException $e) { + // ignore + } + } + Schema::dropIfExists('project_user'); + } +} diff --git a/package.json b/package.json index 4541492..7a09662 100644 --- a/package.json +++ b/package.json @@ -11,16 +11,17 @@ }, "devDependencies": { "axios": "^0.21.1", - "bootstrap": "^4.0.0", "cross-env": "^7.0", "fslightbox": "^3.2.3", - "jquery": "^3.2", "laravel-mix": "^6.0.16", + "laravel-mix-polyfill": "^3.0.1", "lodash": "^4.17.19", - "popper.js": "^1.12", "resolve-url-loader": "^3.1.0", "sass": "^1.15.2", "sass-loader": "^8.0.0", + "vue": "^2.6.14", + "vue-loader": "^15.9.7", + "vue-select": "^3.11.2", "vue-template-compiler": "^2.6.12" } } diff --git a/public/css/app.css b/public/css/app.css new file mode 100644 index 0000000..36deacb --- /dev/null +++ b/public/css/app.css @@ -0,0 +1,4 @@ +@import url(https://fonts.googleapis.com/css?family=Nunito); +.v-select{position:relative;font-family:inherit}.v-select,.v-select *{box-sizing:border-box}@-webkit-keyframes vSelectSpinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes vSelectSpinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.vs__fade-enter-active,.vs__fade-leave-active{pointer-events:none;transition:opacity .15s cubic-bezier(1,.5,.8,1)}.vs__fade-enter,.vs__fade-leave-to{opacity:0}.vs--disabled .vs__clear,.vs--disabled .vs__dropdown-toggle,.vs--disabled .vs__open-indicator,.vs--disabled .vs__search,.vs--disabled .vs__selected{cursor:not-allowed;background-color:#f8f8f8}.v-select[dir=rtl] .vs__actions{padding:0 3px 0 6px}.v-select[dir=rtl] .vs__clear{margin-left:6px;margin-right:0}.v-select[dir=rtl] .vs__deselect{margin-left:0;margin-right:2px}.v-select[dir=rtl] .vs__dropdown-menu{text-align:right}.vs__dropdown-toggle{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:flex;padding:0 0 4px;background:none;border:1px solid rgba(60,60,60,.26);border-radius:4px;white-space:normal}.vs__selected-options{display:flex;flex-basis:100%;flex-grow:1;flex-wrap:wrap;padding:0 2px;position:relative}.vs__actions{display:flex;align-items:center;padding:4px 6px 0 3px}.vs--searchable .vs__dropdown-toggle{cursor:text}.vs--unsearchable .vs__dropdown-toggle{cursor:pointer}.vs--open .vs__dropdown-toggle{border-bottom-color:transparent;border-bottom-left-radius:0;border-bottom-right-radius:0}.vs__open-indicator{fill:rgba(60,60,60,.5);transform:scale(1);transition:transform .15s cubic-bezier(1,-.115,.975,.855);transition-timing-function:cubic-bezier(1,-.115,.975,.855)}.vs--open .vs__open-indicator{transform:rotate(180deg) scale(1)}.vs--loading .vs__open-indicator{opacity:0}.vs__clear{fill:rgba(60,60,60,.5);padding:0;border:0;background-color:transparent;cursor:pointer;margin-right:8px}.vs__dropdown-menu{display:block;box-sizing:border-box;position:absolute;top:calc(100% - 1px);left:0;z-index:1000;padding:5px 0;margin:0;width:100%;max-height:350px;min-width:160px;overflow-y:auto;box-shadow:0 3px 6px 0 rgba(0,0,0,.15);border:1px solid rgba(60,60,60,.26);border-top-style:none;border-radius:0 0 4px 4px;text-align:left;list-style:none;background:#fff}.vs__no-options{text-align:center}.vs__dropdown-option{line-height:1.42857143;display:block;padding:3px 20px;clear:both;color:#333;white-space:nowrap}.vs__dropdown-option:hover{cursor:pointer}.vs__dropdown-option--highlight{background:#5897fb;color:#fff}.vs__dropdown-option--disabled{background:inherit;color:rgba(60,60,60,.5)}.vs__dropdown-option--disabled:hover{cursor:inherit}.vs__selected{display:flex;align-items:center;background-color:#f0f0f0;border:1px solid rgba(60,60,60,.26);border-radius:4px;color:#333;line-height:1.4;margin:4px 2px 0;padding:0 .25em;z-index:0}.vs__deselect{display:inline-flex;-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-left:4px;padding:0;border:0;cursor:pointer;background:none;fill:rgba(60,60,60,.5);text-shadow:0 1px 0 #fff}.vs--single .vs__selected{background-color:transparent;border-color:transparent}.vs--single.vs--open .vs__selected{position:absolute;opacity:.4}.vs--single.vs--searching .vs__selected{display:none}.vs__search::-webkit-search-cancel-button{display:none}.vs__search::-ms-clear,.vs__search::-webkit-search-decoration,.vs__search::-webkit-search-results-button,.vs__search::-webkit-search-results-decoration{display:none}.vs__search,.vs__search:focus{-webkit-appearance:none;-moz-appearance:none;appearance:none;line-height:1.4;font-size:1em;border:1px solid transparent;border-left:none;outline:none;margin:4px 0 0;padding:0 7px;background:none;box-shadow:none;width:0;max-width:100%;flex-grow:1;z-index:1}.vs__search::-moz-placeholder{color:inherit}.vs__search:-ms-input-placeholder{color:inherit}.vs__search::placeholder{color:inherit}.vs--unsearchable .vs__search{opacity:1}.vs--unsearchable:not(.vs--disabled) .vs__search:hover{cursor:pointer}.vs--single.vs--searching:not(.vs--open):not(.vs--loading) .vs__search{opacity:.2}.vs__spinner{align-self:center;opacity:0;font-size:5px;text-indent:-9999em;overflow:hidden;border:.9em solid hsla(0,0%,39.2%,.1);border-left-color:rgba(60,60,60,.45);transform:translateZ(0);-webkit-animation:vSelectSpinner 1.1s linear infinite;animation:vSelectSpinner 1.1s linear infinite;transition:opacity .1s}.vs__spinner,.vs__spinner:after{border-radius:50%;width:5em;height:5em}.vs--loading .vs__spinner{opacity:1}input[type=checkbox][readonly],select[readonly]{pointer-events:none}.btn-group>form:not(:first-child)>.btn-group:not(:last-child)>.btn,.btn-group>form:not(:first-child)>.btn:last-child:not(.dropdown-toggle){border-top-left-radius:0;border-bottom-left-radius:0}a.col_sort :active,a.col_sort :hover,a.col_sort:link,a.col_sort :visited{text-decoration:none}.container-xxl{max-width:1340px!important}.alert-success .text-muted{color:#166218!important}.alert-primary .text-muted,.alert-primary .text-primary{color:#0b475e!important}.alert-secondary .text-muted{color:#bdbdbd!important}.alert-danger .text-muted{color:#cf0b00!important} + +/*# sourceMappingURL=app.css.map*/ \ No newline at end of file diff --git a/public/css/app.css.map b/public/css/app.css.map new file mode 100644 index 0000000..49418fb --- /dev/null +++ b/public/css/app.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./node_modules/vue-select/src/scss/global/_component.scss","webpack:///./app.scss","webpack:///./node_modules/vue-select/src/scss/global/_animations.scss","webpack:///./node_modules/vue-select/src/scss/global/_states.scss","webpack:///./node_modules/vue-select/src/scss/global/_variables.scss","webpack:///./node_modules/vue-select/src/scss/modules/_dropdown-toggle.scss","webpack:///./node_modules/vue-select/src/scss/modules/_open-indicator.scss","webpack:///./node_modules/vue-select/src/scss/modules/_clear.scss","webpack:///./node_modules/vue-select/src/scss/modules/_dropdown-menu.scss","webpack:///./node_modules/vue-select/src/scss/modules/_dropdown-option.scss","webpack:///./node_modules/vue-select/src/scss/modules/_selected.scss","webpack:///./node_modules/vue-select/src/scss/modules/_search-input.scss","webpack:///./node_modules/vue-select/src/scss/modules/_spinner.scss","webpack:///./_crud.scss"],"names":[],"mappings":";AAAA,UACE,kBACA,mBCEF,CDCA,sBAEE,qBCEF,CCLA,kCACE,GACE,sBDSF,CCPA,GACE,uBDSF,CACF,CCNA,0BACE,GACE,sBDQF,CCNA,GACE,uBDQF,CACF,CCJA,8CAEE,oBACA,+CDOF,CCLA,mCAEE,SDQF,CEtBE,oJAKE,kBCFuB,CDGvB,wBFkCJ,CErBE,gCACE,mBF+BJ,CE5BE,8BACE,gBACA,cF8BJ,CE3BE,iCACE,cACA,gBF6BJ,CE1BE,sCACE,gBF4BJ,CI7DA,qBACE,6DACA,aACA,gBACA,gBACA,oCACA,iBDCiB,CCAjB,kBJ2EF,CIxEA,sBACE,aACA,gBACA,YACA,eACA,cACA,iBJ2EF,CIxEA,aACE,aACA,mBACA,qBJ2EF,CIvEA,qCACE,WJ2EF,CIzEA,uCACE,cJ4EF,CI1EA,+BACE,gCACA,4BACA,4BJ6EF,CKtHA,oBACE,sBFgBkB,CEflB,mBACA,0DACA,0DLyHF,CKpHA,8BACE,iCLuHF,CKlHA,iCACE,SLqHF,CM9IA,WACE,sBHyBkB,CGxBlB,UACA,SACA,6BACA,eACA,gBNkJF,CO7IA,mBACE,cACA,sBACA,kBACA,qBACA,OACA,YJoBoB,CInBpB,cACA,SACA,WACA,gBJkBuB,CIjBvB,eJgBsB,CIftB,gBACA,sCJgBuB,CIdvB,0DACA,0BACA,gBACA,gBACA,ePiJF,CO9IA,gBACE,iBPiJF,CQpLA,qBACE,uBACA,cACA,iBACA,WACA,WACA,kBR0LF,CQxLE,2BACE,cR0LJ,CQtLA,gCACE,kBLHmB,CKInB,URyLF,CQtLA,+BACE,mBACA,uBRyLF,CQvLE,qCACE,cRyLJ,CShNA,cACE,aACA,mBACA,wBN6Be,CM5Bf,oCACA,iBNkBiB,CMjBjB,WACA,gBACA,iBACA,gBACA,SToNF,CSjNA,cACE,oBACA,6DACA,gBACA,UACA,SACA,eACA,gBACA,sBNMkB,CMLlB,wBToNF,CS9ME,0BACE,6BACA,wBTkNJ,CShNE,mCACE,kBACA,UTkNJ,CShNE,wCACE,YTkNJ,CU9OA,0CACE,YVuPF,CUpPA,wJAIE,YVuPF,CUpPA,8BAEE,6DACA,ePhByB,COiBzB,aAtBU,CAwBV,8CACA,aACA,eACA,cACA,gBACA,gBACA,QACA,eACA,YACA,SVuPF,CUpPA,8BACE,aVuPF,CUxPA,kCACE,aVuPF,CUxPA,yBACE,aVuPF,CU9OE,8BACE,SVoPJ,CUlPE,uDACE,cVoPJ,CU/OI,uEACE,UVkPN,CW5SA,aACE,kBACA,UACA,cACA,oBACA,gBAIA,2EACA,wBACA,oGACA,sBXgTF,CW9SA,gCAEE,kBACA,UACA,UXiTF,CW7SA,0BACE,SXiTF,CYtUA,gDACI,mBZyUJ,CYrUA,2IACI,yBACA,2BZwUJ,CAxUI,yEACI,oBA2UR,CAvUA,eACI,0BA0UJ,CAtUA,2BAEI,uBAwUJ,CAhUI,wDACI,uBAsUR,CAlUA,6BACI,uBAqUJ,CAlUA,0BACI,uBAqUJ,C","file":"css/app.css","sourcesContent":[".v-select {\n position: relative;\n font-family: inherit;\n}\n\n.v-select,\n.v-select * {\n box-sizing: border-box;\n}\n","@use \"sass:color\";\n\n// Fonts\n@import url('https://fonts.googleapis.com/css?family=Nunito');\n\n@import 'crud';\n// Variables\n@import 'variables';\n\na.col_sort {\n &:link,:visited,:hover,:active {\n text-decoration: none;\n }\n}\n\n.container-xxl {\n max-width: 1340px !important;\n}\n\n\n.alert-success .text-muted {\n //color : #176319 !important;\n color : color.adjust(#28b62c, $lightness: -20%) !important;\n}\n\n.alert-primary {\n .text-muted {\n //color : #0b4a63 !important;\n color : color.adjust(#158cba, $lightness: -20%) !important;\n }\n .text-primary {\n color : color.adjust(#158cba, $lightness: -20%) !important;\n }\n}\n\n.alert-secondary .text-muted {\n color : color.adjust(#f0f0f0, $lightness: -20%) !important;\n}\n\n.alert-danger .text-muted {\n color : color.adjust(#ff4136, $lightness: -20%) !important;\n}\n","$transition-timing-function: cubic-bezier(1.0, 0.5, 0.8, 1.0);\n$transition-duration: .15s;\n\n/* KeyFrames */\n@-webkit-keyframes vSelectSpinner {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@keyframes vSelectSpinner {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n/* Dropdown Default Transition */\n.vs__fade-enter-active,\n.vs__fade-leave-active {\n pointer-events: none;\n transition: opacity $transition-duration $transition-timing-function;\n}\n.vs__fade-enter,\n.vs__fade-leave-to {\n opacity: 0;\n}\n","/** Component States */\n\n/*\n * Disabled\n *\n * When the component is disabled, all interaction\n * should be prevented. Here we modify the bg color,\n * and change the cursor displayed on the interactive\n * components.\n */\n\n$disabled-bg: $vs-state-disabled-bg;\n$disabled-color: $vs-state-disabled-color;\n$disabled-cursor: $vs-state-disabled-cursor;\n\n.vs--disabled {\n .vs__dropdown-toggle,\n .vs__clear,\n .vs__search,\n .vs__selected,\n .vs__open-indicator {\n cursor: $disabled-cursor;\n background-color: $disabled-bg;\n }\n}\n\n/*\n * RTL - Right to Left Support\n *\n * Because we're using a flexbox layout, the `dir=\"rtl\"`\n * HTML attribute does most of the work for us by\n * rearranging the child elements visually.\n */\n\n.v-select[dir=\"rtl\"] {\n .vs__actions {\n padding: 0 3px 0 6px;\n }\n\n .vs__clear {\n margin-left: 6px;\n margin-right: 0;\n }\n\n .vs__deselect {\n margin-left: 0;\n margin-right: 2px;\n }\n\n .vs__dropdown-menu {\n text-align: right;\n }\n}\n","$vs-colors: (\n lightest: rgba(60, 60, 60, 0.26),\n light: rgba(60, 60, 60, 0.5),\n dark: #333,\n darkest: rgba(0, 0, 0, .15),\n) !default;\n\n// Global Component Variables\n$vs-component-line-height: 1.4 !default;\n$vs-component-placeholder-color: inherit !default;\n\n// Active State\n$vs-state-active-bg: #5897fb !default;\n$vs-state-active-color: #fff !default;\n\n// Disabled State\n$vs-state-disabled-bg: rgb(248, 248, 248) !default;\n$vs-state-disabled-color: map_get($vs-colors, 'light') !default;\n$vs-state-disabled-controls-color: map_get($vs-colors, 'light') !default;\n$vs-state-disabled-cursor: not-allowed !default;\n\n// Borders\n$vs-border-width: 1px !default;\n$vs-border-style: solid !default;\n$vs-border-radius: 4px !default;\n$vs-border-color: map_get($vs-colors, 'lightest') !default;\n\n// Component Controls: Clear, Open Indicator\n$vs-controls-color: map_get($vs-colors, 'light') !default;\n$vs-controls-size: 1 !default;\n$vs-controls-deselect-text-shadow: 0 1px 0 #fff;\n\n// Selected\n$vs-selected-bg: #f0f0f0 !default;\n$vs-selected-border-color: $vs-border-color !default;\n$vs-selected-border-style: $vs-border-style !default;\n$vs-selected-border-width: $vs-border-width !default;\n\n// Dropdown\n$vs-dropdown-z-index: 1000 !default;\n$vs-dropdown-min-width: 160px !default;\n$vs-dropdown-max-height: 350px !default;\n$vs-dropdown-box-shadow: 0px 3px 6px 0px map_get($vs-colors, 'darkest') !default;\n$vs-dropdown-bg: #fff !default;\n","/**\n Dropdown Toggle\n\n The dropdown toggle is the primary wrapper of the component. It\n has two direct descendants: .vs__selected-options, and .vs__actions.\n\n .vs__selected-options holds the .vs__selected's as well as the\n main search input.\n\n .vs__actions holds the clear button and dropdown toggle.\n */\n\n$border-width: $vs-border-width;\n$border-style: $vs-border-style;\n$border-color: $vs-border-color;\n$border-radius: $vs-border-radius;\n\n.vs__dropdown-toggle {\n appearance: none;\n display: flex;\n padding: 0 0 4px 0;\n background: none;\n border: $border-width $border-style $border-color;\n border-radius: $border-radius;\n white-space: normal;\n}\n\n.vs__selected-options {\n display: flex;\n flex-basis: 100%;\n flex-grow: 1;\n flex-wrap: wrap;\n padding: 0 2px;\n position: relative;\n}\n\n.vs__actions {\n display: flex;\n align-items: center;\n padding: 4px 6px 0 3px;\n}\n\n/* Dropdown Toggle States */\n.vs--searchable .vs__dropdown-toggle {\n cursor: text;\n}\n.vs--unsearchable .vs__dropdown-toggle {\n cursor: pointer;\n}\n.vs--open .vs__dropdown-toggle {\n border-bottom-color: transparent;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n","// Open Indicator\n\n// The open indicator appears as a down facing\n// caret on the right side of the select.\n\n$transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);\n$transition-duration: 150ms;\n\n$open-indicator-color: $vs-controls-color;\n$open-indicator-size: $vs-controls-size;\n\n.vs__open-indicator {\n fill: $open-indicator-color;\n transform: scale($open-indicator-size);\n transition: transform $transition-duration $transition-timing-function;\n transition-timing-function: $transition-timing-function;\n}\n\n// Open State\n\n.vs--open .vs__open-indicator {\n transform: rotate(180deg) scale($open-indicator-size);\n}\n\n// Loading State\n\n.vs--loading .vs__open-indicator {\n opacity: 0;\n}\n","/* Clear Button */\n\n.vs__clear {\n fill: $vs-controls-color;\n padding: 0;\n border: 0;\n background-color: transparent;\n cursor: pointer;\n margin-right: 8px;\n}\n","/* Dropdown Menu */\n\n$border-width: $vs-border-width;\n$border-style: solid;\n$border-color: $vs-border-color;\n$border-radius: $vs-border-radius;\n$box-shadow: $vs-dropdown-box-shadow;\n\n$bg-color: $vs-dropdown-bg;\n$z-index: $vs-dropdown-z-index;\n$min-width: $vs-dropdown-min-width;\n$max-height: $vs-dropdown-max-height;\n\n.vs__dropdown-menu {\n display: block;\n box-sizing: border-box;\n position: absolute;\n top: calc(100% - #{$border-width}); // -{#$border-width} here ensures the left and right borders of the dropdown appear flush with the toggle.\n left: 0;\n z-index: $z-index;\n padding: 5px 0;\n margin: 0;\n width: 100%;\n max-height: $max-height;\n min-width: $min-width;\n overflow-y: auto;\n box-shadow: $box-shadow;\n border: $border-width $border-style $border-color;\n border-top-style: none;\n border-radius: 0 0 $border-radius $border-radius;\n text-align: left;\n list-style: none;\n background: $bg-color;\n}\n\n.vs__no-options {\n text-align: center;\n}\n","/* List Items */\n.vs__dropdown-option {\n line-height: 1.42857143; /* Normalize line height */\n display: block;\n padding: 3px 20px;\n clear: both;\n color: #333; /* Overrides most CSS frameworks */\n white-space: nowrap;\n\n &:hover {\n cursor: pointer;\n }\n}\n\n.vs__dropdown-option--highlight {\n background: $vs-state-active-bg;\n color: $vs-state-active-color;\n}\n\n.vs__dropdown-option--disabled {\n background: inherit;\n color: $vs-state-disabled-color;\n\n &:hover {\n cursor: inherit;\n }\n}\n","/* Selected Tags */\n.vs__selected {\n display: flex;\n align-items: center;\n background-color: $vs-selected-bg;\n border: $vs-selected-border-width $vs-selected-border-style $vs-selected-border-color;\n border-radius: $vs-border-radius;\n color: map_get($vs-colors, 'dark');\n line-height: $vs-component-line-height;\n margin: 4px 2px 0px 2px;\n padding: 0 0.25em;\n z-index: 0;\n}\n\n.vs__deselect {\n display: inline-flex;\n appearance: none;\n margin-left: 4px;\n padding: 0;\n border: 0;\n cursor: pointer;\n background: none;\n fill: $vs-controls-color;\n text-shadow: $vs-controls-deselect-text-shadow;\n}\n\n/* States */\n\n.vs--single {\n .vs__selected {\n background-color: transparent;\n border-color: transparent;\n }\n &.vs--open .vs__selected {\n position: absolute;\n opacity: .4;\n }\n &.vs--searching .vs__selected {\n display: none;\n }\n}\n","/* Search Input */\n\n$line-height: $vs-component-line-height;\n$font-size: 1em;\n\n/**\n * Super weird bug... If this declaration is grouped\n * below, the cancel button will still appear in chrome.\n * If it's up here on it's own, it'll hide it.\n */\n.vs__search::-webkit-search-cancel-button {\n display: none;\n}\n\n.vs__search::-webkit-search-decoration,\n.vs__search::-webkit-search-results-button,\n.vs__search::-webkit-search-results-decoration,\n.vs__search::-ms-clear {\n display: none;\n}\n\n.vs__search,\n.vs__search:focus {\n appearance: none;\n line-height: $line-height;\n font-size: $font-size;\n border: 1px solid transparent;\n border-left: none;\n outline: none;\n margin: 4px 0 0 0;\n padding: 0 7px;\n background: none;\n box-shadow: none;\n width: 0;\n max-width: 100%;\n flex-grow: 1;\n z-index: 1;\n}\n\n.vs__search::placeholder {\n color: $vs-component-placeholder-color;\n}\n\n/**\n States\n */\n\n// Unsearchable\n.vs--unsearchable {\n .vs__search {\n opacity: 1;\n }\n &:not(.vs--disabled) .vs__search:hover {\n cursor: pointer;\n }\n}\n// Single, when searching but not loading or open\n.vs--single.vs--searching:not(.vs--open):not(.vs--loading) {\n .vs__search {\n opacity: .2;\n }\n}\n","/* Loading Spinner */\n.vs__spinner {\n align-self: center;\n opacity: 0;\n font-size: 5px;\n text-indent: -9999em;\n overflow: hidden;\n border-top: .9em solid rgba(100, 100, 100, .1);\n border-right: .9em solid rgba(100, 100, 100, .1);\n border-bottom: .9em solid rgba(100, 100, 100, .1);\n border-left: .9em solid rgba(60, 60, 60, .45);\n transform: translateZ(0);\n animation: vSelectSpinner 1.1s infinite linear;\n transition: opacity .1s;\n}\n.vs__spinner,\n.vs__spinner:after {\n border-radius: 50%;\n width: 5em;\n height: 5em;\n}\n\n/* Loading Spinner States */\n.vs--loading .vs__spinner {\n opacity: 1;\n}\n","@import \"vue-select/src/scss/vue-select.scss\";\r\n\r\n// readonly checkboxes\r\nselect[readonly],input[type='checkbox'][readonly]{\r\n pointer-events: none;\r\n}\r\n\r\n//bootstrap overload\r\n.btn-group > form:not(:first-child) > .btn-group:not(:last-child) > .btn, .btn-group > form:not(:first-child) > .btn:last-child:not(.dropdown-toggle) {\r\n border-top-left-radius: 0;\r\n border-bottom-left-radius: 0;\r\n}\r\n"],"sourceRoot":""} \ No newline at end of file diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..578a5c8 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,3 @@ +/*! For license information please see app.js.LICENSE.txt */ +(()=>{var t,e={9669:(t,e,n)=>{t.exports=n(1609)},5448:(t,e,n)=>{"use strict";var r=n(4867),o=n(6026),i=n(4372),a=n(5327),s=n(4097),u=n(4109),c=n(7985),l=n(5061);t.exports=function(t){return new Promise((function(e,n){var f=t.data,p=t.headers;r.isFormData(f)&&delete p["Content-Type"];var d=new XMLHttpRequest;if(t.auth){var h=t.auth.username||"",v=t.auth.password?unescape(encodeURIComponent(t.auth.password)):"";p.Authorization="Basic "+btoa(h+":"+v)}var g=s(t.baseURL,t.url);if(d.open(t.method.toUpperCase(),a(g,t.params,t.paramsSerializer),!0),d.timeout=t.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var r="getAllResponseHeaders"in d?u(d.getAllResponseHeaders()):null,i={data:t.responseType&&"text"!==t.responseType?d.response:d.responseText,status:d.status,statusText:d.statusText,headers:r,config:t,request:d};o(e,n,i),d=null}},d.onabort=function(){d&&(n(l("Request aborted",t,"ECONNABORTED",d)),d=null)},d.onerror=function(){n(l("Network Error",t,null,d)),d=null},d.ontimeout=function(){var e="timeout of "+t.timeout+"ms exceeded";t.timeoutErrorMessage&&(e=t.timeoutErrorMessage),n(l(e,t,"ECONNABORTED",d)),d=null},r.isStandardBrowserEnv()){var m=(t.withCredentials||c(g))&&t.xsrfCookieName?i.read(t.xsrfCookieName):void 0;m&&(p[t.xsrfHeaderName]=m)}if("setRequestHeader"in d&&r.forEach(p,(function(t,e){void 0===f&&"content-type"===e.toLowerCase()?delete p[e]:d.setRequestHeader(e,t)})),r.isUndefined(t.withCredentials)||(d.withCredentials=!!t.withCredentials),t.responseType)try{d.responseType=t.responseType}catch(e){if("json"!==t.responseType)throw e}"function"==typeof t.onDownloadProgress&&d.addEventListener("progress",t.onDownloadProgress),"function"==typeof t.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then((function(t){d&&(d.abort(),n(t),d=null)})),f||(f=null),d.send(f)}))}},1609:(t,e,n)=>{"use strict";var r=n(4867),o=n(1849),i=n(321),a=n(7185);function s(t){var e=new i(t),n=o(i.prototype.request,e);return r.extend(n,i.prototype,e),r.extend(n,e),n}var u=s(n(5655));u.Axios=i,u.create=function(t){return s(a(u.defaults,t))},u.Cancel=n(5263),u.CancelToken=n(4972),u.isCancel=n(6502),u.all=function(t){return Promise.all(t)},u.spread=n(8713),u.isAxiosError=n(6268),t.exports=u,t.exports.default=u},5263:t=>{"use strict";function e(t){this.message=t}e.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},e.prototype.__CANCEL__=!0,t.exports=e},4972:(t,e,n)=>{"use strict";var r=n(5263);function o(t){if("function"!=typeof t)throw new TypeError("executor must be a function.");var e;this.promise=new Promise((function(t){e=t}));var n=this;t((function(t){n.reason||(n.reason=new r(t),e(n.reason))}))}o.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},o.source=function(){var t;return{token:new o((function(e){t=e})),cancel:t}},t.exports=o},6502:t=>{"use strict";t.exports=function(t){return!(!t||!t.__CANCEL__)}},321:(t,e,n)=>{"use strict";var r=n(4867),o=n(5327),i=n(782),a=n(3572),s=n(7185);function u(t){this.defaults=t,this.interceptors={request:new i,response:new i}}u.prototype.request=function(t){"string"==typeof t?(t=arguments[1]||{}).url=arguments[0]:t=t||{},(t=s(this.defaults,t)).method?t.method=t.method.toLowerCase():this.defaults.method?t.method=this.defaults.method.toLowerCase():t.method="get";var e=[a,void 0],n=Promise.resolve(t);for(this.interceptors.request.forEach((function(t){e.unshift(t.fulfilled,t.rejected)})),this.interceptors.response.forEach((function(t){e.push(t.fulfilled,t.rejected)}));e.length;)n=n.then(e.shift(),e.shift());return n},u.prototype.getUri=function(t){return t=s(this.defaults,t),o(t.url,t.params,t.paramsSerializer).replace(/^\?/,"")},r.forEach(["delete","get","head","options"],(function(t){u.prototype[t]=function(e,n){return this.request(s(n||{},{method:t,url:e,data:(n||{}).data}))}})),r.forEach(["post","put","patch"],(function(t){u.prototype[t]=function(e,n,r){return this.request(s(r||{},{method:t,url:e,data:n}))}})),t.exports=u},782:(t,e,n)=>{"use strict";var r=n(4867);function o(){this.handlers=[]}o.prototype.use=function(t,e){return this.handlers.push({fulfilled:t,rejected:e}),this.handlers.length-1},o.prototype.eject=function(t){this.handlers[t]&&(this.handlers[t]=null)},o.prototype.forEach=function(t){r.forEach(this.handlers,(function(e){null!==e&&t(e)}))},t.exports=o},4097:(t,e,n)=>{"use strict";var r=n(1793),o=n(7303);t.exports=function(t,e){return t&&!r(e)?o(t,e):e}},5061:(t,e,n)=>{"use strict";var r=n(481);t.exports=function(t,e,n,o,i){var a=new Error(t);return r(a,e,n,o,i)}},3572:(t,e,n)=>{"use strict";var r=n(4867),o=n(8527),i=n(6502),a=n(5655);function s(t){t.cancelToken&&t.cancelToken.throwIfRequested()}t.exports=function(t){return s(t),t.headers=t.headers||{},t.data=o(t.data,t.headers,t.transformRequest),t.headers=r.merge(t.headers.common||{},t.headers[t.method]||{},t.headers),r.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete t.headers[e]})),(t.adapter||a.adapter)(t).then((function(e){return s(t),e.data=o(e.data,e.headers,t.transformResponse),e}),(function(e){return i(e)||(s(t),e&&e.response&&(e.response.data=o(e.response.data,e.response.headers,t.transformResponse))),Promise.reject(e)}))}},481:t=>{"use strict";t.exports=function(t,e,n,r,o){return t.config=e,n&&(t.code=n),t.request=r,t.response=o,t.isAxiosError=!0,t.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},t}},7185:(t,e,n)=>{"use strict";var r=n(4867);t.exports=function(t,e){e=e||{};var n={},o=["url","method","data"],i=["headers","auth","proxy","params"],a=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],s=["validateStatus"];function u(t,e){return r.isPlainObject(t)&&r.isPlainObject(e)?r.merge(t,e):r.isPlainObject(e)?r.merge({},e):r.isArray(e)?e.slice():e}function c(o){r.isUndefined(e[o])?r.isUndefined(t[o])||(n[o]=u(void 0,t[o])):n[o]=u(t[o],e[o])}r.forEach(o,(function(t){r.isUndefined(e[t])||(n[t]=u(void 0,e[t]))})),r.forEach(i,c),r.forEach(a,(function(o){r.isUndefined(e[o])?r.isUndefined(t[o])||(n[o]=u(void 0,t[o])):n[o]=u(void 0,e[o])})),r.forEach(s,(function(r){r in e?n[r]=u(t[r],e[r]):r in t&&(n[r]=u(void 0,t[r]))}));var l=o.concat(i).concat(a).concat(s),f=Object.keys(t).concat(Object.keys(e)).filter((function(t){return-1===l.indexOf(t)}));return r.forEach(f,c),n}},6026:(t,e,n)=>{"use strict";var r=n(5061);t.exports=function(t,e,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?e(r("Request failed with status code "+n.status,n.config,null,n.request,n)):t(n)}},8527:(t,e,n)=>{"use strict";var r=n(4867);t.exports=function(t,e,n){return r.forEach(n,(function(n){t=n(t,e)})),t}},5655:(t,e,n)=>{"use strict";var r=n(4155),o=n(4867),i=n(6016),a={"Content-Type":"application/x-www-form-urlencoded"};function s(t,e){!o.isUndefined(t)&&o.isUndefined(t["Content-Type"])&&(t["Content-Type"]=e)}var u,c={adapter:(("undefined"!=typeof XMLHttpRequest||void 0!==r&&"[object process]"===Object.prototype.toString.call(r))&&(u=n(5448)),u),transformRequest:[function(t,e){return i(e,"Accept"),i(e,"Content-Type"),o.isFormData(t)||o.isArrayBuffer(t)||o.isBuffer(t)||o.isStream(t)||o.isFile(t)||o.isBlob(t)?t:o.isArrayBufferView(t)?t.buffer:o.isURLSearchParams(t)?(s(e,"application/x-www-form-urlencoded;charset=utf-8"),t.toString()):o.isObject(t)?(s(e,"application/json;charset=utf-8"),JSON.stringify(t)):t}],transformResponse:[function(t){if("string"==typeof t)try{t=JSON.parse(t)}catch(t){}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(t){return t>=200&&t<300}};c.headers={common:{Accept:"application/json, text/plain, */*"}},o.forEach(["delete","get","head"],(function(t){c.headers[t]={}})),o.forEach(["post","put","patch"],(function(t){c.headers[t]=o.merge(a)})),t.exports=c},1849:t=>{"use strict";t.exports=function(t,e){return function(){for(var n=new Array(arguments.length),r=0;r{"use strict";var r=n(4867);function o(t){return encodeURIComponent(t).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}t.exports=function(t,e,n){if(!e)return t;var i;if(n)i=n(e);else if(r.isURLSearchParams(e))i=e.toString();else{var a=[];r.forEach(e,(function(t,e){null!=t&&(r.isArray(t)?e+="[]":t=[t],r.forEach(t,(function(t){r.isDate(t)?t=t.toISOString():r.isObject(t)&&(t=JSON.stringify(t)),a.push(o(e)+"="+o(t))})))})),i=a.join("&")}if(i){var s=t.indexOf("#");-1!==s&&(t=t.slice(0,s)),t+=(-1===t.indexOf("?")?"?":"&")+i}return t}},7303:t=>{"use strict";t.exports=function(t,e){return e?t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):t}},4372:(t,e,n)=>{"use strict";var r=n(4867);t.exports=r.isStandardBrowserEnv()?{write:function(t,e,n,o,i,a){var s=[];s.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&s.push("expires="+new Date(n).toGMTString()),r.isString(o)&&s.push("path="+o),r.isString(i)&&s.push("domain="+i),!0===a&&s.push("secure"),document.cookie=s.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},1793:t=>{"use strict";t.exports=function(t){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(t)}},6268:t=>{"use strict";t.exports=function(t){return"object"==typeof t&&!0===t.isAxiosError}},7985:(t,e,n)=>{"use strict";var r=n(4867);t.exports=r.isStandardBrowserEnv()?function(){var t,e=/(msie|trident)/i.test(navigator.userAgent),n=document.createElement("a");function o(t){var r=t;return e&&(n.setAttribute("href",r),r=n.href),n.setAttribute("href",r),{href:n.href,protocol:n.protocol?n.protocol.replace(/:$/,""):"",host:n.host,search:n.search?n.search.replace(/^\?/,""):"",hash:n.hash?n.hash.replace(/^#/,""):"",hostname:n.hostname,port:n.port,pathname:"/"===n.pathname.charAt(0)?n.pathname:"/"+n.pathname}}return t=o(window.location.href),function(e){var n=r.isString(e)?o(e):e;return n.protocol===t.protocol&&n.host===t.host}}():function(){return!0}},6016:(t,e,n)=>{"use strict";var r=n(4867);t.exports=function(t,e){r.forEach(t,(function(n,r){r!==e&&r.toUpperCase()===e.toUpperCase()&&(t[e]=n,delete t[r])}))}},4109:(t,e,n)=>{"use strict";var r=n(4867),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];t.exports=function(t){var e,n,i,a={};return t?(r.forEach(t.split("\n"),(function(t){if(i=t.indexOf(":"),e=r.trim(t.substr(0,i)).toLowerCase(),n=r.trim(t.substr(i+1)),e){if(a[e]&&o.indexOf(e)>=0)return;a[e]="set-cookie"===e?(a[e]?a[e]:[]).concat([n]):a[e]?a[e]+", "+n:n}})),a):a}},8713:t=>{"use strict";t.exports=function(t){return function(e){return t.apply(null,e)}}},4867:(t,e,n)=>{"use strict";var r=n(1849),o=Object.prototype.toString;function i(t){return"[object Array]"===o.call(t)}function a(t){return void 0===t}function s(t){return null!==t&&"object"==typeof t}function u(t){if("[object Object]"!==o.call(t))return!1;var e=Object.getPrototypeOf(t);return null===e||e===Object.prototype}function c(t){return"[object Function]"===o.call(t)}function l(t,e){if(null!=t)if("object"!=typeof t&&(t=[t]),i(t))for(var n=0,r=t.length;n{n(9147),n(9971)},9147:(t,e,n)=>{"use strict";n.r(e);n(1249),n(6992),n(1539),n(3948),n(4916),n(5306),n(3123);var r=n(9938),o=n.n(r);window._=n(6486),window.axios=n(9669),window.axios.defaults.headers.common["X-Requested-With"]="XMLHttpRequest",window.Vue=n(538).Z,Vue.component("v-select",o());var i=n(6132);i.keys().map((function(t){return Vue.mixin(i(t).default)}));var a=n(5985);a.keys().map((function(t){return Vue.component(t.split("/").pop().split(".")[0].replace(/\.?([A-Z])/g,(function(t,e){return"-"+e.toLowerCase()})).replace(/^-/,"").replace("-component",""),a(t).default)}));new Vue({el:"#app"});n(2213)},9971:()=>{window.__CRUD={confirmDelete:function(t){var e=t.target;t.preventDefault(),Swal.fire({title:"Confirmez",text:"Voulez-vous vraiment supprimer cet élément ?",icon:"question",confirmButtonText:"Supprimer",showCancelButton:!0,cancelButtonText:"Annuler",confirmButtonColor:"#dc3545"}).then((function(t){t.isConfirmed&&e.submit()}))}}},2369:(t,e,n)=>{"use strict";n.r(e),n.d(e,{default:()=>r});const r={methods:{asset:function(t){return window._asset+t}}}},4963:(t,e,n)=>{"use strict";n.r(e),n.d(e,{default:()=>r});n(7941);const r={methods:{route:function(t){return-1!==Object.keys(window._routes).indexOf(t)?window._routes[t]:null}}}},480:(t,e,n)=>{"use strict";n.r(e),n.d(e,{default:()=>i});n(4916),n(3123),n(4723),n(5306),n(4603),n(9714),n(7042),n(1539),n(8309),n(1038),n(8783),n(2526),n(1817),n(2165),n(6992),n(3948);function r(t,e){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!n){if(Array.isArray(t)||(n=function(t,e){if(!t)return;if("string"==typeof t)return o(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return o(t,e)}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var r=0,i=function(){};return{s:i,n:function(){return r>=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,s=!0,u=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return s=t.done,t},e:function(t){u=!0,a=t},f:function(){try{s||null==n.return||n.return()}finally{if(u)throw a}}}}function o(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n{t.exports=function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function");return t}},6077:(t,e,n)=>{var r=n(111);t.exports=function(t){if(!r(t)&&null!==t)throw TypeError("Can't set "+String(t)+" as a prototype");return t}},1223:(t,e,n)=>{var r=n(5112),o=n(30),i=n(3070),a=r("unscopables"),s=Array.prototype;null==s[a]&&i.f(s,a,{configurable:!0,value:o(null)}),t.exports=function(t){s[a][t]=!0}},1530:(t,e,n)=>{"use strict";var r=n(8710).charAt;t.exports=function(t,e,n){return e+(n?r(t,e).length:1)}},9670:(t,e,n)=>{var r=n(111);t.exports=function(t){if(!r(t))throw TypeError(String(t)+" is not an object");return t}},8457:(t,e,n)=>{"use strict";var r=n(9974),o=n(7908),i=n(3411),a=n(7659),s=n(7466),u=n(6135),c=n(1246);t.exports=function(t){var e,n,l,f,p,d,h=o(t),v="function"==typeof this?this:Array,g=arguments.length,m=g>1?arguments[1]:void 0,y=void 0!==m,b=c(h),_=0;if(y&&(m=r(m,g>2?arguments[2]:void 0,2)),null==b||v==Array&&a(b))for(n=new v(e=s(h.length));e>_;_++)d=y?m(h[_],_):h[_],u(n,_,d);else for(p=(f=b.call(h)).next,n=new v;!(l=p.call(f)).done;_++)d=y?i(f,m,[l.value,_],!0):l.value,u(n,_,d);return n.length=_,n}},1318:(t,e,n)=>{var r=n(5656),o=n(7466),i=n(1400),a=function(t){return function(e,n,a){var s,u=r(e),c=o(u.length),l=i(a,c);if(t&&n!=n){for(;c>l;)if((s=u[l++])!=s)return!0}else for(;c>l;l++)if((t||l in u)&&u[l]===n)return t||l||0;return!t&&-1}};t.exports={includes:a(!0),indexOf:a(!1)}},2092:(t,e,n)=>{var r=n(9974),o=n(8361),i=n(7908),a=n(7466),s=n(5417),u=[].push,c=function(t){var e=1==t,n=2==t,c=3==t,l=4==t,f=6==t,p=7==t,d=5==t||f;return function(h,v,g,m){for(var y,b,_=i(h),x=o(_),w=r(v,g,3),S=a(x.length),O=0,A=m||s,C=e?A(h,S):n||p?A(h,0):void 0;S>O;O++)if((d||O in x)&&(b=w(y=x[O],O,_),t))if(e)C[O]=b;else if(b)switch(t){case 3:return!0;case 5:return y;case 6:return O;case 2:u.call(C,y)}else switch(t){case 4:return!1;case 7:u.call(C,y)}return f?-1:c||l?l:C}};t.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterOut:c(7)}},1194:(t,e,n)=>{var r=n(7293),o=n(5112),i=n(7392),a=o("species");t.exports=function(t){return i>=51||!r((function(){var e=[];return(e.constructor={})[a]=function(){return{foo:1}},1!==e[t](Boolean).foo}))}},5417:(t,e,n)=>{var r=n(111),o=n(3157),i=n(5112)("species");t.exports=function(t,e){var n;return o(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!o(n.prototype)?r(n)&&null===(n=n[i])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===e?0:e)}},3411:(t,e,n)=>{var r=n(9670),o=n(9212);t.exports=function(t,e,n,i){try{return i?e(r(n)[0],n[1]):e(n)}catch(e){throw o(t),e}}},7072:(t,e,n)=>{var r=n(5112)("iterator"),o=!1;try{var i=0,a={next:function(){return{done:!!i++}},return:function(){o=!0}};a[r]=function(){return this},Array.from(a,(function(){throw 2}))}catch(t){}t.exports=function(t,e){if(!e&&!o)return!1;var n=!1;try{var i={};i[r]=function(){return{next:function(){return{done:n=!0}}}},t(i)}catch(t){}return n}},4326:t=>{var e={}.toString;t.exports=function(t){return e.call(t).slice(8,-1)}},648:(t,e,n)=>{var r=n(1694),o=n(4326),i=n(5112)("toStringTag"),a="Arguments"==o(function(){return arguments}());t.exports=r?o:function(t){var e,n,r;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=function(t,e){try{return t[e]}catch(t){}}(e=Object(t),i))?n:a?o(e):"Object"==(r=o(e))&&"function"==typeof e.callee?"Arguments":r}},9920:(t,e,n)=>{var r=n(6656),o=n(3887),i=n(1236),a=n(3070);t.exports=function(t,e){for(var n=o(e),s=a.f,u=i.f,c=0;c{var r=n(7293);t.exports=!r((function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}))},4994:(t,e,n)=>{"use strict";var r=n(3383).IteratorPrototype,o=n(30),i=n(9114),a=n(8003),s=n(7497),u=function(){return this};t.exports=function(t,e,n){var c=e+" Iterator";return t.prototype=o(r,{next:i(1,n)}),a(t,c,!1,!0),s[c]=u,t}},8880:(t,e,n)=>{var r=n(9781),o=n(3070),i=n(9114);t.exports=r?function(t,e,n){return o.f(t,e,i(1,n))}:function(t,e,n){return t[e]=n,t}},9114:t=>{t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},6135:(t,e,n)=>{"use strict";var r=n(7593),o=n(3070),i=n(9114);t.exports=function(t,e,n){var a=r(e);a in t?o.f(t,a,i(0,n)):t[a]=n}},654:(t,e,n)=>{"use strict";var r=n(2109),o=n(4994),i=n(9518),a=n(7674),s=n(8003),u=n(8880),c=n(1320),l=n(5112),f=n(1913),p=n(7497),d=n(3383),h=d.IteratorPrototype,v=d.BUGGY_SAFARI_ITERATORS,g=l("iterator"),m="keys",y="values",b="entries",_=function(){return this};t.exports=function(t,e,n,l,d,x,w){o(n,e,l);var S,O,A,C=function(t){if(t===d&&j)return j;if(!v&&t in $)return $[t];switch(t){case m:case y:case b:return function(){return new n(this,t)}}return function(){return new n(this)}},k=e+" Iterator",T=!1,$=t.prototype,E=$[g]||$["@@iterator"]||d&&$[d],j=!v&&E||C(d),L="Array"==e&&$.entries||E;if(L&&(S=i(L.call(new t)),h!==Object.prototype&&S.next&&(f||i(S)===h||(a?a(S,h):"function"!=typeof S[g]&&u(S,g,_)),s(S,k,!0,!0),f&&(p[k]=_))),d==y&&E&&E.name!==y&&(T=!0,j=function(){return E.call(this)}),f&&!w||$[g]===j||u($,g,j),p[e]=j,d)if(O={values:C(y),keys:x?j:C(m),entries:C(b)},w)for(A in O)(v||T||!(A in $))&&c($,A,O[A]);else r({target:e,proto:!0,forced:v||T},O);return O}},7235:(t,e,n)=>{var r=n(857),o=n(6656),i=n(6061),a=n(3070).f;t.exports=function(t){var e=r.Symbol||(r.Symbol={});o(e,t)||a(e,t,{value:i.f(t)})}},9781:(t,e,n)=>{var r=n(7293);t.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},317:(t,e,n)=>{var r=n(7854),o=n(111),i=r.document,a=o(i)&&o(i.createElement);t.exports=function(t){return a?i.createElement(t):{}}},8324:t=>{t.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},8113:(t,e,n)=>{var r=n(5005);t.exports=r("navigator","userAgent")||""},7392:(t,e,n)=>{var r,o,i=n(7854),a=n(8113),s=i.process,u=s&&s.versions,c=u&&u.v8;c?o=(r=c.split("."))[0]<4?1:r[0]+r[1]:a&&(!(r=a.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=a.match(/Chrome\/(\d+)/))&&(o=r[1]),t.exports=o&&+o},748:t=>{t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:(t,e,n)=>{var r=n(7854),o=n(1236).f,i=n(8880),a=n(1320),s=n(3505),u=n(9920),c=n(4705);t.exports=function(t,e){var n,l,f,p,d,h=t.target,v=t.global,g=t.stat;if(n=v?r:g?r[h]||s(h,{}):(r[h]||{}).prototype)for(l in e){if(p=e[l],f=t.noTargetGet?(d=o(n,l))&&d.value:n[l],!c(v?l:h+(g?".":"#")+l,t.forced)&&void 0!==f){if(typeof p==typeof f)continue;u(p,f)}(t.sham||f&&f.sham)&&i(p,"sham",!0),a(n,l,p,t)}}},7293:t=>{t.exports=function(t){try{return!!t()}catch(t){return!0}}},7007:(t,e,n)=>{"use strict";n(4916);var r=n(1320),o=n(2261),i=n(7293),a=n(5112),s=n(8880),u=a("species"),c=RegExp.prototype;t.exports=function(t,e,n,l){var f=a(t),p=!i((function(){var e={};return e[f]=function(){return 7},7!=""[t](e)})),d=p&&!i((function(){var e=!1,n=/a/;return"split"===t&&((n={}).constructor={},n.constructor[u]=function(){return n},n.flags="",n[f]=/./[f]),n.exec=function(){return e=!0,null},n[f](""),!e}));if(!p||!d||n){var h=/./[f],v=e(f,""[t],(function(t,e,n,r,i){var a=e.exec;return a===o||a===c.exec?p&&!i?{done:!0,value:h.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}));r(String.prototype,t,v[0]),r(c,f,v[1])}l&&s(c[f],"sham",!0)}},9974:(t,e,n)=>{var r=n(3099);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 0:return function(){return t.call(e)};case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}}},5005:(t,e,n)=>{var r=n(857),o=n(7854),i=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?i(r[t])||i(o[t]):r[t]&&r[t][e]||o[t]&&o[t][e]}},1246:(t,e,n)=>{var r=n(648),o=n(7497),i=n(5112)("iterator");t.exports=function(t){if(null!=t)return t[i]||t["@@iterator"]||o[r(t)]}},647:(t,e,n)=>{var r=n(7908),o=Math.floor,i="".replace,a=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,s=/\$([$&'`]|\d{1,2})/g;t.exports=function(t,e,n,u,c,l){var f=n+t.length,p=u.length,d=s;return void 0!==c&&(c=r(c),d=a),i.call(l,d,(function(r,i){var a;switch(i.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,n);case"'":return e.slice(f);case"<":a=c[i.slice(1,-1)];break;default:var s=+i;if(0===s)return r;if(s>p){var l=o(s/10);return 0===l?r:l<=p?void 0===u[l-1]?i.charAt(1):u[l-1]+i.charAt(1):r}a=u[s-1]}return void 0===a?"":a}))}},7854:(t,e,n)=>{var r=function(t){return t&&t.Math==Math&&t};t.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},6656:(t,e,n)=>{var r=n(7908),o={}.hasOwnProperty;t.exports=Object.hasOwn||function(t,e){return o.call(r(t),e)}},3501:t=>{t.exports={}},490:(t,e,n)=>{var r=n(5005);t.exports=r("document","documentElement")},4664:(t,e,n)=>{var r=n(9781),o=n(7293),i=n(317);t.exports=!r&&!o((function(){return 7!=Object.defineProperty(i("div"),"a",{get:function(){return 7}}).a}))},8361:(t,e,n)=>{var r=n(7293),o=n(4326),i="".split;t.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==o(t)?i.call(t,""):Object(t)}:Object},9587:(t,e,n)=>{var r=n(111),o=n(7674);t.exports=function(t,e,n){var i,a;return o&&"function"==typeof(i=e.constructor)&&i!==n&&r(a=i.prototype)&&a!==n.prototype&&o(t,a),t}},2788:(t,e,n)=>{var r=n(5465),o=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(t){return o.call(t)}),t.exports=r.inspectSource},9909:(t,e,n)=>{var r,o,i,a=n(8536),s=n(7854),u=n(111),c=n(8880),l=n(6656),f=n(5465),p=n(6200),d=n(3501),h="Object already initialized",v=s.WeakMap;if(a||f.state){var g=f.state||(f.state=new v),m=g.get,y=g.has,b=g.set;r=function(t,e){if(y.call(g,t))throw new TypeError(h);return e.facade=t,b.call(g,t,e),e},o=function(t){return m.call(g,t)||{}},i=function(t){return y.call(g,t)}}else{var _=p("state");d[_]=!0,r=function(t,e){if(l(t,_))throw new TypeError(h);return e.facade=t,c(t,_,e),e},o=function(t){return l(t,_)?t[_]:{}},i=function(t){return l(t,_)}}t.exports={set:r,get:o,has:i,enforce:function(t){return i(t)?o(t):r(t,{})},getterFor:function(t){return function(e){var n;if(!u(e)||(n=o(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return n}}}},7659:(t,e,n)=>{var r=n(5112),o=n(7497),i=r("iterator"),a=Array.prototype;t.exports=function(t){return void 0!==t&&(o.Array===t||a[i]===t)}},3157:(t,e,n)=>{var r=n(4326);t.exports=Array.isArray||function(t){return"Array"==r(t)}},4705:(t,e,n)=>{var r=n(7293),o=/#|\.prototype\./,i=function(t,e){var n=s[a(t)];return n==c||n!=u&&("function"==typeof e?r(e):!!e)},a=i.normalize=function(t){return String(t).replace(o,".").toLowerCase()},s=i.data={},u=i.NATIVE="N",c=i.POLYFILL="P";t.exports=i},111:t=>{t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},1913:t=>{t.exports=!1},7850:(t,e,n)=>{var r=n(111),o=n(4326),i=n(5112)("match");t.exports=function(t){var e;return r(t)&&(void 0!==(e=t[i])?!!e:"RegExp"==o(t))}},9212:(t,e,n)=>{var r=n(9670);t.exports=function(t){var e=t.return;if(void 0!==e)return r(e.call(t)).value}},3383:(t,e,n)=>{"use strict";var r,o,i,a=n(7293),s=n(9518),u=n(8880),c=n(6656),l=n(5112),f=n(1913),p=l("iterator"),d=!1;[].keys&&("next"in(i=[].keys())?(o=s(s(i)))!==Object.prototype&&(r=o):d=!0);var h=null==r||a((function(){var t={};return r[p].call(t)!==t}));h&&(r={}),f&&!h||c(r,p)||u(r,p,(function(){return this})),t.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:d}},7497:t=>{t.exports={}},133:(t,e,n)=>{var r=n(7392),o=n(7293);t.exports=!!Object.getOwnPropertySymbols&&!o((function(){var t=Symbol();return!String(t)||!(Object(t)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},8536:(t,e,n)=>{var r=n(7854),o=n(2788),i=r.WeakMap;t.exports="function"==typeof i&&/native code/.test(o(i))},30:(t,e,n)=>{var r,o=n(9670),i=n(6048),a=n(748),s=n(3501),u=n(490),c=n(317),l=n(6200),f=l("IE_PROTO"),p=function(){},d=function(t){return"\r\n","import mod from \"-!../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./belongsTo.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./belongsTo.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./belongsTo.vue?vue&type=template&id=6af254ea&\"\nimport script from \"./belongsTo.vue?vue&type=script&lang=js&\"\nexport * from \"./belongsTo.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('v-select',{ref:\"select\",attrs:{\"id\":_vm.name,\"loading\":_vm.loading,\"clearable\":false,\"label\":\"label\",\"options\":_vm.data,\"filterable\":true,\"disabled\":_vm.dReadonly||_vm.dDisabled,\"searchable\":true},on:{\"search\":_vm.search},model:{value:(_vm.value),callback:function ($$v) {_vm.value=$$v},expression:\"value\"}},[_c('li',{staticClass:\"pagination\",attrs:{\"slot\":\"list-footer\"},slot:\"list-footer\"},[_c('button',{staticClass:\"btn btn-sm btn-primary mt-2 mb-2 ms-2 me-2\",attrs:{\"disabled\":!_vm.hasPrevPage},on:{\"click\":function($event){$event.preventDefault(); _vm.page -= 1}}},[_vm._v(_vm._s(_vm.__('Précédent')))]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-sm btn-primary mt-2 mb-2\",attrs:{\"disabled\":!_vm.hasNextPage},on:{\"click\":function($event){$event.preventDefault(); _vm.page += 1}}},[_vm._v(_vm._s(_vm.__('Suivant')))])])]),_vm._v(\" \"),_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.value.value),expression:\"value.value\"}],attrs:{\"type\":\"hidden\",\"name\":_vm.name},domProps:{\"value\":(_vm.value.value)},on:{\"input\":function($event){if($event.target.composing){ return; }_vm.$set(_vm.value, \"value\", $event.target.value)}}})],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\r\n\r\n\r\n","import mod from \"-!../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./belongsToMany.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./belongsToMany.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./belongsToMany.vue?vue&type=template&id=145203e1&\"\nimport script from \"./belongsToMany.vue?vue&type=script&lang=js&\"\nexport * from \"./belongsToMany.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('v-select',{ref:\"select\",attrs:{\"id\":_vm.name,\"loading\":_vm.loading,\"clearable\":true,\"label\":\"label\",\"options\":_vm.data,\"multiple\":\"\",\"disabled\":_vm.dReadonly||_vm.dDisabled,\"filterable\":true,\"searchable\":true},on:{\"search\":_vm.search},model:{value:(_vm.values),callback:function ($$v) {_vm.values=$$v},expression:\"values\"}},[_c('li',{staticClass:\"pagination\",attrs:{\"slot\":\"list-footer\"},slot:\"list-footer\"},[_c('button',{staticClass:\"btn btn-sm btn-primary mt-2 mb-2 ms-2 me-2\",attrs:{\"disabled\":!_vm.hasPrevPage},on:{\"click\":function($event){$event.preventDefault(); _vm.page -= 1}}},[_vm._v(_vm._s(_vm.__('Précédent')))]),_vm._v(\" \"),_c('button',{staticClass:\"btn btn-sm btn-primary mt-2 mb-2\",attrs:{\"disabled\":!_vm.hasNextPage},on:{\"click\":function($event){$event.preventDefault(); _vm.page += 1}}},[_vm._v(_vm._s(_vm.__('Suivant')))])])]),_vm._v(\" \"),_vm._l((_vm.values),function(item){return _c('input',{key:item.value,attrs:{\"type\":\"hidden\",\"name\":_vm.name+'[]'},domProps:{\"value\":item.value}})})],2)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","/* globals __VUE_SSR_CONTEXT__ */\n\n// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).\n// This module is a runtime utility for cleaner component module output and will\n// be included in the final webpack user bundle.\n\nexport default function normalizeComponent (\n scriptExports,\n render,\n staticRenderFns,\n functionalTemplate,\n injectStyles,\n scopeId,\n moduleIdentifier, /* server only */\n shadowMode /* vue-cli only */\n) {\n // Vue.extend constructor export interop\n var options = typeof scriptExports === 'function'\n ? scriptExports.options\n : scriptExports\n\n // render functions\n if (render) {\n options.render = render\n options.staticRenderFns = staticRenderFns\n options._compiled = true\n }\n\n // functional template\n if (functionalTemplate) {\n options.functional = true\n }\n\n // scopedId\n if (scopeId) {\n options._scopeId = 'data-v-' + scopeId\n }\n\n var hook\n if (moduleIdentifier) { // server build\n hook = function (context) {\n // 2.3 injection\n context =\n context || // cached call\n (this.$vnode && this.$vnode.ssrContext) || // stateful\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional\n // 2.2 with runInNewContext: true\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\n context = __VUE_SSR_CONTEXT__\n }\n // inject component styles\n if (injectStyles) {\n injectStyles.call(this, context)\n }\n // register component module identifier for async chunk inferrence\n if (context && context._registeredComponents) {\n context._registeredComponents.add(moduleIdentifier)\n }\n }\n // used by ssr in case component is cached and beforeCreate\n // never gets called\n options._ssrRegister = hook\n } else if (injectStyles) {\n hook = shadowMode\n ? function () {\n injectStyles.call(\n this,\n (options.functional ? this.parent : this).$root.$options.shadowRoot\n )\n }\n : injectStyles\n }\n\n if (hook) {\n if (options.functional) {\n // for template-only hot-reload because in that case the render fn doesn't\n // go through the normalizer\n options._injectStyles = hook\n // register for functional component in vue file\n var originalRender = options.render\n options.render = function renderWithStyleInjection (h, context) {\n hook.call(context)\n return originalRender(h, context)\n }\n } else {\n // inject component registration as beforeCreate hook\n var existing = options.beforeCreate\n options.beforeCreate = existing\n ? [].concat(existing, hook)\n : [hook]\n }\n }\n\n return {\n exports: scriptExports,\n options: options\n }\n}\n","!function(t,e){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports.VueSelect=e():t.VueSelect=e()}(\"undefined\"!=typeof self?self:this,(function(){return function(t){var e={};function n(o){if(e[o])return e[o].exports;var i=e[o]={i:o,l:!1,exports:{}};return t[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(t,\"__esModule\",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&\"object\"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,\"default\",{enumerable:!0,value:t}),2&e&&\"string\"!=typeof t)for(var i in t)n.d(o,i,function(e){return t[e]}.bind(null,i));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,\"a\",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p=\"/\",n(n.s=8)}([function(t,e,n){var o=n(4),i=n(5),s=n(6);t.exports=function(t){return o(t)||i(t)||s()}},function(t,e){function n(e){return\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?t.exports=n=function(t){return typeof t}:t.exports=n=function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t},n(e)}t.exports=n},function(t,e,n){},function(t,e){t.exports=function(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}},function(t,e){t.exports=function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);en.bottom)return this.$refs.dropdownMenu.scrollTop=e.offsetTop-(n.height-r)}},getDropdownViewport:function(){return this.$refs.dropdownMenu?this.$refs.dropdownMenu.getBoundingClientRect():{height:0,top:0,bottom:0}}}},u={data:function(){return{typeAheadPointer:-1}},watch:{filteredOptions:function(){for(var t=0;t=0;t--)if(this.selectable(this.filteredOptions[t])){this.typeAheadPointer=t;break}},typeAheadDown:function(){for(var t=this.typeAheadPointer+1;t0&&void 0!==arguments[0]?arguments[0]:null;return this.mutableLoading=null==t?!this.mutableLoading:t}}};function h(t,e,n,o,i,s,r,a){var l,c=\"function\"==typeof t?t.options:t;if(e&&(c.render=e,c.staticRenderFns=n,c._compiled=!0),o&&(c.functional=!0),s&&(c._scopeId=\"data-v-\"+s),r?(l=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||\"undefined\"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),i&&i.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(r)},c._ssrRegister=l):i&&(l=a?function(){i.call(this,this.$root.$options.shadowRoot)}:i),l)if(c.functional){c._injectStyles=l;var u=c.render;c.render=function(t,e){return l.call(e),u(t,e)}}else{var p=c.beforeCreate;c.beforeCreate=p?[].concat(p,l):[l]}return{exports:t,options:c}}var d={Deselect:h({},(function(){var t=this.$createElement,e=this._self._c||t;return e(\"svg\",{attrs:{xmlns:\"http://www.w3.org/2000/svg\",width:\"10\",height:\"10\"}},[e(\"path\",{attrs:{d:\"M6.895455 5l2.842897-2.842898c.348864-.348863.348864-.914488 0-1.263636L9.106534.261648c-.348864-.348864-.914489-.348864-1.263636 0L5 3.104545 2.157102.261648c-.348863-.348864-.914488-.348864-1.263636 0L.261648.893466c-.348864.348864-.348864.914489 0 1.263636L3.104545 5 .261648 7.842898c-.348864.348863-.348864.914488 0 1.263636l.631818.631818c.348864.348864.914773.348864 1.263636 0L5 6.895455l2.842898 2.842897c.348863.348864.914772.348864 1.263636 0l.631818-.631818c.348864-.348864.348864-.914489 0-1.263636L6.895455 5z\"}})])}),[],!1,null,null,null).exports,OpenIndicator:h({},(function(){var t=this.$createElement,e=this._self._c||t;return e(\"svg\",{attrs:{xmlns:\"http://www.w3.org/2000/svg\",width:\"14\",height:\"10\"}},[e(\"path\",{attrs:{d:\"M9.211364 7.59931l4.48338-4.867229c.407008-.441854.407008-1.158247 0-1.60046l-.73712-.80023c-.407008-.441854-1.066904-.441854-1.474243 0L7 5.198617 2.51662.33139c-.407008-.441853-1.066904-.441853-1.474243 0l-.737121.80023c-.407008.441854-.407008 1.158248 0 1.600461l4.48338 4.867228L7 10l2.211364-2.40069z\"}})])}),[],!1,null,null,null).exports},f={inserted:function(t,e,n){var o=n.context;if(o.appendToBody){var i=o.$refs.toggle.getBoundingClientRect(),s=i.height,r=i.top,a=i.left,l=i.width,c=window.scrollX||window.pageXOffset,u=window.scrollY||window.pageYOffset;t.unbindPosition=o.calculatePosition(t,o,{width:l+\"px\",left:c+a+\"px\",top:u+r+s+\"px\"}),document.body.appendChild(t)}},unbind:function(t,e,n){n.context.appendToBody&&(t.unbindPosition&&\"function\"==typeof t.unbindPosition&&t.unbindPosition(),t.parentNode&&t.parentNode.removeChild(t))}};var y=function(t){var e={};return Object.keys(t).sort().forEach((function(n){e[n]=t[n]})),JSON.stringify(e)},b=0;var g=function(){return++b};function v(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function m(t){for(var e=1;e-1}},filter:{type:Function,default:function(t,e){var n=this;return t.filter((function(t){var o=n.getOptionLabel(t);return\"number\"==typeof o&&(o=o.toString()),n.filterBy(t,o,e)}))}},createOption:{type:Function,default:function(t){return\"object\"===r()(this.optionList[0])?l()({},this.label,t):t}},resetOnOptionsChange:{default:!1,validator:function(t){return[\"function\",\"boolean\"].includes(r()(t))}},clearSearchOnBlur:{type:Function,default:function(t){var e=t.clearSearchOnSelect,n=t.multiple;return e&&!n}},noDrop:{type:Boolean,default:!1},inputId:{type:String},dir:{type:String,default:\"auto\"},selectOnTab:{type:Boolean,default:!1},selectOnKeyCodes:{type:Array,default:function(){return[13]}},searchInputQuerySelector:{type:String,default:\"[type=search]\"},mapKeydown:{type:Function,default:function(t,e){return t}},appendToBody:{type:Boolean,default:!1},calculatePosition:{type:Function,default:function(t,e,n){var o=n.width,i=n.top,s=n.left;t.style.top=i,t.style.left=s,t.style.width=o}}},data:function(){return{uid:g(),search:\"\",open:!1,isComposing:!1,pushedTags:[],_value:[]}},watch:{options:function(t,e){var n=this;!this.taggable&&(\"function\"==typeof n.resetOnOptionsChange?n.resetOnOptionsChange(t,e,n.selectedValue):n.resetOnOptionsChange)&&this.clearSelection(),this.value&&this.isTrackingValues&&this.setInternalValueFromOptions(this.value)},value:function(t){this.isTrackingValues&&this.setInternalValueFromOptions(t)},multiple:function(){this.clearSelection()},open:function(t){this.$emit(t?\"open\":\"close\")}},created:function(){this.mutableLoading=this.loading,void 0!==this.value&&this.isTrackingValues&&this.setInternalValueFromOptions(this.value),this.$on(\"option:created\",this.pushTag)},methods:{setInternalValueFromOptions:function(t){var e=this;Array.isArray(t)?this.$data._value=t.map((function(t){return e.findOptionFromReducedValue(t)})):this.$data._value=this.findOptionFromReducedValue(t)},select:function(t){this.$emit(\"option:selecting\",t),this.isOptionSelected(t)||(this.taggable&&!this.optionExists(t)&&this.$emit(\"option:created\",t),this.multiple&&(t=this.selectedValue.concat(t)),this.updateValue(t),this.$emit(\"option:selected\",t)),this.onAfterSelect(t)},deselect:function(t){var e=this;this.$emit(\"option:deselecting\",t),this.updateValue(this.selectedValue.filter((function(n){return!e.optionComparator(n,t)}))),this.$emit(\"option:deselected\",t)},clearSelection:function(){this.updateValue(this.multiple?[]:null)},onAfterSelect:function(t){this.closeOnSelect&&(this.open=!this.open,this.searchEl.blur()),this.clearSearchOnSelect&&(this.search=\"\")},updateValue:function(t){var e=this;void 0===this.value&&(this.$data._value=t),null!==t&&(t=Array.isArray(t)?t.map((function(t){return e.reduce(t)})):this.reduce(t)),this.$emit(\"input\",t)},toggleDropdown:function(t){var e=t.target!==this.searchEl;e&&t.preventDefault();var n=[].concat(i()(this.$refs.deselectButtons||[]),i()([this.$refs.clearButton]||!1));void 0===this.searchEl||n.filter(Boolean).some((function(e){return e.contains(t.target)||e===t.target}))?t.preventDefault():this.open&&e?this.searchEl.blur():this.disabled||(this.open=!0,this.searchEl.focus())},isOptionSelected:function(t){var e=this;return this.selectedValue.some((function(n){return e.optionComparator(n,t)}))},optionComparator:function(t,e){return this.getOptionKey(t)===this.getOptionKey(e)},findOptionFromReducedValue:function(t){var e=this,n=[].concat(i()(this.options),i()(this.pushedTags)).filter((function(n){return JSON.stringify(e.reduce(n))===JSON.stringify(t)}));return 1===n.length?n[0]:n.find((function(t){return e.optionComparator(t,e.$data._value)}))||t},closeSearchOptions:function(){this.open=!1,this.$emit(\"search:blur\")},maybeDeleteValue:function(){if(!this.searchEl.value.length&&this.selectedValue&&this.selectedValue.length&&this.clearable){var t=null;this.multiple&&(t=i()(this.selectedValue.slice(0,this.selectedValue.length-1))),this.updateValue(t)}},optionExists:function(t){var e=this;return this.optionList.some((function(n){return e.optionComparator(n,t)}))},normalizeOptionForSlot:function(t){return\"object\"===r()(t)?t:l()({},this.label,t)},pushTag:function(t){this.pushedTags.push(t)},onEscape:function(){this.search.length?this.search=\"\":this.searchEl.blur()},onSearchBlur:function(){if(!this.mousedown||this.searching){var t=this.clearSearchOnSelect,e=this.multiple;return this.clearSearchOnBlur({clearSearchOnSelect:t,multiple:e})&&(this.search=\"\"),void this.closeSearchOptions()}this.mousedown=!1,0!==this.search.length||0!==this.options.length||this.closeSearchOptions()},onSearchFocus:function(){this.open=!0,this.$emit(\"search:focus\")},onMousedown:function(){this.mousedown=!0},onMouseUp:function(){this.mousedown=!1},onSearchKeyDown:function(t){var e=this,n=function(t){return t.preventDefault(),!e.isComposing&&e.typeAheadSelect()},o={8:function(t){return e.maybeDeleteValue()},9:function(t){return e.onTab()},27:function(t){return e.onEscape()},38:function(t){return t.preventDefault(),e.typeAheadUp()},40:function(t){return t.preventDefault(),e.typeAheadDown()}};this.selectOnKeyCodes.forEach((function(t){return o[t]=n}));var i=this.mapKeydown(o,this);if(\"function\"==typeof i[t.keyCode])return i[t.keyCode](t)}},computed:{isTrackingValues:function(){return void 0===this.value||this.$options.propsData.hasOwnProperty(\"reduce\")},selectedValue:function(){var t=this.value;return this.isTrackingValues&&(t=this.$data._value),t?[].concat(t):[]},optionList:function(){return this.options.concat(this.pushTags?this.pushedTags:[])},searchEl:function(){return this.$scopedSlots.search?this.$refs.selectedOptions.querySelector(this.searchInputQuerySelector):this.$refs.search},scope:function(){var t=this,e={search:this.search,loading:this.loading,searching:this.searching,filteredOptions:this.filteredOptions};return{search:{attributes:m({disabled:this.disabled,placeholder:this.searchPlaceholder,tabindex:this.tabindex,readonly:!this.searchable,id:this.inputId,\"aria-autocomplete\":\"list\",\"aria-labelledby\":\"vs\".concat(this.uid,\"__combobox\"),\"aria-controls\":\"vs\".concat(this.uid,\"__listbox\"),ref:\"search\",type:\"search\",autocomplete:this.autocomplete,value:this.search},this.dropdownOpen&&this.filteredOptions[this.typeAheadPointer]?{\"aria-activedescendant\":\"vs\".concat(this.uid,\"__option-\").concat(this.typeAheadPointer)}:{}),events:{compositionstart:function(){return t.isComposing=!0},compositionend:function(){return t.isComposing=!1},keydown:this.onSearchKeyDown,blur:this.onSearchBlur,focus:this.onSearchFocus,input:function(e){return t.search=e.target.value}}},spinner:{loading:this.mutableLoading},noOptions:{search:this.search,loading:this.loading,searching:this.searching},openIndicator:{attributes:{ref:\"openIndicator\",role:\"presentation\",class:\"vs__open-indicator\"}},listHeader:e,listFooter:e,header:m({},e,{deselect:this.deselect}),footer:m({},e,{deselect:this.deselect})}},childComponents:function(){return m({},d,{},this.components)},stateClasses:function(){return{\"vs--open\":this.dropdownOpen,\"vs--single\":!this.multiple,\"vs--searching\":this.searching&&!this.noDrop,\"vs--searchable\":this.searchable&&!this.noDrop,\"vs--unsearchable\":!this.searchable,\"vs--loading\":this.mutableLoading,\"vs--disabled\":this.disabled}},searching:function(){return!!this.search},dropdownOpen:function(){return!this.noDrop&&(this.open&&!this.mutableLoading)},searchPlaceholder:function(){if(this.isValueEmpty&&this.placeholder)return this.placeholder},filteredOptions:function(){var t=[].concat(this.optionList);if(!this.filterable&&!this.taggable)return t;var e=this.search.length?this.filter(t,this.search,this):t;if(this.taggable&&this.search.length){var n=this.createOption(this.search);this.optionExists(n)||e.unshift(n)}return e},isValueEmpty:function(){return 0===this.selectedValue.length},showClearButton:function(){return!this.multiple&&this.clearable&&!this.open&&!this.isValueEmpty}}},O=(n(7),h(_,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return n(\"div\",{staticClass:\"v-select\",class:t.stateClasses,attrs:{dir:t.dir}},[t._t(\"header\",null,null,t.scope.header),t._v(\" \"),n(\"div\",{ref:\"toggle\",staticClass:\"vs__dropdown-toggle\",attrs:{id:\"vs\"+t.uid+\"__combobox\",role:\"combobox\",\"aria-expanded\":t.dropdownOpen.toString(),\"aria-owns\":\"vs\"+t.uid+\"__listbox\",\"aria-label\":\"Search for option\"},on:{mousedown:function(e){return t.toggleDropdown(e)}}},[n(\"div\",{ref:\"selectedOptions\",staticClass:\"vs__selected-options\"},[t._l(t.selectedValue,(function(e){return t._t(\"selected-option-container\",[n(\"span\",{key:t.getOptionKey(e),staticClass:\"vs__selected\"},[t._t(\"selected-option\",[t._v(\"\\n \"+t._s(t.getOptionLabel(e))+\"\\n \")],null,t.normalizeOptionForSlot(e)),t._v(\" \"),t.multiple?n(\"button\",{ref:\"deselectButtons\",refInFor:!0,staticClass:\"vs__deselect\",attrs:{disabled:t.disabled,type:\"button\",title:\"Deselect \"+t.getOptionLabel(e),\"aria-label\":\"Deselect \"+t.getOptionLabel(e)},on:{click:function(n){return t.deselect(e)}}},[n(t.childComponents.Deselect,{tag:\"component\"})],1):t._e()],2)],{option:t.normalizeOptionForSlot(e),deselect:t.deselect,multiple:t.multiple,disabled:t.disabled})})),t._v(\" \"),t._t(\"search\",[n(\"input\",t._g(t._b({staticClass:\"vs__search\"},\"input\",t.scope.search.attributes,!1),t.scope.search.events))],null,t.scope.search)],2),t._v(\" \"),n(\"div\",{ref:\"actions\",staticClass:\"vs__actions\"},[n(\"button\",{directives:[{name:\"show\",rawName:\"v-show\",value:t.showClearButton,expression:\"showClearButton\"}],ref:\"clearButton\",staticClass:\"vs__clear\",attrs:{disabled:t.disabled,type:\"button\",title:\"Clear Selected\",\"aria-label\":\"Clear Selected\"},on:{click:t.clearSelection}},[n(t.childComponents.Deselect,{tag:\"component\"})],1),t._v(\" \"),t._t(\"open-indicator\",[t.noDrop?t._e():n(t.childComponents.OpenIndicator,t._b({tag:\"component\"},\"component\",t.scope.openIndicator.attributes,!1))],null,t.scope.openIndicator),t._v(\" \"),t._t(\"spinner\",[n(\"div\",{directives:[{name:\"show\",rawName:\"v-show\",value:t.mutableLoading,expression:\"mutableLoading\"}],staticClass:\"vs__spinner\"},[t._v(\"Loading...\")])],null,t.scope.spinner)],2)]),t._v(\" \"),n(\"transition\",{attrs:{name:t.transition}},[t.dropdownOpen?n(\"ul\",{directives:[{name:\"append-to-body\",rawName:\"v-append-to-body\"}],key:\"vs\"+t.uid+\"__listbox\",ref:\"dropdownMenu\",staticClass:\"vs__dropdown-menu\",attrs:{id:\"vs\"+t.uid+\"__listbox\",role:\"listbox\",tabindex:\"-1\"},on:{mousedown:function(e){return e.preventDefault(),t.onMousedown(e)},mouseup:t.onMouseUp}},[t._t(\"list-header\",null,null,t.scope.listHeader),t._v(\" \"),t._l(t.filteredOptions,(function(e,o){return n(\"li\",{key:t.getOptionKey(e),staticClass:\"vs__dropdown-option\",class:{\"vs__dropdown-option--selected\":t.isOptionSelected(e),\"vs__dropdown-option--highlight\":o===t.typeAheadPointer,\"vs__dropdown-option--disabled\":!t.selectable(e)},attrs:{role:\"option\",id:\"vs\"+t.uid+\"__option-\"+o,\"aria-selected\":o===t.typeAheadPointer||null},on:{mouseover:function(n){t.selectable(e)&&(t.typeAheadPointer=o)},mousedown:function(n){n.preventDefault(),n.stopPropagation(),t.selectable(e)&&t.select(e)}}},[t._t(\"option\",[t._v(\"\\n \"+t._s(t.getOptionLabel(e))+\"\\n \")],null,t.normalizeOptionForSlot(e))],2)})),t._v(\" \"),0===t.filteredOptions.length?n(\"li\",{staticClass:\"vs__no-options\"},[t._t(\"no-options\",[t._v(\"Sorry, no matching options.\")],null,t.scope.noOptions)],2):t._e(),t._v(\" \"),t._t(\"list-footer\",null,null,t.scope.listFooter)],2):n(\"ul\",{staticStyle:{display:\"none\",visibility:\"hidden\"},attrs:{id:\"vs\"+t.uid+\"__listbox\",role:\"listbox\"}})]),t._v(\" \"),t._t(\"footer\",null,null,t.scope.footer)],2)}),[],!1,null,null,null).exports),w={ajax:p,pointer:u,pointerScroll:c};n.d(e,\"VueSelect\",(function(){return O})),n.d(e,\"mixins\",(function(){return w}));e.default=O}])}));\n//# sourceMappingURL=vue-select.js.map","/*!\n * Vue.js v2.6.14\n * (c) 2014-2021 Evan You\n * Released under the MIT License.\n */\n/* */\n\nvar emptyObject = Object.freeze({});\n\n// These helpers produce better VM code in JS engines due to their\n// explicitness and function inlining.\nfunction isUndef (v) {\n return v === undefined || v === null\n}\n\nfunction isDef (v) {\n return v !== undefined && v !== null\n}\n\nfunction isTrue (v) {\n return v === true\n}\n\nfunction isFalse (v) {\n return v === false\n}\n\n/**\n * Check if value is primitive.\n */\nfunction isPrimitive (value) {\n return (\n typeof value === 'string' ||\n typeof value === 'number' ||\n // $flow-disable-line\n typeof value === 'symbol' ||\n typeof value === 'boolean'\n )\n}\n\n/**\n * Quick object check - this is primarily used to tell\n * Objects from primitive values when we know the value\n * is a JSON-compliant type.\n */\nfunction isObject (obj) {\n return obj !== null && typeof obj === 'object'\n}\n\n/**\n * Get the raw type string of a value, e.g., [object Object].\n */\nvar _toString = Object.prototype.toString;\n\nfunction toRawType (value) {\n return _toString.call(value).slice(8, -1)\n}\n\n/**\n * Strict object type check. Only returns true\n * for plain JavaScript objects.\n */\nfunction isPlainObject (obj) {\n return _toString.call(obj) === '[object Object]'\n}\n\nfunction isRegExp (v) {\n return _toString.call(v) === '[object RegExp]'\n}\n\n/**\n * Check if val is a valid array index.\n */\nfunction isValidArrayIndex (val) {\n var n = parseFloat(String(val));\n return n >= 0 && Math.floor(n) === n && isFinite(val)\n}\n\nfunction isPromise (val) {\n return (\n isDef(val) &&\n typeof val.then === 'function' &&\n typeof val.catch === 'function'\n )\n}\n\n/**\n * Convert a value to a string that is actually rendered.\n */\nfunction toString (val) {\n return val == null\n ? ''\n : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)\n ? JSON.stringify(val, null, 2)\n : String(val)\n}\n\n/**\n * Convert an input value to a number for persistence.\n * If the conversion fails, return original string.\n */\nfunction toNumber (val) {\n var n = parseFloat(val);\n return isNaN(n) ? val : n\n}\n\n/**\n * Make a map and return a function for checking if a key\n * is in that map.\n */\nfunction makeMap (\n str,\n expectsLowerCase\n) {\n var map = Object.create(null);\n var list = str.split(',');\n for (var i = 0; i < list.length; i++) {\n map[list[i]] = true;\n }\n return expectsLowerCase\n ? function (val) { return map[val.toLowerCase()]; }\n : function (val) { return map[val]; }\n}\n\n/**\n * Check if a tag is a built-in tag.\n */\nvar isBuiltInTag = makeMap('slot,component', true);\n\n/**\n * Check if an attribute is a reserved attribute.\n */\nvar isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');\n\n/**\n * Remove an item from an array.\n */\nfunction remove (arr, item) {\n if (arr.length) {\n var index = arr.indexOf(item);\n if (index > -1) {\n return arr.splice(index, 1)\n }\n }\n}\n\n/**\n * Check whether an object has the property.\n */\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nfunction hasOwn (obj, key) {\n return hasOwnProperty.call(obj, key)\n}\n\n/**\n * Create a cached version of a pure function.\n */\nfunction cached (fn) {\n var cache = Object.create(null);\n return (function cachedFn (str) {\n var hit = cache[str];\n return hit || (cache[str] = fn(str))\n })\n}\n\n/**\n * Camelize a hyphen-delimited string.\n */\nvar camelizeRE = /-(\\w)/g;\nvar camelize = cached(function (str) {\n return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })\n});\n\n/**\n * Capitalize a string.\n */\nvar capitalize = cached(function (str) {\n return str.charAt(0).toUpperCase() + str.slice(1)\n});\n\n/**\n * Hyphenate a camelCase string.\n */\nvar hyphenateRE = /\\B([A-Z])/g;\nvar hyphenate = cached(function (str) {\n return str.replace(hyphenateRE, '-$1').toLowerCase()\n});\n\n/**\n * Simple bind polyfill for environments that do not support it,\n * e.g., PhantomJS 1.x. Technically, we don't need this anymore\n * since native bind is now performant enough in most browsers.\n * But removing it would mean breaking code that was able to run in\n * PhantomJS 1.x, so this must be kept for backward compatibility.\n */\n\n/* istanbul ignore next */\nfunction polyfillBind (fn, ctx) {\n function boundFn (a) {\n var l = arguments.length;\n return l\n ? l > 1\n ? fn.apply(ctx, arguments)\n : fn.call(ctx, a)\n : fn.call(ctx)\n }\n\n boundFn._length = fn.length;\n return boundFn\n}\n\nfunction nativeBind (fn, ctx) {\n return fn.bind(ctx)\n}\n\nvar bind = Function.prototype.bind\n ? nativeBind\n : polyfillBind;\n\n/**\n * Convert an Array-like object to a real Array.\n */\nfunction toArray (list, start) {\n start = start || 0;\n var i = list.length - start;\n var ret = new Array(i);\n while (i--) {\n ret[i] = list[i + start];\n }\n return ret\n}\n\n/**\n * Mix properties into target object.\n */\nfunction extend (to, _from) {\n for (var key in _from) {\n to[key] = _from[key];\n }\n return to\n}\n\n/**\n * Merge an Array of Objects into a single Object.\n */\nfunction toObject (arr) {\n var res = {};\n for (var i = 0; i < arr.length; i++) {\n if (arr[i]) {\n extend(res, arr[i]);\n }\n }\n return res\n}\n\n/* eslint-disable no-unused-vars */\n\n/**\n * Perform no operation.\n * Stubbing args to make Flow happy without leaving useless transpiled code\n * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).\n */\nfunction noop (a, b, c) {}\n\n/**\n * Always return false.\n */\nvar no = function (a, b, c) { return false; };\n\n/* eslint-enable no-unused-vars */\n\n/**\n * Return the same value.\n */\nvar identity = function (_) { return _; };\n\n/**\n * Generate a string containing static keys from compiler modules.\n */\nfunction genStaticKeys (modules) {\n return modules.reduce(function (keys, m) {\n return keys.concat(m.staticKeys || [])\n }, []).join(',')\n}\n\n/**\n * Check if two values are loosely equal - that is,\n * if they are plain objects, do they have the same shape?\n */\nfunction looseEqual (a, b) {\n if (a === b) { return true }\n var isObjectA = isObject(a);\n var isObjectB = isObject(b);\n if (isObjectA && isObjectB) {\n try {\n var isArrayA = Array.isArray(a);\n var isArrayB = Array.isArray(b);\n if (isArrayA && isArrayB) {\n return a.length === b.length && a.every(function (e, i) {\n return looseEqual(e, b[i])\n })\n } else if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime()\n } else if (!isArrayA && !isArrayB) {\n var keysA = Object.keys(a);\n var keysB = Object.keys(b);\n return keysA.length === keysB.length && keysA.every(function (key) {\n return looseEqual(a[key], b[key])\n })\n } else {\n /* istanbul ignore next */\n return false\n }\n } catch (e) {\n /* istanbul ignore next */\n return false\n }\n } else if (!isObjectA && !isObjectB) {\n return String(a) === String(b)\n } else {\n return false\n }\n}\n\n/**\n * Return the first index at which a loosely equal value can be\n * found in the array (if value is a plain object, the array must\n * contain an object of the same shape), or -1 if it is not present.\n */\nfunction looseIndexOf (arr, val) {\n for (var i = 0; i < arr.length; i++) {\n if (looseEqual(arr[i], val)) { return i }\n }\n return -1\n}\n\n/**\n * Ensure a function is called only once.\n */\nfunction once (fn) {\n var called = false;\n return function () {\n if (!called) {\n called = true;\n fn.apply(this, arguments);\n }\n }\n}\n\nvar SSR_ATTR = 'data-server-rendered';\n\nvar ASSET_TYPES = [\n 'component',\n 'directive',\n 'filter'\n];\n\nvar LIFECYCLE_HOOKS = [\n 'beforeCreate',\n 'created',\n 'beforeMount',\n 'mounted',\n 'beforeUpdate',\n 'updated',\n 'beforeDestroy',\n 'destroyed',\n 'activated',\n 'deactivated',\n 'errorCaptured',\n 'serverPrefetch'\n];\n\n/* */\n\n\n\nvar config = ({\n /**\n * Option merge strategies (used in core/util/options)\n */\n // $flow-disable-line\n optionMergeStrategies: Object.create(null),\n\n /**\n * Whether to suppress warnings.\n */\n silent: false,\n\n /**\n * Show production mode tip message on boot?\n */\n productionTip: process.env.NODE_ENV !== 'production',\n\n /**\n * Whether to enable devtools\n */\n devtools: process.env.NODE_ENV !== 'production',\n\n /**\n * Whether to record perf\n */\n performance: false,\n\n /**\n * Error handler for watcher errors\n */\n errorHandler: null,\n\n /**\n * Warn handler for watcher warns\n */\n warnHandler: null,\n\n /**\n * Ignore certain custom elements\n */\n ignoredElements: [],\n\n /**\n * Custom user key aliases for v-on\n */\n // $flow-disable-line\n keyCodes: Object.create(null),\n\n /**\n * Check if a tag is reserved so that it cannot be registered as a\n * component. This is platform-dependent and may be overwritten.\n */\n isReservedTag: no,\n\n /**\n * Check if an attribute is reserved so that it cannot be used as a component\n * prop. This is platform-dependent and may be overwritten.\n */\n isReservedAttr: no,\n\n /**\n * Check if a tag is an unknown element.\n * Platform-dependent.\n */\n isUnknownElement: no,\n\n /**\n * Get the namespace of an element\n */\n getTagNamespace: noop,\n\n /**\n * Parse the real tag name for the specific platform.\n */\n parsePlatformTagName: identity,\n\n /**\n * Check if an attribute must be bound using property, e.g. value\n * Platform-dependent.\n */\n mustUseProp: no,\n\n /**\n * Perform updates asynchronously. Intended to be used by Vue Test Utils\n * This will significantly reduce performance if set to false.\n */\n async: true,\n\n /**\n * Exposed for legacy reasons\n */\n _lifecycleHooks: LIFECYCLE_HOOKS\n});\n\n/* */\n\n/**\n * unicode letters used for parsing html tags, component names and property paths.\n * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname\n * skipping \\u10000-\\uEFFFF due to it freezing up PhantomJS\n */\nvar unicodeRegExp = /a-zA-Z\\u00B7\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u203F-\\u2040\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD/;\n\n/**\n * Check if a string starts with $ or _\n */\nfunction isReserved (str) {\n var c = (str + '').charCodeAt(0);\n return c === 0x24 || c === 0x5F\n}\n\n/**\n * Define a property.\n */\nfunction def (obj, key, val, enumerable) {\n Object.defineProperty(obj, key, {\n value: val,\n enumerable: !!enumerable,\n writable: true,\n configurable: true\n });\n}\n\n/**\n * Parse simple path.\n */\nvar bailRE = new RegExp((\"[^\" + (unicodeRegExp.source) + \".$_\\\\d]\"));\nfunction parsePath (path) {\n if (bailRE.test(path)) {\n return\n }\n var segments = path.split('.');\n return function (obj) {\n for (var i = 0; i < segments.length; i++) {\n if (!obj) { return }\n obj = obj[segments[i]];\n }\n return obj\n }\n}\n\n/* */\n\n// can we use __proto__?\nvar hasProto = '__proto__' in {};\n\n// Browser environment sniffing\nvar inBrowser = typeof window !== 'undefined';\nvar inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;\nvar weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();\nvar UA = inBrowser && window.navigator.userAgent.toLowerCase();\nvar isIE = UA && /msie|trident/.test(UA);\nvar isIE9 = UA && UA.indexOf('msie 9.0') > 0;\nvar isEdge = UA && UA.indexOf('edge/') > 0;\nvar isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');\nvar isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');\nvar isChrome = UA && /chrome\\/\\d+/.test(UA) && !isEdge;\nvar isPhantomJS = UA && /phantomjs/.test(UA);\nvar isFF = UA && UA.match(/firefox\\/(\\d+)/);\n\n// Firefox has a \"watch\" function on Object.prototype...\nvar nativeWatch = ({}).watch;\n\nvar supportsPassive = false;\nif (inBrowser) {\n try {\n var opts = {};\n Object.defineProperty(opts, 'passive', ({\n get: function get () {\n /* istanbul ignore next */\n supportsPassive = true;\n }\n })); // https://github.com/facebook/flow/issues/285\n window.addEventListener('test-passive', null, opts);\n } catch (e) {}\n}\n\n// this needs to be lazy-evaled because vue may be required before\n// vue-server-renderer can set VUE_ENV\nvar _isServer;\nvar isServerRendering = function () {\n if (_isServer === undefined) {\n /* istanbul ignore if */\n if (!inBrowser && !inWeex && typeof global !== 'undefined') {\n // detect presence of vue-server-renderer and avoid\n // Webpack shimming the process\n _isServer = global['process'] && global['process'].env.VUE_ENV === 'server';\n } else {\n _isServer = false;\n }\n }\n return _isServer\n};\n\n// detect devtools\nvar devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;\n\n/* istanbul ignore next */\nfunction isNative (Ctor) {\n return typeof Ctor === 'function' && /native code/.test(Ctor.toString())\n}\n\nvar hasSymbol =\n typeof Symbol !== 'undefined' && isNative(Symbol) &&\n typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys);\n\nvar _Set;\n/* istanbul ignore if */ // $flow-disable-line\nif (typeof Set !== 'undefined' && isNative(Set)) {\n // use native Set when available.\n _Set = Set;\n} else {\n // a non-standard Set polyfill that only works with primitive keys.\n _Set = /*@__PURE__*/(function () {\n function Set () {\n this.set = Object.create(null);\n }\n Set.prototype.has = function has (key) {\n return this.set[key] === true\n };\n Set.prototype.add = function add (key) {\n this.set[key] = true;\n };\n Set.prototype.clear = function clear () {\n this.set = Object.create(null);\n };\n\n return Set;\n }());\n}\n\n/* */\n\nvar warn = noop;\nvar tip = noop;\nvar generateComponentTrace = (noop); // work around flow check\nvar formatComponentName = (noop);\n\nif (process.env.NODE_ENV !== 'production') {\n var hasConsole = typeof console !== 'undefined';\n var classifyRE = /(?:^|[-_])(\\w)/g;\n var classify = function (str) { return str\n .replace(classifyRE, function (c) { return c.toUpperCase(); })\n .replace(/[-_]/g, ''); };\n\n warn = function (msg, vm) {\n var trace = vm ? generateComponentTrace(vm) : '';\n\n if (config.warnHandler) {\n config.warnHandler.call(null, msg, vm, trace);\n } else if (hasConsole && (!config.silent)) {\n console.error((\"[Vue warn]: \" + msg + trace));\n }\n };\n\n tip = function (msg, vm) {\n if (hasConsole && (!config.silent)) {\n console.warn(\"[Vue tip]: \" + msg + (\n vm ? generateComponentTrace(vm) : ''\n ));\n }\n };\n\n formatComponentName = function (vm, includeFile) {\n if (vm.$root === vm) {\n return ''\n }\n var options = typeof vm === 'function' && vm.cid != null\n ? vm.options\n : vm._isVue\n ? vm.$options || vm.constructor.options\n : vm;\n var name = options.name || options._componentTag;\n var file = options.__file;\n if (!name && file) {\n var match = file.match(/([^/\\\\]+)\\.vue$/);\n name = match && match[1];\n }\n\n return (\n (name ? (\"<\" + (classify(name)) + \">\") : \"\") +\n (file && includeFile !== false ? (\" at \" + file) : '')\n )\n };\n\n var repeat = function (str, n) {\n var res = '';\n while (n) {\n if (n % 2 === 1) { res += str; }\n if (n > 1) { str += str; }\n n >>= 1;\n }\n return res\n };\n\n generateComponentTrace = function (vm) {\n if (vm._isVue && vm.$parent) {\n var tree = [];\n var currentRecursiveSequence = 0;\n while (vm) {\n if (tree.length > 0) {\n var last = tree[tree.length - 1];\n if (last.constructor === vm.constructor) {\n currentRecursiveSequence++;\n vm = vm.$parent;\n continue\n } else if (currentRecursiveSequence > 0) {\n tree[tree.length - 1] = [last, currentRecursiveSequence];\n currentRecursiveSequence = 0;\n }\n }\n tree.push(vm);\n vm = vm.$parent;\n }\n return '\\n\\nfound in\\n\\n' + tree\n .map(function (vm, i) { return (\"\" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm)\n ? ((formatComponentName(vm[0])) + \"... (\" + (vm[1]) + \" recursive calls)\")\n : formatComponentName(vm))); })\n .join('\\n')\n } else {\n return (\"\\n\\n(found in \" + (formatComponentName(vm)) + \")\")\n }\n };\n}\n\n/* */\n\nvar uid = 0;\n\n/**\n * A dep is an observable that can have multiple\n * directives subscribing to it.\n */\nvar Dep = function Dep () {\n this.id = uid++;\n this.subs = [];\n};\n\nDep.prototype.addSub = function addSub (sub) {\n this.subs.push(sub);\n};\n\nDep.prototype.removeSub = function removeSub (sub) {\n remove(this.subs, sub);\n};\n\nDep.prototype.depend = function depend () {\n if (Dep.target) {\n Dep.target.addDep(this);\n }\n};\n\nDep.prototype.notify = function notify () {\n // stabilize the subscriber list first\n var subs = this.subs.slice();\n if (process.env.NODE_ENV !== 'production' && !config.async) {\n // subs aren't sorted in scheduler if not running async\n // we need to sort them now to make sure they fire in correct\n // order\n subs.sort(function (a, b) { return a.id - b.id; });\n }\n for (var i = 0, l = subs.length; i < l; i++) {\n subs[i].update();\n }\n};\n\n// The current target watcher being evaluated.\n// This is globally unique because only one watcher\n// can be evaluated at a time.\nDep.target = null;\nvar targetStack = [];\n\nfunction pushTarget (target) {\n targetStack.push(target);\n Dep.target = target;\n}\n\nfunction popTarget () {\n targetStack.pop();\n Dep.target = targetStack[targetStack.length - 1];\n}\n\n/* */\n\nvar VNode = function VNode (\n tag,\n data,\n children,\n text,\n elm,\n context,\n componentOptions,\n asyncFactory\n) {\n this.tag = tag;\n this.data = data;\n this.children = children;\n this.text = text;\n this.elm = elm;\n this.ns = undefined;\n this.context = context;\n this.fnContext = undefined;\n this.fnOptions = undefined;\n this.fnScopeId = undefined;\n this.key = data && data.key;\n this.componentOptions = componentOptions;\n this.componentInstance = undefined;\n this.parent = undefined;\n this.raw = false;\n this.isStatic = false;\n this.isRootInsert = true;\n this.isComment = false;\n this.isCloned = false;\n this.isOnce = false;\n this.asyncFactory = asyncFactory;\n this.asyncMeta = undefined;\n this.isAsyncPlaceholder = false;\n};\n\nvar prototypeAccessors = { child: { configurable: true } };\n\n// DEPRECATED: alias for componentInstance for backwards compat.\n/* istanbul ignore next */\nprototypeAccessors.child.get = function () {\n return this.componentInstance\n};\n\nObject.defineProperties( VNode.prototype, prototypeAccessors );\n\nvar createEmptyVNode = function (text) {\n if ( text === void 0 ) text = '';\n\n var node = new VNode();\n node.text = text;\n node.isComment = true;\n return node\n};\n\nfunction createTextVNode (val) {\n return new VNode(undefined, undefined, undefined, String(val))\n}\n\n// optimized shallow clone\n// used for static nodes and slot nodes because they may be reused across\n// multiple renders, cloning them avoids errors when DOM manipulations rely\n// on their elm reference.\nfunction cloneVNode (vnode) {\n var cloned = new VNode(\n vnode.tag,\n vnode.data,\n // #7975\n // clone children array to avoid mutating original in case of cloning\n // a child.\n vnode.children && vnode.children.slice(),\n vnode.text,\n vnode.elm,\n vnode.context,\n vnode.componentOptions,\n vnode.asyncFactory\n );\n cloned.ns = vnode.ns;\n cloned.isStatic = vnode.isStatic;\n cloned.key = vnode.key;\n cloned.isComment = vnode.isComment;\n cloned.fnContext = vnode.fnContext;\n cloned.fnOptions = vnode.fnOptions;\n cloned.fnScopeId = vnode.fnScopeId;\n cloned.asyncMeta = vnode.asyncMeta;\n cloned.isCloned = true;\n return cloned\n}\n\n/*\n * not type checking this file because flow doesn't play well with\n * dynamically accessing methods on Array prototype\n */\n\nvar arrayProto = Array.prototype;\nvar arrayMethods = Object.create(arrayProto);\n\nvar methodsToPatch = [\n 'push',\n 'pop',\n 'shift',\n 'unshift',\n 'splice',\n 'sort',\n 'reverse'\n];\n\n/**\n * Intercept mutating methods and emit events\n */\nmethodsToPatch.forEach(function (method) {\n // cache original method\n var original = arrayProto[method];\n def(arrayMethods, method, function mutator () {\n var args = [], len = arguments.length;\n while ( len-- ) args[ len ] = arguments[ len ];\n\n var result = original.apply(this, args);\n var ob = this.__ob__;\n var inserted;\n switch (method) {\n case 'push':\n case 'unshift':\n inserted = args;\n break\n case 'splice':\n inserted = args.slice(2);\n break\n }\n if (inserted) { ob.observeArray(inserted); }\n // notify change\n ob.dep.notify();\n return result\n });\n});\n\n/* */\n\nvar arrayKeys = Object.getOwnPropertyNames(arrayMethods);\n\n/**\n * In some cases we may want to disable observation inside a component's\n * update computation.\n */\nvar shouldObserve = true;\n\nfunction toggleObserving (value) {\n shouldObserve = value;\n}\n\n/**\n * Observer class that is attached to each observed\n * object. Once attached, the observer converts the target\n * object's property keys into getter/setters that\n * collect dependencies and dispatch updates.\n */\nvar Observer = function Observer (value) {\n this.value = value;\n this.dep = new Dep();\n this.vmCount = 0;\n def(value, '__ob__', this);\n if (Array.isArray(value)) {\n if (hasProto) {\n protoAugment(value, arrayMethods);\n } else {\n copyAugment(value, arrayMethods, arrayKeys);\n }\n this.observeArray(value);\n } else {\n this.walk(value);\n }\n};\n\n/**\n * Walk through all properties and convert them into\n * getter/setters. This method should only be called when\n * value type is Object.\n */\nObserver.prototype.walk = function walk (obj) {\n var keys = Object.keys(obj);\n for (var i = 0; i < keys.length; i++) {\n defineReactive$$1(obj, keys[i]);\n }\n};\n\n/**\n * Observe a list of Array items.\n */\nObserver.prototype.observeArray = function observeArray (items) {\n for (var i = 0, l = items.length; i < l; i++) {\n observe(items[i]);\n }\n};\n\n// helpers\n\n/**\n * Augment a target Object or Array by intercepting\n * the prototype chain using __proto__\n */\nfunction protoAugment (target, src) {\n /* eslint-disable no-proto */\n target.__proto__ = src;\n /* eslint-enable no-proto */\n}\n\n/**\n * Augment a target Object or Array by defining\n * hidden properties.\n */\n/* istanbul ignore next */\nfunction copyAugment (target, src, keys) {\n for (var i = 0, l = keys.length; i < l; i++) {\n var key = keys[i];\n def(target, key, src[key]);\n }\n}\n\n/**\n * Attempt to create an observer instance for a value,\n * returns the new observer if successfully observed,\n * or the existing observer if the value already has one.\n */\nfunction observe (value, asRootData) {\n if (!isObject(value) || value instanceof VNode) {\n return\n }\n var ob;\n if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {\n ob = value.__ob__;\n } else if (\n shouldObserve &&\n !isServerRendering() &&\n (Array.isArray(value) || isPlainObject(value)) &&\n Object.isExtensible(value) &&\n !value._isVue\n ) {\n ob = new Observer(value);\n }\n if (asRootData && ob) {\n ob.vmCount++;\n }\n return ob\n}\n\n/**\n * Define a reactive property on an Object.\n */\nfunction defineReactive$$1 (\n obj,\n key,\n val,\n customSetter,\n shallow\n) {\n var dep = new Dep();\n\n var property = Object.getOwnPropertyDescriptor(obj, key);\n if (property && property.configurable === false) {\n return\n }\n\n // cater for pre-defined getter/setters\n var getter = property && property.get;\n var setter = property && property.set;\n if ((!getter || setter) && arguments.length === 2) {\n val = obj[key];\n }\n\n var childOb = !shallow && observe(val);\n Object.defineProperty(obj, key, {\n enumerable: true,\n configurable: true,\n get: function reactiveGetter () {\n var value = getter ? getter.call(obj) : val;\n if (Dep.target) {\n dep.depend();\n if (childOb) {\n childOb.dep.depend();\n if (Array.isArray(value)) {\n dependArray(value);\n }\n }\n }\n return value\n },\n set: function reactiveSetter (newVal) {\n var value = getter ? getter.call(obj) : val;\n /* eslint-disable no-self-compare */\n if (newVal === value || (newVal !== newVal && value !== value)) {\n return\n }\n /* eslint-enable no-self-compare */\n if (process.env.NODE_ENV !== 'production' && customSetter) {\n customSetter();\n }\n // #7981: for accessor properties without setter\n if (getter && !setter) { return }\n if (setter) {\n setter.call(obj, newVal);\n } else {\n val = newVal;\n }\n childOb = !shallow && observe(newVal);\n dep.notify();\n }\n });\n}\n\n/**\n * Set a property on an object. Adds the new property and\n * triggers change notification if the property doesn't\n * already exist.\n */\nfunction set (target, key, val) {\n if (process.env.NODE_ENV !== 'production' &&\n (isUndef(target) || isPrimitive(target))\n ) {\n warn((\"Cannot set reactive property on undefined, null, or primitive value: \" + ((target))));\n }\n if (Array.isArray(target) && isValidArrayIndex(key)) {\n target.length = Math.max(target.length, key);\n target.splice(key, 1, val);\n return val\n }\n if (key in target && !(key in Object.prototype)) {\n target[key] = val;\n return val\n }\n var ob = (target).__ob__;\n if (target._isVue || (ob && ob.vmCount)) {\n process.env.NODE_ENV !== 'production' && warn(\n 'Avoid adding reactive properties to a Vue instance or its root $data ' +\n 'at runtime - declare it upfront in the data option.'\n );\n return val\n }\n if (!ob) {\n target[key] = val;\n return val\n }\n defineReactive$$1(ob.value, key, val);\n ob.dep.notify();\n return val\n}\n\n/**\n * Delete a property and trigger change if necessary.\n */\nfunction del (target, key) {\n if (process.env.NODE_ENV !== 'production' &&\n (isUndef(target) || isPrimitive(target))\n ) {\n warn((\"Cannot delete reactive property on undefined, null, or primitive value: \" + ((target))));\n }\n if (Array.isArray(target) && isValidArrayIndex(key)) {\n target.splice(key, 1);\n return\n }\n var ob = (target).__ob__;\n if (target._isVue || (ob && ob.vmCount)) {\n process.env.NODE_ENV !== 'production' && warn(\n 'Avoid deleting properties on a Vue instance or its root $data ' +\n '- just set it to null.'\n );\n return\n }\n if (!hasOwn(target, key)) {\n return\n }\n delete target[key];\n if (!ob) {\n return\n }\n ob.dep.notify();\n}\n\n/**\n * Collect dependencies on array elements when the array is touched, since\n * we cannot intercept array element access like property getters.\n */\nfunction dependArray (value) {\n for (var e = (void 0), i = 0, l = value.length; i < l; i++) {\n e = value[i];\n e && e.__ob__ && e.__ob__.dep.depend();\n if (Array.isArray(e)) {\n dependArray(e);\n }\n }\n}\n\n/* */\n\n/**\n * Option overwriting strategies are functions that handle\n * how to merge a parent option value and a child option\n * value into the final value.\n */\nvar strats = config.optionMergeStrategies;\n\n/**\n * Options with restrictions\n */\nif (process.env.NODE_ENV !== 'production') {\n strats.el = strats.propsData = function (parent, child, vm, key) {\n if (!vm) {\n warn(\n \"option \\\"\" + key + \"\\\" can only be used during instance \" +\n 'creation with the `new` keyword.'\n );\n }\n return defaultStrat(parent, child)\n };\n}\n\n/**\n * Helper that recursively merges two data objects together.\n */\nfunction mergeData (to, from) {\n if (!from) { return to }\n var key, toVal, fromVal;\n\n var keys = hasSymbol\n ? Reflect.ownKeys(from)\n : Object.keys(from);\n\n for (var i = 0; i < keys.length; i++) {\n key = keys[i];\n // in case the object is already observed...\n if (key === '__ob__') { continue }\n toVal = to[key];\n fromVal = from[key];\n if (!hasOwn(to, key)) {\n set(to, key, fromVal);\n } else if (\n toVal !== fromVal &&\n isPlainObject(toVal) &&\n isPlainObject(fromVal)\n ) {\n mergeData(toVal, fromVal);\n }\n }\n return to\n}\n\n/**\n * Data\n */\nfunction mergeDataOrFn (\n parentVal,\n childVal,\n vm\n) {\n if (!vm) {\n // in a Vue.extend merge, both should be functions\n if (!childVal) {\n return parentVal\n }\n if (!parentVal) {\n return childVal\n }\n // when parentVal & childVal are both present,\n // we need to return a function that returns the\n // merged result of both functions... no need to\n // check if parentVal is a function here because\n // it has to be a function to pass previous merges.\n return function mergedDataFn () {\n return mergeData(\n typeof childVal === 'function' ? childVal.call(this, this) : childVal,\n typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal\n )\n }\n } else {\n return function mergedInstanceDataFn () {\n // instance merge\n var instanceData = typeof childVal === 'function'\n ? childVal.call(vm, vm)\n : childVal;\n var defaultData = typeof parentVal === 'function'\n ? parentVal.call(vm, vm)\n : parentVal;\n if (instanceData) {\n return mergeData(instanceData, defaultData)\n } else {\n return defaultData\n }\n }\n }\n}\n\nstrats.data = function (\n parentVal,\n childVal,\n vm\n) {\n if (!vm) {\n if (childVal && typeof childVal !== 'function') {\n process.env.NODE_ENV !== 'production' && warn(\n 'The \"data\" option should be a function ' +\n 'that returns a per-instance value in component ' +\n 'definitions.',\n vm\n );\n\n return parentVal\n }\n return mergeDataOrFn(parentVal, childVal)\n }\n\n return mergeDataOrFn(parentVal, childVal, vm)\n};\n\n/**\n * Hooks and props are merged as arrays.\n */\nfunction mergeHook (\n parentVal,\n childVal\n) {\n var res = childVal\n ? parentVal\n ? parentVal.concat(childVal)\n : Array.isArray(childVal)\n ? childVal\n : [childVal]\n : parentVal;\n return res\n ? dedupeHooks(res)\n : res\n}\n\nfunction dedupeHooks (hooks) {\n var res = [];\n for (var i = 0; i < hooks.length; i++) {\n if (res.indexOf(hooks[i]) === -1) {\n res.push(hooks[i]);\n }\n }\n return res\n}\n\nLIFECYCLE_HOOKS.forEach(function (hook) {\n strats[hook] = mergeHook;\n});\n\n/**\n * Assets\n *\n * When a vm is present (instance creation), we need to do\n * a three-way merge between constructor options, instance\n * options and parent options.\n */\nfunction mergeAssets (\n parentVal,\n childVal,\n vm,\n key\n) {\n var res = Object.create(parentVal || null);\n if (childVal) {\n process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm);\n return extend(res, childVal)\n } else {\n return res\n }\n}\n\nASSET_TYPES.forEach(function (type) {\n strats[type + 's'] = mergeAssets;\n});\n\n/**\n * Watchers.\n *\n * Watchers hashes should not overwrite one\n * another, so we merge them as arrays.\n */\nstrats.watch = function (\n parentVal,\n childVal,\n vm,\n key\n) {\n // work around Firefox's Object.prototype.watch...\n if (parentVal === nativeWatch) { parentVal = undefined; }\n if (childVal === nativeWatch) { childVal = undefined; }\n /* istanbul ignore if */\n if (!childVal) { return Object.create(parentVal || null) }\n if (process.env.NODE_ENV !== 'production') {\n assertObjectType(key, childVal, vm);\n }\n if (!parentVal) { return childVal }\n var ret = {};\n extend(ret, parentVal);\n for (var key$1 in childVal) {\n var parent = ret[key$1];\n var child = childVal[key$1];\n if (parent && !Array.isArray(parent)) {\n parent = [parent];\n }\n ret[key$1] = parent\n ? parent.concat(child)\n : Array.isArray(child) ? child : [child];\n }\n return ret\n};\n\n/**\n * Other object hashes.\n */\nstrats.props =\nstrats.methods =\nstrats.inject =\nstrats.computed = function (\n parentVal,\n childVal,\n vm,\n key\n) {\n if (childVal && process.env.NODE_ENV !== 'production') {\n assertObjectType(key, childVal, vm);\n }\n if (!parentVal) { return childVal }\n var ret = Object.create(null);\n extend(ret, parentVal);\n if (childVal) { extend(ret, childVal); }\n return ret\n};\nstrats.provide = mergeDataOrFn;\n\n/**\n * Default strategy.\n */\nvar defaultStrat = function (parentVal, childVal) {\n return childVal === undefined\n ? parentVal\n : childVal\n};\n\n/**\n * Validate component names\n */\nfunction checkComponents (options) {\n for (var key in options.components) {\n validateComponentName(key);\n }\n}\n\nfunction validateComponentName (name) {\n if (!new RegExp((\"^[a-zA-Z][\\\\-\\\\.0-9_\" + (unicodeRegExp.source) + \"]*$\")).test(name)) {\n warn(\n 'Invalid component name: \"' + name + '\". Component names ' +\n 'should conform to valid custom element name in html5 specification.'\n );\n }\n if (isBuiltInTag(name) || config.isReservedTag(name)) {\n warn(\n 'Do not use built-in or reserved HTML elements as component ' +\n 'id: ' + name\n );\n }\n}\n\n/**\n * Ensure all props option syntax are normalized into the\n * Object-based format.\n */\nfunction normalizeProps (options, vm) {\n var props = options.props;\n if (!props) { return }\n var res = {};\n var i, val, name;\n if (Array.isArray(props)) {\n i = props.length;\n while (i--) {\n val = props[i];\n if (typeof val === 'string') {\n name = camelize(val);\n res[name] = { type: null };\n } else if (process.env.NODE_ENV !== 'production') {\n warn('props must be strings when using array syntax.');\n }\n }\n } else if (isPlainObject(props)) {\n for (var key in props) {\n val = props[key];\n name = camelize(key);\n res[name] = isPlainObject(val)\n ? val\n : { type: val };\n }\n } else if (process.env.NODE_ENV !== 'production') {\n warn(\n \"Invalid value for option \\\"props\\\": expected an Array or an Object, \" +\n \"but got \" + (toRawType(props)) + \".\",\n vm\n );\n }\n options.props = res;\n}\n\n/**\n * Normalize all injections into Object-based format\n */\nfunction normalizeInject (options, vm) {\n var inject = options.inject;\n if (!inject) { return }\n var normalized = options.inject = {};\n if (Array.isArray(inject)) {\n for (var i = 0; i < inject.length; i++) {\n normalized[inject[i]] = { from: inject[i] };\n }\n } else if (isPlainObject(inject)) {\n for (var key in inject) {\n var val = inject[key];\n normalized[key] = isPlainObject(val)\n ? extend({ from: key }, val)\n : { from: val };\n }\n } else if (process.env.NODE_ENV !== 'production') {\n warn(\n \"Invalid value for option \\\"inject\\\": expected an Array or an Object, \" +\n \"but got \" + (toRawType(inject)) + \".\",\n vm\n );\n }\n}\n\n/**\n * Normalize raw function directives into object format.\n */\nfunction normalizeDirectives (options) {\n var dirs = options.directives;\n if (dirs) {\n for (var key in dirs) {\n var def$$1 = dirs[key];\n if (typeof def$$1 === 'function') {\n dirs[key] = { bind: def$$1, update: def$$1 };\n }\n }\n }\n}\n\nfunction assertObjectType (name, value, vm) {\n if (!isPlainObject(value)) {\n warn(\n \"Invalid value for option \\\"\" + name + \"\\\": expected an Object, \" +\n \"but got \" + (toRawType(value)) + \".\",\n vm\n );\n }\n}\n\n/**\n * Merge two option objects into a new one.\n * Core utility used in both instantiation and inheritance.\n */\nfunction mergeOptions (\n parent,\n child,\n vm\n) {\n if (process.env.NODE_ENV !== 'production') {\n checkComponents(child);\n }\n\n if (typeof child === 'function') {\n child = child.options;\n }\n\n normalizeProps(child, vm);\n normalizeInject(child, vm);\n normalizeDirectives(child);\n\n // Apply extends and mixins on the child options,\n // but only if it is a raw options object that isn't\n // the result of another mergeOptions call.\n // Only merged options has the _base property.\n if (!child._base) {\n if (child.extends) {\n parent = mergeOptions(parent, child.extends, vm);\n }\n if (child.mixins) {\n for (var i = 0, l = child.mixins.length; i < l; i++) {\n parent = mergeOptions(parent, child.mixins[i], vm);\n }\n }\n }\n\n var options = {};\n var key;\n for (key in parent) {\n mergeField(key);\n }\n for (key in child) {\n if (!hasOwn(parent, key)) {\n mergeField(key);\n }\n }\n function mergeField (key) {\n var strat = strats[key] || defaultStrat;\n options[key] = strat(parent[key], child[key], vm, key);\n }\n return options\n}\n\n/**\n * Resolve an asset.\n * This function is used because child instances need access\n * to assets defined in its ancestor chain.\n */\nfunction resolveAsset (\n options,\n type,\n id,\n warnMissing\n) {\n /* istanbul ignore if */\n if (typeof id !== 'string') {\n return\n }\n var assets = options[type];\n // check local registration variations first\n if (hasOwn(assets, id)) { return assets[id] }\n var camelizedId = camelize(id);\n if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }\n var PascalCaseId = capitalize(camelizedId);\n if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }\n // fallback to prototype chain\n var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];\n if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {\n warn(\n 'Failed to resolve ' + type.slice(0, -1) + ': ' + id,\n options\n );\n }\n return res\n}\n\n/* */\n\n\n\nfunction validateProp (\n key,\n propOptions,\n propsData,\n vm\n) {\n var prop = propOptions[key];\n var absent = !hasOwn(propsData, key);\n var value = propsData[key];\n // boolean casting\n var booleanIndex = getTypeIndex(Boolean, prop.type);\n if (booleanIndex > -1) {\n if (absent && !hasOwn(prop, 'default')) {\n value = false;\n } else if (value === '' || value === hyphenate(key)) {\n // only cast empty string / same name to boolean if\n // boolean has higher priority\n var stringIndex = getTypeIndex(String, prop.type);\n if (stringIndex < 0 || booleanIndex < stringIndex) {\n value = true;\n }\n }\n }\n // check default value\n if (value === undefined) {\n value = getPropDefaultValue(vm, prop, key);\n // since the default value is a fresh copy,\n // make sure to observe it.\n var prevShouldObserve = shouldObserve;\n toggleObserving(true);\n observe(value);\n toggleObserving(prevShouldObserve);\n }\n if (\n process.env.NODE_ENV !== 'production' &&\n // skip validation for weex recycle-list child component props\n !(false)\n ) {\n assertProp(prop, key, value, vm, absent);\n }\n return value\n}\n\n/**\n * Get the default value of a prop.\n */\nfunction getPropDefaultValue (vm, prop, key) {\n // no default, return undefined\n if (!hasOwn(prop, 'default')) {\n return undefined\n }\n var def = prop.default;\n // warn against non-factory defaults for Object & Array\n if (process.env.NODE_ENV !== 'production' && isObject(def)) {\n warn(\n 'Invalid default value for prop \"' + key + '\": ' +\n 'Props with type Object/Array must use a factory function ' +\n 'to return the default value.',\n vm\n );\n }\n // the raw prop value was also undefined from previous render,\n // return previous default value to avoid unnecessary watcher trigger\n if (vm && vm.$options.propsData &&\n vm.$options.propsData[key] === undefined &&\n vm._props[key] !== undefined\n ) {\n return vm._props[key]\n }\n // call factory function for non-Function types\n // a value is Function if its prototype is function even across different execution context\n return typeof def === 'function' && getType(prop.type) !== 'Function'\n ? def.call(vm)\n : def\n}\n\n/**\n * Assert whether a prop is valid.\n */\nfunction assertProp (\n prop,\n name,\n value,\n vm,\n absent\n) {\n if (prop.required && absent) {\n warn(\n 'Missing required prop: \"' + name + '\"',\n vm\n );\n return\n }\n if (value == null && !prop.required) {\n return\n }\n var type = prop.type;\n var valid = !type || type === true;\n var expectedTypes = [];\n if (type) {\n if (!Array.isArray(type)) {\n type = [type];\n }\n for (var i = 0; i < type.length && !valid; i++) {\n var assertedType = assertType(value, type[i], vm);\n expectedTypes.push(assertedType.expectedType || '');\n valid = assertedType.valid;\n }\n }\n\n var haveExpectedTypes = expectedTypes.some(function (t) { return t; });\n if (!valid && haveExpectedTypes) {\n warn(\n getInvalidTypeMessage(name, value, expectedTypes),\n vm\n );\n return\n }\n var validator = prop.validator;\n if (validator) {\n if (!validator(value)) {\n warn(\n 'Invalid prop: custom validator check failed for prop \"' + name + '\".',\n vm\n );\n }\n }\n}\n\nvar simpleCheckRE = /^(String|Number|Boolean|Function|Symbol|BigInt)$/;\n\nfunction assertType (value, type, vm) {\n var valid;\n var expectedType = getType(type);\n if (simpleCheckRE.test(expectedType)) {\n var t = typeof value;\n valid = t === expectedType.toLowerCase();\n // for primitive wrapper objects\n if (!valid && t === 'object') {\n valid = value instanceof type;\n }\n } else if (expectedType === 'Object') {\n valid = isPlainObject(value);\n } else if (expectedType === 'Array') {\n valid = Array.isArray(value);\n } else {\n try {\n valid = value instanceof type;\n } catch (e) {\n warn('Invalid prop type: \"' + String(type) + '\" is not a constructor', vm);\n valid = false;\n }\n }\n return {\n valid: valid,\n expectedType: expectedType\n }\n}\n\nvar functionTypeCheckRE = /^\\s*function (\\w+)/;\n\n/**\n * Use function string name to check built-in types,\n * because a simple equality check will fail when running\n * across different vms / iframes.\n */\nfunction getType (fn) {\n var match = fn && fn.toString().match(functionTypeCheckRE);\n return match ? match[1] : ''\n}\n\nfunction isSameType (a, b) {\n return getType(a) === getType(b)\n}\n\nfunction getTypeIndex (type, expectedTypes) {\n if (!Array.isArray(expectedTypes)) {\n return isSameType(expectedTypes, type) ? 0 : -1\n }\n for (var i = 0, len = expectedTypes.length; i < len; i++) {\n if (isSameType(expectedTypes[i], type)) {\n return i\n }\n }\n return -1\n}\n\nfunction getInvalidTypeMessage (name, value, expectedTypes) {\n var message = \"Invalid prop: type check failed for prop \\\"\" + name + \"\\\".\" +\n \" Expected \" + (expectedTypes.map(capitalize).join(', '));\n var expectedType = expectedTypes[0];\n var receivedType = toRawType(value);\n // check if we need to specify expected value\n if (\n expectedTypes.length === 1 &&\n isExplicable(expectedType) &&\n isExplicable(typeof value) &&\n !isBoolean(expectedType, receivedType)\n ) {\n message += \" with value \" + (styleValue(value, expectedType));\n }\n message += \", got \" + receivedType + \" \";\n // check if we need to specify received value\n if (isExplicable(receivedType)) {\n message += \"with value \" + (styleValue(value, receivedType)) + \".\";\n }\n return message\n}\n\nfunction styleValue (value, type) {\n if (type === 'String') {\n return (\"\\\"\" + value + \"\\\"\")\n } else if (type === 'Number') {\n return (\"\" + (Number(value)))\n } else {\n return (\"\" + value)\n }\n}\n\nvar EXPLICABLE_TYPES = ['string', 'number', 'boolean'];\nfunction isExplicable (value) {\n return EXPLICABLE_TYPES.some(function (elem) { return value.toLowerCase() === elem; })\n}\n\nfunction isBoolean () {\n var args = [], len = arguments.length;\n while ( len-- ) args[ len ] = arguments[ len ];\n\n return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; })\n}\n\n/* */\n\nfunction handleError (err, vm, info) {\n // Deactivate deps tracking while processing error handler to avoid possible infinite rendering.\n // See: https://github.com/vuejs/vuex/issues/1505\n pushTarget();\n try {\n if (vm) {\n var cur = vm;\n while ((cur = cur.$parent)) {\n var hooks = cur.$options.errorCaptured;\n if (hooks) {\n for (var i = 0; i < hooks.length; i++) {\n try {\n var capture = hooks[i].call(cur, err, vm, info) === false;\n if (capture) { return }\n } catch (e) {\n globalHandleError(e, cur, 'errorCaptured hook');\n }\n }\n }\n }\n }\n globalHandleError(err, vm, info);\n } finally {\n popTarget();\n }\n}\n\nfunction invokeWithErrorHandling (\n handler,\n context,\n args,\n vm,\n info\n) {\n var res;\n try {\n res = args ? handler.apply(context, args) : handler.call(context);\n if (res && !res._isVue && isPromise(res) && !res._handled) {\n res.catch(function (e) { return handleError(e, vm, info + \" (Promise/async)\"); });\n // issue #9511\n // avoid catch triggering multiple times when nested calls\n res._handled = true;\n }\n } catch (e) {\n handleError(e, vm, info);\n }\n return res\n}\n\nfunction globalHandleError (err, vm, info) {\n if (config.errorHandler) {\n try {\n return config.errorHandler.call(null, err, vm, info)\n } catch (e) {\n // if the user intentionally throws the original error in the handler,\n // do not log it twice\n if (e !== err) {\n logError(e, null, 'config.errorHandler');\n }\n }\n }\n logError(err, vm, info);\n}\n\nfunction logError (err, vm, info) {\n if (process.env.NODE_ENV !== 'production') {\n warn((\"Error in \" + info + \": \\\"\" + (err.toString()) + \"\\\"\"), vm);\n }\n /* istanbul ignore else */\n if ((inBrowser || inWeex) && typeof console !== 'undefined') {\n console.error(err);\n } else {\n throw err\n }\n}\n\n/* */\n\nvar isUsingMicroTask = false;\n\nvar callbacks = [];\nvar pending = false;\n\nfunction flushCallbacks () {\n pending = false;\n var copies = callbacks.slice(0);\n callbacks.length = 0;\n for (var i = 0; i < copies.length; i++) {\n copies[i]();\n }\n}\n\n// Here we have async deferring wrappers using microtasks.\n// In 2.5 we used (macro) tasks (in combination with microtasks).\n// However, it has subtle problems when state is changed right before repaint\n// (e.g. #6813, out-in transitions).\n// Also, using (macro) tasks in event handler would cause some weird behaviors\n// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).\n// So we now use microtasks everywhere, again.\n// A major drawback of this tradeoff is that there are some scenarios\n// where microtasks have too high a priority and fire in between supposedly\n// sequential events (e.g. #4521, #6690, which have workarounds)\n// or even between bubbling of the same event (#6566).\nvar timerFunc;\n\n// The nextTick behavior leverages the microtask queue, which can be accessed\n// via either native Promise.then or MutationObserver.\n// MutationObserver has wider support, however it is seriously bugged in\n// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It\n// completely stops working after triggering a few times... so, if native\n// Promise is available, we will use it:\n/* istanbul ignore next, $flow-disable-line */\nif (typeof Promise !== 'undefined' && isNative(Promise)) {\n var p = Promise.resolve();\n timerFunc = function () {\n p.then(flushCallbacks);\n // In problematic UIWebViews, Promise.then doesn't completely break, but\n // it can get stuck in a weird state where callbacks are pushed into the\n // microtask queue but the queue isn't being flushed, until the browser\n // needs to do some other work, e.g. handle a timer. Therefore we can\n // \"force\" the microtask queue to be flushed by adding an empty timer.\n if (isIOS) { setTimeout(noop); }\n };\n isUsingMicroTask = true;\n} else if (!isIE && typeof MutationObserver !== 'undefined' && (\n isNative(MutationObserver) ||\n // PhantomJS and iOS 7.x\n MutationObserver.toString() === '[object MutationObserverConstructor]'\n)) {\n // Use MutationObserver where native Promise is not available,\n // e.g. PhantomJS, iOS7, Android 4.4\n // (#6466 MutationObserver is unreliable in IE11)\n var counter = 1;\n var observer = new MutationObserver(flushCallbacks);\n var textNode = document.createTextNode(String(counter));\n observer.observe(textNode, {\n characterData: true\n });\n timerFunc = function () {\n counter = (counter + 1) % 2;\n textNode.data = String(counter);\n };\n isUsingMicroTask = true;\n} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {\n // Fallback to setImmediate.\n // Technically it leverages the (macro) task queue,\n // but it is still a better choice than setTimeout.\n timerFunc = function () {\n setImmediate(flushCallbacks);\n };\n} else {\n // Fallback to setTimeout.\n timerFunc = function () {\n setTimeout(flushCallbacks, 0);\n };\n}\n\nfunction nextTick (cb, ctx) {\n var _resolve;\n callbacks.push(function () {\n if (cb) {\n try {\n cb.call(ctx);\n } catch (e) {\n handleError(e, ctx, 'nextTick');\n }\n } else if (_resolve) {\n _resolve(ctx);\n }\n });\n if (!pending) {\n pending = true;\n timerFunc();\n }\n // $flow-disable-line\n if (!cb && typeof Promise !== 'undefined') {\n return new Promise(function (resolve) {\n _resolve = resolve;\n })\n }\n}\n\n/* */\n\nvar mark;\nvar measure;\n\nif (process.env.NODE_ENV !== 'production') {\n var perf = inBrowser && window.performance;\n /* istanbul ignore if */\n if (\n perf &&\n perf.mark &&\n perf.measure &&\n perf.clearMarks &&\n perf.clearMeasures\n ) {\n mark = function (tag) { return perf.mark(tag); };\n measure = function (name, startTag, endTag) {\n perf.measure(name, startTag, endTag);\n perf.clearMarks(startTag);\n perf.clearMarks(endTag);\n // perf.clearMeasures(name)\n };\n }\n}\n\n/* not type checking this file because flow doesn't play well with Proxy */\n\nvar initProxy;\n\nif (process.env.NODE_ENV !== 'production') {\n var allowedGlobals = makeMap(\n 'Infinity,undefined,NaN,isFinite,isNaN,' +\n 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +\n 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,' +\n 'require' // for Webpack/Browserify\n );\n\n var warnNonPresent = function (target, key) {\n warn(\n \"Property or method \\\"\" + key + \"\\\" is not defined on the instance but \" +\n 'referenced during render. Make sure that this property is reactive, ' +\n 'either in the data option, or for class-based components, by ' +\n 'initializing the property. ' +\n 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',\n target\n );\n };\n\n var warnReservedPrefix = function (target, key) {\n warn(\n \"Property \\\"\" + key + \"\\\" must be accessed with \\\"$data.\" + key + \"\\\" because \" +\n 'properties starting with \"$\" or \"_\" are not proxied in the Vue instance to ' +\n 'prevent conflicts with Vue internals. ' +\n 'See: https://vuejs.org/v2/api/#data',\n target\n );\n };\n\n var hasProxy =\n typeof Proxy !== 'undefined' && isNative(Proxy);\n\n if (hasProxy) {\n var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact');\n config.keyCodes = new Proxy(config.keyCodes, {\n set: function set (target, key, value) {\n if (isBuiltInModifier(key)) {\n warn((\"Avoid overwriting built-in modifier in config.keyCodes: .\" + key));\n return false\n } else {\n target[key] = value;\n return true\n }\n }\n });\n }\n\n var hasHandler = {\n has: function has (target, key) {\n var has = key in target;\n var isAllowed = allowedGlobals(key) ||\n (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data));\n if (!has && !isAllowed) {\n if (key in target.$data) { warnReservedPrefix(target, key); }\n else { warnNonPresent(target, key); }\n }\n return has || !isAllowed\n }\n };\n\n var getHandler = {\n get: function get (target, key) {\n if (typeof key === 'string' && !(key in target)) {\n if (key in target.$data) { warnReservedPrefix(target, key); }\n else { warnNonPresent(target, key); }\n }\n return target[key]\n }\n };\n\n initProxy = function initProxy (vm) {\n if (hasProxy) {\n // determine which proxy handler to use\n var options = vm.$options;\n var handlers = options.render && options.render._withStripped\n ? getHandler\n : hasHandler;\n vm._renderProxy = new Proxy(vm, handlers);\n } else {\n vm._renderProxy = vm;\n }\n };\n}\n\n/* */\n\nvar seenObjects = new _Set();\n\n/**\n * Recursively traverse an object to evoke all converted\n * getters, so that every nested property inside the object\n * is collected as a \"deep\" dependency.\n */\nfunction traverse (val) {\n _traverse(val, seenObjects);\n seenObjects.clear();\n}\n\nfunction _traverse (val, seen) {\n var i, keys;\n var isA = Array.isArray(val);\n if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {\n return\n }\n if (val.__ob__) {\n var depId = val.__ob__.dep.id;\n if (seen.has(depId)) {\n return\n }\n seen.add(depId);\n }\n if (isA) {\n i = val.length;\n while (i--) { _traverse(val[i], seen); }\n } else {\n keys = Object.keys(val);\n i = keys.length;\n while (i--) { _traverse(val[keys[i]], seen); }\n }\n}\n\n/* */\n\nvar normalizeEvent = cached(function (name) {\n var passive = name.charAt(0) === '&';\n name = passive ? name.slice(1) : name;\n var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first\n name = once$$1 ? name.slice(1) : name;\n var capture = name.charAt(0) === '!';\n name = capture ? name.slice(1) : name;\n return {\n name: name,\n once: once$$1,\n capture: capture,\n passive: passive\n }\n});\n\nfunction createFnInvoker (fns, vm) {\n function invoker () {\n var arguments$1 = arguments;\n\n var fns = invoker.fns;\n if (Array.isArray(fns)) {\n var cloned = fns.slice();\n for (var i = 0; i < cloned.length; i++) {\n invokeWithErrorHandling(cloned[i], null, arguments$1, vm, \"v-on handler\");\n }\n } else {\n // return handler return value for single handlers\n return invokeWithErrorHandling(fns, null, arguments, vm, \"v-on handler\")\n }\n }\n invoker.fns = fns;\n return invoker\n}\n\nfunction updateListeners (\n on,\n oldOn,\n add,\n remove$$1,\n createOnceHandler,\n vm\n) {\n var name, def$$1, cur, old, event;\n for (name in on) {\n def$$1 = cur = on[name];\n old = oldOn[name];\n event = normalizeEvent(name);\n if (isUndef(cur)) {\n process.env.NODE_ENV !== 'production' && warn(\n \"Invalid handler for event \\\"\" + (event.name) + \"\\\": got \" + String(cur),\n vm\n );\n } else if (isUndef(old)) {\n if (isUndef(cur.fns)) {\n cur = on[name] = createFnInvoker(cur, vm);\n }\n if (isTrue(event.once)) {\n cur = on[name] = createOnceHandler(event.name, cur, event.capture);\n }\n add(event.name, cur, event.capture, event.passive, event.params);\n } else if (cur !== old) {\n old.fns = cur;\n on[name] = old;\n }\n }\n for (name in oldOn) {\n if (isUndef(on[name])) {\n event = normalizeEvent(name);\n remove$$1(event.name, oldOn[name], event.capture);\n }\n }\n}\n\n/* */\n\nfunction mergeVNodeHook (def, hookKey, hook) {\n if (def instanceof VNode) {\n def = def.data.hook || (def.data.hook = {});\n }\n var invoker;\n var oldHook = def[hookKey];\n\n function wrappedHook () {\n hook.apply(this, arguments);\n // important: remove merged hook to ensure it's called only once\n // and prevent memory leak\n remove(invoker.fns, wrappedHook);\n }\n\n if (isUndef(oldHook)) {\n // no existing hook\n invoker = createFnInvoker([wrappedHook]);\n } else {\n /* istanbul ignore if */\n if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {\n // already a merged invoker\n invoker = oldHook;\n invoker.fns.push(wrappedHook);\n } else {\n // existing plain hook\n invoker = createFnInvoker([oldHook, wrappedHook]);\n }\n }\n\n invoker.merged = true;\n def[hookKey] = invoker;\n}\n\n/* */\n\nfunction extractPropsFromVNodeData (\n data,\n Ctor,\n tag\n) {\n // we are only extracting raw values here.\n // validation and default values are handled in the child\n // component itself.\n var propOptions = Ctor.options.props;\n if (isUndef(propOptions)) {\n return\n }\n var res = {};\n var attrs = data.attrs;\n var props = data.props;\n if (isDef(attrs) || isDef(props)) {\n for (var key in propOptions) {\n var altKey = hyphenate(key);\n if (process.env.NODE_ENV !== 'production') {\n var keyInLowerCase = key.toLowerCase();\n if (\n key !== keyInLowerCase &&\n attrs && hasOwn(attrs, keyInLowerCase)\n ) {\n tip(\n \"Prop \\\"\" + keyInLowerCase + \"\\\" is passed to component \" +\n (formatComponentName(tag || Ctor)) + \", but the declared prop name is\" +\n \" \\\"\" + key + \"\\\". \" +\n \"Note that HTML attributes are case-insensitive and camelCased \" +\n \"props need to use their kebab-case equivalents when using in-DOM \" +\n \"templates. You should probably use \\\"\" + altKey + \"\\\" instead of \\\"\" + key + \"\\\".\"\n );\n }\n }\n checkProp(res, props, key, altKey, true) ||\n checkProp(res, attrs, key, altKey, false);\n }\n }\n return res\n}\n\nfunction checkProp (\n res,\n hash,\n key,\n altKey,\n preserve\n) {\n if (isDef(hash)) {\n if (hasOwn(hash, key)) {\n res[key] = hash[key];\n if (!preserve) {\n delete hash[key];\n }\n return true\n } else if (hasOwn(hash, altKey)) {\n res[key] = hash[altKey];\n if (!preserve) {\n delete hash[altKey];\n }\n return true\n }\n }\n return false\n}\n\n/* */\n\n// The template compiler attempts to minimize the need for normalization by\n// statically analyzing the template at compile time.\n//\n// For plain HTML markup, normalization can be completely skipped because the\n// generated render function is guaranteed to return Array. There are\n// two cases where extra normalization is needed:\n\n// 1. When the children contains components - because a functional component\n// may return an Array instead of a single root. In this case, just a simple\n// normalization is needed - if any child is an Array, we flatten the whole\n// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep\n// because functional components already normalize their own children.\nfunction simpleNormalizeChildren (children) {\n for (var i = 0; i < children.length; i++) {\n if (Array.isArray(children[i])) {\n return Array.prototype.concat.apply([], children)\n }\n }\n return children\n}\n\n// 2. When the children contains constructs that always generated nested Arrays,\n// e.g.