Bruno Predot

Merge branch 'release/0.3.0'

  1 +0.3.0:
  2 +- Installation vuelidate et vuelidate/validator.
  3 +- Ajout composant SkeletonMovieDetailLoader.
  4 +- Ajout Model + Interface credit.
  5 +- Ajout composant ScoreAndVote.
  6 +- Ajout composant MovieGender.
  7 +- Ajout composant Poster.
  8 +- Ajout composant BackdropImage.
  9 +- Ajout composant MovieCommentForm.
  10 +- Ajout model + interface MovieComment.
  11 +- Ajout composant MovieCommentForm.
  12 +- Ajout composant MovieCommentList.
  13 +- Ajout dépendance TinyMCE.
  14 +- Ajout composant TinyMceFieldEditor.
  15 +- Ajout composant Loader.
  16 +- Ajout composant MovieCard.
  17 +
1 0.2.0: 18 0.2.0:
2 - Mise en place du CHANGELOG_RELEASE. 19 - Mise en place du CHANGELOG_RELEASE.
3 - Ajout page index. 20 - Ajout page index.
  1 +<script lang="ts" setup>
  2 +//#region --Props--.
  3 +import { useDateFormat } from "@vueuse/core";
  4 +import { FilmIcon } from "lucide-vue-next";
  5 +//#endregion
  6 +
  7 +//#region --Props--.
  8 +defineProps({
  9 + movie: {
  10 + type: Object,
  11 + required: true,
  12 + nullable: false,
  13 + },
  14 +});
  15 +//#endregion
  16 +</script>
  17 +
  18 +<template>
  19 + <section
  20 + class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer"
  21 + @click="navigateTo(`/movies/${movie.id}`)"
  22 + >
  23 + <div class="relative pb-[150%]">
  24 + <img
  25 + v-if="movie.poster_path"
  26 + :alt="movie.title"
  27 + :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
  28 + class="absolute inset-0 w-full h-full object-cover"
  29 + />
  30 + <div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center">
  31 + <FilmIcon :size="48" class="text-gray-500" />
  32 + </div>
  33 + <div
  34 + class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold"
  35 + >
  36 + {{ movie.vote_average.toFixed(1) }}
  37 + </div>
  38 + </div>
  39 + <div class="p-4">
  40 + <h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2>
  41 + <p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p>
  42 + </div>
  43 + </section>
  44 +</template>
  45 +
  46 +<style scoped></style>
  1 +<script lang="ts" setup>
  2 +//#region --Import--.
  3 +import type { MovieCommentInterface } from "~/interfaces/movieComment";
  4 +import { MessageSquareIcon } from "lucide-vue-next";
  5 +//#endregion
  6 +
  7 +//#region --Props--.
  8 +const props = defineProps({
  9 + comments: {
  10 + type: Array<MovieCommentInterface>,
  11 + required: true,
  12 + nullable: false,
  13 + },
  14 +});
  15 +//#endregion
  16 +
  17 +//#region --Watch--.
  18 +watch(
  19 + () => props.comments,
  20 + (comments) => {
  21 + nextTick(() => {
  22 + if (comments.length) {
  23 + comments.forEach((comment, index) => {
  24 + const element = document.getElementById(`message${index}`) as HTMLParagraphElement;
  25 + element.innerHTML = comment.message;
  26 + });
  27 + }
  28 + });
  29 + }, { immediate: true }
  30 +);
  31 +//#endregion
  32 +</script>
  33 +
  34 +<template>
  35 + <section>
  36 + <!-- Liste des commentaires -->
  37 + <section v-if="comments.length > 0" class="mt-10">
  38 + <h2>Commentaires publiés</h2>
  39 + <div v-for="(comment, index) in comments" :key="index" class="bg-gray-800 rounded-lg p-6 mb-4">
  40 + <div class="flex justify-between items-start mb-2">
  41 + <section>
  42 + <h4 class="font-bold text-lg">Par {{ comment.username }}</h4>
  43 + <p class="text-sm text-gray-400">Le {{ useDateFormat(comment.createdAt, "DD-MM-YYYY") }}</p>
  44 + </section>
  45 + <section class="bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold">
  46 + {{ comment.rating }}
  47 + </section>
  48 + </div>
  49 + <p :id="`message${index}`" class="text-gray-300">{{ comment.message }}</p>
  50 + </div>
  51 + </section>
  52 + <!-- Si aucun commentaire -->
  53 + <section v-else class="text-center py-8 bg-gray-800 rounded-lg mt-10">
  54 + <MessageSquareIcon :size="48" class="mx-auto mb-3 text-gray-600" />
  55 + <p class="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à donner votre avis !</p>
  56 + </section>
  57 + </section>
  58 +</template>
  59 +
  60 +<style scoped></style>
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 //#region --import--. 2 //#region --import--.
3 -import SearchBar from "~/components/SearchBar.vue";  
4 import { onBeforeUnmount, ref } from "vue"; 3 import { onBeforeUnmount, ref } from "vue";
5 import { useTMDB } from "~/composables/tMDB"; 4 import { useTMDB } from "~/composables/tMDB";
6 import { Movie } from "~/models/movie"; 5 import { Movie } from "~/models/movie";
7 -import { FilmIcon, SearchXIcon } from "lucide-vue-next"; 6 +import { SearchXIcon } from "lucide-vue-next";
8 import type { MovieInterface } from "~/interfaces/movie"; 7 import type { MovieInterface } from "~/interfaces/movie";
9 -import { useDateFormat } from "@vueuse/core";  
10 //#endregion 8 //#endregion
11 9
12 //#region --Declaration--. 10 //#region --Declaration--.
@@ -100,7 +98,7 @@ function createIntersectionObserver() { @@ -100,7 +98,7 @@ function createIntersectionObserver() {
100 if (entry.isIntersecting && !isLoadingMore.value && currentPage.value < totalPages.value) { 98 if (entry.isIntersecting && !isLoadingMore.value && currentPage.value < totalPages.value) {
101 if (searchQuery.value) { 99 if (searchQuery.value) {
102 // Continue searching query if already active. 100 // Continue searching query if already active.
103 - search(searchQuery.value, currentPage.value + 1) 101 + search(searchQuery.value, currentPage.value + 1);
104 } else { 102 } else {
105 // Continue fetching popular movies. 103 // Continue fetching popular movies.
106 fetchMovies(currentPage.value + 1); 104 fetchMovies(currentPage.value + 1);
@@ -118,7 +116,7 @@ function handleSearchEvent(event: string) { @@ -118,7 +116,7 @@ function handleSearchEvent(event: string) {
118 } 116 }
119 117
120 function handleClearSearchEvent() { 118 function handleClearSearchEvent() {
121 - searchQuery.value = ''; 119 + searchQuery.value = "";
122 currentPage.value = 1; 120 currentPage.value = 1;
123 // Fetch popular movies after clear. 121 // Fetch popular movies after clear.
124 fetchMovies(1); 122 fetchMovies(1);
@@ -155,41 +153,23 @@ onBeforeUnmount(() => { @@ -155,41 +153,23 @@ onBeforeUnmount(() => {
155 <section> 153 <section>
156 <h1 class="text-4xl font-bold mb-8 text-center">Découvrez les films populaires</h1> 154 <h1 class="text-4xl font-bold mb-8 text-center">Découvrez les films populaires</h1>
157 <!-- Barre de recherche --> 155 <!-- Barre de recherche -->
158 - <search-bar 156 + <ui-components-search-bar
159 placeholder="Rechercher un film..." 157 placeholder="Rechercher un film..."
160 @event:search="handleSearchEvent" 158 @event:search="handleSearchEvent"
161 @event:clear_search="handleClearSearchEvent" 159 @event:clear_search="handleClearSearchEvent"
162 /> 160 />
  161 +
163 <!-- Loading Skeleton --> 162 <!-- Loading Skeleton -->
164 - <skeleton-movies-loader v-if="isInitialLoading" :is-initial-loading="isInitialLoading" :skeleton-number="20" /> 163 + <ui-components-skeleton-movies-loader
  164 + v-if="isInitialLoading"
  165 + :is-initial-loading="isInitialLoading"
  166 + :skeleton-number="20"
  167 + />
  168 +
165 <!-- Liste des films --> 169 <!-- Liste des films -->
166 <div v-else-if="movies.length > 0" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> 170 <div v-else-if="movies.length > 0" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
167 - <div 171 + <div v-for="movie in movies" :key="movie.id">
168 - v-for="movie in movies" 172 + <movie-card :movie="movie" />
169 - :key="movie.id"  
170 - class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer"  
171 - @click="navigateTo(`/movies/${movie.id}`)"  
172 - >  
173 - <div class="relative pb-[150%]">  
174 - <img  
175 - v-if="movie.poster_path"  
176 - :alt="movie.title"  
177 - :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"  
178 - class="absolute inset-0 w-full h-full object-cover"  
179 - />  
180 - <div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center">  
181 - <FilmIcon :size="48" class="text-gray-500" />  
182 - </div>  
183 - <div  
184 - class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold"  
185 - >  
186 - {{ movie.vote_average.toFixed(1) }}  
187 - </div>  
188 - </div>  
189 - <div class="p-4">  
190 - <h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2>  
191 - <p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p>  
192 - </div>  
193 </div> 173 </div>
194 </div> 174 </div>
195 175
@@ -201,9 +181,7 @@ onBeforeUnmount(() => { @@ -201,9 +181,7 @@ onBeforeUnmount(() => {
201 </section> 181 </section>
202 182
203 <!-- Loader pour le chargement de plus de films --> 183 <!-- Loader pour le chargement de plus de films -->
204 - <section v-if="isLoadingMore && !isInitialLoading" class="flex justify-center mt-8"> 184 + <ui-components-loader :is-initial-loading="isInitialLoading" :is-loading="isLoadingMore" />
205 - <div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />  
206 - </section>  
207 185
208 <!-- Élément observé pour le défilement infini --> 186 <!-- Élément observé pour le défilement infini -->
209 <div ref="loadMoreTrigger" class="h-10 mt-4" /> 187 <div ref="loadMoreTrigger" class="h-10 mt-4" />
  1 +<script lang="ts" setup>
  2 +//#region --Import--.
  3 +import type { Genre } from "~/interfaces/movie";
  4 +//#endregion
  5 +
  6 +//#region --Props--.
  7 +defineProps({
  8 + genres: {
  9 + type: Array<Genre>,
  10 + required: true,
  11 + nullable: false,
  12 + },
  13 +});
  14 +//#endregion
  15 +</script>
  16 +
  17 +<template>
  18 + <section class="mb-6">
  19 + <div class="flex flex-wrap gap-2">
  20 + <span v-for="genre in genres" :key="genre.id" class="px-3 py-1 bg-gray-800 rounded-full text-sm">
  21 + {{ genre.name }}
  22 + </span>
  23 + </div>
  24 + </section>
  25 +</template>
  26 +
  27 +<style scoped></style>
  1 +<script lang="ts" setup>
  2 +//#region --Props--.
  3 +const props = defineProps({
  4 + score: {
  5 + type: Number,
  6 + required: true,
  7 + nullable: false,
  8 + },
  9 + nbVote: {
  10 + type: Number,
  11 + required: true,
  12 + nullable: false,
  13 + },
  14 +});
  15 +//#endregion
  16 +
  17 +//#region --Function--.
  18 +/**
  19 + * Format vote count if > 1000.
  20 + * @param count
  21 + */
  22 +const formatVoteCount = (count: number) => {
  23 + if (count >= 1000) {
  24 + return `${(count / 1000).toFixed(1)}k votes`;
  25 + }
  26 + return `${count} votes`;
  27 +};
  28 +//#endregion
  29 +</script>
  30 +
  31 +<template>
  32 + <section class="flex items-center mb-6">
  33 + <section class="bg-primary text-white rounded-full w-12 h-12 flex items-center justify-center font-bold mr-3">
  34 + {{ score.toFixed(1) }}
  35 + </section>
  36 + <section>
  37 + <p class="font-semibold">Note TMDB</p>
  38 + <div class="text-sm text-gray-400">{{ formatVoteCount(nbVote) }}</div>
  39 + </section>
  40 + </section>
  41 +</template>
  42 +
  43 +<style scoped></style>
  1 +<script lang="ts" setup>
  2 +//#region --Import--.
  3 +import { useVuelidate } from "@vuelidate/core";
  4 +import { helpers, maxLength, maxValue, minLength, minValue, required } from "@vuelidate/validators";
  5 +import type { Comment } from "~/type/commentForm";
  6 +//#endregion
  7 +
  8 +//#region --Emit--.
  9 +const emit = defineEmits(["event:submit"]);
  10 +//#endregion
  11 +
  12 +//#region --Props--.
  13 +defineProps({
  14 + isSubmitting: {
  15 + type: Boolean,
  16 + required: false,
  17 + nullable: false,
  18 + default: false,
  19 + },
  20 +});
  21 +//#endregion
  22 +
  23 +//#region --Data/ref--.
  24 +const initialState: Comment = {
  25 + username: "",
  26 + message: "",
  27 + rating: 5,
  28 +};
  29 +
  30 +// Validation rules
  31 +const rules = {
  32 + username: {
  33 + required: helpers.withMessage("Le nom d'utilisateur est requis", required),
  34 + minLength: helpers.withMessage("Le nom d'utilisateur doit contenir au moins 3 caractères", minLength(3)),
  35 + maxLength: helpers.withMessage("Le nom d'utilisateur ne peut pas dépasser 50 caractères", maxLength(50)),
  36 + alpha: helpers.withMessage(
  37 + "Le nom d'utilisateur ne peut contenir que des lettres",
  38 + helpers.regex(/^[a-zA-ZÀ-ÿ\s]+$/),
  39 + ),
  40 + },
  41 + message: {
  42 + required: helpers.withMessage("Le message est requis", required),
  43 + minLength: helpers.withMessage("Le message doit contenir au moins 3 caractères", minLength(3)),
  44 + maxLength: helpers.withMessage("Le message ne peut pas dépasser 500 caractères", maxLength(500)),
  45 + },
  46 + rating: {
  47 + required: helpers.withMessage("La notation est requise", required),
  48 + minValue: helpers.withMessage("Le message ne être inférieure à 0", minValue(0)),
  49 + maxValue: helpers.withMessage("Le message ne être suppérieur à 10", maxValue(10)),
  50 + },
  51 +};
  52 +
  53 +const formData = reactive({
  54 + ...initialState,
  55 +});
  56 +const v$ = useVuelidate(rules, formData);
  57 +//#endregion
  58 +
  59 +const errormessages = computed(() => {
  60 + return v$.value.message.$errors.map((e) => e.$message);
  61 +});
  62 +
  63 +//#region --Function--.
  64 +async function submitComment() {
  65 + emit("event:submit", formData);
  66 +}
  67 +
  68 +function clear() {
  69 + v$.value.$reset();
  70 + formData.username = initialState.username;
  71 + formData.message = initialState.message;
  72 + formData.rating = initialState.rating;
  73 +}
  74 +
  75 +function handleMessageEvent(event: string) {
  76 + formData.message = event;
  77 + // todo : revoir ici la validation manquante (dû au retour de TinyMCE).
  78 + v$.value.message.$touch();
  79 + // console.log(formData.message.replace(/<[^>]*>/g, ''));
  80 + // console.log(formData.message.replace(/(&lt;([^&gt;]+)&gt;)/ig, ''));
  81 +}
  82 +
  83 +//#endregion
  84 +</script>
  85 +
  86 +<template>
  87 + <section>
  88 + <VForm>
  89 + <v-text-field
  90 + v-model="formData.username"
  91 + :error-messages="v$.username.$errors.map((e) => e.$message) as readonly string[]"
  92 + label="nom d'utilisateur"
  93 + placeholder="nom d'utilisateur"
  94 + required
  95 + @blur="v$.username.$touch()"
  96 + @input="v$.username.$touch()"
  97 + />
  98 + <v-text-field
  99 + v-model="formData.rating"
  100 + :error-messages="v$.rating.$errors.map((e) => e.$message) as readonly string[]"
  101 + label="Note (1-10)"
  102 + placeholder=""
  103 + required
  104 + type="number"
  105 + @blur="v$.rating.$touch"
  106 + @input="v$.rating.$touch"
  107 + />
  108 +<!-- <pre>{{ errormessages }}</pre>-->
  109 + <ui-components-tiny-mce-field-editor
  110 + :error-message="v$?.message?.$errors[0]?.$message ? (v$.message.$errors[0].$message as string) : ''"
  111 + :model-value="formData.message"
  112 + @update:model-value="handleMessageEvent"
  113 + />
  114 + <v-btn
  115 + class="mt-6 mr-4"
  116 + color="primary"
  117 + @click="
  118 + async () => {
  119 + const validForm = await v$.$validate();
  120 + if (validForm) {
  121 + submitComment();
  122 + }
  123 + }
  124 + "
  125 + >
  126 + <span v-if="isSubmitting" class="flex items-center justify-center">
  127 + <span class="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
  128 + Envoi en cours...
  129 + </span>
  130 + <span v-else>Publier le commentaire</span>
  131 + </v-btn>
  132 + <v-btn class="mt-6 mr-4" color="primary" @click="clear"> effacer</v-btn>
  133 + </VForm>
  134 + </section>
  135 +</template>
  136 +
  137 +<style scoped></style>
  1 +import { describe, it, expect } from 'vitest'
  2 +import { mount } from '@vue/test-utils'
  3 +
  4 +import HelloWorld from './HelloWorld.vue'
  5 +
  6 +describe('HelloWorld', () => {
  7 + it('component renders Hello world properly', () => {
  8 + const wrapper = mount(HelloWorld)
  9 + expect(wrapper.text()).toContain('Hello world')
  10 + })
  11 +})
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 + <p>Hello world</p>
  7 +</template>
  8 +
  9 +<style scoped lang="scss">
  10 +
  11 +</style>
  1 +<script lang="ts" setup>
  2 +//#region --Props--.
  3 +defineProps({
  4 + src: {
  5 + type: String,
  6 + required: true,
  7 + nullable: false,
  8 + },
  9 + title: {
  10 + type: String,
  11 + required: true,
  12 + nullable: false,
  13 + },
  14 +});
  15 +//#endregion
  16 +
  17 +//#region --Declaration--.
  18 +const w: Window = window;
  19 +//#endregion
  20 +</script>
  21 +
  22 +<template>
  23 + <section class="absolute inset-0 h-[500px] overflow-hidden z-0">
  24 + <v-img
  25 + v-if="src"
  26 + :alt="title"
  27 + :src="`https://image.tmdb.org/t/p/original${src}`"
  28 + :width="w.screen.width"
  29 + aspect-ratio="16/9"
  30 + class="w-full h-full object-cover opacity-30"
  31 + cover
  32 + max-height="500"
  33 + />
  34 + </section>
  35 +</template>
  36 +
  37 +<style scoped></style>
  1 +<script lang="ts" setup>
  2 +//#region --Props--.
  3 +defineProps({
  4 + isLoading: {
  5 + type: Boolean,
  6 + required: true,
  7 + nullable: false,
  8 + },
  9 + isInitialLoading: {
  10 + type: Boolean,
  11 + required: false,
  12 + nullable: false,
  13 + default: false,
  14 + },
  15 +});
  16 +//#endregion
  17 +</script>
  18 +
  19 +<template>
  20 + <section v-if="isLoading && !isInitialLoading" class="flex justify-center mt-8">
  21 + <div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
  22 + </section>
  23 +</template>
  24 +
  25 +<style scoped></style>
  1 +<script setup lang="ts">
  2 +//#region --Props--.
  3 +import { FilmIcon } from "lucide-vue-next";
  4 +
  5 +defineProps({
  6 + src: {
  7 + type: String,
  8 + required: true,
  9 + nullable: false,
  10 + },
  11 + title: {
  12 + type: String,
  13 + required: true,
  14 + nullable: false,
  15 + },
  16 +});
  17 +//#endregion
  18 +</script>
  19 +
  20 +<template>
  21 + <section class="w-full md:w-1/3 lg:w-1/4">
  22 + <div class="rounded-lg overflow-hidden shadow-lg bg-gray-800">
  23 + <v-img
  24 + v-if="src"
  25 + :alt="title"
  26 + :src="`https://image.tmdb.org/t/p/w500${src}`"
  27 + class="w-full h-auto"
  28 + />
  29 + <div v-else class="aspect-[2/3] bg-gray-700 flex items-center justify-center">
  30 + <FilmIcon :size="64" class="text-gray-500" />
  31 + </div>
  32 + </div>
  33 + </section>
  34 +</template>
  35 +
  36 +<style scoped>
  37 +
  38 +</style>
  1 +<script lang="ts" setup>
  2 +//#region --Import--.
  3 +import Editor from "@tinymce/tinymce-vue";
  4 +import { ref, watch } from "vue";
  5 +//#endregion
  6 +
  7 +//#region --Declaration--.
  8 +const runtimeConfig = useRuntimeConfig();
  9 +//#endregion
  10 +
  11 +//#region --Emit--.
  12 +const emit = defineEmits<{
  13 + (e: "update:modelValue", value: string): void;
  14 +}>();
  15 +//#endregion
  16 +
  17 +//#region --Props--.
  18 +const props = defineProps<{
  19 + modelValue: string;
  20 + errorMessage: string;
  21 +}>();
  22 +//#endregion
  23 +
  24 +//#region --Data/ref--.
  25 +const content = ref(props.modelValue);
  26 +const init = {
  27 + height: 300,
  28 + menubar: false,
  29 + plugins: [
  30 + // Core editing features
  31 + "advlist",
  32 + "autolink",
  33 + "lists",
  34 + "link",
  35 + "image",
  36 + "charmap",
  37 + "preview",
  38 + "anchor",
  39 + "searchreplace",
  40 + "visualblocks",
  41 + "code",
  42 + "fullscreen",
  43 + "insertdatetime",
  44 + "media",
  45 + "table",
  46 + "code",
  47 + "help",
  48 + "wordcount",
  49 + ],
  50 + toolbar:
  51 + "undo redo | blocks | bold italic underline strikethrough |" +
  52 + "bold italic forecolor | alignleft aligncenter " +
  53 + "alignright alignjustify | bullist numlist outdent indent | " +
  54 + "removeformat | help",
  55 + content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }",
  56 + skin: "oxide-dark",
  57 + content_css: "dark",
  58 + // forced_root_block: false,
  59 + // valid_elements: [],
  60 + // entity_encoding : "raw",
  61 +};
  62 +//#endregion
  63 +
  64 +//#region --Watch--.
  65 +watch(content, (newValue) => {
  66 + emit("update:modelValue", newValue);
  67 +});
  68 +
  69 +watch(
  70 + () => props.modelValue,
  71 + (newValue) => {
  72 + if (newValue !== content.value) {
  73 + content.value = newValue;
  74 + }
  75 + },
  76 +);
  77 +//#endregion
  78 +</script>
  79 +
  80 +<template>
  81 + <div>
  82 + <editor v-model="content" :api-key="runtimeConfig.public.apiTinyMceSecret" :init="init" />
  83 + </div>
  84 + <div v-if="errorMessage" class="text-red-500 text-sm mt-1">
  85 + {{ errorMessage }}
  86 + </div>
  87 +</template>
  88 +
  89 +<style scoped></style>
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 + <v-container class="bg-gray-900">
  7 + <v-row class="bg-gray-900" >
  8 + <v-col cols="12" sm="4">
  9 + <v-skeleton-loader
  10 + class="mx-auto border bg-gray-800"
  11 + color="#1f2937"
  12 + width="auto"
  13 + height="600px"
  14 + type="paragraph, image"
  15 + />
  16 + </v-col>
  17 + <v-col cols="12" sm="8">
  18 + <v-skeleton-loader
  19 + class="mx-auto mt-10"
  20 + color="#1f2937"
  21 + elevation="12"
  22 + min-height="400px"
  23 + type="table-heading, list-item-two-line, article, actions, table-tfoot"
  24 + />
  25 + </v-col>
  26 + </v-row>
  27 + </v-container>
  28 +</template>
  29 +
  30 +<style scoped>
  31 +
  32 +</style>
1 import type { RuntimeConfig } from "nuxt/schema"; 1 import type { RuntimeConfig } from "nuxt/schema";
2 2
3 -export const useTMDB = function() { 3 +export const useTMDB = function () {
4 const runtimeconfig: RuntimeConfig = useRuntimeConfig(); 4 const runtimeconfig: RuntimeConfig = useRuntimeConfig();
5 const apiUrl = runtimeconfig.public.apiTMDBUrl; 5 const apiUrl = runtimeconfig.public.apiTMDBUrl;
6 const apiKey = runtimeconfig.public.apiTMDBSecret; 6 const apiKey = runtimeconfig.public.apiTMDBSecret;
@@ -11,9 +11,7 @@ export const useTMDB = function() { @@ -11,9 +11,7 @@ export const useTMDB = function() {
11 */ 11 */
12 const fetchPopularMovies = async (page: number) => { 12 const fetchPopularMovies = async (page: number) => {
13 try { 13 try {
14 - const response = await fetch( 14 + const response = await fetch(`${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`);
15 - `${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`,  
16 - );  
17 if (!response.ok) { 15 if (!response.ok) {
18 console.error("An error occurred when fetching popular movies:"); 16 console.error("An error occurred when fetching popular movies:");
19 } else { 17 } else {
@@ -44,5 +42,38 @@ export const useTMDB = function() { @@ -44,5 +42,38 @@ export const useTMDB = function() {
44 } 42 }
45 }; 43 };
46 44
47 - return { fetchPopularMovies, searchMovies } 45 + /**
48 -} 46 + * Fetch movie details by id.
  47 + * @param id
  48 + */
  49 + const fetchMovieDetails = async (id: number | string) => {
  50 + try {
  51 + const response = await fetch(`${apiUrl}/movie/${id}?api_key=${apiKey}&language=fr-FR`);
  52 + if (!response.ok) {
  53 + console.error("An error occurred when fetching movie details:");
  54 + } else {
  55 + return await response.json();
  56 + }
  57 + } catch (error) {
  58 + console.error("Error fetching details:", error);
  59 + }
  60 + };
  61 +
  62 + /**
  63 + * Fetch movie credits
  64 + */
  65 + const fetchMovieCredits = async (id: number | string) => {
  66 + try {
  67 + const response = await fetch(`${apiUrl}/movie/${id}/credits?api_key=${apiKey}&language=fr-FR`);
  68 + if (!response.ok) {
  69 + console.error("An error occurred when fetching movie credits:");
  70 + } else {
  71 + return await response.json();
  72 + }
  73 + } catch (error) {
  74 + console.error("Error fetching movie credits:", error);
  75 + }
  76 + };
  77 +
  78 + return { fetchPopularMovies, searchMovies, fetchMovieDetails, fetchMovieCredits };
  79 +};
  1 +import type { MovieInterface } from "~/interfaces/movie";
  2 +
  3 +export interface CreditInterface {
  4 + id: number;
  5 + name: string;
  6 + job?: string;
  7 + character?: string;
  8 +}
  9 +
  10 +export type CreditsResponse = {
  11 + id: number;
  12 + cast: CreditInterface[],
  13 + crew: CreditInterface[],
  14 + movie_id: unknown;
  15 + movie: MovieInterface;
  16 +}
  1 +import type { CreditInterface, CreditsResponse } from "~/interfaces/credit";
  2 +
1 export interface MovieInterface { 3 export interface MovieInterface {
2 id: number; 4 id: number;
3 - title: string; 5 + adult: boolean;
  6 + backdrop_path: string;
  7 + genre_ids: number[];
  8 + genres: Genre[];
  9 + original_language: string;
  10 + original_title: string;
  11 + overview: string;
  12 + popularity: number;
4 poster_path: string | null; 13 poster_path: string | null;
5 - vote_average: number;  
6 release_date: string; 14 release_date: string;
  15 + runtime: number
  16 + title: string;
  17 + video: boolean;
  18 + vote_average: number;
  19 + vote_count: number;
  20 + credit: CreditsResponse;
  21 +}
  22 +
  23 +export type Genre = {
  24 + id: number,
  25 + name: string,
7 } 26 }
  1 +import type { MovieInterface } from "~/interfaces/movie";
  2 +
  3 +export interface MovieCommentInterface {
  4 + createdAt: string;
  5 + username: string;
  6 + message: string;
  7 + rating: number;
  8 + movie_id: unknown;
  9 + movie: MovieInterface;
  10 +}
  1 +import { Model } from "pinia-orm";
  2 +import { Movie } from "~/models/movie";
  3 +
  4 +export class Credit extends Model {
  5 + /**
  6 + *
  7 + * @return {string}
  8 + */
  9 + static get entity() {
  10 + return "Credit";
  11 + }
  12 +
  13 + /**
  14 + *
  15 + * @return {string}
  16 + */
  17 + static get primaryKey() {
  18 + return "id";
  19 + }
  20 +
  21 + static fields() {
  22 + return {
  23 + // Attributs.
  24 + id: this.number(null),
  25 + cast: this.attr([]),
  26 + crew: this.attr([]),
  27 + // Relations.
  28 + movie_id: this.attr(null),
  29 + movie: this.belongsTo(Movie, "movie_id", "id"),
  30 + };
  31 + }
  32 +
  33 + static piniaOptions = {
  34 + persist: true,
  35 + };
  36 +}
1 import { Model } from "pinia-orm"; 1 import { Model } from "pinia-orm";
  2 +import { Credit } from "~/models/credit";
2 3
3 export class Movie extends Model { 4 export class Movie extends Model {
4 /** 5 /**
@@ -22,24 +23,37 @@ export class Movie extends Model { @@ -22,24 +23,37 @@ export class Movie extends Model {
22 // Attributs. 23 // Attributs.
23 id: this.number(null), 24 id: this.number(null),
24 adult: this.boolean(false), 25 adult: this.boolean(false),
25 - backdrop_pat: this.string(null), 26 + backdrop_path: this.string(null),
  27 + belongs_to_collection: this.attr(null),
  28 + budget: this.number(null),
26 genre_ids: this.attr([]), 29 genre_ids: this.attr([]),
  30 + genres: this.attr([]),
  31 + homepage: this.string(null),
  32 + imdb_id: this.string(null),
  33 + origin_country: this.attr([]),
27 original_language: this.string(null), 34 original_language: this.string(null),
28 original_title: this.string(null), 35 original_title: this.string(null),
29 overview: this.string(null), 36 overview: this.string(null),
30 popularity: this.number(null), 37 popularity: this.number(null),
31 poster_path: this.string(null), 38 poster_path: this.string(null),
  39 + production_companies: this.attr([]),
  40 + production_cuntries: this.attr([]),
32 release_date: this.string(null), 41 release_date: this.string(null),
  42 + revenue: this.number(null),
  43 + runtime: this.number(null),
  44 + spoken_languages: this.attr([]),
  45 + status: this.string(null),
  46 + tagline: this.string(null),
33 title: this.string(null), 47 title: this.string(null),
34 video: this.boolean(false), 48 video: this.boolean(false),
35 vote_average: this.number(null), 49 vote_average: this.number(null),
36 vote_count: this.number(null), 50 vote_count: this.number(null),
37 // Relations. 51 // Relations.
  52 + credit: this.hasOne(Credit, "movie_id", "id"),
38 }; 53 };
39 } 54 }
40 55
41 static piniaOptions = { 56 static piniaOptions = {
42 persist: true, 57 persist: true,
43 }; 58 };
44 -  
45 } 59 }
  1 +import { Model } from "pinia-orm";
  2 +import { Movie } from "~/models/movie";
  3 +
  4 +export class MovieComment extends Model {
  5 + /**
  6 + *
  7 + * @return {string}
  8 + */
  9 + static get entity() {
  10 + return "MovieComment";
  11 + }
  12 +
  13 + /**
  14 + *
  15 + * @return {string}
  16 + */
  17 + static get primaryKey() {
  18 + return "id";
  19 + }
  20 +
  21 + static fields() {
  22 + return {
  23 + // Attributs.
  24 + id: this.uid(),
  25 + createdAt: this.string(''),
  26 + username: this.string(''),
  27 + message: this.string(''),
  28 + rating: this.string(''),
  29 + // Relations.
  30 + movie_id: this.attr(null),
  31 + movie: this.belongsTo(Movie, "movie_id", "id"),
  32 + };
  33 + }
  34 +
  35 + static piniaOptions = {
  36 + persist: true,
  37 + };
  38 +}
@@ -25,6 +25,7 @@ export default defineNuxtConfig({ @@ -25,6 +25,7 @@ export default defineNuxtConfig({
25 "@nuxt/eslint", 25 "@nuxt/eslint",
26 "@nuxt/icon", 26 "@nuxt/icon",
27 "@nuxt/image", 27 "@nuxt/image",
  28 + "@nuxt/test-utils/module",
28 [ 29 [
29 "@pinia/nuxt", 30 "@pinia/nuxt",
30 { 31 {
@@ -68,8 +69,8 @@ export default defineNuxtConfig({ @@ -68,8 +69,8 @@ export default defineNuxtConfig({
68 // Keys within public are also exposed client-side. 69 // Keys within public are also exposed client-side.
69 public: { 70 public: {
70 apiTMDBSecret: process.env.NUXT_ENV_TMDB_API_KEY, 71 apiTMDBSecret: process.env.NUXT_ENV_TMDB_API_KEY,
71 - apiTMDBBearer: process.env.NUXT_ENV_TMDB_BEARER,  
72 apiTMDBUrl: process.env.NUXT_ENV_TMDB_URL, 72 apiTMDBUrl: process.env.NUXT_ENV_TMDB_URL,
  73 + apiTinyMceSecret: process.env.NUXT_ENV_TINY_MCE_API_KEY,
73 }, 74 },
74 }, 75 },
75 76
1 { 1 {
2 "name": "nuxt-app", 2 "name": "nuxt-app",
3 - "version": "0.2.0", 3 + "version": "0.3.0",
4 "lockfileVersion": 3, 4 "lockfileVersion": 3,
5 "requires": true, 5 "requires": true,
6 "packages": { 6 "packages": {
7 "": { 7 "": {
8 "name": "nuxt-app", 8 "name": "nuxt-app",
9 - "version": "0.2.0", 9 + "version": "0.3.0",
10 "hasInstallScript": true, 10 "hasInstallScript": true,
11 "dependencies": { 11 "dependencies": {
12 "@nuxt/eslint": "^1.3.0", 12 "@nuxt/eslint": "^1.3.0",
13 "@nuxt/icon": "^1.12.0", 13 "@nuxt/icon": "^1.12.0",
14 "@nuxt/image": "^1.10.0", 14 "@nuxt/image": "^1.10.0",
15 "@nuxt/scripts": "^0.11.6", 15 "@nuxt/scripts": "^0.11.6",
16 - "@nuxt/test-utils": "^3.17.2",  
17 "@nuxt/ui": "^2.22.0", 16 "@nuxt/ui": "^2.22.0",
18 "@pinia-orm/nuxt": "^1.10.2", 17 "@pinia-orm/nuxt": "^1.10.2",
19 "@pinia/nuxt": "^0.9.0", 18 "@pinia/nuxt": "^0.9.0",
  19 + "@tinymce/tinymce-vue": "^5.1.1",
  20 + "@types/vuelidate": "^0.7.22",
20 "@unhead/vue": "^2.0.8", 21 "@unhead/vue": "^2.0.8",
  22 + "@vitejs/plugin-vue": "^5.2.3",
  23 + "@vuelidate/core": "^2.0.3",
  24 + "@vuelidate/validators": "^2.0.4",
21 "@vueuse/core": "^13.1.0", 25 "@vueuse/core": "^13.1.0",
22 "@vueuse/nuxt": "^13.1.0", 26 "@vueuse/nuxt": "^13.1.0",
23 "eslint": "^9.25.1", 27 "eslint": "^9.25.1",
@@ -30,10 +34,16 @@ @@ -30,10 +34,16 @@
30 "vuetify-nuxt-module": "^0.18.6" 34 "vuetify-nuxt-module": "^0.18.6"
31 }, 35 },
32 "devDependencies": { 36 "devDependencies": {
  37 + "@nuxt/test-utils": "^3.17.2",
33 "@nuxtjs/tailwindcss": "^6.13.2", 38 "@nuxtjs/tailwindcss": "^6.13.2",
  39 + "@vue/test-utils": "^2.4.6",
34 "eslint-config-prettier": "^10.1.2", 40 "eslint-config-prettier": "^10.1.2",
35 "eslint-plugin-prettier": "^5.2.6", 41 "eslint-plugin-prettier": "^5.2.6",
36 - "prettier": "^3.5.3" 42 + "happy-dom": "^17.4.4",
  43 + "jsdom": "^26.1.0",
  44 + "playwright-core": "^1.52.0",
  45 + "prettier": "^3.5.3",
  46 + "vitest": "^3.1.2"
37 } 47 }
38 }, 48 },
39 "node_modules/@alloc/quick-lru": { 49 "node_modules/@alloc/quick-lru": {
@@ -115,6 +125,27 @@ @@ -115,6 +125,27 @@
115 "url": "https://github.com/sponsors/philsturgeon" 125 "url": "https://github.com/sponsors/philsturgeon"
116 } 126 }
117 }, 127 },
  128 + "node_modules/@asamuzakjp/css-color": {
  129 + "version": "3.1.5",
  130 + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.5.tgz",
  131 + "integrity": "sha512-w7AmVyTTiU41fNLsFDf+gA2Dwtbx2EJtn2pbJNAGSRAg50loXy1uLXA3hEpD8+eydcomTurw09tq5/AyceCaGg==",
  132 + "dev": true,
  133 + "license": "MIT",
  134 + "dependencies": {
  135 + "@csstools/css-calc": "^2.1.3",
  136 + "@csstools/css-color-parser": "^3.0.9",
  137 + "@csstools/css-parser-algorithms": "^3.0.4",
  138 + "@csstools/css-tokenizer": "^3.0.3",
  139 + "lru-cache": "^10.4.3"
  140 + }
  141 + },
  142 + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
  143 + "version": "10.4.3",
  144 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
  145 + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
  146 + "dev": true,
  147 + "license": "ISC"
  148 + },
118 "node_modules/@babel/code-frame": { 149 "node_modules/@babel/code-frame": {
119 "version": "7.26.2", 150 "version": "7.26.2",
120 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", 151 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
@@ -557,6 +588,121 @@ @@ -557,6 +588,121 @@
557 "node": ">=0.1.90" 588 "node": ">=0.1.90"
558 } 589 }
559 }, 590 },
  591 + "node_modules/@csstools/color-helpers": {
  592 + "version": "5.0.2",
  593 + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
  594 + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
  595 + "dev": true,
  596 + "funding": [
  597 + {
  598 + "type": "github",
  599 + "url": "https://github.com/sponsors/csstools"
  600 + },
  601 + {
  602 + "type": "opencollective",
  603 + "url": "https://opencollective.com/csstools"
  604 + }
  605 + ],
  606 + "license": "MIT-0",
  607 + "engines": {
  608 + "node": ">=18"
  609 + }
  610 + },
  611 + "node_modules/@csstools/css-calc": {
  612 + "version": "2.1.3",
  613 + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz",
  614 + "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==",
  615 + "dev": true,
  616 + "funding": [
  617 + {
  618 + "type": "github",
  619 + "url": "https://github.com/sponsors/csstools"
  620 + },
  621 + {
  622 + "type": "opencollective",
  623 + "url": "https://opencollective.com/csstools"
  624 + }
  625 + ],
  626 + "license": "MIT",
  627 + "engines": {
  628 + "node": ">=18"
  629 + },
  630 + "peerDependencies": {
  631 + "@csstools/css-parser-algorithms": "^3.0.4",
  632 + "@csstools/css-tokenizer": "^3.0.3"
  633 + }
  634 + },
  635 + "node_modules/@csstools/css-color-parser": {
  636 + "version": "3.0.9",
  637 + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz",
  638 + "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==",
  639 + "dev": true,
  640 + "funding": [
  641 + {
  642 + "type": "github",
  643 + "url": "https://github.com/sponsors/csstools"
  644 + },
  645 + {
  646 + "type": "opencollective",
  647 + "url": "https://opencollective.com/csstools"
  648 + }
  649 + ],
  650 + "license": "MIT",
  651 + "dependencies": {
  652 + "@csstools/color-helpers": "^5.0.2",
  653 + "@csstools/css-calc": "^2.1.3"
  654 + },
  655 + "engines": {
  656 + "node": ">=18"
  657 + },
  658 + "peerDependencies": {
  659 + "@csstools/css-parser-algorithms": "^3.0.4",
  660 + "@csstools/css-tokenizer": "^3.0.3"
  661 + }
  662 + },
  663 + "node_modules/@csstools/css-parser-algorithms": {
  664 + "version": "3.0.4",
  665 + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
  666 + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
  667 + "dev": true,
  668 + "funding": [
  669 + {
  670 + "type": "github",
  671 + "url": "https://github.com/sponsors/csstools"
  672 + },
  673 + {
  674 + "type": "opencollective",
  675 + "url": "https://opencollective.com/csstools"
  676 + }
  677 + ],
  678 + "license": "MIT",
  679 + "engines": {
  680 + "node": ">=18"
  681 + },
  682 + "peerDependencies": {
  683 + "@csstools/css-tokenizer": "^3.0.3"
  684 + }
  685 + },
  686 + "node_modules/@csstools/css-tokenizer": {
  687 + "version": "3.0.3",
  688 + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
  689 + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
  690 + "dev": true,
  691 + "funding": [
  692 + {
  693 + "type": "github",
  694 + "url": "https://github.com/sponsors/csstools"
  695 + },
  696 + {
  697 + "type": "opencollective",
  698 + "url": "https://opencollective.com/csstools"
  699 + }
  700 + ],
  701 + "license": "MIT",
  702 + "engines": {
  703 + "node": ">=18"
  704 + }
  705 + },
560 "node_modules/@csstools/selector-resolve-nested": { 706 "node_modules/@csstools/selector-resolve-nested": {
561 "version": "3.0.0", 707 "version": "3.0.0",
562 "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", 708 "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz",
@@ -3073,6 +3219,7 @@ @@ -3073,6 +3219,7 @@
3073 "version": "3.17.2", 3219 "version": "3.17.2",
3074 "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.17.2.tgz", 3220 "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.17.2.tgz",
3075 "integrity": "sha512-i1NiWsJx8sv8Zg8z3WD7ITehMi9s8DaR6ArgmDHaKkQ6RJSaVhrPKyGBTv3gzdoF8CHUKa3MNhdX62JWblvLMg==", 3221 "integrity": "sha512-i1NiWsJx8sv8Zg8z3WD7ITehMi9s8DaR6ArgmDHaKkQ6RJSaVhrPKyGBTv3gzdoF8CHUKa3MNhdX62JWblvLMg==",
  3222 + "dev": true,
3076 "license": "MIT", 3223 "license": "MIT",
3077 "dependencies": { 3224 "dependencies": {
3078 "@nuxt/kit": "^3.16.0", 3225 "@nuxt/kit": "^3.16.0",
@@ -3153,6 +3300,7 @@ @@ -3153,6 +3300,7 @@
3153 "version": "0.3.2", 3300 "version": "0.3.2",
3154 "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", 3301 "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
3155 "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", 3302 "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
  3303 + "dev": true,
3156 "license": "MIT" 3304 "license": "MIT"
3157 }, 3305 },
3158 "node_modules/@nuxt/ui": { 3306 "node_modules/@nuxt/ui": {
@@ -3319,6 +3467,13 @@ @@ -3319,6 +3467,13 @@
3319 "unctx": "^2.4.1" 3467 "unctx": "^2.4.1"
3320 } 3468 }
3321 }, 3469 },
  3470 + "node_modules/@one-ini/wasm": {
  3471 + "version": "0.1.1",
  3472 + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
  3473 + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
  3474 + "dev": true,
  3475 + "license": "MIT"
  3476 + },
3322 "node_modules/@oxc-parser/binding-darwin-arm64": { 3477 "node_modules/@oxc-parser/binding-darwin-arm64": {
3323 "version": "0.56.5", 3478 "version": "0.56.5",
3324 "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.56.5.tgz", 3479 "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.56.5.tgz",
@@ -4563,6 +4718,18 @@ @@ -4563,6 +4718,18 @@
4563 "vue": "^2.7.0 || ^3.0.0" 4718 "vue": "^2.7.0 || ^3.0.0"
4564 } 4719 }
4565 }, 4720 },
  4721 + "node_modules/@tinymce/tinymce-vue": {
  4722 + "version": "5.1.1",
  4723 + "resolved": "https://registry.npmjs.org/@tinymce/tinymce-vue/-/tinymce-vue-5.1.1.tgz",
  4724 + "integrity": "sha512-iO57HOWesFOhsaqjA5Ea6sDvQBmJJH3/dq00Uvg7metlct2kLF+ctRgoDsetLt6gmeZ7COPftr814/XzqnJ/dg==",
  4725 + "license": "MIT",
  4726 + "dependencies": {
  4727 + "tinymce": "^6.0.0 || ^5.5.1"
  4728 + },
  4729 + "peerDependencies": {
  4730 + "vue": "^3.0.0"
  4731 + }
  4732 + },
4566 "node_modules/@trysound/sax": { 4733 "node_modules/@trysound/sax": {
4567 "version": "0.2.0", 4734 "version": "0.2.0",
4568 "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", 4735 "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -4634,6 +4801,64 @@ @@ -4634,6 +4801,64 @@
4634 "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", 4801 "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
4635 "license": "MIT" 4802 "license": "MIT"
4636 }, 4803 },
  4804 + "node_modules/@types/vuelidate": {
  4805 + "version": "0.7.22",
  4806 + "resolved": "https://registry.npmjs.org/@types/vuelidate/-/vuelidate-0.7.22.tgz",
  4807 + "integrity": "sha512-bD3pP9FgL3pxMVQ9NJ3d8BbV8Ij6xsrDKdCO4l1Wq/AksXxRRmQ9lmYjRJwn/hLMcgWO/k0QdULfZWpRz13adw==",
  4808 + "license": "MIT",
  4809 + "dependencies": {
  4810 + "vue": "^2.7.15"
  4811 + }
  4812 + },
  4813 + "node_modules/@types/vuelidate/node_modules/@vue/compiler-sfc": {
  4814 + "version": "2.7.16",
  4815 + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
  4816 + "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
  4817 + "dependencies": {
  4818 + "@babel/parser": "^7.23.5",
  4819 + "postcss": "^8.4.14",
  4820 + "source-map": "^0.6.1"
  4821 + },
  4822 + "optionalDependencies": {
  4823 + "prettier": "^1.18.2 || ^2.0.0"
  4824 + }
  4825 + },
  4826 + "node_modules/@types/vuelidate/node_modules/prettier": {
  4827 + "version": "2.8.8",
  4828 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
  4829 + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
  4830 + "license": "MIT",
  4831 + "optional": true,
  4832 + "bin": {
  4833 + "prettier": "bin-prettier.js"
  4834 + },
  4835 + "engines": {
  4836 + "node": ">=10.13.0"
  4837 + },
  4838 + "funding": {
  4839 + "url": "https://github.com/prettier/prettier?sponsor=1"
  4840 + }
  4841 + },
  4842 + "node_modules/@types/vuelidate/node_modules/source-map": {
  4843 + "version": "0.6.1",
  4844 + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
  4845 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
  4846 + "license": "BSD-3-Clause",
  4847 + "engines": {
  4848 + "node": ">=0.10.0"
  4849 + }
  4850 + },
  4851 + "node_modules/@types/vuelidate/node_modules/vue": {
  4852 + "version": "2.7.16",
  4853 + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
  4854 + "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
  4855 + "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
  4856 + "license": "MIT",
  4857 + "dependencies": {
  4858 + "@vue/compiler-sfc": "2.7.16",
  4859 + "csstype": "^3.1.0"
  4860 + }
  4861 + },
4637 "node_modules/@types/web-bluetooth": { 4862 "node_modules/@types/web-bluetooth": {
4638 "version": "0.0.21", 4863 "version": "0.0.21",
4639 "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", 4864 "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
@@ -5468,6 +5693,119 @@ @@ -5468,6 +5693,119 @@
5468 "vue": "^3.0.0" 5693 "vue": "^3.0.0"
5469 } 5694 }
5470 }, 5695 },
  5696 + "node_modules/@vitest/expect": {
  5697 + "version": "3.1.2",
  5698 + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.2.tgz",
  5699 + "integrity": "sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA==",
  5700 + "dev": true,
  5701 + "license": "MIT",
  5702 + "dependencies": {
  5703 + "@vitest/spy": "3.1.2",
  5704 + "@vitest/utils": "3.1.2",
  5705 + "chai": "^5.2.0",
  5706 + "tinyrainbow": "^2.0.0"
  5707 + },
  5708 + "funding": {
  5709 + "url": "https://opencollective.com/vitest"
  5710 + }
  5711 + },
  5712 + "node_modules/@vitest/mocker": {
  5713 + "version": "3.1.2",
  5714 + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.2.tgz",
  5715 + "integrity": "sha512-kOtd6K2lc7SQ0mBqYv/wdGedlqPdM/B38paPY+OwJ1XiNi44w3Fpog82UfOibmHaV9Wod18A09I9SCKLyDMqgw==",
  5716 + "dev": true,
  5717 + "license": "MIT",
  5718 + "dependencies": {
  5719 + "@vitest/spy": "3.1.2",
  5720 + "estree-walker": "^3.0.3",
  5721 + "magic-string": "^0.30.17"
  5722 + },
  5723 + "funding": {
  5724 + "url": "https://opencollective.com/vitest"
  5725 + },
  5726 + "peerDependencies": {
  5727 + "msw": "^2.4.9",
  5728 + "vite": "^5.0.0 || ^6.0.0"
  5729 + },
  5730 + "peerDependenciesMeta": {
  5731 + "msw": {
  5732 + "optional": true
  5733 + },
  5734 + "vite": {
  5735 + "optional": true
  5736 + }
  5737 + }
  5738 + },
  5739 + "node_modules/@vitest/pretty-format": {
  5740 + "version": "3.1.2",
  5741 + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.2.tgz",
  5742 + "integrity": "sha512-R0xAiHuWeDjTSB3kQ3OQpT8Rx3yhdOAIm/JM4axXxnG7Q/fS8XUwggv/A4xzbQA+drYRjzkMnpYnOGAc4oeq8w==",
  5743 + "dev": true,
  5744 + "license": "MIT",
  5745 + "dependencies": {
  5746 + "tinyrainbow": "^2.0.0"
  5747 + },
  5748 + "funding": {
  5749 + "url": "https://opencollective.com/vitest"
  5750 + }
  5751 + },
  5752 + "node_modules/@vitest/runner": {
  5753 + "version": "3.1.2",
  5754 + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.2.tgz",
  5755 + "integrity": "sha512-bhLib9l4xb4sUMPXnThbnhX2Yi8OutBMA8Yahxa7yavQsFDtwY/jrUZwpKp2XH9DhRFJIeytlyGpXCqZ65nR+g==",
  5756 + "dev": true,
  5757 + "license": "MIT",
  5758 + "dependencies": {
  5759 + "@vitest/utils": "3.1.2",
  5760 + "pathe": "^2.0.3"
  5761 + },
  5762 + "funding": {
  5763 + "url": "https://opencollective.com/vitest"
  5764 + }
  5765 + },
  5766 + "node_modules/@vitest/snapshot": {
  5767 + "version": "3.1.2",
  5768 + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.2.tgz",
  5769 + "integrity": "sha512-Q1qkpazSF/p4ApZg1vfZSQ5Yw6OCQxVMVrLjslbLFA1hMDrT2uxtqMaw8Tc/jy5DLka1sNs1Y7rBcftMiaSH/Q==",
  5770 + "dev": true,
  5771 + "license": "MIT",
  5772 + "dependencies": {
  5773 + "@vitest/pretty-format": "3.1.2",
  5774 + "magic-string": "^0.30.17",
  5775 + "pathe": "^2.0.3"
  5776 + },
  5777 + "funding": {
  5778 + "url": "https://opencollective.com/vitest"
  5779 + }
  5780 + },
  5781 + "node_modules/@vitest/spy": {
  5782 + "version": "3.1.2",
  5783 + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.2.tgz",
  5784 + "integrity": "sha512-OEc5fSXMws6sHVe4kOFyDSj/+4MSwst0ib4un0DlcYgQvRuYQ0+M2HyqGaauUMnjq87tmUaMNDxKQx7wNfVqPA==",
  5785 + "dev": true,
  5786 + "license": "MIT",
  5787 + "dependencies": {
  5788 + "tinyspy": "^3.0.2"
  5789 + },
  5790 + "funding": {
  5791 + "url": "https://opencollective.com/vitest"
  5792 + }
  5793 + },
  5794 + "node_modules/@vitest/utils": {
  5795 + "version": "3.1.2",
  5796 + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.2.tgz",
  5797 + "integrity": "sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg==",
  5798 + "dev": true,
  5799 + "license": "MIT",
  5800 + "dependencies": {
  5801 + "@vitest/pretty-format": "3.1.2",
  5802 + "loupe": "^3.1.3",
  5803 + "tinyrainbow": "^2.0.0"
  5804 + },
  5805 + "funding": {
  5806 + "url": "https://opencollective.com/vitest"
  5807 + }
  5808 + },
5471 "node_modules/@vue-macros/common": { 5809 "node_modules/@vue-macros/common": {
5472 "version": "1.16.1", 5810 "version": "1.16.1",
5473 "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.16.1.tgz", 5811 "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.16.1.tgz",
@@ -5702,35 +6040,134 @@ @@ -5702,35 +6040,134 @@
5702 "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", 6040 "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
5703 "license": "MIT" 6041 "license": "MIT"
5704 }, 6042 },
5705 - "node_modules/@vuetify/loader-shared": { 6043 + "node_modules/@vue/test-utils": {
5706 - "version": "2.1.0", 6044 + "version": "2.4.6",
5707 - "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.0.tgz", 6045 + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz",
5708 - "integrity": "sha512-dNE6Ceym9ijFsmJKB7YGW0cxs7xbYV8+1LjU6jd4P14xOt/ji4Igtgzt0rJFbxu+ZhAzqz853lhB0z8V9Dy9cQ==", 6046 + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==",
  6047 + "dev": true,
5709 "license": "MIT", 6048 "license": "MIT",
5710 "dependencies": { 6049 "dependencies": {
5711 - "upath": "^2.0.1" 6050 + "js-beautify": "^1.14.9",
5712 - }, 6051 + "vue-component-type-helpers": "^2.0.0"
5713 - "peerDependencies": {  
5714 - "vue": "^3.0.0",  
5715 - "vuetify": "^3.0.0"  
5716 } 6052 }
5717 }, 6053 },
5718 - "node_modules/@vueuse/core": { 6054 + "node_modules/@vuelidate/core": {
5719 - "version": "13.1.0", 6055 + "version": "2.0.3",
5720 - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz", 6056 + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz",
5721 - "integrity": "sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==", 6057 + "integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==",
5722 "license": "MIT", 6058 "license": "MIT",
5723 "dependencies": { 6059 "dependencies": {
5724 - "@types/web-bluetooth": "^0.0.21", 6060 + "vue-demi": "^0.13.11"
5725 - "@vueuse/metadata": "13.1.0",  
5726 - "@vueuse/shared": "13.1.0"  
5727 - },  
5728 - "funding": {  
5729 - "url": "https://github.com/sponsors/antfu"  
5730 }, 6061 },
5731 "peerDependencies": { 6062 "peerDependencies": {
5732 - "vue": "^3.5.0" 6063 + "@vue/composition-api": "^1.0.0-rc.1",
5733 - } 6064 + "vue": "^2.0.0 || >=3.0.0"
  6065 + },
  6066 + "peerDependenciesMeta": {
  6067 + "@vue/composition-api": {
  6068 + "optional": true
  6069 + }
  6070 + }
  6071 + },
  6072 + "node_modules/@vuelidate/core/node_modules/vue-demi": {
  6073 + "version": "0.13.11",
  6074 + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
  6075 + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
  6076 + "hasInstallScript": true,
  6077 + "license": "MIT",
  6078 + "bin": {
  6079 + "vue-demi-fix": "bin/vue-demi-fix.js",
  6080 + "vue-demi-switch": "bin/vue-demi-switch.js"
  6081 + },
  6082 + "engines": {
  6083 + "node": ">=12"
  6084 + },
  6085 + "funding": {
  6086 + "url": "https://github.com/sponsors/antfu"
  6087 + },
  6088 + "peerDependencies": {
  6089 + "@vue/composition-api": "^1.0.0-rc.1",
  6090 + "vue": "^3.0.0-0 || ^2.6.0"
  6091 + },
  6092 + "peerDependenciesMeta": {
  6093 + "@vue/composition-api": {
  6094 + "optional": true
  6095 + }
  6096 + }
  6097 + },
  6098 + "node_modules/@vuelidate/validators": {
  6099 + "version": "2.0.4",
  6100 + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.4.tgz",
  6101 + "integrity": "sha512-odTxtUZ2JpwwiQ10t0QWYJkkYrfd0SyFYhdHH44QQ1jDatlZgTh/KRzrWVmn/ib9Gq7H4hFD4e8ahoo5YlUlDw==",
  6102 + "license": "MIT",
  6103 + "dependencies": {
  6104 + "vue-demi": "^0.13.11"
  6105 + },
  6106 + "peerDependencies": {
  6107 + "@vue/composition-api": "^1.0.0-rc.1",
  6108 + "vue": "^2.0.0 || >=3.0.0"
  6109 + },
  6110 + "peerDependenciesMeta": {
  6111 + "@vue/composition-api": {
  6112 + "optional": true
  6113 + }
  6114 + }
  6115 + },
  6116 + "node_modules/@vuelidate/validators/node_modules/vue-demi": {
  6117 + "version": "0.13.11",
  6118 + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
  6119 + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
  6120 + "hasInstallScript": true,
  6121 + "license": "MIT",
  6122 + "bin": {
  6123 + "vue-demi-fix": "bin/vue-demi-fix.js",
  6124 + "vue-demi-switch": "bin/vue-demi-switch.js"
  6125 + },
  6126 + "engines": {
  6127 + "node": ">=12"
  6128 + },
  6129 + "funding": {
  6130 + "url": "https://github.com/sponsors/antfu"
  6131 + },
  6132 + "peerDependencies": {
  6133 + "@vue/composition-api": "^1.0.0-rc.1",
  6134 + "vue": "^3.0.0-0 || ^2.6.0"
  6135 + },
  6136 + "peerDependenciesMeta": {
  6137 + "@vue/composition-api": {
  6138 + "optional": true
  6139 + }
  6140 + }
  6141 + },
  6142 + "node_modules/@vuetify/loader-shared": {
  6143 + "version": "2.1.0",
  6144 + "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.0.tgz",
  6145 + "integrity": "sha512-dNE6Ceym9ijFsmJKB7YGW0cxs7xbYV8+1LjU6jd4P14xOt/ji4Igtgzt0rJFbxu+ZhAzqz853lhB0z8V9Dy9cQ==",
  6146 + "license": "MIT",
  6147 + "dependencies": {
  6148 + "upath": "^2.0.1"
  6149 + },
  6150 + "peerDependencies": {
  6151 + "vue": "^3.0.0",
  6152 + "vuetify": "^3.0.0"
  6153 + }
  6154 + },
  6155 + "node_modules/@vueuse/core": {
  6156 + "version": "13.1.0",
  6157 + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz",
  6158 + "integrity": "sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==",
  6159 + "license": "MIT",
  6160 + "dependencies": {
  6161 + "@types/web-bluetooth": "^0.0.21",
  6162 + "@vueuse/metadata": "13.1.0",
  6163 + "@vueuse/shared": "13.1.0"
  6164 + },
  6165 + "funding": {
  6166 + "url": "https://github.com/sponsors/antfu"
  6167 + },
  6168 + "peerDependencies": {
  6169 + "vue": "^3.5.0"
  6170 + }
5734 }, 6171 },
5735 "node_modules/@vueuse/integrations": { 6172 "node_modules/@vueuse/integrations": {
5736 "version": "13.1.0", 6173 "version": "13.1.0",
@@ -6248,6 +6685,16 @@ @@ -6248,6 +6685,16 @@
6248 "node": ">=8" 6685 "node": ">=8"
6249 } 6686 }
6250 }, 6687 },
  6688 + "node_modules/assertion-error": {
  6689 + "version": "2.0.1",
  6690 + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
  6691 + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
  6692 + "dev": true,
  6693 + "license": "MIT",
  6694 + "engines": {
  6695 + "node": ">=12"
  6696 + }
  6697 + },
6251 "node_modules/ast-kit": { 6698 "node_modules/ast-kit": {
6252 "version": "1.4.2", 6699 "version": "1.4.2",
6253 "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.4.2.tgz", 6700 "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.4.2.tgz",
@@ -6828,6 +7275,23 @@ @@ -6828,6 +7275,23 @@
6828 ], 7275 ],
6829 "license": "CC-BY-4.0" 7276 "license": "CC-BY-4.0"
6830 }, 7277 },
  7278 + "node_modules/chai": {
  7279 + "version": "5.2.0",
  7280 + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
  7281 + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
  7282 + "dev": true,
  7283 + "license": "MIT",
  7284 + "dependencies": {
  7285 + "assertion-error": "^2.0.1",
  7286 + "check-error": "^2.1.1",
  7287 + "deep-eql": "^5.0.1",
  7288 + "loupe": "^3.1.0",
  7289 + "pathval": "^2.0.0"
  7290 + },
  7291 + "engines": {
  7292 + "node": ">=12"
  7293 + }
  7294 + },
6831 "node_modules/chalk": { 7295 "node_modules/chalk": {
6832 "version": "4.1.2", 7296 "version": "4.1.2",
6833 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 7297 "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -6856,6 +7320,16 @@ @@ -6856,6 +7320,16 @@
6856 "node": ">=8" 7320 "node": ">=8"
6857 } 7321 }
6858 }, 7322 },
  7323 + "node_modules/check-error": {
  7324 + "version": "2.1.1",
  7325 + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
  7326 + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
  7327 + "dev": true,
  7328 + "license": "MIT",
  7329 + "engines": {
  7330 + "node": ">= 16"
  7331 + }
  7332 + },
6859 "node_modules/chokidar": { 7333 "node_modules/chokidar": {
6860 "version": "4.0.3", 7334 "version": "4.0.3",
6861 "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", 7335 "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -7138,6 +7612,24 @@ @@ -7138,6 +7612,24 @@
7138 "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", 7612 "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
7139 "license": "MIT" 7613 "license": "MIT"
7140 }, 7614 },
  7615 + "node_modules/config-chain": {
  7616 + "version": "1.1.13",
  7617 + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
  7618 + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
  7619 + "dev": true,
  7620 + "license": "MIT",
  7621 + "dependencies": {
  7622 + "ini": "^1.3.4",
  7623 + "proto-list": "~1.2.1"
  7624 + }
  7625 + },
  7626 + "node_modules/config-chain/node_modules/ini": {
  7627 + "version": "1.3.8",
  7628 + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
  7629 + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
  7630 + "dev": true,
  7631 + "license": "ISC"
  7632 + },
7141 "node_modules/consola": { 7633 "node_modules/consola": {
7142 "version": "3.4.2", 7634 "version": "3.4.2",
7143 "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", 7635 "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
@@ -7530,6 +8022,20 @@ @@ -7530,6 +8022,20 @@
7530 "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", 8022 "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
7531 "license": "CC0-1.0" 8023 "license": "CC0-1.0"
7532 }, 8024 },
  8025 + "node_modules/cssstyle": {
  8026 + "version": "4.3.1",
  8027 + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz",
  8028 + "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==",
  8029 + "dev": true,
  8030 + "license": "MIT",
  8031 + "dependencies": {
  8032 + "@asamuzakjp/css-color": "^3.1.2",
  8033 + "rrweb-cssom": "^0.8.0"
  8034 + },
  8035 + "engines": {
  8036 + "node": ">=18"
  8037 + }
  8038 + },
7533 "node_modules/csstype": { 8039 "node_modules/csstype": {
7534 "version": "3.1.3", 8040 "version": "3.1.3",
7535 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 8041 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -7545,6 +8051,67 @@ @@ -7545,6 +8051,67 @@
7545 "node": ">= 12" 8051 "node": ">= 12"
7546 } 8052 }
7547 }, 8053 },
  8054 + "node_modules/data-urls": {
  8055 + "version": "5.0.0",
  8056 + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
  8057 + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
  8058 + "dev": true,
  8059 + "license": "MIT",
  8060 + "dependencies": {
  8061 + "whatwg-mimetype": "^4.0.0",
  8062 + "whatwg-url": "^14.0.0"
  8063 + },
  8064 + "engines": {
  8065 + "node": ">=18"
  8066 + }
  8067 + },
  8068 + "node_modules/data-urls/node_modules/tr46": {
  8069 + "version": "5.1.1",
  8070 + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
  8071 + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
  8072 + "dev": true,
  8073 + "license": "MIT",
  8074 + "dependencies": {
  8075 + "punycode": "^2.3.1"
  8076 + },
  8077 + "engines": {
  8078 + "node": ">=18"
  8079 + }
  8080 + },
  8081 + "node_modules/data-urls/node_modules/webidl-conversions": {
  8082 + "version": "7.0.0",
  8083 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
  8084 + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
  8085 + "dev": true,
  8086 + "license": "BSD-2-Clause",
  8087 + "engines": {
  8088 + "node": ">=12"
  8089 + }
  8090 + },
  8091 + "node_modules/data-urls/node_modules/whatwg-mimetype": {
  8092 + "version": "4.0.0",
  8093 + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
  8094 + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
  8095 + "dev": true,
  8096 + "license": "MIT",
  8097 + "engines": {
  8098 + "node": ">=18"
  8099 + }
  8100 + },
  8101 + "node_modules/data-urls/node_modules/whatwg-url": {
  8102 + "version": "14.2.0",
  8103 + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
  8104 + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
  8105 + "dev": true,
  8106 + "license": "MIT",
  8107 + "dependencies": {
  8108 + "tr46": "^5.1.0",
  8109 + "webidl-conversions": "^7.0.0"
  8110 + },
  8111 + "engines": {
  8112 + "node": ">=18"
  8113 + }
  8114 + },
7548 "node_modules/db0": { 8115 "node_modules/db0": {
7549 "version": "0.3.2", 8116 "version": "0.3.2",
7550 "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.2.tgz", 8117 "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.2.tgz",
@@ -7605,6 +8172,13 @@ @@ -7605,6 +8172,13 @@
7605 "callsite": "^1.0.0" 8172 "callsite": "^1.0.0"
7606 } 8173 }
7607 }, 8174 },
  8175 + "node_modules/decimal.js": {
  8176 + "version": "10.5.0",
  8177 + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
  8178 + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
  8179 + "dev": true,
  8180 + "license": "MIT"
  8181 + },
7608 "node_modules/decompress-response": { 8182 "node_modules/decompress-response": {
7609 "version": "6.0.0", 8183 "version": "6.0.0",
7610 "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 8184 "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -7621,6 +8195,16 @@ @@ -7621,6 +8195,16 @@
7621 "url": "https://github.com/sponsors/sindresorhus" 8195 "url": "https://github.com/sponsors/sindresorhus"
7622 } 8196 }
7623 }, 8197 },
  8198 + "node_modules/deep-eql": {
  8199 + "version": "5.0.2",
  8200 + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
  8201 + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
  8202 + "dev": true,
  8203 + "license": "MIT",
  8204 + "engines": {
  8205 + "node": ">=6"
  8206 + }
  8207 + },
7624 "node_modules/deep-equal": { 8208 "node_modules/deep-equal": {
7625 "version": "1.0.1", 8209 "version": "1.0.1",
7626 "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 8210 "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
@@ -8028,6 +8612,41 @@ @@ -8028,6 +8612,41 @@
8028 "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 8612 "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
8029 "license": "MIT" 8613 "license": "MIT"
8030 }, 8614 },
  8615 + "node_modules/editorconfig": {
  8616 + "version": "1.0.4",
  8617 + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
  8618 + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
  8619 + "dev": true,
  8620 + "license": "MIT",
  8621 + "dependencies": {
  8622 + "@one-ini/wasm": "0.1.1",
  8623 + "commander": "^10.0.0",
  8624 + "minimatch": "9.0.1",
  8625 + "semver": "^7.5.3"
  8626 + },
  8627 + "bin": {
  8628 + "editorconfig": "bin/editorconfig"
  8629 + },
  8630 + "engines": {
  8631 + "node": ">=14"
  8632 + }
  8633 + },
  8634 + "node_modules/editorconfig/node_modules/minimatch": {
  8635 + "version": "9.0.1",
  8636 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
  8637 + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
  8638 + "dev": true,
  8639 + "license": "ISC",
  8640 + "dependencies": {
  8641 + "brace-expansion": "^2.0.1"
  8642 + },
  8643 + "engines": {
  8644 + "node": ">=16 || 14 >=14.17"
  8645 + },
  8646 + "funding": {
  8647 + "url": "https://github.com/sponsors/isaacs"
  8648 + }
  8649 + },
8031 "node_modules/ee-first": { 8650 "node_modules/ee-first": {
8032 "version": "1.1.1", 8651 "version": "1.1.1",
8033 "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 8652 "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -9020,6 +9639,16 @@ @@ -9020,6 +9639,16 @@
9020 "node": ">=6" 9639 "node": ">=6"
9021 } 9640 }
9022 }, 9641 },
  9642 + "node_modules/expect-type": {
  9643 + "version": "1.2.1",
  9644 + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz",
  9645 + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==",
  9646 + "dev": true,
  9647 + "license": "Apache-2.0",
  9648 + "engines": {
  9649 + "node": ">=12.0.0"
  9650 + }
  9651 + },
9023 "node_modules/exsolve": { 9652 "node_modules/exsolve": {
9024 "version": "1.0.5", 9653 "version": "1.0.5",
9025 "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz", 9654 "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz",
@@ -9083,6 +9712,7 @@ @@ -9083,6 +9712,7 @@
9083 "version": "6.0.0", 9712 "version": "6.0.0",
9084 "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz", 9713 "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz",
9085 "integrity": "sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==", 9714 "integrity": "sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==",
  9715 + "dev": true,
9086 "license": "Apache-2.0", 9716 "license": "Apache-2.0",
9087 "engines": { 9717 "engines": {
9088 "node": ">=18" 9718 "node": ">=18"
@@ -9807,6 +10437,30 @@ @@ -9807,6 +10437,30 @@
9807 "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", 10437 "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==",
9808 "license": "MIT" 10438 "license": "MIT"
9809 }, 10439 },
  10440 + "node_modules/happy-dom": {
  10441 + "version": "17.4.4",
  10442 + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.4.4.tgz",
  10443 + "integrity": "sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==",
  10444 + "dev": true,
  10445 + "license": "MIT",
  10446 + "dependencies": {
  10447 + "webidl-conversions": "^7.0.0",
  10448 + "whatwg-mimetype": "^3.0.0"
  10449 + },
  10450 + "engines": {
  10451 + "node": ">=18.0.0"
  10452 + }
  10453 + },
  10454 + "node_modules/happy-dom/node_modules/webidl-conversions": {
  10455 + "version": "7.0.0",
  10456 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
  10457 + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
  10458 + "dev": true,
  10459 + "license": "BSD-2-Clause",
  10460 + "engines": {
  10461 + "node": ">=12"
  10462 + }
  10463 + },
9810 "node_modules/has-flag": { 10464 "node_modules/has-flag": {
9811 "version": "4.0.0", 10465 "version": "4.0.0",
9812 "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 10466 "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -9885,6 +10539,19 @@ @@ -9885,6 +10539,19 @@
9885 "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", 10539 "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
9886 "license": "ISC" 10540 "license": "ISC"
9887 }, 10541 },
  10542 + "node_modules/html-encoding-sniffer": {
  10543 + "version": "4.0.0",
  10544 + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
  10545 + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
  10546 + "dev": true,
  10547 + "license": "MIT",
  10548 + "dependencies": {
  10549 + "whatwg-encoding": "^3.1.1"
  10550 + },
  10551 + "engines": {
  10552 + "node": ">=18"
  10553 + }
  10554 + },
9888 "node_modules/http-assert": { 10555 "node_modules/http-assert": {
9889 "version": "1.5.0", 10556 "version": "1.5.0",
9890 "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", 10557 "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
@@ -9948,6 +10615,20 @@ @@ -9948,6 +10615,20 @@
9948 "node": ">= 0.8" 10615 "node": ">= 0.8"
9949 } 10616 }
9950 }, 10617 },
  10618 + "node_modules/http-proxy-agent": {
  10619 + "version": "7.0.2",
  10620 + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
  10621 + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
  10622 + "dev": true,
  10623 + "license": "MIT",
  10624 + "dependencies": {
  10625 + "agent-base": "^7.1.0",
  10626 + "debug": "^4.3.4"
  10627 + },
  10628 + "engines": {
  10629 + "node": ">= 14"
  10630 + }
  10631 + },
9951 "node_modules/http-shutdown": { 10632 "node_modules/http-shutdown": {
9952 "version": "1.2.2", 10633 "version": "1.2.2",
9953 "resolved": "https://registry.npmjs.org/http-shutdown/-/http-shutdown-1.2.2.tgz", 10634 "resolved": "https://registry.npmjs.org/http-shutdown/-/http-shutdown-1.2.2.tgz",
@@ -9986,6 +10667,19 @@ @@ -9986,6 +10667,19 @@
9986 "node": ">=16.17.0" 10667 "node": ">=16.17.0"
9987 } 10668 }
9988 }, 10669 },
  10670 + "node_modules/iconv-lite": {
  10671 + "version": "0.6.3",
  10672 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
  10673 + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
  10674 + "dev": true,
  10675 + "license": "MIT",
  10676 + "dependencies": {
  10677 + "safer-buffer": ">= 2.1.2 < 3.0.0"
  10678 + },
  10679 + "engines": {
  10680 + "node": ">=0.10.0"
  10681 + }
  10682 + },
9989 "node_modules/ieee754": { 10683 "node_modules/ieee754": {
9990 "version": "1.2.1", 10684 "version": "1.2.1",
9991 "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 10685 "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -10823,6 +11517,13 @@ @@ -10823,6 +11517,13 @@
10823 "node": ">=8" 11517 "node": ">=8"
10824 } 11518 }
10825 }, 11519 },
  11520 + "node_modules/is-potential-custom-element-name": {
  11521 + "version": "1.0.1",
  11522 + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
  11523 + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
  11524 + "dev": true,
  11525 + "license": "MIT"
  11526 + },
10826 "node_modules/is-reference": { 11527 "node_modules/is-reference": {
10827 "version": "1.2.1", 11528 "version": "1.2.1",
10828 "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", 11529 "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
@@ -10980,31 +11681,207 @@ @@ -10980,31 +11681,207 @@
10980 "jiti": "bin/jiti.js" 11681 "jiti": "bin/jiti.js"
10981 } 11682 }
10982 }, 11683 },
10983 - "node_modules/js-tokens": { 11684 + "node_modules/js-beautify": {
  11685 + "version": "1.15.4",
  11686 + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
  11687 + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==",
  11688 + "dev": true,
  11689 + "license": "MIT",
  11690 + "dependencies": {
  11691 + "config-chain": "^1.1.13",
  11692 + "editorconfig": "^1.0.4",
  11693 + "glob": "^10.4.2",
  11694 + "js-cookie": "^3.0.5",
  11695 + "nopt": "^7.2.1"
  11696 + },
  11697 + "bin": {
  11698 + "css-beautify": "js/bin/css-beautify.js",
  11699 + "html-beautify": "js/bin/html-beautify.js",
  11700 + "js-beautify": "js/bin/js-beautify.js"
  11701 + },
  11702 + "engines": {
  11703 + "node": ">=14"
  11704 + }
  11705 + },
  11706 + "node_modules/js-beautify/node_modules/abbrev": {
  11707 + "version": "2.0.0",
  11708 + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
  11709 + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
  11710 + "dev": true,
  11711 + "license": "ISC",
  11712 + "engines": {
  11713 + "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
  11714 + }
  11715 + },
  11716 + "node_modules/js-beautify/node_modules/glob": {
  11717 + "version": "10.4.5",
  11718 + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
  11719 + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
  11720 + "dev": true,
  11721 + "license": "ISC",
  11722 + "dependencies": {
  11723 + "foreground-child": "^3.1.0",
  11724 + "jackspeak": "^3.1.2",
  11725 + "minimatch": "^9.0.4",
  11726 + "minipass": "^7.1.2",
  11727 + "package-json-from-dist": "^1.0.0",
  11728 + "path-scurry": "^1.11.1"
  11729 + },
  11730 + "bin": {
  11731 + "glob": "dist/esm/bin.mjs"
  11732 + },
  11733 + "funding": {
  11734 + "url": "https://github.com/sponsors/isaacs"
  11735 + }
  11736 + },
  11737 + "node_modules/js-beautify/node_modules/nopt": {
  11738 + "version": "7.2.1",
  11739 + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
  11740 + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
  11741 + "dev": true,
  11742 + "license": "ISC",
  11743 + "dependencies": {
  11744 + "abbrev": "^2.0.0"
  11745 + },
  11746 + "bin": {
  11747 + "nopt": "bin/nopt.js"
  11748 + },
  11749 + "engines": {
  11750 + "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
  11751 + }
  11752 + },
  11753 + "node_modules/js-cookie": {
  11754 + "version": "3.0.5",
  11755 + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
  11756 + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
  11757 + "dev": true,
  11758 + "license": "MIT",
  11759 + "engines": {
  11760 + "node": ">=14"
  11761 + }
  11762 + },
  11763 + "node_modules/js-tokens": {
  11764 + "version": "4.0.0",
  11765 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
  11766 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
  11767 + "license": "MIT"
  11768 + },
  11769 + "node_modules/js-yaml": {
  11770 + "version": "4.1.0",
  11771 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
  11772 + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
  11773 + "license": "MIT",
  11774 + "dependencies": {
  11775 + "argparse": "^2.0.1"
  11776 + },
  11777 + "bin": {
  11778 + "js-yaml": "bin/js-yaml.js"
  11779 + }
  11780 + },
  11781 + "node_modules/jsdoc-type-pratt-parser": {
  11782 + "version": "4.1.0",
  11783 + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
  11784 + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
  11785 + "license": "MIT",
  11786 + "engines": {
  11787 + "node": ">=12.0.0"
  11788 + }
  11789 + },
  11790 + "node_modules/jsdom": {
  11791 + "version": "26.1.0",
  11792 + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
  11793 + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
  11794 + "dev": true,
  11795 + "license": "MIT",
  11796 + "dependencies": {
  11797 + "cssstyle": "^4.2.1",
  11798 + "data-urls": "^5.0.0",
  11799 + "decimal.js": "^10.5.0",
  11800 + "html-encoding-sniffer": "^4.0.0",
  11801 + "http-proxy-agent": "^7.0.2",
  11802 + "https-proxy-agent": "^7.0.6",
  11803 + "is-potential-custom-element-name": "^1.0.1",
  11804 + "nwsapi": "^2.2.16",
  11805 + "parse5": "^7.2.1",
  11806 + "rrweb-cssom": "^0.8.0",
  11807 + "saxes": "^6.0.0",
  11808 + "symbol-tree": "^3.2.4",
  11809 + "tough-cookie": "^5.1.1",
  11810 + "w3c-xmlserializer": "^5.0.0",
  11811 + "webidl-conversions": "^7.0.0",
  11812 + "whatwg-encoding": "^3.1.1",
  11813 + "whatwg-mimetype": "^4.0.0",
  11814 + "whatwg-url": "^14.1.1",
  11815 + "ws": "^8.18.0",
  11816 + "xml-name-validator": "^5.0.0"
  11817 + },
  11818 + "engines": {
  11819 + "node": ">=18"
  11820 + },
  11821 + "peerDependencies": {
  11822 + "canvas": "^3.0.0"
  11823 + },
  11824 + "peerDependenciesMeta": {
  11825 + "canvas": {
  11826 + "optional": true
  11827 + }
  11828 + }
  11829 + },
  11830 + "node_modules/jsdom/node_modules/tr46": {
  11831 + "version": "5.1.1",
  11832 + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
  11833 + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
  11834 + "dev": true,
  11835 + "license": "MIT",
  11836 + "dependencies": {
  11837 + "punycode": "^2.3.1"
  11838 + },
  11839 + "engines": {
  11840 + "node": ">=18"
  11841 + }
  11842 + },
  11843 + "node_modules/jsdom/node_modules/webidl-conversions": {
  11844 + "version": "7.0.0",
  11845 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
  11846 + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
  11847 + "dev": true,
  11848 + "license": "BSD-2-Clause",
  11849 + "engines": {
  11850 + "node": ">=12"
  11851 + }
  11852 + },
  11853 + "node_modules/jsdom/node_modules/whatwg-mimetype": {
10984 "version": "4.0.0", 11854 "version": "4.0.0",
10985 - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 11855 + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
10986 - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 11856 + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
10987 - "license": "MIT" 11857 + "dev": true,
  11858 + "license": "MIT",
  11859 + "engines": {
  11860 + "node": ">=18"
  11861 + }
10988 }, 11862 },
10989 - "node_modules/js-yaml": { 11863 + "node_modules/jsdom/node_modules/whatwg-url": {
10990 - "version": "4.1.0", 11864 + "version": "14.2.0",
10991 - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 11865 + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
10992 - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 11866 + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
  11867 + "dev": true,
10993 "license": "MIT", 11868 "license": "MIT",
10994 "dependencies": { 11869 "dependencies": {
10995 - "argparse": "^2.0.1" 11870 + "tr46": "^5.1.0",
  11871 + "webidl-conversions": "^7.0.0"
10996 }, 11872 },
10997 - "bin": { 11873 + "engines": {
10998 - "js-yaml": "bin/js-yaml.js" 11874 + "node": ">=18"
10999 } 11875 }
11000 }, 11876 },
11001 - "node_modules/jsdoc-type-pratt-parser": { 11877 + "node_modules/jsdom/node_modules/xml-name-validator": {
11002 - "version": "4.1.0", 11878 + "version": "5.0.0",
11003 - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", 11879 + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
11004 - "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", 11880 + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
11005 - "license": "MIT", 11881 + "dev": true,
  11882 + "license": "Apache-2.0",
11006 "engines": { 11883 "engines": {
11007 - "node": ">=12.0.0" 11884 + "node": ">=18"
11008 } 11885 }
11009 }, 11886 },
11010 "node_modules/jsesc": { 11887 "node_modules/jsesc": {
@@ -11578,6 +12455,13 @@ @@ -11578,6 +12455,13 @@
11578 "node": ">= 12.0.0" 12455 "node": ">= 12.0.0"
11579 } 12456 }
11580 }, 12457 },
  12458 + "node_modules/loupe": {
  12459 + "version": "3.1.3",
  12460 + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
  12461 + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
  12462 + "dev": true,
  12463 + "license": "MIT"
  12464 + },
11581 "node_modules/lru-cache": { 12465 "node_modules/lru-cache": {
11582 "version": "5.1.1", 12466 "version": "5.1.1",
11583 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 12467 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -12509,6 +13393,13 @@ @@ -12509,6 +13393,13 @@
12509 } 13393 }
12510 } 13394 }
12511 }, 13395 },
  13396 + "node_modules/nwsapi": {
  13397 + "version": "2.2.20",
  13398 + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
  13399 + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
  13400 + "dev": true,
  13401 + "license": "MIT"
  13402 + },
12512 "node_modules/nypm": { 13403 "node_modules/nypm": {
12513 "version": "0.6.0", 13404 "version": "0.6.0",
12514 "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz", 13405 "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz",
@@ -12912,6 +13803,32 @@ @@ -12912,6 +13803,32 @@
12912 "node": ">=14.13.0" 13803 "node": ">=14.13.0"
12913 } 13804 }
12914 }, 13805 },
  13806 + "node_modules/parse5": {
  13807 + "version": "7.3.0",
  13808 + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
  13809 + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
  13810 + "dev": true,
  13811 + "license": "MIT",
  13812 + "dependencies": {
  13813 + "entities": "^6.0.0"
  13814 + },
  13815 + "funding": {
  13816 + "url": "https://github.com/inikulin/parse5?sponsor=1"
  13817 + }
  13818 + },
  13819 + "node_modules/parse5/node_modules/entities": {
  13820 + "version": "6.0.0",
  13821 + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
  13822 + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
  13823 + "dev": true,
  13824 + "license": "BSD-2-Clause",
  13825 + "engines": {
  13826 + "node": ">=0.12"
  13827 + },
  13828 + "funding": {
  13829 + "url": "https://github.com/fb55/entities?sponsor=1"
  13830 + }
  13831 + },
