Bruno Predot

Merge branch 'release/0.3.0'

0.3.0:
- Installation vuelidate et vuelidate/validator.
- Ajout composant SkeletonMovieDetailLoader.
- Ajout Model + Interface credit.
- Ajout composant ScoreAndVote.
- Ajout composant MovieGender.
- Ajout composant Poster.
- Ajout composant BackdropImage.
- Ajout composant MovieCommentForm.
- Ajout model + interface MovieComment.
- Ajout composant MovieCommentForm.
- Ajout composant MovieCommentList.
- Ajout dépendance TinyMCE.
- Ajout composant TinyMceFieldEditor.
- Ajout composant Loader.
- Ajout composant MovieCard.
0.2.0:
- Mise en place du CHANGELOG_RELEASE.
- Ajout page index.
... ...
<script lang="ts" setup>
//#region --Props--.
import { useDateFormat } from "@vueuse/core";
import { FilmIcon } from "lucide-vue-next";
//#endregion
//#region --Props--.
defineProps({
movie: {
type: Object,
required: true,
nullable: false,
},
});
//#endregion
</script>
<template>
<section
class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer"
@click="navigateTo(`/movies/${movie.id}`)"
>
<div class="relative pb-[150%]">
<img
v-if="movie.poster_path"
:alt="movie.title"
:src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
class="absolute inset-0 w-full h-full object-cover"
/>
<div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center">
<FilmIcon :size="48" class="text-gray-500" />
</div>
<div
class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold"
>
{{ movie.vote_average.toFixed(1) }}
</div>
</div>
<div class="p-4">
<h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2>
<p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p>
</div>
</section>
</template>
<style scoped></style>
... ...
<script lang="ts" setup>
//#region --Import--.
import type { MovieCommentInterface } from "~/interfaces/movieComment";
import { MessageSquareIcon } from "lucide-vue-next";
//#endregion
//#region --Props--.
const props = defineProps({
comments: {
type: Array<MovieCommentInterface>,
required: true,
nullable: false,
},
});
//#endregion
//#region --Watch--.
watch(
() => props.comments,
(comments) => {
nextTick(() => {
if (comments.length) {
comments.forEach((comment, index) => {
const element = document.getElementById(`message${index}`) as HTMLParagraphElement;
element.innerHTML = comment.message;
});
}
});
}, { immediate: true }
);
//#endregion
</script>
<template>
<section>
<!-- Liste des commentaires -->
<section v-if="comments.length > 0" class="mt-10">
<h2>Commentaires publiés</h2>
<div v-for="(comment, index) in comments" :key="index" class="bg-gray-800 rounded-lg p-6 mb-4">
<div class="flex justify-between items-start mb-2">
<section>
<h4 class="font-bold text-lg">Par {{ comment.username }}</h4>
<p class="text-sm text-gray-400">Le {{ useDateFormat(comment.createdAt, "DD-MM-YYYY") }}</p>
</section>
<section class="bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold">
{{ comment.rating }}
</section>
</div>
<p :id="`message${index}`" class="text-gray-300">{{ comment.message }}</p>
</div>
</section>
<!-- Si aucun commentaire -->
<section v-else class="text-center py-8 bg-gray-800 rounded-lg mt-10">
<MessageSquareIcon :size="48" class="mx-auto mb-3 text-gray-600" />
<p class="text-gray-400">Aucun commentaire pour le moment. Soyez le premier à donner votre avis !</p>
</section>
</section>
</template>
<style scoped></style>
... ...
<script lang="ts" setup>
//#region --import--.
import SearchBar from "~/components/SearchBar.vue";
import { onBeforeUnmount, ref } from "vue";
import { useTMDB } from "~/composables/tMDB";
import { Movie } from "~/models/movie";
import { FilmIcon, SearchXIcon } from "lucide-vue-next";
import { SearchXIcon } from "lucide-vue-next";
import type { MovieInterface } from "~/interfaces/movie";
import { useDateFormat } from "@vueuse/core";
//#endregion
//#region --Declaration--.
... ... @@ -100,7 +98,7 @@ function createIntersectionObserver() {
if (entry.isIntersecting && !isLoadingMore.value && currentPage.value < totalPages.value) {
if (searchQuery.value) {
// Continue searching query if already active.
search(searchQuery.value, currentPage.value + 1)
search(searchQuery.value, currentPage.value + 1);
} else {
// Continue fetching popular movies.
fetchMovies(currentPage.value + 1);
... ... @@ -118,7 +116,7 @@ function handleSearchEvent(event: string) {
}
function handleClearSearchEvent() {
searchQuery.value = '';
searchQuery.value = "";
currentPage.value = 1;
// Fetch popular movies after clear.
fetchMovies(1);
... ... @@ -155,41 +153,23 @@ onBeforeUnmount(() => {
<section>
<h1 class="text-4xl font-bold mb-8 text-center">Découvrez les films populaires</h1>
<!-- Barre de recherche -->
<search-bar
<ui-components-search-bar
placeholder="Rechercher un film..."
@event:search="handleSearchEvent"
@event:clear_search="handleClearSearchEvent"
/>
<!-- Loading Skeleton -->
<skeleton-movies-loader v-if="isInitialLoading" :is-initial-loading="isInitialLoading" :skeleton-number="20" />
<ui-components-skeleton-movies-loader
v-if="isInitialLoading"
:is-initial-loading="isInitialLoading"
:skeleton-number="20"
/>
<!-- Liste des films -->
<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">
<div
v-for="movie in movies"
:key="movie.id"
class="bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-transform duration-300 hover:scale-105 cursor-pointer"
@click="navigateTo(`/movies/${movie.id}`)"
>
<div class="relative pb-[150%]">
<img
v-if="movie.poster_path"
:alt="movie.title"
:src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
class="absolute inset-0 w-full h-full object-cover"
/>
<div v-else class="absolute inset-0 w-full h-full bg-gray-700 flex items-center justify-center">
<FilmIcon :size="48" class="text-gray-500" />
</div>
<div
class="absolute top-2 right-2 bg-primary text-white rounded-full w-10 h-10 flex items-center justify-center font-bold"
>
{{ movie.vote_average.toFixed(1) }}
</div>
</div>
<div class="p-4">
<h2 class="text-lg font-bold mb-1 line-clamp-1">{{ movie.title }}</h2>
<p class="text-sm text-gray-400">{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }}</p>
</div>
<div v-for="movie in movies" :key="movie.id">
<movie-card :movie="movie" />
</div>
</div>
... ... @@ -201,9 +181,7 @@ onBeforeUnmount(() => {
</section>
<!-- Loader pour le chargement de plus de films -->
<section v-if="isLoadingMore && !isInitialLoading" class="flex justify-center mt-8">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</section>
<ui-components-loader :is-initial-loading="isInitialLoading" :is-loading="isLoadingMore" />
<!-- Élément observé pour le défilement infini -->
<div ref="loadMoreTrigger" class="h-10 mt-4" />
... ...
<script lang="ts" setup>
//#region --Import--.
import type { Genre } from "~/interfaces/movie";
//#endregion
//#region --Props--.
defineProps({
genres: {
type: Array<Genre>,
required: true,
nullable: false,
},
});
//#endregion
</script>
<template>
<section class="mb-6">
<div class="flex flex-wrap gap-2">
<span v-for="genre in genres" :key="genre.id" class="px-3 py-1 bg-gray-800 rounded-full text-sm">
{{ genre.name }}
</span>
</div>
</section>
</template>
<style scoped></style>
... ...
<script lang="ts" setup>
//#region --Props--.
const props = defineProps({
score: {
type: Number,
required: true,
nullable: false,
},
nbVote: {
type: Number,
required: true,
nullable: false,
},
});
//#endregion
//#region --Function--.
/**
* Format vote count if > 1000.
* @param count
*/
const formatVoteCount = (count: number) => {
if (count >= 1000) {
return `${(count / 1000).toFixed(1)}k votes`;
}
return `${count} votes`;
};
//#endregion
</script>
<template>
<section class="flex items-center mb-6">
<section class="bg-primary text-white rounded-full w-12 h-12 flex items-center justify-center font-bold mr-3">
{{ score.toFixed(1) }}
</section>
<section>
<p class="font-semibold">Note TMDB</p>
<div class="text-sm text-gray-400">{{ formatVoteCount(nbVote) }}</div>
</section>
</section>
</template>
<style scoped></style>
... ...
<script lang="ts" setup>
//#region --Import--.
import { useVuelidate } from "@vuelidate/core";
import { helpers, maxLength, maxValue, minLength, minValue, required } from "@vuelidate/validators";
import type { Comment } from "~/type/commentForm";
//#endregion
//#region --Emit--.
const emit = defineEmits(["event:submit"]);
//#endregion
//#region --Props--.
defineProps({
isSubmitting: {
type: Boolean,
required: false,
nullable: false,
default: false,
},
});
//#endregion
//#region --Data/ref--.
const initialState: Comment = {
username: "",
message: "",
rating: 5,
};
// Validation rules
const rules = {
username: {
required: helpers.withMessage("Le nom d'utilisateur est requis", required),
minLength: helpers.withMessage("Le nom d'utilisateur doit contenir au moins 3 caractères", minLength(3)),
maxLength: helpers.withMessage("Le nom d'utilisateur ne peut pas dépasser 50 caractères", maxLength(50)),
alpha: helpers.withMessage(
"Le nom d'utilisateur ne peut contenir que des lettres",
helpers.regex(/^[a-zA-ZÀ-ÿ\s]+$/),
),
},
message: {
required: helpers.withMessage("Le message est requis", required),
minLength: helpers.withMessage("Le message doit contenir au moins 3 caractères", minLength(3)),
maxLength: helpers.withMessage("Le message ne peut pas dépasser 500 caractères", maxLength(500)),
},
rating: {
required: helpers.withMessage("La notation est requise", required),
minValue: helpers.withMessage("Le message ne être inférieure à 0", minValue(0)),
maxValue: helpers.withMessage("Le message ne être suppérieur à 10", maxValue(10)),
},
};
const formData = reactive({
...initialState,
});
const v$ = useVuelidate(rules, formData);
//#endregion
const errormessages = computed(() => {
return v$.value.message.$errors.map((e) => e.$message);
});
//#region --Function--.
async function submitComment() {
emit("event:submit", formData);
}
function clear() {
v$.value.$reset();
formData.username = initialState.username;
formData.message = initialState.message;
formData.rating = initialState.rating;
}
function handleMessageEvent(event: string) {
formData.message = event;
// todo : revoir ici la validation manquante (dû au retour de TinyMCE).
v$.value.message.$touch();
// console.log(formData.message.replace(/<[^>]*>/g, ''));
// console.log(formData.message.replace(/(&lt;([^&gt;]+)&gt;)/ig, ''));
}
//#endregion
</script>
<template>
<section>
<VForm>
<v-text-field
v-model="formData.username"
:error-messages="v$.username.$errors.map((e) => e.$message) as readonly string[]"
label="nom d'utilisateur"
placeholder="nom d'utilisateur"
required
@blur="v$.username.$touch()"
@input="v$.username.$touch()"
/>
<v-text-field
v-model="formData.rating"
:error-messages="v$.rating.$errors.map((e) => e.$message) as readonly string[]"
label="Note (1-10)"
placeholder=""
required
type="number"
@blur="v$.rating.$touch"
@input="v$.rating.$touch"
/>
<!-- <pre>{{ errormessages }}</pre>-->
<ui-components-tiny-mce-field-editor
:error-message="v$?.message?.$errors[0]?.$message ? (v$.message.$errors[0].$message as string) : ''"
:model-value="formData.message"
@update:model-value="handleMessageEvent"
/>
<v-btn
class="mt-6 mr-4"
color="primary"
@click="
async () => {
const validForm = await v$.$validate();
if (validForm) {
submitComment();
}
}
"
>
<span v-if="isSubmitting" class="flex items-center justify-center">
<span class="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
Envoi en cours...
</span>
<span v-else>Publier le commentaire</span>
</v-btn>
<v-btn class="mt-6 mr-4" color="primary" @click="clear"> effacer</v-btn>
</VForm>
</section>
</template>
<style scoped></style>
... ...
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from './HelloWorld.vue'
describe('HelloWorld', () => {
it('component renders Hello world properly', () => {
const wrapper = mount(HelloWorld)
expect(wrapper.text()).toContain('Hello world')
})
})
... ...
<script setup lang="ts">
</script>
<template>
<p>Hello world</p>
</template>
<style scoped lang="scss">
</style>
\ No newline at end of file
... ...
<script lang="ts" setup>
//#region --Props--.
defineProps({
src: {
type: String,
required: true,
nullable: false,
},
title: {
type: String,
required: true,
nullable: false,
},
});
//#endregion
//#region --Declaration--.
const w: Window = window;
//#endregion
</script>
<template>
<section class="absolute inset-0 h-[500px] overflow-hidden z-0">
<v-img
v-if="src"
:alt="title"
:src="`https://image.tmdb.org/t/p/original${src}`"
:width="w.screen.width"
aspect-ratio="16/9"
class="w-full h-full object-cover opacity-30"
cover
max-height="500"
/>
</section>
</template>
<style scoped></style>
... ...
<script lang="ts" setup>
//#region --Props--.
defineProps({
isLoading: {
type: Boolean,
required: true,
nullable: false,
},
isInitialLoading: {
type: Boolean,
required: false,
nullable: false,
default: false,
},
});
//#endregion
</script>
<template>
<section v-if="isLoading && !isInitialLoading" class="flex justify-center mt-8">
<div class="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</section>
</template>
<style scoped></style>
\ No newline at end of file
... ...
<script setup lang="ts">
//#region --Props--.
import { FilmIcon } from "lucide-vue-next";
defineProps({
src: {
type: String,
required: true,
nullable: false,
},
title: {
type: String,
required: true,
nullable: false,
},
});
//#endregion
</script>
<template>
<section class="w-full md:w-1/3 lg:w-1/4">
<div class="rounded-lg overflow-hidden shadow-lg bg-gray-800">
<v-img
v-if="src"
:alt="title"
:src="`https://image.tmdb.org/t/p/w500${src}`"
class="w-full h-auto"
/>
<div v-else class="aspect-[2/3] bg-gray-700 flex items-center justify-center">
<FilmIcon :size="64" class="text-gray-500" />
</div>
</div>
</section>
</template>
<style scoped>
</style>
\ No newline at end of file
... ...
<script lang="ts" setup>
//#region --Import--.
import Editor from "@tinymce/tinymce-vue";
import { ref, watch } from "vue";
//#endregion
//#region --Declaration--.
const runtimeConfig = useRuntimeConfig();
//#endregion
//#region --Emit--.
const emit = defineEmits<{
(e: "update:modelValue", value: string): void;
}>();
//#endregion
//#region --Props--.
const props = defineProps<{
modelValue: string;
errorMessage: string;
}>();
//#endregion
//#region --Data/ref--.
const content = ref(props.modelValue);
const init = {
height: 300,
menubar: false,
plugins: [
// Core editing features
"advlist",
"autolink",
"lists",
"link",
"image",
"charmap",
"preview",
"anchor",
"searchreplace",
"visualblocks",
"code",
"fullscreen",
"insertdatetime",
"media",
"table",
"code",
"help",
"wordcount",
],
toolbar:
"undo redo | blocks | bold italic underline strikethrough |" +
"bold italic forecolor | alignleft aligncenter " +
"alignright alignjustify | bullist numlist outdent indent | " +
"removeformat | help",
content_style: "body { font-family:Helvetica,Arial,sans-serif; font-size:14px }",
skin: "oxide-dark",
content_css: "dark",
// forced_root_block: false,
// valid_elements: [],
// entity_encoding : "raw",
};
//#endregion
//#region --Watch--.
watch(content, (newValue) => {
emit("update:modelValue", newValue);
});
watch(
() => props.modelValue,
(newValue) => {
if (newValue !== content.value) {
content.value = newValue;
}
},
);
//#endregion
</script>
<template>
<div>
<editor v-model="content" :api-key="runtimeConfig.public.apiTinyMceSecret" :init="init" />
</div>
<div v-if="errorMessage" class="text-red-500 text-sm mt-1">
{{ errorMessage }}
</div>
</template>
<style scoped></style>
... ...
<script setup lang="ts">
</script>
<template>
<v-container class="bg-gray-900">
<v-row class="bg-gray-900" >
<v-col cols="12" sm="4">
<v-skeleton-loader
class="mx-auto border bg-gray-800"
color="#1f2937"
width="auto"
height="600px"
type="paragraph, image"
/>
</v-col>
<v-col cols="12" sm="8">
<v-skeleton-loader
class="mx-auto mt-10"
color="#1f2937"
elevation="12"
min-height="400px"
type="table-heading, list-item-two-line, article, actions, table-tfoot"
/>
</v-col>
</v-row>
</v-container>
</template>
<style scoped>
</style>
\ No newline at end of file
... ...
import type { RuntimeConfig } from "nuxt/schema";
export const useTMDB = function() {
export const useTMDB = function () {
const runtimeconfig: RuntimeConfig = useRuntimeConfig();
const apiUrl = runtimeconfig.public.apiTMDBUrl;
const apiKey = runtimeconfig.public.apiTMDBSecret;
... ... @@ -11,9 +11,7 @@ export const useTMDB = function() {
*/
const fetchPopularMovies = async (page: number) => {
try {
const response = await fetch(
`${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`,
);
const response = await fetch(`${apiUrl}/movie/popular?api_key=${apiKey}&language=fr-FR&page=${page}`);
if (!response.ok) {
console.error("An error occurred when fetching popular movies:");
} else {
... ... @@ -44,5 +42,38 @@ export const useTMDB = function() {
}
};
return { fetchPopularMovies, searchMovies }
}
\ No newline at end of file
/**
* Fetch movie details by id.
* @param id
*/
const fetchMovieDetails = async (id: number | string) => {
try {
const response = await fetch(`${apiUrl}/movie/${id}?api_key=${apiKey}&language=fr-FR`);
if (!response.ok) {
console.error("An error occurred when fetching movie details:");
} else {
return await response.json();
}
} catch (error) {
console.error("Error fetching details:", error);
}
};
/**
* Fetch movie credits
*/
const fetchMovieCredits = async (id: number | string) => {
try {
const response = await fetch(`${apiUrl}/movie/${id}/credits?api_key=${apiKey}&language=fr-FR`);
if (!response.ok) {
console.error("An error occurred when fetching movie credits:");
} else {
return await response.json();
}
} catch (error) {
console.error("Error fetching movie credits:", error);
}
};
return { fetchPopularMovies, searchMovies, fetchMovieDetails, fetchMovieCredits };
};
... ...
import type { MovieInterface } from "~/interfaces/movie";
export interface CreditInterface {
id: number;
name: string;
job?: string;
character?: string;
}
export type CreditsResponse = {
id: number;
cast: CreditInterface[],
crew: CreditInterface[],
movie_id: unknown;
movie: MovieInterface;
}
... ...
import type { CreditInterface, CreditsResponse } from "~/interfaces/credit";
export interface MovieInterface {
id: number;
title: string;
adult: boolean;
backdrop_path: string;
genre_ids: number[];
genres: Genre[];
original_language: string;
original_title: string;
overview: string;
popularity: number;
poster_path: string | null;
vote_average: number;
release_date: string;
runtime: number
title: string;
video: boolean;
vote_average: number;
vote_count: number;
credit: CreditsResponse;
}
export type Genre = {
id: number,
name: string,
}
\ No newline at end of file
... ...
import type { MovieInterface } from "~/interfaces/movie";
export interface MovieCommentInterface {
createdAt: string;
username: string;
message: string;
rating: number;
movie_id: unknown;
movie: MovieInterface;
}
... ...
import { Model } from "pinia-orm";
import { Movie } from "~/models/movie";
export class Credit extends Model {
/**
*
* @return {string}
*/
static get entity() {
return "Credit";
}
/**
*
* @return {string}
*/
static get primaryKey() {
return "id";
}
static fields() {
return {
// Attributs.
id: this.number(null),
cast: this.attr([]),
crew: this.attr([]),
// Relations.
movie_id: this.attr(null),
movie: this.belongsTo(Movie, "movie_id", "id"),
};
}
static piniaOptions = {
persist: true,
};
}
... ...
import { Model } from "pinia-orm";
import { Credit } from "~/models/credit";
export class Movie extends Model {
/**
... ... @@ -22,24 +23,37 @@ export class Movie extends Model {
// Attributs.
id: this.number(null),
adult: this.boolean(false),
backdrop_pat: this.string(null),
backdrop_path: this.string(null),
belongs_to_collection: this.attr(null),
budget: this.number(null),
genre_ids: this.attr([]),
genres: this.attr([]),
homepage: this.string(null),
imdb_id: this.string(null),
origin_country: this.attr([]),
original_language: this.string(null),
original_title: this.string(null),
overview: this.string(null),
popularity: this.number(null),
poster_path: this.string(null),
production_companies: this.attr([]),
production_cuntries: this.attr([]),
release_date: this.string(null),
revenue: this.number(null),
runtime: this.number(null),
spoken_languages: this.attr([]),
status: this.string(null),
tagline: this.string(null),
title: this.string(null),
video: this.boolean(false),
vote_average: this.number(null),
vote_count: this.number(null),
// Relations.
credit: this.hasOne(Credit, "movie_id", "id"),
};
}
static piniaOptions = {
persist: true,
};
}
... ...
import { Model } from "pinia-orm";
import { Movie } from "~/models/movie";
export class MovieComment extends Model {
/**
*
* @return {string}
*/
static get entity() {
return "MovieComment";
}
/**
*
* @return {string}
*/
static get primaryKey() {
return "id";
}
static fields() {
return {
// Attributs.
id: this.uid(),
createdAt: this.string(''),
username: this.string(''),
message: this.string(''),
rating: this.string(''),
// Relations.
movie_id: this.attr(null),
movie: this.belongsTo(Movie, "movie_id", "id"),
};
}
static piniaOptions = {
persist: true,
};
}
... ...
... ... @@ -25,6 +25,7 @@ export default defineNuxtConfig({
"@nuxt/eslint",
"@nuxt/icon",
"@nuxt/image",
"@nuxt/test-utils/module",
[
"@pinia/nuxt",
{
... ... @@ -68,8 +69,8 @@ export default defineNuxtConfig({
// Keys within public are also exposed client-side.
public: {
apiTMDBSecret: process.env.NUXT_ENV_TMDB_API_KEY,
apiTMDBBearer: process.env.NUXT_ENV_TMDB_BEARER,
apiTMDBUrl: process.env.NUXT_ENV_TMDB_URL,
apiTinyMceSecret: process.env.NUXT_ENV_TINY_MCE_API_KEY,
},
},
... ...
{
"name": "nuxt-app",
"version": "0.2.0",
"version": "0.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nuxt-app",
"version": "0.2.0",
"version": "0.3.0",
"hasInstallScript": true,
"dependencies": {
"@nuxt/eslint": "^1.3.0",
"@nuxt/icon": "^1.12.0",
"@nuxt/image": "^1.10.0",
"@nuxt/scripts": "^0.11.6",
"@nuxt/test-utils": "^3.17.2",
"@nuxt/ui": "^2.22.0",
"@pinia-orm/nuxt": "^1.10.2",
"@pinia/nuxt": "^0.9.0",
"@tinymce/tinymce-vue": "^5.1.1",
"@types/vuelidate": "^0.7.22",
"@unhead/vue": "^2.0.8",
"@vitejs/plugin-vue": "^5.2.3",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/core": "^13.1.0",
"@vueuse/nuxt": "^13.1.0",
"eslint": "^9.25.1",
... ... @@ -30,10 +34,16 @@
"vuetify-nuxt-module": "^0.18.6"
},
"devDependencies": {
"@nuxt/test-utils": "^3.17.2",
"@nuxtjs/tailwindcss": "^6.13.2",
"@vue/test-utils": "^2.4.6",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"prettier": "^3.5.3"
"happy-dom": "^17.4.4",
"jsdom": "^26.1.0",
"playwright-core": "^1.52.0",
"prettier": "^3.5.3",
"vitest": "^3.1.2"
}
},
"node_modules/@alloc/quick-lru": {
... ... @@ -115,6 +125,27 @@
"url": "https://github.com/sponsors/philsturgeon"
}
},
"node_modules/@asamuzakjp/css-color": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.5.tgz",
"integrity": "sha512-w7AmVyTTiU41fNLsFDf+gA2Dwtbx2EJtn2pbJNAGSRAg50loXy1uLXA3hEpD8+eydcomTurw09tq5/AyceCaGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@csstools/css-calc": "^2.1.3",
"@csstools/css-color-parser": "^3.0.9",
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3",
"lru-cache": "^10.4.3"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/@babel/code-frame": {
"version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
... ... @@ -557,6 +588,121 @@
"node": ">=0.1.90"
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
"integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"engines": {
"node": ">=18"
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz",
"integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz",
"integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
"@csstools/css-calc": "^2.1.3"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
"integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.3"
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
"integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
},
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@csstools/selector-resolve-nested": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz",
... ... @@ -3073,6 +3219,7 @@
"version": "3.17.2",
"resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.17.2.tgz",
"integrity": "sha512-i1NiWsJx8sv8Zg8z3WD7ITehMi9s8DaR6ArgmDHaKkQ6RJSaVhrPKyGBTv3gzdoF8CHUKa3MNhdX62JWblvLMg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nuxt/kit": "^3.16.0",
... ... @@ -3153,6 +3300,7 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"dev": true,
"license": "MIT"
},
"node_modules/@nuxt/ui": {
... ... @@ -3319,6 +3467,13 @@
"unctx": "^2.4.1"
}
},
"node_modules/@one-ini/wasm": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"dev": true,
"license": "MIT"
},
"node_modules/@oxc-parser/binding-darwin-arm64": {
"version": "0.56.5",
"resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.56.5.tgz",
... ... @@ -4563,6 +4718,18 @@
"vue": "^2.7.0 || ^3.0.0"
}
},
"node_modules/@tinymce/tinymce-vue": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@tinymce/tinymce-vue/-/tinymce-vue-5.1.1.tgz",
"integrity": "sha512-iO57HOWesFOhsaqjA5Ea6sDvQBmJJH3/dq00Uvg7metlct2kLF+ctRgoDsetLt6gmeZ7COPftr814/XzqnJ/dg==",
"license": "MIT",
"dependencies": {
"tinymce": "^6.0.0 || ^5.5.1"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
... ... @@ -4634,6 +4801,64 @@
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
"node_modules/@types/vuelidate": {
"version": "0.7.22",
"resolved": "https://registry.npmjs.org/@types/vuelidate/-/vuelidate-0.7.22.tgz",
"integrity": "sha512-bD3pP9FgL3pxMVQ9NJ3d8BbV8Ij6xsrDKdCO4l1Wq/AksXxRRmQ9lmYjRJwn/hLMcgWO/k0QdULfZWpRz13adw==",
"license": "MIT",
"dependencies": {
"vue": "^2.7.15"
}
},
"node_modules/@types/vuelidate/node_modules/@vue/compiler-sfc": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
"integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
"dependencies": {
"@babel/parser": "^7.23.5",
"postcss": "^8.4.14",
"source-map": "^0.6.1"
},
"optionalDependencies": {
"prettier": "^1.18.2 || ^2.0.0"
}
},
"node_modules/@types/vuelidate/node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"license": "MIT",
"optional": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/@types/vuelidate/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@types/vuelidate/node_modules/vue": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
"license": "MIT",
"dependencies": {
"@vue/compiler-sfc": "2.7.16",
"csstype": "^3.1.0"
}
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.21",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
... ... @@ -5468,6 +5693,119 @@
"vue": "^3.0.0"
}
},
"node_modules/@vitest/expect": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.2.tgz",
"integrity": "sha512-O8hJgr+zREopCAqWl3uCVaOdqJwZ9qaDwUP7vy3Xigad0phZe9APxKhPcDNqYYi0rX5oMvwJMSCAXY2afqeTSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.1.2",
"@vitest/utils": "3.1.2",
"chai": "^5.2.0",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/mocker": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.2.tgz",
"integrity": "sha512-kOtd6K2lc7SQ0mBqYv/wdGedlqPdM/B38paPY+OwJ1XiNi44w3Fpog82UfOibmHaV9Wod18A09I9SCKLyDMqgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.1.2",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"msw": "^2.4.9",
"vite": "^5.0.0 || ^6.0.0"
},
"peerDependenciesMeta": {
"msw": {
"optional": true
},
"vite": {
"optional": true
}
}
},
"node_modules/@vitest/pretty-format": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.2.tgz",
"integrity": "sha512-R0xAiHuWeDjTSB3kQ3OQpT8Rx3yhdOAIm/JM4axXxnG7Q/fS8XUwggv/A4xzbQA+drYRjzkMnpYnOGAc4oeq8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.2.tgz",
"integrity": "sha512-bhLib9l4xb4sUMPXnThbnhX2Yi8OutBMA8Yahxa7yavQsFDtwY/jrUZwpKp2XH9DhRFJIeytlyGpXCqZ65nR+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "3.1.2",
"pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.2.tgz",
"integrity": "sha512-Q1qkpazSF/p4ApZg1vfZSQ5Yw6OCQxVMVrLjslbLFA1hMDrT2uxtqMaw8Tc/jy5DLka1sNs1Y7rBcftMiaSH/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.1.2",
"magic-string": "^0.30.17",
"pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/spy": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.2.tgz",
"integrity": "sha512-OEc5fSXMws6sHVe4kOFyDSj/+4MSwst0ib4un0DlcYgQvRuYQ0+M2HyqGaauUMnjq87tmUaMNDxKQx7wNfVqPA==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyspy": "^3.0.2"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.2.tgz",
"integrity": "sha512-5GGd0ytZ7BH3H6JTj9Kw7Prn1Nbg0wZVrIvou+UWxm54d+WoXXgAgjFJ8wn3LdagWLFSEfpPeyYrByZaGEZHLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.1.2",
"loupe": "^3.1.3",
"tinyrainbow": "^2.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vue-macros/common": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.16.1.tgz",
... ... @@ -5702,35 +6040,134 @@
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
"license": "MIT"
},
"node_modules/@vuetify/loader-shared": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.0.tgz",
"integrity": "sha512-dNE6Ceym9ijFsmJKB7YGW0cxs7xbYV8+1LjU6jd4P14xOt/ji4Igtgzt0rJFbxu+ZhAzqz853lhB0z8V9Dy9cQ==",
"node_modules/@vue/test-utils": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz",
"integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==",
"dev": true,
"license": "MIT",
"dependencies": {
"upath": "^2.0.1"
},
"peerDependencies": {
"vue": "^3.0.0",
"vuetify": "^3.0.0"
"js-beautify": "^1.14.9",
"vue-component-type-helpers": "^2.0.0"
}
},
"node_modules/@vueuse/core": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz",
"integrity": "sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==",
"node_modules/@vuelidate/core": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz",
"integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "13.1.0",
"@vueuse/shared": "13.1.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
"vue-demi": "^0.13.11"
},
"peerDependencies": {
"vue": "^3.5.0"
}
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^2.0.0 || >=3.0.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vuelidate/core/node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vuelidate/validators": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.4.tgz",
"integrity": "sha512-odTxtUZ2JpwwiQ10t0QWYJkkYrfd0SyFYhdHH44QQ1jDatlZgTh/KRzrWVmn/ib9Gq7H4hFD4e8ahoo5YlUlDw==",
"license": "MIT",
"dependencies": {
"vue-demi": "^0.13.11"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^2.0.0 || >=3.0.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vuelidate/validators/node_modules/vue-demi": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vuetify/loader-shared": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.0.tgz",
"integrity": "sha512-dNE6Ceym9ijFsmJKB7YGW0cxs7xbYV8+1LjU6jd4P14xOt/ji4Igtgzt0rJFbxu+ZhAzqz853lhB0z8V9Dy9cQ==",
"license": "MIT",
"dependencies": {
"upath": "^2.0.1"
},
"peerDependencies": {
"vue": "^3.0.0",
"vuetify": "^3.0.0"
}
},
"node_modules/@vueuse/core": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.1.0.tgz",
"integrity": "sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "13.1.0",
"@vueuse/shared": "13.1.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@vueuse/integrations": {
"version": "13.1.0",
... ... @@ -6248,6 +6685,16 @@
"node": ">=8"
}
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/ast-kit": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.4.2.tgz",
... ... @@ -6828,6 +7275,23 @@
],
"license": "CC-BY-4.0"
},
"node_modules/chai": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
"integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
"dev": true,
"license": "MIT",
"dependencies": {
"assertion-error": "^2.0.1",
"check-error": "^2.1.1",
"deep-eql": "^5.0.1",
"loupe": "^3.1.0",
"pathval": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
... ... @@ -6856,6 +7320,16 @@
"node": ">=8"
}
},
"node_modules/check-error": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 16"
}
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
... ... @@ -7138,6 +7612,24 @@
"integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
"license": "MIT"
},
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"node_modules/config-chain/node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true,
"license": "ISC"
},
"node_modules/consola": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
... ... @@ -7530,6 +8022,20 @@
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
"license": "CC0-1.0"
},
"node_modules/cssstyle": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz",
"integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^3.1.2",
"rrweb-cssom": "^0.8.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
... ... @@ -7545,6 +8051,67 @@
"node": ">= 12"
}
},
"node_modules/data-urls": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/data-urls/node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/data-urls/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/db0": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/db0/-/db0-0.3.2.tgz",
... ... @@ -7605,6 +8172,13 @@
"callsite": "^1.0.0"
}
},
"node_modules/decimal.js": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
"dev": true,
"license": "MIT"
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
... ... @@ -7621,6 +8195,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-eql": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/deep-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
... ... @@ -8028,6 +8612,41 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/editorconfig": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@one-ini/wasm": "0.1.1",
"commander": "^10.0.0",
"minimatch": "9.0.1",
"semver": "^7.5.3"
},
"bin": {
"editorconfig": "bin/editorconfig"
},
"engines": {
"node": ">=14"
}
},
"node_modules/editorconfig/node_modules/minimatch": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
... ... @@ -9020,6 +9639,16 @@
"node": ">=6"
}
},
"node_modules/expect-type": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz",
"integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/exsolve": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz",
... ... @@ -9083,6 +9712,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz",
"integrity": "sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18"
... ... @@ -9807,6 +10437,30 @@
"integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==",
"license": "MIT"
},
"node_modules/happy-dom": {
"version": "17.4.4",
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.4.4.tgz",
"integrity": "sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==",
"dev": true,
"license": "MIT",
"dependencies": {
"webidl-conversions": "^7.0.0",
"whatwg-mimetype": "^3.0.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/happy-dom/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
... ... @@ -9885,6 +10539,19 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"whatwg-encoding": "^3.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/http-assert": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
... ... @@ -9948,6 +10615,20 @@
"node": ">= 0.8"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/http-shutdown": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/http-shutdown/-/http-shutdown-1.2.2.tgz",
... ... @@ -9986,6 +10667,19 @@
"node": ">=16.17.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
... ... @@ -10823,6 +11517,13 @@
"node": ">=8"
}
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"dev": true,
"license": "MIT"
},
"node_modules/is-reference": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
... ... @@ -10980,31 +11681,207 @@
"jiti": "bin/jiti.js"
}
},
"node_modules/js-tokens": {
"node_modules/js-beautify": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
"integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==",
"dev": true,
"license": "MIT",
"dependencies": {
"config-chain": "^1.1.13",
"editorconfig": "^1.0.4",
"glob": "^10.4.2",
"js-cookie": "^3.0.5",
"nopt": "^7.2.1"
},
"bin": {
"css-beautify": "js/bin/css-beautify.js",
"html-beautify": "js/bin/html-beautify.js",
"js-beautify": "js/bin/js-beautify.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/js-beautify/node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/js-beautify/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-beautify/node_modules/nopt": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
"integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
"dev": true,
"license": "ISC",
"dependencies": {
"abbrev": "^2.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsdoc-type-pratt-parser": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
"integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/jsdom": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
"decimal.js": "^10.5.0",
"html-encoding-sniffer": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"is-potential-custom-element-name": "^1.0.1",
"nwsapi": "^2.2.16",
"parse5": "^7.2.1",
"rrweb-cssom": "^0.8.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^5.1.1",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
"whatwg-encoding": "^3.1.1",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^14.1.1",
"ws": "^8.18.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"canvas": "^3.0.0"
},
"peerDependenciesMeta": {
"canvas": {
"optional": true
}
}
},
"node_modules/jsdom/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/jsdom/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/jsdom/node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"node_modules/jsdom/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
"engines": {
"node": ">=18"
}
},
"node_modules/jsdoc-type-pratt-parser": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz",
"integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==",
"license": "MIT",
"node_modules/jsdom/node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=12.0.0"
"node": ">=18"
}
},
"node_modules/jsesc": {
... ... @@ -11578,6 +12455,13 @@
"node": ">= 12.0.0"
}
},
"node_modules/loupe": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
"integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
"dev": true,
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
... ... @@ -12509,6 +13393,13 @@
}
}
},
"node_modules/nwsapi": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
"integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
"dev": true,
"license": "MIT"
},
"node_modules/nypm": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz",
... ... @@ -12912,6 +13803,32 @@
"node": ">=14.13.0"
}
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
... ... @@ -13000,6 +13917,16 @@
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"license": "MIT"
},
"node_modules/pathval": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14.16"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
... ... @@ -13121,6 +14048,19 @@
"pathe": "^2.0.3"
}
},
"node_modules/playwright-core": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/pluralize": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
... ... @@ -14037,6 +14977,13 @@
"node": ">= 6"
}
},
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
"dev": true,
"license": "ISC"
},
"node_modules/protocols": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz",
... ... @@ -14690,6 +15637,13 @@
}
}
},
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"dev": true,
"license": "MIT"
},
"node_modules/run-applescript": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz",
... ... @@ -14771,6 +15725,26 @@
"node": ">=10"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT"
},
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"dev": true,
"license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=v12.22.7"
}
},
"node_modules/scslre": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz",
... ... @@ -15043,6 +16017,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
"dev": true,
"license": "ISC"
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
... ... @@ -15263,6 +16244,13 @@
"node": "*"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
"dev": true,
"license": "MIT"
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
... ... @@ -15627,6 +16615,13 @@
"node": ">= 10"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true,
"license": "MIT"
},
"node_modules/synckit": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
... ... @@ -16033,6 +17028,13 @@
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
"dev": true,
"license": "MIT"
},
"node_modules/tinyexec": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz",
... ... @@ -16055,6 +17057,62 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinymce": {
"version": "6.8.5",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.5.tgz",
"integrity": "sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g==",
"license": "MIT"
},
"node_modules/tinypool": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
"integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || >=20.0.0"
}
},
"node_modules/tinyrainbow": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
"integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tinyspy": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"tldts-core": "^6.1.86"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"dev": true,
"license": "MIT"
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
... ... @@ -16109,6 +17167,19 @@
"node": ">=6"
}
},
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tldts": "^6.1.32"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
... ... @@ -17144,15 +18215,111 @@
"vuetify": "^3.0.0"
}
},
"node_modules/vitest": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.2.tgz",
"integrity": "sha512-WaxpJe092ID1C0mr+LH9MmNrhfzi8I65EX/NRU/Ld016KqQNRgxSOlGNP1hHN+a/F8L15Mh8klwaF77zR3GeDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "3.1.2",
"@vitest/mocker": "3.1.2",
"@vitest/pretty-format": "^3.1.2",
"@vitest/runner": "3.1.2",
"@vitest/snapshot": "3.1.2",
"@vitest/spy": "3.1.2",
"@vitest/utils": "3.1.2",
"chai": "^5.2.0",
"debug": "^4.4.0",
"expect-type": "^1.2.1",
"magic-string": "^0.30.17",
"pathe": "^2.0.3",
"std-env": "^3.9.0",
"tinybench": "^2.9.0",
"tinyexec": "^0.3.2",
"tinyglobby": "^0.2.13",
"tinypool": "^1.0.2",
"tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0",
"vite-node": "3.1.2",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.1.2",
"@vitest/ui": "3.1.2",
"happy-dom": "*",
"jsdom": "*"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
"optional": true
},
"@types/debug": {
"optional": true
},
"@types/node": {
"optional": true
},
"@vitest/browser": {
"optional": true
},
"@vitest/ui": {
"optional": true
},
"happy-dom": {
"optional": true
},
"jsdom": {
"optional": true
}
}
},
"node_modules/vitest-environment-nuxt": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-1.0.1.tgz",
"integrity": "sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nuxt/test-utils": ">=3.13.1"
}
},
"node_modules/vitest/node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
"dev": true,
"license": "MIT"
},
"node_modules/vitest/node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/vscode-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
... ... @@ -17189,6 +18356,13 @@
"ufo": "^1.5.4"
}
},
"node_modules/vue-component-type-helpers": {
"version": "2.2.10",
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.2.10.tgz",
"integrity": "sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==",
"dev": true,
"license": "MIT"
},
"node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
... ... @@ -17366,6 +18540,29 @@
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"license": "MIT"
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"xml-name-validator": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/w3c-xmlserializer/node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
... ... @@ -17387,6 +18584,29 @@
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"license": "MIT"
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
"integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
... ... @@ -17412,6 +18632,23 @@
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/why-is-node-running": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"siginfo": "^2.0.0",
"stackback": "0.0.2"
},
"bin": {
"why-is-node-running": "cli.js"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
... ... @@ -17632,6 +18869,13 @@
"node": ">=12"
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true,
"license": "MIT"
},
"node_modules/xss": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz",
... ...
{
"name": "nuxt-app",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"type": "module",
"scripts": {
... ... @@ -12,18 +12,23 @@
"lint:js": "eslint --ext \".ts,.vue\" .",
"lint:prettier": "prettier --write .",
"lint": "npm run lint:js && npm run lint:prettier",
"format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\""
"format": "prettier --write \"{components,pages,plugins,middleware,layouts,composables,assets}/**/*.{js,jsx,ts,tsx,vue,html,css,scss,json,md}\"",
"test": "vitest"
},
"dependencies": {
"@nuxt/eslint": "^1.3.0",
"@nuxt/icon": "^1.12.0",
"@nuxt/image": "^1.10.0",
"@nuxt/scripts": "^0.11.6",
"@nuxt/test-utils": "^3.17.2",
"@nuxt/ui": "^2.22.0",
"@pinia-orm/nuxt": "^1.10.2",
"@pinia/nuxt": "^0.9.0",
"@tinymce/tinymce-vue": "^5.1.1",
"@types/vuelidate": "^0.7.22",
"@unhead/vue": "^2.0.8",
"@vitejs/plugin-vue": "^5.2.3",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/core": "^13.1.0",
"@vueuse/nuxt": "^13.1.0",
"eslint": "^9.25.1",
... ... @@ -36,9 +41,15 @@
"vuetify-nuxt-module": "^0.18.6"
},
"devDependencies": {
"@nuxt/test-utils": "^3.17.2",
"@nuxtjs/tailwindcss": "^6.13.2",
"@vue/test-utils": "^2.4.6",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"prettier": "^3.5.3"
"happy-dom": "^17.4.4",
"jsdom": "^26.1.0",
"playwright-core": "^1.52.0",
"prettier": "^3.5.3",
"vitest": "^3.1.2"
}
}
... ...
<script setup lang="ts">
<script lang="ts" setup>
//#region --import--.
import { AlertTriangleIcon, ArrowLeftIcon } from "lucide-vue-next";
import { useTMDB } from "~/composables/tMDB";
import { computed, onMounted, ref } from "vue";
import { Movie } from "~/models/movie";
import type { MovieInterface } from "~/interfaces/movie";
import { Credit } from "~/models/credit";
import type { CreditsResponse } from "~/interfaces/credit";
import type { MovieCommentInterface } from "~/interfaces/movieComment";
import { MovieComment } from "~/models/movieComment";
import type { WhereSecondaryClosure } from "pinia-orm";
//#endregion
//#region --Declaration--.
const { fetchMovieDetails, fetchMovieCredits } = useTMDB();
//#endregion
//#region --Declaration--.
const { currentRoute } = useRouter();
//#endregion
//#region --Data/ref--.
const isLoading = ref(true);
const isSubmitting = ref(false);
//#endregion
//#region --Computed--.
const movieId = computed(() => {
if (currentRoute.value.params.id) {
if (typeof currentRoute.value.params.id === "string") {
if (typeof Number(+currentRoute.value.params.id) === "number") {
return +currentRoute.value.params.id as number;
} else {
return currentRoute.value.params.id as string;
}
} else {
return null;
}
} else {
return null;
}
});
const movie = computed(() => {
if (unref(movieId)) {
return useRepo(Movie)
.query()
.where("id", movieId.value as WhereSecondaryClosure<never> | null | undefined)
.withAll()
.first() as unknown as MovieInterface;
} else {
return null;
}
});
/**
* Computed property for director
*/
const director = computed(() => {
if (unref(movie)?.credit?.crew) {
return movie.value?.credit.crew.find((person) => person.job === "Director");
} else {
return null;
}
});
/**
* Retourne les commentaires liés au film, du plus récent au plus ancien.
*/
const comments = computed(() => {
return useRepo(MovieComment)
.query()
.where((comment) => {
const searched = comment as unknown as MovieCommentInterface;
return searched.movie_id === unref(movieId);
})
.orderBy("createdAt", "desc")
.get();
});
//#endregion
//#region --Function--.
/**
* Fetch movie details
*/
const fetchDetails = async (id: number | string) => {
try {
isLoading.value = true;
const data = await fetchMovieDetails(id);
// Add to store collection.
useRepo(Movie).save(data);
} catch (error) {
console.error("Error fetching movie details:", error);
} finally {
isLoading.value = false;
}
};
/**
* Format runtime
* @param minutes
*/
const formatRuntime = (minutes: number) => {
if (!minutes) return "Durée inconnue";
// Find nb hours.
const hours = Math.floor(minutes / 60);
// Find last minutes.
const mins = minutes % 60;
return `${hours}h ${mins}min`;
};
async function fetchCredits(id: number | string) {
try {
const data = (await fetchMovieCredits(id)) as CreditsResponse;
data.movie_id = id;
// Add to store collection.
useRepo(Credit).save(data);
} catch (error) {
console.error("Error fetching movie credits:", error);
}
}
//Ce n'est pas le film du siècle cependant, il est suffisamment intéressant pour passer une bonne soirée !
function handleSubmitEvent(event: MovieCommentInterface) {
isSubmitting.value = true;
event.movie_id = unref(movieId);
event.createdAt = `${new Date(Date.now())}`;
useRepo(MovieComment).save(event);
isSubmitting.value = false;
}
//#endregion
//#region --Global event--.
onMounted(() => {
// Fetch data on component mount.
if (unref(movieId)) {
const id = unref(movieId) as string | number;
fetchDetails(id);
fetchCredits(id);
}
// loadComments()
});
//#endregion
</script>
<template>
<section>
composant détail d'un film.
<!-- Skeleton loader pendant le chargement -->
<ui-components-skeleton-movie-detail-loader v-if="isLoading" />
<!-- Contenu du film -->
<div v-else-if="movie" class="relative">
<!-- Backdrop image -->
<ui-components-backdrop-image v-if="movie.backdrop_path" :src="movie.backdrop_path" :title="movie.title" />
<!-- Contenu principal -->
<div class="container mx-auto px-4 py-8 relative z-10 pt-20">
<button
class="flex items-center text-gray-400 hover:text-white mb-8 transition-colors"
@click="navigateTo('/')"
>
<ArrowLeftIcon :size="20" class="mr-2" />
Retour
</button>
<div class="flex flex-col md:flex-row gap-8">
<!-- Poster -->
<ui-components-poster v-if="movie.poster_path" :src="movie.poster_path" :title="movie.title" />
<!-- Informations du film -->
<section class="w-full md:w-2/3 lg:w-3/4">
<h1 class="text-3xl md:text-4xl font-bold mb-2">{{ movie.title }}</h1>
<p v-if="movie.release_date" class="text-gray-400 mb-4">
{{ useDateFormat(movie.release_date, "DD-MM-YYYY") }} • {{ formatRuntime(movie.runtime) }}
</p>
<!-- Note et votes -->
<details-score-and-vote :nb-vote="movie.vote_count" :score="movie.vote_average" />
<!-- Genres -->
<details-movie-gender :genres="movie.genres" />
<!-- Synopsis -->
<div class="mb-6">
<h2 class="text-xl font-bold mb-2">Synopsis</h2>
<p class="text-gray-300">{{ movie.overview || "Aucun synopsis disponible." }}</p>
</div>
<!-- Réalisateur et têtes d'affiche -->
<div v-if="movie.credit" class="mb-6">
<h2 class="text-xl font-bold mb-2">Équipe</h2>
<div v-if="director" class="mb-2">
<span class="font-semibold">Réalisateur:</span> {{ director.name }}
</div>
<div v-if="movie.credit.cast.length > 0">
<span class="font-semibold">Têtes d'affiche:</span>
{{
movie.credit.cast
.slice(0, 15)
.map((person) => person.name)
.join(", ")
}}
<span v-if="movie.credit.cast.length > 15">..</span>
</div>
</div>
<!-- Comments form. -->
<h3 class="text-xl font-bold mt-8 mb-4">Ajouter un commentaire</h3>
<form-movie-comment-form @event:submit="handleSubmitEvent" />
<!-- Liste des commentaires -->
<movie-comment-list :comments="comments as unknown as MovieCommentInterface[]" />
</section>
</template>
</div>
</div>
</div>
<style scoped>
<!-- Erreur -->
<section v-else class="container mx-auto px-4 py-16 text-center">
<AlertTriangleIcon :size="64" class="mx-auto mb-4 text-red-500" />
<h2 class="text-2xl font-bold mb-2">Film non trouvé</h2>
<p class="text-gray-400 mb-6">Nous n'avons pas pu trouver le film que vous cherchez.</p>
<button
class="px-6 py-2 bg-primary text-white font-bold rounded-md hover:bg-primary-dark transition-colors"
@click="navigateTo('/')"
>
Retour à l'accueil
</button>
</section>
</section>
</template>
</style>
\ No newline at end of file
<style scoped></style>
... ...
export type Comment = {
username: string
message: string
rating: number
}
\ No newline at end of file
... ...
// vite.config.js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [vue()],
test: {
globals: true,
environment: "jsdom",
// Additional test configurations can be added here
},
}
\ No newline at end of file
... ...
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
/**
* Documentation here : https://nuxt.com/docs/getting-started/testing
* any custom Vitest config you require
*/
test: {
environment: 'nuxt',
// you can optionally set Nuxt-specific environment options
// environmentOptions: {
// nuxt: {
// rootDir: fileURLToPath(new URL('./playground', import.meta.url)),
// domEnvironment: 'happy-dom', // 'happy-dom' (default) or 'jsdom'
// overrides: {
// // other Nuxt config you want to pass
// }
// }
// }
}
})
... ...