From df340e363c765610977be2df8e8cb7d5fa3bef7f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 21 May 2026 09:59:47 +0000 Subject: [PATCH] Load notes into editor --- src/backend/InvenTree/common/api.py | 4 ++ src/backend/InvenTree/common/models.py | 4 -- .../src/components/editors/NotesEditor.tsx | 69 +++++++++++++++++-- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/backend/InvenTree/common/api.py b/src/backend/InvenTree/common/api.py index 2ce6eff975..8a5c8d66fe 100644 --- a/src/backend/InvenTree/common/api.py +++ b/src/backend/InvenTree/common/api.py @@ -840,6 +840,10 @@ class NoteFilter(FilterSet): class NoteMixin: """Mixin class for the Note views.""" + # Ignore default sanitizing of the 'content' field + # Note: This is handled explicitly in the 'save' method of the Note model + SAFE_FIELDS = ['content'] + queryset = common.models.Note.objects.all() serializer_class = common.serializers.NoteSerializer permission_classes = [IsAuthenticatedOrReadScope] diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py index 2cd07f3b61..cc0e3566a3 100644 --- a/src/backend/InvenTree/common/models.py +++ b/src/backend/InvenTree/common/models.py @@ -2921,10 +2921,6 @@ class Note( created: Date/time that the note was created """ - # Ignore default sanitizing of the 'content' field - # Note: This is handled explicitly in the 'save' method - SAFE_FIELDS = ['content'] - class Meta: """Meta options for Note model.""" diff --git a/src/frontend/src/components/editors/NotesEditor.tsx b/src/frontend/src/components/editors/NotesEditor.tsx index 0a191d0707..3b8156753e 100644 --- a/src/frontend/src/components/editors/NotesEditor.tsx +++ b/src/frontend/src/components/editors/NotesEditor.tsx @@ -28,7 +28,11 @@ import { Tabs, Text } from '@mantine/core'; -import { IconCirclePlus, IconInfoCircle } from '@tabler/icons-react'; +import { + IconCirclePlus, + IconInfoCircle, + IconUpload +} from '@tabler/icons-react'; import { useNoteFields } from '../../forms/CommonForms'; import { useCreateApiFormModal } from '../../hooks/UseForm'; import { useUserState } from '../../states/UserState'; @@ -47,6 +51,8 @@ export default function NotesEditor({ const api = useApi(); const user = useUserState(); + const editor = useCreateBlockNote(); + // The ID of the selected note const [selectedNote, setSelectedNote] = useState( undefined @@ -71,6 +77,20 @@ export default function NotesEditor({ enabled: !!modelId && !!modelType }); + useEffect(() => { + const note = notesQuery.data?.find((note: any) => note.pk === selectedNote); + + if (note) { + const blocks = editor.tryParseHTMLToBlocks(note.content ?? ''); + + if (blocks) { + editor.replaceBlocks(editor.document, blocks); + } + } else { + editor.replaceBlocks(editor.document, []); + } + }, [editor, selectedNote, notesQuery.data]); + // Adjust the note selection useEffect(() => { // If the currently selected note is not in the list of available notes, then we need to adjust the selection @@ -98,8 +118,6 @@ export default function NotesEditor({ return notesQuery.data && notesQuery.data.length > 0; }, [notesQuery.data]); - const editor = useCreateBlockNote(); - const noteFields = useNoteFields({ modelType: modelType, modelId: modelId }); const createNote = useCreateApiFormModal({ @@ -116,6 +134,47 @@ export default function NotesEditor({ } }); + const saveNote = useCallback(() => { + // if (!selectedNote) { + // return; + // } + + const blocks = editor.document; + const html = editor.blocksToHTMLLossy(blocks); + + // TODO: Sanitize the HTML content before sending to the server (or ensure it's sanitized on the back-end) + + if (selectedNote) { + const url = apiUrl(ApiEndpoints.note_list, selectedNote); + + notifications.hide('note-update-status'); + + api + .patch(url, { content: html }) + .then(() => { + notifications.show({ + title: t`Success`, + message: t`Note updated`, + color: 'green', + id: 'note-update-status', + autoClose: 2000 + }); + }) + .catch((error) => { + notifications.show({ + title: t`Error`, + message: t`Failed to update note: ${error.message}`, + color: 'red', + id: 'note-update-status', + autoClose: 2000 + }); + }) + .finally(() => { + notesQuery.refetch(); + }); + } + }, [selectedNote, editor]); + return ( <> {createNote.modal} @@ -126,7 +185,6 @@ export default function NotesEditor({ @@ -140,6 +198,9 @@ export default function NotesEditor({ +