12915 "node_modules/parseurl": { 13832 "node_modules/parseurl": {
12916 "version": "1.3.3", 13833 "version": "1.3.3",
12917 "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 13834 "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -13000,6 +13917,16 @@ @@ -13000,6 +13917,16 @@
13000 "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 13917 "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
13001 "license": "MIT" 13918 "license": "MIT"
13002 }, 13919 },
  13920 + "node_modules/pathval": {
  13921 + "version": "2.0.0",
  13922 + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
  13923 + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
  13924 + "dev": true,
  13925 + "license": "MIT",
  13926 + "engines": {
  13927 + "node": ">= 14.16"
  13928 + }
  13929 + },
13003 "node_modules/pend": { 13930 "node_modules/pend": {
13004 "version": "1.2.0", 13931 "version": "1.2.0",
13005 "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 13932 "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -13121,6 +14048,19 @@ @@ -13121,6 +14048,19 @@
13121 "pathe": "^2.0.3" 14048 "pathe": "^2.0.3"
13122 } 14049 }
13123 }, 14050 },
  14051 + "node_modules/playwright-core": {
  14052 + "version": "1.52.0",
  14053 + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
  14054 + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
  14055 + "dev": true,
  14056 + "license": "Apache-2.0",
  14057 + "bin": {
  14058 + "playwright-core": "cli.js"
  14059 + },
  14060 + "engines": {
  14061 + "node": ">=18"
  14062 + }
  14063 + },
