-
Notifications
You must be signed in to change notification settings - Fork 0
/
\
372 lines (324 loc) · 13.1 KB
/
\
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
'use client';
import React, { useEffect, useState } from 'react';
import styles from '../../../styles/editor.module.scss';
import { useSearchParams, useRouter } from 'next/navigation';
import { v4 as uuidv4 } from 'uuid';
import { saveBlogPost, getPost, publishBlogPost } from '../../../utils/fileUploadUtils'
import { BlogPost, DraftVersionKeys } from '../../../utils/types';
import { produce } from "immer";
import { Editor } from '@tinymce/tinymce-react';
import MediaViewer from '../../../components/MediaViewer';
import ImageSlot from '../../../components/ImageSlot';
import SelectWindow from '../../../components/SelectWindow';
// Change a title to a slug
const titleToSlug = (title: string | undefined) => {
if (title !== undefined) {
return title
.toLowerCase()
.replace(/[^\w\s]/gi, '')
.trim()
.replace(/\s+/g, '-')
}
return '';
}
const BlogEditor = ({
params } : {
params: { post_id: string };
}) => {
const editorKey = process.env.NEXT_PUBLIC_TINY_MCE_EDITOR_KEY;
const router = useRouter();
// States: used to store the values the user will be changing
const [editorInitialized, setEditorInitialized] = useState(false);
const [editMade, setEditMade] = useState(false);
const [selectedItemId, setSelectedItemId] = useState('');
const [isImageSlotSelected, setImageSlotSelected] = useState(false);
const [changesSaved, setChangesSaved] = useState(false);
const [showMediaGallery, setShowMediaGallery] = useState<boolean>(false);
const [selectedMedia, setSelectedMedia] = useState(['',''])
const [editorRef, setEditorRef] = useState(null);
const [isEditOptionsVisible, showEditOptions] = useState<boolean>(false);
const [postState, setPostState] = useState<BlogPost>({
post_id: '',
draft_version: {
title: '',
description: '',
body: '',
slug: '',
tags: [''],
featured_image: '',
},
published_version: '',
published_date: '',
author: '',
status: '',
seo_metadata: {}
})
const [isClient, setIsClient] = useState(false);
/////////----------- SESSION STORAGE METHODS ----------------/////////////
const getSessionStorageKey = (id: string) => `blogPostState_${id}`;
const loadStateFromSessionStorage = (id: string) => {
const savedState = sessionStorage.getItem(getSessionStorageKey(id));
return savedState ? JSON.parse(savedState) : null;
}
const saveStateToSessionStorage = (id: string, state: BlogPost) => {
sessionStorage.setItem(getSessionStorageKey(id), JSON.stringify(state));
};
// Load Post data function.
const loadPostData = async () => {
try {
// const postData: BlogPost = await getPost(id);
const postData = JSON.parse(sessionStorage.getItem('postToEdit'));
setPostState(currentData => produce(currentData, draft => {
draft.post_id = postData.post_id;
draft.draft_version.title = postData.draft_version.title;
draft.draft_version.body = postData.draft_version.body;
draft.draft_version.description = postData.draft_version.description;
draft.draft_version.tags = postData.draft_version.tags;
draft.draft_version.featured_image = postData.draft_version.featured_image;
draft.draft_version.slug = postData.draft_version.slug;
draft.author = postData.author;
draft.published_date = postData.published_date;
draft.status = 'Edit';
draft.published_version = postData.published_version;
draft.seo_metadata = postData.seo_metadata;
}));
return postData;
} catch (error) {
console.error('Error loading post data:', error);
}
}
// useEffect for loading data annd initializing states
useEffect(() => {
setIsClient(true);
}, []);
useEffect(() => {
console.log('initial load')
if (typeof params.post_id === 'string' && params.post_id !== 'new') {
const savedState = loadStateFromSessionStorage(params.post_id);
if (savedState){
setPostState(currentData => produce(currentData, draft => {
draft.post_id = savedState.post_id;
draft.draft_version.title = savedState.draft_version.title;
draft.draft_version.body = savedState.draft_version.body;
draft.draft_version.description = savedState.draft_version.description;
draft.draft_version.tags = savedState.draft_version.tags;
draft.draft_version.featured_image = savedState.draft_version.featured_image;
draft.draft_version.slug = savedState.draft_version.slug;
draft.author = savedState.slug;
draft.published_date = savedState.published_date;
draft.status = savedState.author;
draft.published_version = savedState.published_version;
draft.seo_metadata = savedState.seo_metadata;
}));
} else {
// Fetch post data or handle as missing post
console.log('Loading from server');
loadPostData();
}
} else if (params.post_id === 'new'){
const newPostId = uuidv4();
setPostState(currentState => produce(currentState, draftState => {
draftState.post_id = newPostId;
draftState.status = 'draft';
}));
router.replace(`/admin-panel/blog/${newPostId}`, undefined,{ shallow: true } )
}
}, [params.post_id, router]);
// useEffect for initializing Session Storage and handling the unmount and recall of the post data
useEffect(() => {
if (postState.post_id){
saveStateToSessionStorage(postState.post_id, postState);
}
}, [postState])
// Handle changes en save to states
const handleChange = (value: string, stateToChange: DraftVersionKeys) => {
setEditMade(true);
switch (stateToChange) {
case 'title':
case 'description':
case 'body':
setPostState(currentData => produce(currentData, draft => { draft.draft_version[stateToChange] = value}));
break;
case 'author':
setPostState(currentData => produce(currentData, draft => { draft.author = value}));
break;
case 'tags':
if (value.includes(',')) {
const newTags = value.split(',')
.map(tag => tag.trim())
.filter(tag => tag);
setPostState(currentData => produce(currentData, draft => { draft.draft_version.tags.push(...newTags);
}));
}
break;
default:
console.log('No state property was chosen');
break;
}
}
////////----------Handle the Saves into the Database -------/////////////
const handleSaveChanges = async () => {
try {
// Nos recoge el titulo y nos lo convierte a un slug .
const postSlug = titleToSlug(postState.draft_version.title);
// Recogemos la información que se ha guardado en el State.
const postToSave: BlogPost = {
post_id: postState.post_id,
draft_version: {
title: postState.draft_version.title,
description: postState.draft_version.description,
body: postState.draft_version.body,
slug: postSlug,
tags: postState.draft_version.tags,
featured_image: postState.draft_version.featured_image
},
published_version: postState.published_version,
published_date: postState.published_date,
author: postState.author,
status: postState.status,
seo_metadata: postState.seo_metadata
}
console.log('Post to save:', postToSave);
const result = await saveBlogPost(postToSave);
console.log('Saving blog was successful', result)
setEditMade(false);
setChangesSaved(true);
} catch (error) {
console.error('Error during post upload:', error);
setEditMade(true);
}
}
const handlePublishPost = async () => {
const result = await publishBlogPost(postState.post_id);
console.log('Publishing blog post was successful', result)
setEditMade(false);
setChangesSaved(false);
}
const updateSelectedSlot = () => {
const [url, alt] = selectedMedia;
if (isImageSlotSelected === false){
insertImageIntoTinyMCE(url, alt);
setShowMediaGallery(false);
setEditMade(true);
} else if (isImageSlotSelected === true) {
setPostState(currentData => produce(currentData, draft => {
draft.draft_version.featured_image = url }));
setEditMade(true);
setShowMediaGallery(false)
setImageSlotSelected(false);
}
//Now here is must take the selected media and use the selectedItemId to link it to the imageSlot (if the imageslot was selected)
}
const handleMediaSelected = (mediaSelected: [string, string]) => {
setSelectedMedia(mediaSelected);
}
const openMediaUploader = () => {
//open Media Uploader
setShowMediaGallery(true);
}
const handleEditorInit = (editor) => {
setEditorRef(editor);
setEditorInitialized(true);
}
const insertImageIntoTinyMCE = (url, alt) => {
if (editorRef) {
editorRef.insertContent(`<img src=\"${url}\" alt=\"${alt}\" />`);
}
}
console.log('This is the params post: ',params.post_id);
const handleItemSelection= (selectedId: string) => {
showEditOptions(true);
setImageSlotSelected(true);
setSelectedItemId(selectedId);
}
const toggleMediaGalleryVisibility = () => {
setShowMediaGallery(true);
showEditOptions(false);
}
const handleEditLink = () => {
/// Don't do anything
}
/////////////---------The Actual Component----------/////////////
return (
<div id={styles.blogContainer} >
{isClient ? (
<>
<h1>Welcome to the <em>blog editor</em>!</h1>
{ showMediaGallery && <MediaViewer sendSelect={handleMediaSelected} modalWindow={true} setImageSlot={updateSelectedSlot}/> }
{ isEditOptionsVisible && <SelectWindow select={toggleMediaGalleryVisibility} editLink={handleEditLink} hasLink={false}/>}
{/* Featured Image */}
<ImageSlot
src={postState.draft_version.featured_image}
alt=""
link=""
setSelectedId={handleItemSelection}
connection_id={postState.post_id}
type="illustration"
/>
{/*Title*/}
<input
type="text"
value={postState.draft_version.title}
onChange={e => handleChange(e.target.value, 'title')}
placeholder="Post Title"
/>
{/*Description*/}
<input
type="text"
value={postState.draft_version.description}
onChange={e => handleChange(e.target.value, 'description')}
placeholder="Post Description"
/>
{/*--------/////////------Tags--------/////////---------
<input
type="text"
value={postState.draft_version.tags}
onChange={e => handleChange(e.target.value, 'tags')}
placeholder="Post tags"
/>
*/}
{/*Editor*/}
<Editor
apiKey={editorKey}
init={{
plugins: 'tinycomments mentions anchor autolink charmap codesample emoticons image link lists media searchreplace table visualblocks wordcount checklist mediaembed casechange export formatpainter pageembed permanentpen footnotes advtemplate advtable advcode editimage tableofcontents powerpaste tinymcespellchecker autocorrect a11ychecker typography inlinecss',
toolbar: 'undo redo | blocks fontfamily fontsize | bold italic underline strikethrough | link image media table mergetags | align lineheight | tinycomments | checklist numlist bullist indent outdent | emoticons charmap | removeformat | mediaSelector',
tinycomments_mode: 'embedded',
tinycomments_author: 'Author name',
setup: function(editor) {
editor.ui.registry.addButton('mediaSelector', {
icon: 'gallery',
tooltip: 'Insert image from Media Library',
onAction: function(_) {
openMediaUploader();
}
})
},
mergetags_list: [
{ value: 'First.Name', title: 'First Name' },
{ value: 'Email', title: 'Email' },
],
}}
initialValue={editorInitialized ? undefined : postState.draft_version.body}
onInit={(evt, editor) => {
// Set the flag to true once the editor is initialized
handleEditorInit(editor);
if (!editorInitialized) {
setEditorInitialized(true);
}
}}
onEditorChange={(content) =>handleChange(content, 'body')}
/>
<label>Author: </label>
<select id="author" onChange={e => handleChange(e.target.value, 'author')}>
<option value="lucia-castro">Lucía Castro</option>
</select>
{editMade && <button onClick={handleSaveChanges}>Save Changes</button>}
{changesSaved && <button onClick={handlePublishPost}>Publish</button>}
</>
) : (
<div>Loading editor...</div>
)}
</div>
)};
export default BlogEditor;