Bruno Predot

- Ajout dépendance TinyMCE.

- Ajout composant TinyMceFieldEditor.
- Intégration de TinyMCE en remplacement du v-text-field dans le composant MovieComponentForm & MovieCommentList.
@@ -10,3 +10,5 @@ @@ -10,3 +10,5 @@
10 - Ajout model + interface MovieComment. 10 - Ajout model + interface MovieComment.
11 - Ajout composant MovieCommentForm. 11 - Ajout composant MovieCommentForm.
12 - Ajout composant MovieCommentList. 12 - Ajout composant MovieCommentList.
  13 +- Ajout dépendance TinyMCE.
  14 +- Ajout composant TinyMceFieldEditor.
1 -<script setup lang="ts"> 1 +<script lang="ts" setup>
2 //#region --Import--. 2 //#region --Import--.
3 import type { MovieCommentInterface } from "~/interfaces/movieComment"; 3 import type { MovieCommentInterface } from "~/interfaces/movieComment";
4 import { MessageSquareIcon } from "lucide-vue-next"; 4 import { MessageSquareIcon } from "lucide-vue-next";
5 //#endregion 5 //#endregion
6 6
7 //#region --Props--. 7 //#region --Props--.
8 -defineProps({ 8 +const props = defineProps({
9 comments: { 9 comments: {
10 type: Array<MovieCommentInterface>, 10 type: Array<MovieCommentInterface>,
11 required: true, 11 required: true,
12 - nullable: false 12 + nullable: false,
13 }, 13 },
14 }); 14 });
15 //#endregion 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 + console.log(element);
  26 + element.innerHTML = comment.message;
  27 + });
  28 + }
  29 + });
  30 + },
  31 +);
  32 +//#endregion