13124 "node_modules/pluralize": { 14064 "node_modules/pluralize": {
13125 "version": "8.0.0", 14065 "version": "8.0.0",
13126 "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", 14066 "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
@@ -14037,6 +14977,13 @@ @@ -14037,6 +14977,13 @@
14037 "node": ">= 6" 14977 "node": ">= 6"
14038 } 14978 }
14039 }, 14979 },
  14980 + "node_modules/proto-list": {
  14981 + "version": "1.2.4",
  14982 + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
  14983 + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
  14984 + "dev": true,
  14985 + "license": "ISC"
  14986 + },
14040 "node_modules/protocols": { 14987 "node_modules/protocols": {
14041 "version": "2.0.2", 14988 "version": "2.0.2",
14042 "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", 14989 "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz",
@@ -14690,6 +15637,13 @@ @@ -14690,6 +15637,13 @@
14690 } 15637 }
14691 } 15638 }
14692 }, 15639 },
  15640 + "node_modules/rrweb-cssom": {
  15641 + "version": "0.8.0",
  15642 + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
  15643 + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
  15644 + "dev": true,
  15645 + "license": "MIT"
  15646 + },
14693 "node_modules/run-applescript": { 15647 "node_modules/run-applescript": {
14694 "version": "7.0.0", 15648 "version": "7.0.0",
14695 "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", 15649 "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz",
@@ -14771,6 +15725,26 @@ @@ -14771,6 +15725,26 @@
14771 "node": ">=10" 15725 "node": ">=10"
14772 } 15726 }
14773 }, 15727 },
  15728 + "node_modules/safer-buffer": {
  15729 + "version": "2.1.2",
  15730 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
  15731 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
  15732 + "dev": true,
  15733 + "license": "MIT"
  15734 + },
  15735 + "node_modules/saxes": {
  15736 + "version": "6.0.0",
  15737 + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
  15738 + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
  15739 + "dev": true,
  15740 + "license": "ISC",
  15741 + "dependencies": {
  15742 + "xmlchars": "^2.2.0"
  15743 + },
  15744 + "engines": {
  15745 + "node": ">=v12.22.7"
  15746 + }
  15747 + },
