From bbd243d2146b75864c76f44603012d5cfc16b56e Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Thu, 11 Jun 2026 16:01:10 +0100 Subject: [PATCH] fix: render newsfeed content as html --- .../features/course/api/newsfeed.svelte.ts | 25 +++++++++--- .../features/program/api/newsfeed.svelte.ts | 28 ++++++++----- apps/dashboard/src/lib/features/ui/index.ts | 1 + .../src/lib/features/ui/text-editor.svelte | 22 +++++++++- .../routes/(app)/courses/[id]/+page.svelte | 9 ++++- .../(app)/programs/[id]/newsfeed/+page.svelte | 6 ++- packages/ui/src/custom/editor/editor.ts | 6 ++- .../ui/src/custom/editor/ui/menus/Link.svelte | 40 +++++++++++++------ .../ui/src/custom/editor/ui/menus/Menu.svelte | 2 +- packages/utils/src/functions/sanitize.ts | 2 +- 10 files changed, 106 insertions(+), 35 deletions(-) diff --git a/apps/dashboard/src/lib/features/course/api/newsfeed.svelte.ts b/apps/dashboard/src/lib/features/course/api/newsfeed.svelte.ts index e35b93c3..74921360 100644 --- a/apps/dashboard/src/lib/features/course/api/newsfeed.svelte.ts +++ b/apps/dashboard/src/lib/features/course/api/newsfeed.svelte.ts @@ -23,7 +23,7 @@ import { } from '@cio/utils/validation/newsfeed'; import { get } from 'svelte/store'; -import { getTextFromHTML } from '$lib/utils/functions/toHtml'; +import { isHtmlValueEmpty } from '$lib/utils/functions/toHtml'; import { mapZodErrorsToTranslations } from '$lib/utils/validation'; import { profile } from '$lib/utils/store/user'; import { snackbar } from '$features/ui/snackbar/store'; @@ -109,7 +109,15 @@ export class NewsfeedApi extends BaseApiWithErrors { * Creates a new newsfeed item */ async create(courseId: string, fields: TNewsfeedCreate) { - const result = ZNewsfeedCreate.safeParse({ ...fields, content: getTextFromHTML(fields.content), courseId }); + if (isHtmlValueEmpty(fields.content)) { + this.errors = mapZodErrorsToTranslations( + ZNewsfeedCreate.safeParse({ ...fields, content: '', courseId }).error!, + 'newsfeed' + ); + return; + } + + const result = ZNewsfeedCreate.safeParse({ ...fields, courseId }); if (!result.success) { this.errors = mapZodErrorsToTranslations(result.error, 'newsfeed'); return; @@ -160,10 +168,15 @@ export class NewsfeedApi extends BaseApiWithErrors { * Updates a newsfeed item (content and isPinned only) */ async update(courseId: string, feedId: string, fields: TNewsfeedUpdate) { - const result = ZNewsfeedUpdate.safeParse({ - ...fields, - content: getTextFromHTML(fields.content ?? '') - }); + if (fields.content !== undefined && isHtmlValueEmpty(fields.content)) { + this.errors = mapZodErrorsToTranslations( + ZNewsfeedUpdate.safeParse({ ...fields, content: '' }).error!, + 'newsfeed' + ); + return; + } + + const result = ZNewsfeedUpdate.safeParse(fields); if (!result.success) { this.errors = mapZodErrorsToTranslations(result.error, 'newsfeed'); diff --git a/apps/dashboard/src/lib/features/program/api/newsfeed.svelte.ts b/apps/dashboard/src/lib/features/program/api/newsfeed.svelte.ts index 169c7928..5dfad2de 100644 --- a/apps/dashboard/src/lib/features/program/api/newsfeed.svelte.ts +++ b/apps/dashboard/src/lib/features/program/api/newsfeed.svelte.ts @@ -24,7 +24,7 @@ import { ZUpdateProgramReaction } from '@cio/utils/validation/program'; import { get } from 'svelte/store'; -import { getTextFromHTML } from '$lib/utils/functions/toHtml'; +import { isHtmlValueEmpty } from '$lib/utils/functions/toHtml'; import { mapZodErrorsToTranslations } from '$lib/utils/validation'; import { profile } from '$lib/utils/store/user'; import { snackbar } from '$features/ui/snackbar/store'; @@ -77,10 +77,15 @@ export class ProgramNewsfeedApi extends BaseApiWithErrors { async create(programId: string, fields: TCreateProgramNewsfeed) { this.success = false; - const validationResult = ZCreateProgramNewsfeed.safeParse({ - ...fields, - content: getTextFromHTML(fields.content) - }); + if (isHtmlValueEmpty(fields.content)) { + this.errors = mapZodErrorsToTranslations( + ZCreateProgramNewsfeed.safeParse({ ...fields, content: '' }).error!, + 'newsfeed' + ); + return; + } + + const validationResult = ZCreateProgramNewsfeed.safeParse(fields); if (!validationResult.success) { this.errors = mapZodErrorsToTranslations(validationResult.error, 'newsfeed'); @@ -115,10 +120,15 @@ export class ProgramNewsfeedApi extends BaseApiWithErrors { async update(programId: string, feedId: string, fields: TUpdateProgramNewsfeed) { this.success = false; - const validationResult = ZUpdateProgramNewsfeed.safeParse({ - ...fields, - ...(fields.content ? { content: getTextFromHTML(fields.content) } : {}) - }); + if (fields.content !== undefined && isHtmlValueEmpty(fields.content)) { + this.errors = mapZodErrorsToTranslations( + ZUpdateProgramNewsfeed.safeParse({ ...fields, content: '' }).error!, + 'newsfeed' + ); + return; + } + + const validationResult = ZUpdateProgramNewsfeed.safeParse(fields); if (!validationResult.success) { this.errors = mapZodErrorsToTranslations(validationResult.error, 'newsfeed'); diff --git a/apps/dashboard/src/lib/features/ui/index.ts b/apps/dashboard/src/lib/features/ui/index.ts index 0001182c..5bf0e094 100644 --- a/apps/dashboard/src/lib/features/ui/index.ts +++ b/apps/dashboard/src/lib/features/ui/index.ts @@ -23,6 +23,7 @@ export { default as Search } from './search.svelte'; export { default as SenjaEmbed } from './senja-embed.svelte'; export { default as Snackbar } from './snackbar/snackbar.svelte'; export { default as TextEditor } from './text-editor.svelte'; +export { preloadTextEditor } from './text-editor.svelte'; export { default as UnsavedChanges } from './unsaved-changes.svelte'; export { default as UploadImage } from './upload-image.svelte'; export { default as UpgradeBanner } from './upgrade-banner.svelte'; diff --git a/apps/dashboard/src/lib/features/ui/text-editor.svelte b/apps/dashboard/src/lib/features/ui/text-editor.svelte index 3a7a6056..6f7009fc 100644 --- a/apps/dashboard/src/lib/features/ui/text-editor.svelte +++ b/apps/dashboard/src/lib/features/ui/text-editor.svelte @@ -8,11 +8,17 @@ pending ??= import('@cio/ui/custom/editor'); return pending; } + + /** Warm the editor chunk without mounting an instance (e.g. on newsfeed page load). */ + export function preloadTextEditor() { + return loadEditor(); + } {#await loadEditor()} -
+