I mixed both types of posts in the same editor.

This commit is contained in:
PascalMarchesseault
2024-10-23 13:31:18 -04:00
parent 9396f91d18
commit efb138bfcd
4 changed files with 388 additions and 181 deletions

View File

@@ -1,193 +1,73 @@
<script setup>
import { ref } from 'vue';
import Editor from '@tinymce/tinymce-vue';
import '@tinymce/tinymce-vue';
import {useClient} from "@/plugins/api.js";
import {useUserProfileStore} from "@/stores/userProfileStore.js";
import {v7} from "uuid";
import {useRouter} from "vue-router";
import HTMLContentEditor from "@/views/contents/HTMLContentEditor.vue";
import QuickyContentEditor from "@/views/contents/QuickyContentEditor.vue";
const router = useRouter();
const client = useClient();
// Variables réactives pour gérer l'état des éditeurs
const showQuickyEditor = ref(true); // Par défaut, Quicky Editor est affiché
const showHtmlEditor = ref(false);
const tinymceScriptSrc = '/tinymce/js/tinymce/tinymce.min.js';
const content = ref('');
const title = ref('');
let lastUploadedFileName = '';
const isSnackbarOpen = ref(false);
const snackbarTimeout = ref(2000);
const snackbarText = ref('');
const snackbarColor = ref('red');
const selectedBackgroundColor = ref('#f0f0f0');
const userStore = useUserProfileStore();
// Custom image upload handler
const imagesUploadHandler = async (blobInfo) => {
const formData = new FormData();
formData.append('id', v7());
formData.append('files', blobInfo.blob(), lastUploadedFileName);
formData.append('creatorId', userStore.user.id);
let response = await client.post("/api/content/insert-image", formData);
let imageUrl = response.data[0];
/* global tinymce */
const editor = tinymce.activeEditor;
const images = editor.dom.select('img');
const lastImage = images.find(x => x.alt = lastUploadedFileName);
if (lastImage) {
// Replace the source of the image
editor.dom.setAttrib(lastImage, 'src', imageUrl);
editor.dom.setAttrib(lastImage, 'alt', lastUploadedFileName);
// Adds the change to the undo stack
editor.undoManager.add();
} else {
console.error('No image found in the content.');
}
// Fonction pour afficher le Quicky Editor
const toggleQuickyEditor = () => {
showQuickyEditor.value = true;
showHtmlEditor.value = false;
};
const filePickerCallback = (callback, value, meta) => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.onchange = function () {
const file = input.files[0];
const reader = new FileReader();
reader.onload = function () {
lastUploadedFileName = file.name;
callback(reader.result);
}
reader.readAsDataURL(file);
// Fonction pour afficher le HTML Editor
const toggleHtmlEditor = () => {
showHtmlEditor.value = true;
showQuickyEditor.value = false;
};
input.click();
};
const saveAsync = async () => {
if (title.value === '') {
snackbarText.value = "Vous avez besoin d'un titre";
isSnackbarOpen.value = true;
}
const fullHtmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title.value}</title>
</head>
<body>
${content.value}
</body>
</html>`;
const formData = new FormData();
formData.append('id', v7());
formData.append('creatorId', userStore.user.id);
formData.append('title', title.value);
formData.append('htmlContent', fullHtmlContent);
const response = await client.post("/api/contents/html", formData);
if (response.status === 200) {
snackbarText.value = "Publier";
snackbarColor.value = "green";
isSnackbarOpen.value = true;
router.go(-1)
}
};
const setupTinyMCE = (editor) => {
// Custom button for selecting background color
editor.ui.registry.addButton('myCustomBgColorButton', {
text: 'Page BG Color',
onAction: function () {
editor.windowManager.open({
title: 'Select Page Background Color',
body: {
type: 'panel',
items: [
{
type: 'colorpicker',
name: 'colorpicker',
label: 'Background Color'
}
]
},
buttons: [
{
text: 'Save',
type: 'submit',
primary: true
}
],
onSubmit: function (dialog) {
console.log('supppp');
const color = dialog.getData().colorpicker;
console.log(color);
selectedBackgroundColor.value = color;
// Insert style into TinyMCE's content
const styleTag = `<style>body { background-color: ${color}; }</style>`;
editor.execCommand('mceInsertContent', false, styleTag);
dialog.close(); // Close dialog after selecting color
}
});
}
});
}
</script>
<template>
<v-snackbar v-model="isSnackbarOpen" :timeout="snackbarTimeout">
{{ snackbarText }}
<div class="editor-container">
<div class="editor-buttons">
<!-- Boutons -->
<button @click="toggleQuickyEditor">Quicky</button>
<button @click="toggleHtmlEditor">HTML</button>
</div>
<template v-slot:actions>
<v-btn :color="snackbarColor" variant="text" @click="isSnackbarOpen = false">
Fermer
</v-btn>
</template>
</v-snackbar>
<!-- Conteneur qui s'affiche selon le bouton cliqué -->
<div v-if="showQuickyEditor" class="mt-16"> <!-- Ajouter un margin-top pour éviter le chevauchement -->
<quicky-content-editor />
</div>
<div class="w-full h-full flex flex-col items-center justify-start">
<v-btn class="mb-4 text-xl px-6 py-3" @click="router.go(-1)">Return</v-btn>
<v-text-field
v-model="title"
placeholder="Title"
style="width: 40%; font-size: 1.5rem; padding: 10px;"
></v-text-field>
<Editor
style="max-width: 400px; width: 50%; font-size: 1.5rem; padding: 10px; height: 120%"
:tinymceScriptSrc="tinymceScriptSrc"
v-model="content"
:init="{
branding: false,
promotion: false,
plugins: 'lists link emoticons image imagetools code help wordcount media autoresize textcolor colorpicker',
block_formats: 'Paragraph=p; Header 1=h1; Header 2=h2; Header 3=h3',
toolbar: 'undo redo image align myCustomBgColorButton',
automatic_uploads: true,
file_picker_types: 'image',
min_height: 600,
max_height: 1200,
images_upload_handler: imagesUploadHandler,
file_picker_callback: filePickerCallback,
// setup: setupTinyMCE, Possible to change background color of the html
}"
/>
<v-btn @click="saveAsync()">POST</v-btn>
<div v-if="showHtmlEditor" class="mt-16"> <!-- Ajouter un margin-top pour éviter le chevauchement -->
<HTMLContentEditor />
</div>
</div>
</template>
<style scoped>
.editor-container {
width: 1000px;
margin: 0 auto;
}
.editor-buttons {
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 2;
background-color: white;
padding: 1rem;
border-bottom: 1px solid #ccc;
width: 100%;
text-align: center;
}
button {
padding: 0.5rem 1rem;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #f5f5f5;
cursor: pointer;
margin: 0 0.5rem;
}
button:hover {
background-color: #e0e0e0;
}
</style>

View File

@@ -0,0 +1,194 @@
<script setup>
import {ref} from 'vue';
import Editor from '@tinymce/tinymce-vue';
import '@tinymce/tinymce-vue';
import {useClient} from "@/plugins/api.js";
import {useUserProfileStore} from "@/stores/userProfileStore.js";
import {v7} from "uuid";
import {useRouter} from "vue-router";
const router = useRouter();
const client = useClient();
const tinymceScriptSrc = '/tinymce/js/tinymce/tinymce.min.js';
const content = ref('');
const title = ref('');
let lastUploadedFileName = '';
const isSnackbarOpen = ref(false);
const snackbarTimeout = ref(2000);
const snackbarText = ref('');
const snackbarColor = ref('red');
const selectedBackgroundColor = ref('#f0f0f0');
const userStore = useUserProfileStore();
// Custom image upload handler
const imagesUploadHandler = async (blobInfo) => {
const formData = new FormData();
formData.append('id', v7());
formData.append('files', blobInfo.blob(), lastUploadedFileName);
formData.append('creatorId', userStore.user.id);
let response = await client.post("/api/content/insert-image", formData);
let imageUrl = response.data[0];
/* global tinymce */
const editor = tinymce.activeEditor;
const images = editor.dom.select('img');
const lastImage = images.find(x => x.alt = lastUploadedFileName);
if (lastImage) {
// Replace the source of the image
editor.dom.setAttrib(lastImage, 'src', imageUrl);
editor.dom.setAttrib(lastImage, 'alt', lastUploadedFileName);
// Adds the change to the undo stack
editor.undoManager.add();
} else {
console.error('No image found in the content.');
}
};
const filePickerCallback = (callback, value, meta) => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.onchange = function () {
const file = input.files[0];
const reader = new FileReader();
reader.onload = function () {
lastUploadedFileName = file.name;
callback(reader.result);
}
reader.readAsDataURL(file);
};
input.click();
};
const saveAsync = async () => {
if (title.value === '') {
snackbarText.value = "Vous avez besoin d'un titre";
isSnackbarOpen.value = true;
}
const fullHtmlContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title.value}</title>
</head>
<body>
${content.value}
</body>
</html>`;
const formData = new FormData();
formData.append('id', v7());
formData.append('creatorId', userStore.user.id);
formData.append('title', title.value);
formData.append('htmlContent', fullHtmlContent);
const response = await client.post("/api/contents/html", formData);
if (response.status === 200) {
snackbarText.value = "Publier";
snackbarColor.value = "green";
isSnackbarOpen.value = true;
router.go(-1)
}
};
const setupTinyMCE = (editor) => {
// Custom button for selecting background color
editor.ui.registry.addButton('myCustomBgColorButton', {
text: 'Page BG Color',
onAction: function () {
editor.windowManager.open({
title: 'Select Page Background Color',
body: {
type: 'panel',
items: [
{
type: 'colorpicker',
name: 'colorpicker',
label: 'Background Color'
}
]
},
buttons: [
{
text: 'Save',
type: 'submit',
primary: true
}
],
onSubmit: function (dialog) {
console.log('supppp');
const color = dialog.getData().colorpicker;
console.log(color);
selectedBackgroundColor.value = color;
// Insert style into TinyMCE's content
const styleTag = `<style>body { background-color: ${color}; }</style>`;
editor.execCommand('mceInsertContent', false, styleTag);
dialog.close(); // Close dialog after selecting color
}
});
}
});
}
</script>
<template>
<v-snackbar v-model="isSnackbarOpen" :timeout="snackbarTimeout">
{{ snackbarText }}
<template v-slot:actions>
<v-btn :color="snackbarColor" variant="text" @click="isSnackbarOpen = false">
Fermer
</v-btn>
</template>
</v-snackbar>
<div class="w-full h-full flex flex-col items-center justify-start">
Html
<v-btn class="mb-4 text-xl px-6 py-3" @click="router.go(-1)">Return</v-btn>
<v-text-field
v-model="title"
placeholder="Title"
style="width: 40%; font-size: 1.5rem; padding: 10px;"
></v-text-field>
<Editor
style="max-width: 400px; width: 50%; font-size: 1.5rem; padding: 10px; height: 120%"
:tinymceScriptSrc="tinymceScriptSrc"
v-model="content"
:init="{
branding: false,
promotion: false,
plugins: 'lists link emoticons image imagetools code help wordcount media autoresize textcolor colorpicker',
block_formats: 'Paragraph=p; Header 1=h1; Header 2=h2; Header 3=h3',
toolbar: 'undo redo image align myCustomBgColorButton',
automatic_uploads: true,
file_picker_types: 'image',
min_height: 600,
max_height: 1200,
images_upload_handler: imagesUploadHandler,
file_picker_callback: filePickerCallback,
// setup: setupTinyMCE, Possible to change background color of the html
}"
/>
<v-btn @click="saveAsync()">POST</v-btn>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,133 @@
<script setup>
import {useClient} from '@/plugins/api.js';
import {ref} from 'vue';
import {v7} from 'uuid';
import {useCreatorProfileStore} from "@/stores/creatorProfileStore.js";
const client = useClient();
const title = ref('');
const message = ref('');
const files = ref([]);
const externalUrls = ref([]);
const creatorProfileStore = useCreatorProfileStore();
const addUrl = () => {
externalUrls.value.push('');
};
const removeUrl = (index) => {
externalUrls.value.splice(index, 1);
};
async function publishPost() {
const formData = new FormData();
formData.append('id', v7());
formData.append('creatorId', creatorProfileStore.creator.id);
formData.append('title', title.value);
formData.append('description', message.value);
files.value.forEach(file => {
formData.append('files', file);
});
externalUrls.value.forEach(externalUrl => {
formData.append('externalUrls', externalUrl);
});
try {
const content = await client.post(
`/api/contents/`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
}
});
title.value = '';
message.value = '';
files.value = [];
externalUrls.value = [];
} catch (error) {
console.error(error);
}
}
const cancelPost = () => {
title.value = '';
message.value = '';
files.value = [];
externalUrls.value = [];
};
</script>
<template>
<v-form>
<v-card class="text-center rounded-xl">
<v-card-title class="font-medium">
Créer un Contenu
</v-card-title>
<v-card-text>
<v-text-field v-model="title"
class="p-2"
label="Titre"
density="comfortable"
variant="outlined"
hide-details
clearable
></v-text-field>
<v-textarea v-model="message"
label="Écrivez votre message ici..."
class="p-2"
density="comfortable"
variant="outlined"
hide-details
clearable
outlined
></v-textarea>
<div v-for="(url, index) in externalUrls" :key="index" class="d-flex align-center">
<v-text-field v-model="externalUrls[index]"
class="p-2 flex-grow-1"
label="Url Externe"
density="comfortable"
variant="outlined"
hide-details
></v-text-field>
<v-btn icon @click="removeUrl(index)" class="ml-2">
<v-icon>mdi-minus</v-icon>
</v-btn>
</div>
<v-btn icon @click="addUrl" class="mt-2">
<v-icon>mdi-plus</v-icon>
</v-btn>
<v-file-input v-model="files"
label="Glissez vos images"
class="p-2 custom-file-input"
variant="outlined"
multiple
dropzone
prepend-icon=""
placeholder="Glissez et déposez des fichiers ici ou cliquez pour sélectionner des fichiers"
></v-file-input>
</v-card-text>
<v-card-actions>
<v-btn variant="flat"
@click="cancelPost"
class="p-20">
Cancel
</v-btn>
<v-btn variant="flat"
color="primary"
@click="publishPost">
Publier
</v-btn>
</v-card-actions>
</v-card>
</v-form>
</template>

View File

@@ -18,7 +18,7 @@ const creatorProfileStore = useCreatorProfileStore();
const brandingStore = useBrandingStore();
const authStore = useAuthStore();
const creatorIsCurrentUser = computed(() => authStore.isAuthenticated && authStore.userId === brandingStore.value.id);
const creatorIsCurrentUser = computed(() => authStore.isAuthenticated && authStore.userId);
const createHtmlContent = () => {
router.push('/content/editor');
@@ -57,8 +57,8 @@ initializeLocale();
</router-link>
</div>
<div v-if="creatorIsCurrentUser" class="justify-center text-center">
<publish-content-button></publish-content-button>
<v-btn variant="flat" icon @click="createHtmlContent">
<v-icon>mdi-pencil</v-icon>
</v-btn>