14774 "node_modules/scslre": { 15748 "node_modules/scslre": {
14775 "version": "0.3.0", 15749 "version": "0.3.0",
14776 "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", 15750 "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz",
@@ -15043,6 +16017,13 @@ @@ -15043,6 +16017,13 @@
15043 "url": "https://github.com/sponsors/ljharb" 16017 "url": "https://github.com/sponsors/ljharb"
15044 } 16018 }
15045 }, 16019 },
  16020 + "node_modules/siginfo": {
  16021 + "version": "2.0.0",
  16022 + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
  16023 + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
  16024 + "dev": true,
  16025 + "license": "ISC"
  16026 + },
15046 "node_modules/signal-exit": { 16027 "node_modules/signal-exit": {
15047 "version": "4.1.0", 16028 "version": "4.1.0",
15048 "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 16029 "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -15263,6 +16244,13 @@ @@ -15263,6 +16244,13 @@
15263 "node": "*" 16244 "node": "*"
15264 } 16245 }
15265 }, 16246 },
  16247 + "node_modules/stackback": {
  16248 + "version": "0.0.2",
  16249 + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
  16250 + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
  16251 + "dev": true,
  16252 + "license": "MIT"
  16253 + },
15266 "node_modules/standard-as-callback": { 16254 "node_modules/standard-as-callback": {
15267 "version": "2.1.0", 16255 "version": "2.1.0",
15268 "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", 16256 "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
@@ -15627,6 +16615,13 @@ @@ -15627,6 +16615,13 @@
15627 "node": ">= 10" 16615 "node": ">= 10"
15628 } 16616 }
15629 }, 16617 },
  16618 + "node_modules/symbol-tree": {
  16619 + "version": "3.2.4",
  16620 + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
  16621 + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
  16622 + "dev": true,
  16623 + "license": "MIT"
  16624 + },
