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 @@
- Ajout model + interface MovieComment.
- Ajout composant MovieCommentForm.
- Ajout composant MovieCommentList.
- Ajout dépendance TinyMCE.
- Ajout composant TinyMceFieldEditor.
\ No newline at end of file
... ...
<script setup lang="ts">
<script lang="ts" setup>
//#region --Import--.
import type { MovieCommentInterface } from "~/interfaces/movieComment";
import { MessageSquareIcon } from "lucide-vue-next";
//#endregion
//#region --Props--.
defineProps({
const props = defineProps({
comments: {
type: Array<MovieCommentInterface>,
required: true,
nullable: false
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;
console.log(element);
element.innerHTML = comment.message;
});
}
});
},
);
//#endregion
</script>
<template>
... ... @@ -20,11 +37,7 @@ defineProps({
<!-- 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 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>
... ... @@ -34,7 +47,7 @@ defineProps({
{{ comment.rating }}
</section>
</div>
<p class="text-gray-300">{{ comment.message }}</p>
<p :id="`message${index}`" class="text-gray-300">{{ comment.message }}</p>
</div>
</section>
<!-- Si aucun commentaire -->
... ... @@ -45,6 +58,4 @@ defineProps({
</section>
</template>
<style scoped>
</style>
\ No newline at end of file
<style scoped></style>
... ...
... ... @@ -6,7 +6,7 @@ import type { Comment } from "~/type/commentForm";
//#endregion
//#region --Emit--.
const emit = defineEmits(['event:submit']);
const emit = defineEmits(["event:submit"]);
//#endregion
//#region --Props--.
... ... @@ -58,7 +58,7 @@ const v$ = useVuelidate(rules, formData);
//#region --Function--.
async function submitComment() {
emit('event:submit', formData);
emit("event:submit", formData);
}
function clear() {
... ... @@ -68,6 +68,29 @@ function clear() {
formData.rating = initialState.rating;
}
function handleMessageEvent(event: string) {
formData.message = event;
v$.value.message.$touch();
}
/*
if (event.length < 3) {
v$.value.message.$errors[0] = {
$propertyPath: "username",
$property: "username",
$validator: "required",
$uid: "username-required",
$message: rules.message.minLength as unknown as string,
$params: {
type:"required"
},
$response: false,
$pending: false,
};
}
*/
//#endregion
</script>
... ... @@ -76,25 +99,16 @@ function clear() {
<VForm>
<v-text-field
v-model="formData.username"
:error-messages="v$.username.$errors.map((e) => e.$message)"
: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-textarea
v-model="formData.message"
:error-messages="v$.message.$errors.map((e) => e.$message)"
label="message"
placeholder="Saisissez votre commentaire"
required
@blur="v$.message.$touch"
@input="v$.message.$touch"
/>
<v-text-field
v-model="formData.rating"
:error-messages="v$.rating.$errors.map((e) => e.$message)"
:error-messages="v$.rating.$errors.map((e) => e.$message) as readonly string[]"
label="Note (1-10)"
placeholder=""
required
... ... @@ -102,6 +116,11 @@ function clear() {
@blur="v$.rating.$touch"
@input="v$.rating.$touch"
/>
<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"
... ... @@ -120,7 +139,7 @@ function clear() {
</span>
<span v-else>Publier le commentaire</span>
</v-btn>
<v-btn class="mt-6 mr-4" color="primary" @click="clear"> effacer </v-btn>
<v-btn class="mt-6 mr-4" color="primary" @click="clear"> effacer</v-btn>
</VForm>
</section>
</template>
... ...
<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",
};
//#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>
... ...
... ... @@ -68,8 +68,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,
},
},
... ...
... ... @@ -17,6 +17,7 @@
"@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",
"@vuelidate/core": "^2.0.3",
... ... @@ -4566,6 +4567,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",
... ... @@ -16204,6 +16217,12 @@
"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/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
... ...
... ... @@ -23,6 +23,7 @@
"@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",
"@vuelidate/core": "^2.0.3",
... ...
... ... @@ -9,15 +9,11 @@ import { Credit } from "~/models/credit";
import type { CreditsResponse } from "~/interfaces/credit";
import type { MovieCommentInterface } from "~/interfaces/movieComment";
import { MovieComment } from "~/models/movieComment";
// Infos sur le composable date de Vuetify : https://vuetifyjs.com/en/features/dates/
// Et l'api date : https://vuetifyjs.com/en/api/use-date/#exposed
import { useDate } from "vuetify";
import { WhereSecondaryClosure } from "pinia-orm";
import type { WhereSecondaryClosure } from "pinia-orm";
//#endregion
//#region --Declaration--.
const { fetchMovieDetails, fetchMovieCredits } = useTMDB();
const { date, parseISO, toISO, toJsDate, format, locale } = useDate();
//#endregion
//#region --Declaration--.
... ...