16 </script> 33 </script>
17 34
18 <template> 35 <template>
@@ -20,11 +37,7 @@ defineProps({ @@ -20,11 +37,7 @@ defineProps({
20 <!-- Liste des commentaires --> 37 <!-- Liste des commentaires -->
21 <section v-if="comments.length > 0" class="mt-10"> 38 <section v-if="comments.length > 0" class="mt-10">
22 <h2>Commentaires publiés</h2> 39 <h2>Commentaires publiés</h2>
23 - <div 40 + <div v-for="(comment, index) in comments" :key="index" class="bg-gray-800 rounded-lg p-6 mb-4">
24 - v-for="(comment, index) in comments"  
25 - :key="index"  
26 - class="bg-gray-800 rounded-lg p-6 mb-4"  
27 - >  
28 <div class="flex justify-between items-start mb-2"> 41 <div class="flex justify-between items-start mb-2">
29 <section> 42 <section>
30 <h4 class="font-bold text-lg">Par {{ comment.username }}</h4> 43 <h4 class="font-bold text-lg">Par {{ comment.username }}</h4>
@@ -34,7 +47,7 @@ defineProps({ @@ -34,7 +47,7 @@ defineProps({
34 {{ comment.rating }} 47 {{ comment.rating }}
35 </section> 48 </section>
36 </div> 49 </div>
37 - <p class="text-gray-300">{{ comment.message }}</p> 50 + <p :id="`message${index}`" class="text-gray-300">{{ comment.message }}</p>
38 </div> 51 </div>
39 </section> 52 </section>
40 <!-- Si aucun commentaire --> 53 <!-- Si aucun commentaire -->
@@ -45,6 +58,4 @@ defineProps({ @@ -45,6 +58,4 @@ defineProps({
45 </section> 58 </section>
46 </template> 59 </template>
47 60
48 -<style scoped> 61 +<style scoped></style>
49 -  
50 -</style>  
@@ -6,7 +6,7 @@ import type { Comment } from "~/type/commentForm"; @@ -6,7 +6,7 @@ import type { Comment } from "~/type/commentForm";
6 //#endregion 6 //#endregion
7 7
8 //#region --Emit--. 8 //#region --Emit--.
9 -const emit = defineEmits(['event:submit']); 9 +const emit = defineEmits(["event:submit"]);
10 //#endregion 10 //#endregion
11 11
12 //#region --Props--. 12 //#region --Props--.
@@ -58,7 +58,7 @@ const v$ = useVuelidate(rules, formData); @@ -58,7 +58,7 @@ const v$ = useVuelidate(rules, formData);
58 58
59 //#region --Function--. 59 //#region --Function--.
60 async function submitComment() { 60 async function submitComment() {
61 - emit('event:submit', formData); 61 + emit("event:submit", formData);
62 } 62 }
63 63
64 function clear() { 64 function clear() {
@@ -68,6 +68,29 @@ function clear() { @@ -68,6 +68,29 @@ function clear() {
68 formData.rating = initialState.rating; 68 formData.rating = initialState.rating;
69 } 69 }
70 70
  71 +function handleMessageEvent(event: string) {
  72 + formData.message = event;
  73 + v$.value.message.$touch();
  74 +}
  75 +
  76 +/*
  77 +
  78 + if (event.length < 3) {
  79 + v$.value.message.$errors[0] = {
  80 + $propertyPath: "username",
  81 + $property: "username",
  82 + $validator: "required",
  83 + $uid: "username-required",
  84 + $message: rules.message.minLength as unknown as string,
  85 + $params: {
  86 + type:"required"
  87 + },
  88 + $response: false,
  89 + $pending: false,
  90 + };
  91 + }
  92 + */
  93 +
71 //#endregion 94 //#endregion
72 </script> 95 </script>
73 96
@@ -76,25 +99,16 @@ function clear() { @@ -76,25 +99,16 @@ function clear() {
76 <VForm> 99 <VForm>
77 <v-text-field 100 <v-text-field
78 v-model="formData.username" 101 v-model="formData.username"
79 - :error-messages="v$.username.$errors.map((e) => e.$message)" 102 + :error-messages="v$.username.$errors.map((e) => e.$message) as readonly string[]"
80 label="nom d'utilisateur" 103 label="nom d'utilisateur"
81 placeholder="nom d'utilisateur" 104 placeholder="nom d'utilisateur"
82 required 105 required
83 @blur="v$.username.$touch()" 106 @blur="v$.username.$touch()"
84 @input="v$.username.$touch()" 107 @input="v$.username.$touch()"
85 /> 108 />
86 - <v-textarea  
87 - v-model="formData.message"  
88 - :error-messages="v$.message.$errors.map((e) => e.$message)"  
89 - label="message"  
90 - placeholder="Saisissez votre commentaire"  
91 - required  
92 - @blur="v$.message.$touch"  
93 - @input="v$.message.$touch"  
94 - />  
95 <v-text-field 109 <v-text-field
96 v-model="formData.rating" 110 v-model="formData.rating"
97 - :error-messages="v$.rating.$errors.map((e) => e.$message)" 111 + :error-messages="v$.rating.$errors.map((e) => e.$message) as readonly string[]"
98 label="Note (1-10)" 112 label="Note (1-10)"
99 placeholder="" 113 placeholder=""
100 required 114 required
@@ -102,6 +116,11 @@ function clear() { @@ -102,6 +116,11 @@ function clear() {
102 @blur="v$.rating.$touch" 116 @blur="v$.rating.$touch"
103 @input="v$.rating.$touch" 117 @input="v$.rating.$touch"
104 /> 118 />
  119 + <ui-components-tiny-mce-field-editor
  120 + :error-message="v$?.message?.$errors[0]?.$message ? (v$.message.$errors[0].$message as string) : ''"
  121 + :model-value="formData.message"
  122 + @update:model-value="handleMessageEvent"
  123 + />
105 <v-btn 124 <v-btn
106 class="mt-6 mr-4" 125 class="mt-6 mr-4"
107 color="primary" 126 color="primary"
@@ -120,7 +139,7 @@ function clear() { @@ -120,7 +139,7 @@ function clear() {
120 </span> 139 </span>
121 <span v-else>Publier le commentaire</span> 140 <span v-else>Publier le commentaire</span>
122 </v-btn> 141 </v-btn>
123 - <v-btn class="mt-6 mr-4" color="primary" @click="clear"> effacer </v-btn> 142 + <v-btn class="mt-6 mr-4" color="primary" @click="clear"> effacer</v-btn>
124 </VForm> 143 </VForm>
125 </section> 144 </section>
126 </template> 145 </template>
  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 +};
  59 +//#endregion
  60 +
  61 +//#region --Watch--.
  62 +watch(content, (newValue) => {
  63 + emit("update:modelValue", newValue);
  64 +});
  65 +
  66 +watch(
  67 + () => props.modelValue,
  68 + (newValue) => {
  69 + if (newValue !== content.value) {
  70 + content.value = newValue;
  71 + }
  72 + },
  73 +);
  74 +//#endregion
  75 +</script>
  76 +
  77 +<template>
  78 + <div>
  79 + <editor v-model="content" :api-key="runtimeConfig.public.apiTinyMceSecret" :init="init" />
  80 + </div>
  81 + <div v-if="errorMessage" class="text-red-500 text-sm mt-1">
  82 + {{ errorMessage }}
  83 + </div>
  84 +</template>
  85 +
  86 +<style scoped></style>
@@ -68,8 +68,8 @@ export default defineNuxtConfig({ @@ -68,8 +68,8 @@ export default defineNuxtConfig({
68 // Keys within public are also exposed client-side. 68 // Keys within public are also exposed client-side.
69 public: { 69 public: {
70 apiTMDBSecret: process.env.NUXT_ENV_TMDB_API_KEY, 70 apiTMDBSecret: process.env.NUXT_ENV_TMDB_API_KEY,
71 - apiTMDBBearer: process.env.NUXT_ENV_TMDB_BEARER,  
72 apiTMDBUrl: process.env.NUXT_ENV_TMDB_URL, 71 apiTMDBUrl: process.env.NUXT_ENV_TMDB_URL,
  72 + apiTinyMceSecret: process.env.NUXT_ENV_TINY_MCE_API_KEY,
73 }, 73 },
74 }, 74 },
75 75
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 "@nuxt/ui": "^2.22.0", 17 "@nuxt/ui": "^2.22.0",
18 "@pinia-orm/nuxt": "^1.10.2", 18 "@pinia-orm/nuxt": "^1.10.2",
19 "@pinia/nuxt": "^0.9.0", 19 "@pinia/nuxt": "^0.9.0",
  20 + "@tinymce/tinymce-vue": "^5.1.1",
20 "@types/vuelidate": "^0.7.22", 21 "@types/vuelidate": "^0.7.22",
21 "@unhead/vue": "^2.0.8", 22 "@unhead/vue": "^2.0.8",
22 "@vuelidate/core": "^2.0.3", 23 "@vuelidate/core": "^2.0.3",
@@ -4566,6 +4567,18 @@ @@ -4566,6 +4567,18 @@
4566 "vue": "^2.7.0 || ^3.0.0" 4567 "vue": "^2.7.0 || ^3.0.0"
4567 } 4568 }
4568 }, 4569 },
  4570 + "node_modules/@tinymce/tinymce-vue": {
  4571 + "version": "5.1.1",
  4572 + "resolved": "https://registry.npmjs.org/@tinymce/tinymce-vue/-/tinymce-vue-5.1.1.tgz",
  4573 + "integrity": "sha512-iO57HOWesFOhsaqjA5Ea6sDvQBmJJH3/dq00Uvg7metlct2kLF+ctRgoDsetLt6gmeZ7COPftr814/XzqnJ/dg==",
  4574 + "license": "MIT",
  4575 + "dependencies": {
  4576 + "tinymce": "^6.0.0 || ^5.5.1"
  4577 + },
  4578 + "peerDependencies": {
  4579 + "vue": "^3.0.0"
  4580 + }
  4581 + },
4569 "node_modules/@trysound/sax": { 4582 "node_modules/@trysound/sax": {
4570 "version": "0.2.0", 4583 "version": "0.2.0",
4571 "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", 4584 "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@@ -16204,6 +16217,12 @@ @@ -16204,6 +16217,12 @@
16204 "url": "https://github.com/sponsors/SuperchupuDev" 16217 "url": "https://github.com/sponsors/SuperchupuDev"
16205 } 16218 }
16206 }, 16219 },
  16220 + "node_modules/tinymce": {
  16221 + "version": "6.8.5",
  16222 + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.5.tgz",
  16223 + "integrity": "sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g==",
  16224 + "license": "MIT"
  16225 + },
16207 "node_modules/tmp": { 16226 "node_modules/tmp": {
16208 "version": "0.2.3", 16227 "version": "0.2.3",
16209 "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", 16228 "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -23,6 +23,7 @@ @@ -23,6 +23,7 @@
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",
26 "@types/vuelidate": "^0.7.22", 27 "@types/vuelidate": "^0.7.22",
27 "@unhead/vue": "^2.0.8", 28 "@unhead/vue": "^2.0.8",
28 "@vuelidate/core": "^2.0.3", 29 "@vuelidate/core": "^2.0.3",
@@ -9,15 +9,11 @@ import { Credit } from "~/models/credit"; @@ -9,15 +9,11 @@ import { Credit } from "~/models/credit";
9 import type { CreditsResponse } from "~/interfaces/credit"; 9 import type { CreditsResponse } from "~/interfaces/credit";
10 import type { MovieCommentInterface } from "~/interfaces/movieComment"; 10 import type { MovieCommentInterface } from "~/interfaces/movieComment";
11 import { MovieComment } from "~/models/movieComment"; 11 import { MovieComment } from "~/models/movieComment";
12 -// Infos sur le composable date de Vuetify : https://vuetifyjs.com/en/features/dates/ 12 +import type { WhereSecondaryClosure } from "pinia-orm";
13 -// Et l'api date : https://vuetifyjs.com/en/api/use-date/#exposed  
14 -import { useDate } from "vuetify";  
15 -import { WhereSecondaryClosure } from "pinia-orm";  
16 //#endregion 13 //#endregion
17 14
18 //#region --Declaration--. 15 //#region --Declaration--.
19 const { fetchMovieDetails, fetchMovieCredits } = useTMDB(); 16 const { fetchMovieDetails, fetchMovieCredits } = useTMDB();
20 -const { date, parseISO, toISO, toJsDate, format, locale } = useDate();  
21 //#endregion 17 //#endregion
22 18
23 //#region --Declaration--. 19 //#region --Declaration--.