15630 "node_modules/synckit": { 16625 "node_modules/synckit": {
15631 "version": "0.9.2", 16626 "version": "0.9.2",
15632 "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", 16627 "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
@@ -16033,6 +17028,13 @@ @@ -16033,6 +17028,13 @@
16033 "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", 17028 "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
16034 "license": "MIT" 17029 "license": "MIT"
16035 }, 17030 },
  17031 + "node_modules/tinybench": {
  17032 + "version": "2.9.0",
  17033 + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
  17034 + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
  17035 + "dev": true,
  17036 + "license": "MIT"
  17037 + },
16036 "node_modules/tinyexec": { 17038 "node_modules/tinyexec": {
16037 "version": "1.0.1", 17039 "version": "1.0.1",
16038 "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", 17040 "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
@@ -16055,6 +17057,62 @@ @@ -16055,6 +17057,62 @@
16055 "url": "https://github.com/sponsors/SuperchupuDev" 17057 "url": "https://github.com/sponsors/SuperchupuDev"
16056 } 17058 }
16057 }, 17059 },
  17060 + "node_modules/tinymce": {
  17061 + "version": "6.8.5",
  17062 + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.5.tgz",
  17063 + "integrity": "sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g==",
  17064 + "license": "MIT"
  17065 + },
  17066 + "node_modules/tinypool": {
  17067 + "version": "1.0.2",
  17068 + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
  17069 + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
  17070 + "dev": true,
  17071 + "license": "MIT",
  17072 + "engines": {
  17073 + "node": "^18.0.0 || >=20.0.0"
  17074 + }
  17075 + },
  17076 + "node_modules/tinyrainbow": {
  17077 + "version": "2.0.0",
  17078 + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
  17079 + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
  17080 + "dev": true,
  17081 + "license": "MIT",
  17082 + "engines": {
  17083 + "node": ">=14.0.0"
  17084 + }
  17085 + },
  17086 + "node_modules/tinyspy": {
  17087 + "version": "3.0.2",
  17088 + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
  17089 + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
  17090 + "dev": true,
  17091 + "license": "MIT",
  17092 + "engines": {
  17093 + "node": ">=14.0.0"
  17094 + }
  17095 + },
  17096 + "node_modules/tldts": {
  17097 + "version": "6.1.86",
  17098 + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
  17099 + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
  17100 + "dev": true,
  17101 + "license": "MIT",
  17102 + "dependencies": {
  17103 + "tldts-core": "^6.1.86"
  17104 + },
  17105 + "bin": {
  17106 + "tldts": "bin/cli.js"
  17107 + }
  17108 + },
  17109 + "node_modules/tldts-core": {
  17110 + "version": "6.1.86",
  17111 + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
  17112 + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
  17113 + "dev": true,
  17114 + "license": "MIT"
  17115 + },
