Skip to content

Commit 37510dc

Browse files
author
Ahtesham Quraish
committed
fixing few issues in ckeditor plugins
1 parent 9908ef5 commit 37510dc

File tree

5 files changed

+213
-169
lines changed

5 files changed

+213
-169
lines changed

frontends/ol-ckeditor/src/components/CKEditorClient.tsx

Lines changed: 114 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,13 @@ import React, { useState, useEffect, useMemo } from "react"
44
import dynamic from "next/dynamic"
55
import { type EditorConfig } from "ckeditor5"
66
import type { Editor } from "@ckeditor/ckeditor5-core"
7-
8-
import { Dialog, Typography } from "ol-components"
9-
import { Course } from "./types"
7+
import { Dialog, Typography, SearchInput, LoadingSpinner } from "ol-components"
8+
import { useLearningResourcesSearch } from "api/hooks/learningResources"
9+
import type { Course } from "./types"
1010

1111
import "ckeditor5/ckeditor5.css"
1212
import "./styles.css"
1313

14-
const mockCourses = [
15-
{
16-
id: "1",
17-
title: "React for Beginners",
18-
image:
19-
"https://35904.cdn.cke-cs.com/A8zpYq0deQ8s5ZchTmUI/images/5025b810afce449ed73f6c18c929040073a27f14dd6ba674.webp",
20-
description:
21-
'Free open source text" can refer to many programs, including text editors like Notepad++ and LibreOffice Writer, text-to-speech models like Mozilla TTS and eSpeak, and speech-to-text models such as Whisper and DeepSpeech. The best choice depends on the specific need, such as writing, editing code, or converting speech to tex',
22-
},
23-
{
24-
id: "2",
25-
title: "Next.js Advanced",
26-
image:
27-
"https://35904.cdn.cke-cs.com/A8zpYq0deQ8s5ZchTmUI/images/5025b810afce449ed73f6c18c929040073a27f14dd6ba674.webp",
28-
description: "Master SSR and routing.",
29-
},
30-
]
31-
3214
const CKEditor = dynamic(
3315
async () => (await import("@ckeditor/ckeditor5-react")).CKEditor,
3416
{ ssr: false },
@@ -47,13 +29,24 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
4729
uploadUrl,
4830
getCKEditorTokenFetchUrl,
4931
}) => {
32+
// --- Core editor and modal states
5033
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5134
const [EditorModules, setEditorModules] = useState<any>(null)
5235
const [data, setData] = useState(value || "")
5336
const [open, setOpen] = useState(false)
5437

55-
// store selected course info (for Dialog content)
56-
const [selectedCourse] = useState<Course | null>(null)
38+
const [query, setQuery] = useState("")
39+
const [debouncedQuery, setDebouncedQuery] = useState("")
40+
41+
useEffect(() => {
42+
const timeout = setTimeout(() => setDebouncedQuery(query), 400)
43+
return () => clearTimeout(timeout)
44+
}, [query])
45+
46+
const { data: coursesData, isLoading } = useLearningResourcesSearch(
47+
{ q: debouncedQuery },
48+
{ keepPreviousData: true },
49+
)
5750

5851
useEffect(() => {
5952
const handler = (e: CustomEvent) => {
@@ -66,13 +59,12 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
6659
}, [])
6760

6861
const handleCourseSelect = (course: Course) => {
69-
if (!EditorModules) {
70-
console.warn("Editor instance not ready")
71-
setOpen(false)
72-
return
73-
}
62+
if (!EditorModules) return
7463
setOpen(false)
75-
EditorModules.execute("insertCourse", course)
64+
EditorModules.execute("insertCourse", {
65+
...course,
66+
image: course.image || "",
67+
})
7668
EditorModules.editing.view.focus()
7769
}
7870

@@ -106,7 +98,6 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
10698
Underline,
10799
Strikethrough,
108100
Alignment,
109-
110101
CodeBlock,
111102
FontSize,
112103
FontColor,
@@ -161,11 +152,10 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
161152
ImageStyleEditing,
162153
ImageStyleUI,
163154
MediaEmbed,
164-
Widget, // ✅ ensure this line exists
155+
Widget,
165156
MediaFloatPlugin,
166157
DefaultImageStylePlugin,
167158
InsertCoursePlugin,
168-
// also expose the original modules object if you want:
169159
_CKEditorModules: CKEditorModules,
170160
})
171161
})()
@@ -185,23 +175,32 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
185175
EditorModules.List,
186176
EditorModules.BlockQuote,
187177
EditorModules.Autoformat,
188-
// 👇 Image-related plugins (all required for resizing + styling)
178+
EditorModules.Underline,
179+
EditorModules.Strikethrough,
180+
EditorModules.Alignment,
181+
EditorModules.CodeBlock,
182+
EditorModules.FontSize,
183+
EditorModules.FontColor,
184+
EditorModules.FontBackgroundColor,
185+
EditorModules.Undo,
186+
EditorModules.Table,
187+
EditorModules.TableToolbar,
189188
EditorModules.Image,
190189
EditorModules.ImageToolbar,
191190
EditorModules.ImageUpload,
192191
EditorModules.EasyImage,
192+
EditorModules.CloudServices,
193193
EditorModules.ImageResize,
194194
EditorModules.ImageResizeEditing,
195195
EditorModules.ImageResizeHandles,
196196
EditorModules.ImageStyle,
197197
EditorModules.ImageStyleEditing,
198198
EditorModules.ImageStyleUI,
199-
EditorModules.CloudServices,
200199
EditorModules.MediaEmbed,
201-
EditorModules.Widget, // ✅ add Widget here
202-
EditorModules.MediaFloatPlugin, // ✅ your plugin now safe
203-
EditorModules.DefaultImageStylePlugin, // ✅ your plugin now safe
204-
EditorModules.InsertCoursePlugin, // ✅ your plugin now safe
200+
EditorModules.Widget,
201+
EditorModules.MediaFloatPlugin,
202+
EditorModules.DefaultImageStylePlugin,
203+
EditorModules.InsertCoursePlugin,
205204
],
206205
toolbar: {
207206
items: [
@@ -216,7 +215,7 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
216215
"numberedList",
217216
"strikethrough",
218217
"|",
219-
"alignment", // text alignment
218+
"alignment",
220219
"|",
221220
"blockQuote",
222221
"|",
@@ -235,7 +234,7 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
235234
"undo",
236235
"redo",
237236
"|",
238-
"insertCourse", // 👈 our custom button
237+
"insertCourse",
239238
"|",
240239
],
241240
},
@@ -287,12 +286,12 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
287286
editor={EditorModules.ClassicEditor}
288287
data={data}
289288
config={editorConfig}
290-
onReady={(editor) => {
289+
onReady={(editor) =>
291290
setEditorModules((prev: Editor) => ({
292291
...prev,
293292
_activeEditor: editor,
294293
}))
295-
}}
294+
}
296295
onChange={(_, editor) => {
297296
const html = editor.getData()
298297
setData(html)
@@ -304,73 +303,90 @@ export const CKEditorClient: React.FC<CKEditorClientProps> = ({
304303
onClose={() => setOpen(false)}
305304
open={open}
306305
title="Select a Course"
307-
actions={null}
308306
>
309-
{!selectedCourse ? (
310-
<>
311-
<Typography sx={{ marginBottom: "16px" }}>
312-
Choose a course to insert into the editor:
313-
</Typography>
307+
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
308+
<SearchInput
309+
value={query}
310+
onChange={(e) => setQuery(e.target.value)}
311+
onClear={() => setQuery("")}
312+
onSubmit={(e) => setDebouncedQuery(e.target.value)} // triggers search
313+
placeholder="Search for courses..."
314+
fullWidth
315+
/>
316+
317+
{isLoading ? (
318+
<div
319+
style={{
320+
display: "flex",
321+
justifyContent: "center",
322+
padding: "20px 0",
323+
}}
324+
>
325+
<LoadingSpinner color="inherit" loading={isLoading} size={16} />
326+
</div>
327+
) : (
314328
<div
315329
style={{
316330
display: "flex",
317331
flexDirection: "column",
318-
gap: "16px",
332+
gap: "12px",
319333
maxHeight: "400px",
320334
overflowY: "auto",
321335
}}
322336
>
323-
{mockCourses.map((course) => (
324-
<div
325-
key={course.id}
326-
role="button"
327-
tabIndex={0}
328-
onClick={() => handleCourseSelect(course)}
329-
onKeyDown={(e) => {
330-
if (e.key === "Enter" || e.key === " ") {
331-
e.preventDefault()
332-
handleCourseSelect(course)
333-
}
334-
}}
335-
className="course-list-container"
336-
onFocus={(e) =>
337-
(e.currentTarget.style.boxShadow = "0 0 0 2px #007aff33")
338-
}
339-
onBlur={(e) => (e.currentTarget.style.boxShadow = "none")}
340-
onMouseEnter={(e) =>
341-
(e.currentTarget.style.background = "#fafafa")
337+
{coursesData?.results?.length ? (
338+
coursesData.results.map((resource) => {
339+
const course: Course = {
340+
id: resource.id,
341+
title: resource.title,
342+
description: resource.description || "",
343+
image: resource.image?.url || "",
342344
}
343-
onMouseLeave={(e) =>
344-
(e.currentTarget.style.background = "white")
345-
}
346-
>
347-
<img
348-
src={course.image}
349-
alt={course.title}
350-
style={{
351-
width: "80px",
352-
height: "60px",
353-
objectFit: "cover",
354-
borderRadius: "6px",
355-
}}
356-
/>
357-
<div style={{ flex: 1 }}>
358-
<Typography variant="subtitle1">{course.title}</Typography>
359-
<Typography variant="body2" sx={{ color: "#555" }}>
360-
{course.description}
361-
</Typography>
362-
</div>
363-
</div>
364-
))}
345+
346+
return (
347+
<div
348+
key={course.id}
349+
role="button"
350+
tabIndex={0}
351+
onClick={() => handleCourseSelect(course)}
352+
className="course-list-container"
353+
onKeyDown={(e) =>
354+
["Enter", " "].includes(e.key) &&
355+
handleCourseSelect(course)
356+
}
357+
>
358+
<img
359+
src={course.image}
360+
alt={course.title}
361+
style={{
362+
width: "80px",
363+
height: "60px",
364+
objectFit: "cover",
365+
borderRadius: "6px",
366+
}}
367+
/>
368+
<div style={{ flex: 1, marginLeft: "12px" }}>
369+
<Typography variant="subtitle2">
370+
{course.title}
371+
</Typography>
372+
{course.short_description && (
373+
<Typography
374+
variant="body2"
375+
sx={{ color: "gray", fontSize: "0.85rem" }}
376+
>
377+
{course.short_description.slice(0, 80)}
378+
</Typography>
379+
)}
380+
</div>
381+
</div>
382+
)
383+
})
384+
) : (
385+
<Typography>No courses found.</Typography>
386+
)}
365387
</div>
366-
</>
367-
) : (
368-
<>
369-
<Typography sx={{ marginBottom: "16px" }}>
370-
{selectedCourse.description}
371-
</Typography>
372-
</>
373-
)}
388+
)}
389+
</div>
374390
</Dialog>
375391
</>
376392
)

frontends/ol-ckeditor/src/components/InsertCoursePlugin.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,17 @@ export function createInsertCoursePlugin(
2929
description: course.description,
3030
})
3131

32-
editor.model.insertContent(courseElement)
32+
// 🟢 Insert course at the end of the document instead of replacing selection
33+
const root = editor.model.document.getRoot()
34+
if (!root) {
35+
console.warn("Editor model root not available yet.")
36+
return
37+
}
38+
const endPosition = writer.createPositionAt(root, "end")
3339

34-
// Add a paragraph after to prevent selection errors
40+
editor.model.insertContent(courseElement, endPosition)
41+
42+
// 🟢 Add a paragraph after to allow user to keep typing
3543
const paragraph = writer.createElement("paragraph")
3644
editor.model.insertContent(
3745
paragraph,

0 commit comments

Comments
 (0)