- Ajout dépendance TinyMCE.
- Ajout composant TinyMceFieldEditor. - Intégration de TinyMCE en remplacement du v-text-field dans le composant MovieComponentForm & MovieCommentList.
Showing
8 changed files
with
166 additions
and
32 deletions
@@ -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--. |
-
Please register or login to post a comment