16058 "node_modules/tmp": { 17116 "node_modules/tmp": {
16059 "version": "0.2.3", 17117 "version": "0.2.3",
16060 "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", 17118 "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -16109,6 +17167,19 @@ @@ -16109,6 +17167,19 @@
16109 "node": ">=6" 17167 "node": ">=6"
16110 } 17168 }
16111 }, 17169 },
  17170 + "node_modules/tough-cookie": {
  17171 + "version": "5.1.2",
  17172 + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
  17173 + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
  17174 + "dev": true,
  17175 + "license": "BSD-3-Clause",
  17176 + "dependencies": {
  17177 + "tldts": "^6.1.32"
  17178 + },
  17179 + "engines": {
  17180 + "node": ">=16"
  17181 + }
  17182 + },
16112 "node_modules/tr46": { 17183 "node_modules/tr46": {
16113 "version": "0.0.3", 17184 "version": "0.0.3",
16114 "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 17185 "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -17144,15 +18215,111 @@ @@ -17144,15 +18215,111 @@
17144 "vuetify": "^3.0.0" 18215 "vuetify": "^3.0.0"
17145 } 18216 }
17146 }, 18217 },
  18218 + "node_modules/vitest": {
  18219 + "version": "3.1.2",
  18220 + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.2.tgz",
  18221 + "integrity": "sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ==",
  18222 + "dev": true,
  18223 + "license": "MIT",
  18224 + "dependencies": {
  18225 + "@vitest/expect": "3.1.2",
  18226 + "@vitest/mocker": "3.1.2",
  18227 + "@vitest/pretty-format": "^3.1.2",
  18228 + "@vitest/runner": "3.1.2",
  18229 + "@vitest/snapshot": "3.1.2",
  18230 + "@vitest/spy": "3.1.2",
  18231 + "@vitest/utils": "3.1.2",
  18232 + "chai": "^5.2.0",
  18233 + "debug": "^4.4.0",
  18234 + "expect-type": "^1.2.1",
  18235 + "magic-string": "^0.30.17",
  18236 + "pathe": "^2.0.3",
  18237 + "std-env": "^3.9.0",
  18238 + "tinybench": "^2.9.0",
  18239 + "tinyexec": "^0.3.2",
  18240 + "tinyglobby": "^0.2.13",
  18241 + "tinypool": "^1.0.2",
  18242 + "tinyrainbow": "^2.0.0",
  18243 + "vite": "^5.0.0 || ^6.0.0",
  18244 + "vite-node": "3.1.2",
  18245 + "why-is-node-running": "^2.3.0"
  18246 + },
  18247 + "bin": {
  18248 + "vitest": "vitest.mjs"
  18249 + },
  18250 + "engines": {
  18251 + "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
  18252 + },
  18253 + "funding": {
  18254 + "url": "https://opencollective.com/vitest"
  18255 + },
  18256 + "peerDependencies": {
  18257 + "@edge-runtime/vm": "*",
  18258 + "@types/debug": "^4.1.12",
  18259 + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
  18260 + "@vitest/browser": "3.1.2",
  18261 + "@vitest/ui": "3.1.2",
  18262 + "happy-dom": "*",
  18263 + "jsdom": "*"
  18264 + },
  18265 + "peerDependenciesMeta": {
  18266 + "@edge-runtime/vm": {
  18267 + "optional": true
  18268 + },
  18269 + "@types/debug": {
  18270 + "optional": true
  18271 + },
  18272 + "@types/node": {
  18273 + "optional": true
  18274 + },
  18275 + "@vitest/browser": {
  18276 + "optional": true
  18277 + },
  18278 + "@vitest/ui": {
  18279 + "optional": true
  18280 + },
  18281 + "happy-dom": {
  18282 + "optional": true
  18283 + },
  18284 + "jsdom": {
  18285 + "optional": true
  18286 + }
  18287 + }
  18288 + },
17147 "node_modules/vitest-environment-nuxt": { 18289 "node_modules/vitest-environment-nuxt": {
17148 "version": "1.0.1", 18290 "version": "1.0.1",
17149 "resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-1.0.1.tgz", 18291 "resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-1.0.1.tgz",
17150 "integrity": "sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==", 18292 "integrity": "sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==",
  18293 + "dev": true,
17151 "license": "MIT", 18294 "license": "MIT",
17152 "dependencies": { 18295 "dependencies": {
17153 "@nuxt/test-utils": ">=3.13.1" 18296 "@nuxt/test-utils": ">=3.13.1"
17154 } 18297 }
17155 }, 18298 },
  18299 + "node_modules/vitest/node_modules/tinyexec": {
  18300 + "version": "0.3.2",
  18301 + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
  18302 + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
  18303 + "dev": true,
  18304 + "license": "MIT"
  18305 + },
  18306 + "node_modules/vitest/node_modules/tinyglobby": {
  18307 + "version": "0.2.13",
  18308 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
  18309 + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
  18310 + "dev": true,
  18311 + "license": "MIT",
  18312 + "dependencies": {
  18313 + "fdir": "^6.4.4",
  18314 + "picomatch": "^4.0.2"
  18315 + },
  18316 + "engines": {
  18317 + "node": ">=12.0.0"
  18318 + },
  18319 + "funding": {
  18320 + "url": "https://github.com/sponsors/SuperchupuDev"
  18321 + }
  18322 + },
17156 "node_modules/vscode-uri": { 18323 "node_modules/vscode-uri": {
17157 "version": "3.1.0", 18324 "version": "3.1.0",
17158 "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", 18325 "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
@@ -17189,6 +18356,13 @@ @@ -17189,6 +18356,13 @@
17189 "ufo": "^1.5.4" 18356 "ufo": "^1.5.4"
17190 } 18357 }
17191 }, 18358 },
  18359 + "node_modules/vue-component-type-helpers": {
  18360 + "version": "2.2.10",
  18361 + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.10.tgz",
  18362 + "integrity": "sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==",
  18363 + "dev": true,
  18364 + "license": "MIT"
  18365 + },
17192 "node_modules/vue-demi": { 18366 "node_modules/vue-demi": {
17193 "version": "0.14.10", 18367 "version": "0.14.10",
17194 "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", 18368 "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
@@ -17366,6 +18540,29 @@ @@ -17366,6 +18540,29 @@
17366 "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", 18540 "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
17367 "license": "MIT" 18541 "license": "MIT"
17368 }, 18542 },
  18543 + "node_modules/w3c-xmlserializer": {
  18544 + "version": "5.0.0",
  18545 + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
  18546 + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
  18547 + "dev": true,
  18548 + "license": "MIT",
  18549 + "dependencies": {
  18550 + "xml-name-validator": "^5.0.0"
  18551 + },
  18552 + "engines": {
  18553 + "node": ">=18"
  18554 + }
  18555 + },
  18556 + "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": {
  18557 + "version": "5.0.0",
  18558 + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
  18559 + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
  18560 + "dev": true,
  18561 + "license": "Apache-2.0",
  18562 + "engines": {
  18563 + "node": ">=18"
  18564 + }
  18565 + },
17369 "node_modules/web-streams-polyfill": { 18566 "node_modules/web-streams-polyfill": {
17370 "version": "3.3.3", 18567 "version": "3.3.3",
17371 "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 18568 "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
@@ -17387,6 +18584,29 @@ @@ -17387,6 +18584,29 @@
17387 "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", 18584 "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
17388 "license": "MIT" 18585 "license": "MIT"
17389 }, 18586 },
  18587 + "node_modules/whatwg-encoding": {
  18588 + "version": "3.1.1",
  18589 + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
  18590 + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
  18591 + "dev": true,
  18592 + "license": "MIT",
  18593 + "dependencies": {
  18594 + "iconv-lite": "0.6.3"
  18595 + },
  18596 + "engines": {
  18597 + "node": ">=18"
  18598 + }
  18599 + },
  18600 + "node_modules/whatwg-mimetype": {
  18601 + "version": "3.0.0",
  18602 + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
  18603 + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
  18604 + "dev": true,
  18605 + "license": "MIT",
  18606 + "engines": {
  18607 + "node": ">=12"
  18608 + }
  18609 + },
17390 "node_modules/whatwg-url": { 18610 "node_modules/whatwg-url": {
17391 "version": "5.0.0", 18611 "version": "5.0.0",
17392 "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 18612 "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -17412,6 +18632,23 @@ @@ -17412,6 +18632,23 @@
17412 "node": "^18.17.0 || >=20.5.0" 18632 "node": "^18.17.0 || >=20.5.0"
17413 } 18633 }
17414 }, 18634 },
  18635 + "node_modules/why-is-node-running": {
  18636 + "version": "2.3.0",
  18637 + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
  18638 + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
  18639 + "dev": true,
  18640 + "license": "MIT",
  18641 + "dependencies": {
  18642 + "siginfo": "^2.0.0",
  18643 + "stackback": "0.0.2"
  18644 + },
  18645 + "bin": {
  18646 + "why-is-node-running": "cli.js"
  18647 + },
  18648 + "engines": {
  18649 + "node": ">=8"
  18650 + }
  18651 + },
17415 "node_modules/wide-align": { 18652 "node_modules/wide-align": {
17416 "version": "1.1.5", 18653 "version": "1.1.5",
17417 "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 18654 "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
@@ -17632,6 +18869,13 @@ @@ -17632,6 +18869,13 @@
17632 "node": ">=12" 18869 "node": ">=12"
17633 } 18870 }
17634 }, 18871 },
  18872 + "node_modules/xmlchars": {
  18873 + "version": "2.2.0",
  18874 + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
  18875 + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
  18876 + "dev": true,
  18877 + "license": "MIT"
  18878 + },
17635 "node_modules/xss": { 18879 "node_modules/xss": {
17636 "version": "1.0.15", 18880 "version": "1.0.15",
17637 "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz", 18881 "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz",
1 { 1 {
2 "name": "nuxt-app", 2 "name": "nuxt-app",
3 - "version": "0.2.0", 3 + "version": "0.3.0",
4 "private": true, 4 "private": true,
5 "type": "module", 5 "type": "module",
6 "scripts": { 6 "scripts": {
@@ -12,18 +12,23 @@ @@ -12,18 +12,23 @@
12 "lint:js": "eslint --ext \".ts,.vue\" .", 12 "lint:js": "eslint --ext \".ts,.vue\" .",
13 "lint:prettier": "prettier --write .", 13 "lint:prettier": "prettier --write .",
14 "lint": "npm run lint:js && npm run lint:prettier", 14 "lint": "npm run lint:js && npm run lint:prettier",
15 - "format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\"" 15 + "format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\"",
  16 + "test": "vitest"
16 }, 17 },
17 "dependencies": { 18 "dependencies": {
18 "@nuxt/eslint": "^1.3.0", 19 "@nuxt/eslint": "^1.3.0",
19 "@nuxt/icon": "^1.12.0", 20 "@nuxt/icon": "^1.12.0",
20 "@nuxt/image": "^1.10.0", 21 "@nuxt/image": "^1.10.0",
21 "@nuxt/scripts": "^0.11.6", 22 "@nuxt/scripts": "^0.11.6",
22 - "@nuxt/test-utils": "^3.17.2",  
23 "@nuxt/ui": "^2.22.0", 23 "@nuxt/ui": "^2.22.0",
24 "@pinia-orm/nuxt": "^1.10.2", 24 "@pinia-orm/nuxt": "^1.10.2",
25 "@pinia/nuxt": "^0.9.0", 25 "@pinia/nuxt": "^0.9.0",
  26 + "@tinymce/tinymce-vue": "^5.1.1",
  27 + "@types/vuelidate": "^0.7.22",
26 "@unhead/vue": "^2.0.8", 28 "@unhead/vue": "^2.0.8",
  29 + "@vitejs/plugin-vue": "^5.2.3",
  30 + "@vuelidate/core": "^2.0.3",
  31 + "@vuelidate/validators": "^2.0.4",
27 "@vueuse/core": "^13.1.0", 32 "@vueuse/core": "^13.1.0",
28 "@vueuse/nuxt": "^13.1.0", 33 "@vueuse/nuxt": "^13.1.0",
29 "eslint": "^9.25.1", 34 "eslint": "^9.25.1",
@@ -36,9 +41,15 @@ @@ -36,9 +41,15 @@
36 "vuetify-nuxt-module": "^0.18.6" 41 "vuetify-nuxt-module": "^0.18.6"
37 }, 42 },
38 "devDependencies": { 43 "devDependencies": {
  44 + "@nuxt/test-utils": "^3.17.2",
39 "@nuxtjs/tailwindcss": "^6.13.2", 45 "@nuxtjs/tailwindcss": "^6.13.2",
  46 + "@vue/test-utils": "^2.4.6",
40 "eslint-config-prettier": "^10.1.2", 47 "eslint-config-prettier": "^10.1.2",
41 "eslint-plugin-prettier": "^5.2.6", 48 "eslint-plugin-prettier": "^5.2.6",
42 - "prettier": "^3.5.3" 49 + "happy-dom": "^17.4.4",
  50 + "jsdom": "^26.1.0",
  51 + "playwright-core": "^1.52.0",
  52 + "prettier": "^3.5.3",
  53 + "vitest": "^3.1.2"
43 } 54 }
44 } 55 }
1 -<script setup lang="ts"> 1 +<script lang="ts" setup>
  2 +//#region --import--.
  3 +import { AlertTriangleIcon, ArrowLeftIcon } from "lucide-vue-next";
  4 +import { useTMDB } from "~/composables/tMDB";
  5 +import { computed, onMounted, ref } from "vue";
  6 +import { Movie } from "~/models/movie";
  7 +import type { MovieInterface } from "~/interfaces/movie";
  8 +import { Credit } from "~/models/credit";
  9 +import type { CreditsResponse } from "~/interfaces/credit";
  10 +import type { MovieCommentInterface } from "~/interfaces/movieComment";
  11 +import { MovieComment } from "~/models/movieComment";
  12 +import type { WhereSecondaryClosure } from "pinia-orm";
  13 +//#endregion
2 14
  15 +//#region --Declaration--.
  16 +const { fetchMovieDetails, fetchMovieCredits } = useTMDB();
  17 +//#endregion
  18 +
  19 +//#region --Declaration--.
  20 +const { currentRoute } = useRouter();
  21 +//#endregion
  22 +
  23 +//#region --Data/ref--.
  24 +const isLoading = ref(true);
  25 +const isSubmitting = ref(false);
  26 +//#endregion
  27 +
  28 +//#region --Computed--.
  29 +const movieId = computed(() => {
  30 + if (currentRoute.value.params.id) {
  31 + if (typeof currentRoute.value.params.id === "string") {
  32 + if (typeof Number(+currentRoute.value.params.id) === "number") {
  33 + return +currentRoute.value.params.id as number;
  34 + } else {
  35 + return currentRoute.value.params.id as string;
  36 + }
  37 + } else {
  38 + return null;
  39 + }
  40 + } else {
  41 + return null;
  42 + }
  43 +});
  44 +
  45 +const movie = computed(() => {
  46 + if (unref(movieId)) {
  47 + return useRepo(Movie)
  48 + .query()
  49 + .where("id", movieId.value as WhereSecondaryClosure<never> | null | undefined)
  50 + .withAll()
  51 + .first() as unknown as MovieInterface;
  52 + } else {
  53 + return null;
  54 + }
  55 +});
  56 +
  57 +/**
  58 + * Computed property for director
  59 + */
  60 +const director = computed(() => {
  61 + if (unref(movie)?.credit?.crew) {
  62 + return movie.value?.credit.crew.find((person) => person.job === "Director");
  63 + } else {
  64 + return null;
  65 + }
  66 +});
  67 +
  68 +/**
  69 + * Retourne les commentaires liés au film, du plus récent au plus ancien.
  70 + */
  71 +const comments = computed(() => {
  72 + return useRepo(MovieComment)
  73 + .query()
  74 + .where((comment) => {
  75 + const searched = comment as unknown as MovieCommentInterface;
  76 + return searched.movie_id === unref(movieId);
  77 + })
  78 + .orderBy("createdAt", "desc")
  79 + .get();
  80 +});
  81 +//#endregion
  82 +
  83 +//#region --Function--.
  84 +/**
  85 + * Fetch movie details
  86 + */
  87 +const fetchDetails = async (id: number | string) => {
  88 + try {
  89 + isLoading.value = true;
  90 +
  91 + const data = await fetchMovieDetails(id);
  92 + // Add to store collection.
  93 + useRepo(Movie).save(data);
  94 + } catch (error) {
  95 + console.error("Error fetching movie details:", error);
  96 + } finally {
  97 + isLoading.value = false;
  98 + }
  99 +};
  100 +
  101 +/**
  102 + * Format runtime
  103 + * @param minutes
  104 + */
  105 +const formatRuntime = (minutes: number) => {
  106 + if (!minutes) return "Durée inconnue";
  107 + // Find nb hours.
  108 + const hours = Math.floor(minutes / 60);
  109 + // Find last minutes.
  110 + const mins = minutes % 60;
  111 +
  112 + return `${hours}h ${mins}min`;
  113 +};
  114 +
  115 +async function fetchCredits(id: number | string) {
  116 + try {
  117 + const data = (await fetchMovieCredits(id)) as CreditsResponse;
  118 + data.movie_id = id;
  119 + // Add to store collection.
  120 + useRepo(Credit).save(data);
  121 + } catch (error) {
  122 + console.error("Error fetching movie credits:", error);
  123 + }
  124 +}
  125 +
  126 +//Ce n'est pas le film du siècle cependant, il est suffisamment intéressant pour passer une bonne soirée !
  127 +function handleSubmitEvent(event: MovieCommentInterface) {
  128 + isSubmitting.value = true;
  129 + event.movie_id = unref(movieId);
  130 + event.createdAt = `${new Date(Date.now())}`;
  131 + useRepo(MovieComment).save(event);
  132 + isSubmitting.value = false;
  133 +}
  134 +
  135 +//#endregion
  136 +
  137 +//#region --Global event--.
  138 +onMounted(() => {
  139 + // Fetch data on component mount.
  140 + if (unref(movieId)) {
  141 + const id = unref(movieId) as string | number;
  142 + fetchDetails(id);
  143 + fetchCredits(id);
  144 + }
  145 + // loadComments()
  146 +});
  147 +//#endregion
3 </script> 148 </script>
4 149
5 <template> 150 <template>
6 <section> 151 <section>
7 - composant détail d'un film. 152 + <!-- Skeleton loader pendant le chargement -->
  153 + <ui-components-skeleton-movie-detail-loader v-if="isLoading" />
  154 +
  155 + <!-- Contenu du film -->
  156 + <div v-else-if="movie" class="relative">
  157 + <!-- Backdrop image -->
  158 + <ui-components-backdrop-image v-if="movie.backdrop_path" :src="movie.backdrop_path" :title="movie.title" />
  159 +
  160 + <!-- Contenu principal -->
  161 + <div class="container mx-auto px-4 py-8 relative z-10 pt-20">
  162 + <button
  163 + class="flex items-center text-gray-400 hover:text-white mb-8 transition-colors"
  164 + @click="navigateTo('/')"
  165 + >
  166 + <ArrowLeftIcon :size="20" class="mr-2" />
  167 + Retour
  168 + </button>
  169 +
  170 + <div class="flex flex-col md:flex-row gap-8">
  171 + <!-- Poster -->
  172 + <ui-components-poster v-if="movie.poster_path" :src="movie.poster_path" :title="movie.title" />
  173 +
  174 + <!-- Informations du film -->
  175 + <section class="w-full md:w-2/3 lg:w-3/4">
  176 + <h1 class="text-3xl md:text-4xl font-bold mb-2">{{ movie.title }}</h1>
  177 + <p v-if="movie.release_date" class="text-gray-400 mb-4">
  178 + {{ useDateFormat(movie.release_date, "DD-MM-YYYY") }} • {{ formatRuntime(movie.runtime) }}
  179 + </p>
  180 +
  181 + <!-- Note et votes -->
  182 + <details-score-and-vote :nb-vote="movie.vote_count" :score="movie.vote_average" />
  183 +
  184 + <!-- Genres -->
  185 + <details-movie-gender :genres="movie.genres" />
  186 +
  187 + <!-- Synopsis -->
  188 + <div class="mb-6">
  189 + <h2 class="text-xl font-bold mb-2">Synopsis</h2>
  190 + <p class="text-gray-300">{{ movie.overview || "Aucun synopsis disponible." }}</p>
  191 + </div>
  192 +
  193 + <!-- Réalisateur et têtes d'affiche -->
  194 + <div v-if="movie.credit" class="mb-6">
  195 + <h2 class="text-xl font-bold mb-2">Équipe</h2>
  196 + <div v-if="director" class="mb-2">
  197 + <span class="font-semibold">Réalisateur:</span> {{ director.name }}
  198 + </div>
  199 + <div v-if="movie.credit.cast.length > 0">
  200 + <span class="font-semibold">Têtes d'affiche:</span>
  201 + {{
  202 + movie.credit.cast
  203 + .slice(0, 15)
  204 + .map((person) => person.name)
  205 + .join(", ")
  206 + }}
  207 + <span v-if="movie.credit.cast.length > 15">..</span>
  208 + </div>
  209 + </div>
  210 + <!-- Comments form. -->
  211 + <h3 class="text-xl font-bold mt-8 mb-4">Ajouter un commentaire</h3>
  212 + <form-movie-comment-form @event:submit="handleSubmitEvent" />
  213 +
  214 + <!-- Liste des commentaires -->
  215 + <movie-comment-list :comments="comments as unknown as MovieCommentInterface[]" />
8 </section> 216 </section>
9 -</template> 217 + </div>
  218 + </div>
  219 + </div>
10 220
11 -<style scoped> 221 + <!-- Erreur -->
  222 + <section v-else class="container mx-auto px-4 py-16 text-center">
  223 + <AlertTriangleIcon :size="64" class="mx-auto mb-4 text-red-500" />
  224 + <h2 class="text-2xl font-bold mb-2">Film non trouvé</h2>
  225 + <p class="text-gray-400 mb-6">Nous n'avons pas pu trouver le film que vous cherchez.</p>
  226 + <button
  227 + class="px-6 py-2 bg-primary text-white font-bold rounded-md hover:bg-primary-dark transition-colors"
  228 + @click="navigateTo('/')"
  229 + >
  230 + Retour à l'accueil
  231 + </button>
  232 + </section>
  233 + </section>
  234 +</template>
12 235
13 -</style> 236 +<style scoped></style>
  1 +export type Comment = {
  2 + username: string
  3 + message: string
  4 + rating: number
  5 +}
  1 +// vite.config.js
  2 +import vue from '@vitejs/plugin-vue'
  3 +
  4 +export default {
  5 + plugins: [vue()],
  6 + test: {
  7 + globals: true,
  8 + environment: "jsdom",
  9 + // Additional test configurations can be added here
  10 + },
  11 +}
  1 +import { defineVitestConfig } from '@nuxt/test-utils/config'
  2 +
  3 +export default defineVitestConfig({
  4 + /**
  5 + * Documentation here : https://nuxt.com/docs/getting-started/testing
  6 + * any custom Vitest config you require
  7 + */
  8 + test: {
  9 + environment: 'nuxt',
  10 + // you can optionally set Nuxt-specific environment options
  11 + // environmentOptions: {
  12 + // nuxt: {
  13 + // rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
  14 + // domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
  15 + // overrides: {
  16 + // // other Nuxt config you want to pass
  17 + // }
  18 + // }
  19 + // }
  20 + }
  21 +})