mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Implement Notes editor (#5529)
* Add react-simplemde-editor React wrapper for simplemde which we already use * Barebones implementation of markdown editor field * Implement notes editor * Implement drag-and-drop image uplaod
This commit is contained in:
parent
7e753523d1
commit
816b60850d
@ -31,6 +31,7 @@
|
||||
"@tanstack/react-query": "^4.33.0",
|
||||
"axios": "^1.5.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"easymde": "^2.18.0",
|
||||
"embla-carousel-react": "^8.0.0-rc12",
|
||||
"html5-qrcode": "^2.3.8",
|
||||
"mantine-datatable": "^2.9.13",
|
||||
@ -39,6 +40,7 @@
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"react-select": "^5.7.4",
|
||||
"react-simplemde-editor": "^5.2.0",
|
||||
"zustand": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
165
src/frontend/src/components/widgets/MarkdownEditor.tsx
Normal file
165
src/frontend/src/components/widgets/MarkdownEditor.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import EasyMDE from 'easymde';
|
||||
import 'easymde/dist/easymde.min.css';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useState } from 'react';
|
||||
import SimpleMDE from 'react-simplemde-editor';
|
||||
|
||||
import { api } from '../../App';
|
||||
|
||||
/**
|
||||
* Markdon editor component. Uses react-simplemde-editor
|
||||
*/
|
||||
export function MarkdownEditor({
|
||||
data,
|
||||
allowEdit,
|
||||
saveValue
|
||||
}: {
|
||||
data?: string;
|
||||
allowEdit?: boolean;
|
||||
saveValue?: (value: string) => void;
|
||||
}): ReactNode {
|
||||
const [value, setValue] = useState(data);
|
||||
|
||||
// Construct markdown editor options
|
||||
const options = useMemo(() => {
|
||||
// Custom set of toolbar icons for the editor
|
||||
let icons: any[] = ['preview', 'side-by-side'];
|
||||
|
||||
if (allowEdit) {
|
||||
icons.push(
|
||||
'|',
|
||||
|
||||
// Heading icons
|
||||
'heading-1',
|
||||
'heading-2',
|
||||
'heading-3',
|
||||
'|',
|
||||
|
||||
// Font styles
|
||||
'bold',
|
||||
'italic',
|
||||
'strikethrough',
|
||||
'|',
|
||||
|
||||
// Text formatting
|
||||
'unordered-list',
|
||||
'ordered-list',
|
||||
'code',
|
||||
'quote',
|
||||
'|',
|
||||
|
||||
// Link and image icons
|
||||
'table',
|
||||
'link',
|
||||
'image'
|
||||
);
|
||||
}
|
||||
|
||||
if (allowEdit) {
|
||||
icons.push(
|
||||
'|',
|
||||
|
||||
// Save button
|
||||
{
|
||||
name: 'save',
|
||||
action: (editor: EasyMDE) => {
|
||||
if (saveValue) {
|
||||
saveValue(editor.value());
|
||||
}
|
||||
},
|
||||
className: 'fa fa-save',
|
||||
title: t`Save`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
minHeight: '400px',
|
||||
toolbar: icons,
|
||||
sideBySideFullscreen: false,
|
||||
uploadImage: allowEdit,
|
||||
imagePathAbsolute: true,
|
||||
imageUploadFunction: (
|
||||
file: File,
|
||||
onSuccess: (url: string) => void,
|
||||
onError: (error: string) => void
|
||||
) => {
|
||||
api
|
||||
.post(
|
||||
'/notes-image-upload/',
|
||||
{
|
||||
image: file
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data?.image) {
|
||||
onSuccess(response.data.image);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotification({
|
||||
title: t`Error`,
|
||||
message: t`Failed to upload image`,
|
||||
color: 'red'
|
||||
});
|
||||
onError(error);
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [allowEdit]);
|
||||
|
||||
return (
|
||||
<SimpleMDE
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={(v: string) => setValue(v)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom implementation of the MarkdownEditor widget for editing notes.
|
||||
* Includes a callback hook for saving the notes to the server.
|
||||
*/
|
||||
export function NotesEditor({
|
||||
url,
|
||||
data,
|
||||
allowEdit
|
||||
}: {
|
||||
url: string;
|
||||
data?: string;
|
||||
allowEdit?: boolean;
|
||||
}): ReactNode {
|
||||
// Callback function to upload data to the server
|
||||
const uploadData = useCallback((value: string) => {
|
||||
api
|
||||
.patch(url, { notes: value })
|
||||
.then((response) => {
|
||||
showNotification({
|
||||
title: t`Success`,
|
||||
message: t`Notes saved`,
|
||||
color: 'green'
|
||||
});
|
||||
return response;
|
||||
})
|
||||
.catch((error) => {
|
||||
showNotification({
|
||||
title: t`Error`,
|
||||
message: t`Failed to save notes`,
|
||||
color: 'red'
|
||||
});
|
||||
return error;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MarkdownEditor data={data} allowEdit={allowEdit} saveValue={uploadData} />
|
||||
);
|
||||
}
|
@ -35,6 +35,10 @@ import { api } from '../../App';
|
||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { AttachmentTable } from '../../components/tables/AttachmentTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import {
|
||||
MarkdownEditor,
|
||||
NotesEditor
|
||||
} from '../../components/widgets/MarkdownEditor';
|
||||
import { editPart } from '../../functions/forms/PartForms';
|
||||
|
||||
export default function PartDetail() {
|
||||
@ -136,7 +140,7 @@ export default function PartDetail() {
|
||||
name: 'notes',
|
||||
label: t`Notes`,
|
||||
icon: <IconNotes size="18" />,
|
||||
content: <Text>part notes go here</Text>
|
||||
content: partNotesTab()
|
||||
}
|
||||
];
|
||||
}, [part]);
|
||||
@ -167,6 +171,17 @@ export default function PartDetail() {
|
||||
);
|
||||
}
|
||||
|
||||
function partNotesTab(): React.ReactNode {
|
||||
// TODO: Set edit permission based on user permissions
|
||||
return (
|
||||
<NotesEditor
|
||||
url={`/part/${part.pk}/`}
|
||||
data={part.notes ?? ''}
|
||||
allowEdit={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function partStockTab(): React.ReactNode {
|
||||
return (
|
||||
<StockItemTable
|
||||
|
@ -1196,6 +1196,18 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.20.7"
|
||||
|
||||
"@types/codemirror@^5.60.4", "@types/codemirror@~5.60.5":
|
||||
version "5.60.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-5.60.10.tgz#ac836a3ac20483988a0507cdbbaeb6ee0affa1e6"
|
||||
integrity sha512-ZTA3teiCWKT8HUUofqlGPlShu5ojdIajizsS0HpH6GL0/iEdjRt7fXbCLHHqKYP5k7dC/HnnWIjZAiELUwBdjQ==
|
||||
dependencies:
|
||||
"@types/tern" "*"
|
||||
|
||||
"@types/estree@*":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
|
||||
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
|
||||
|
||||
"@types/history@^4.7.11":
|
||||
version "4.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
||||
@ -1220,6 +1232,11 @@
|
||||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/marked@^4.0.7":
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.3.1.tgz#45fb6dfd47afb595766c71ed7749ead23f137de3"
|
||||
integrity sha512-vSSbKZFbNktrQ15v7o1EaH78EbWV+sPQbPjHG+Cp8CaNcPFUEfjZ0Iml/V0bFDwsTlYe8o6XC5Hfdp91cqPV2g==
|
||||
|
||||
"@types/node@*", "@types/node@^20.5.9":
|
||||
version "20.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.9.tgz#a70ec9d8fa0180a314c3ede0e20ea56ff71aed9a"
|
||||
@ -1287,6 +1304,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
|
||||
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
|
||||
|
||||
"@types/tern@*":
|
||||
version "0.23.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.4.tgz#03926eb13dbeaf3ae0d390caf706b2643a0127fb"
|
||||
integrity sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "21.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
||||
@ -1536,6 +1560,18 @@ clsx@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
||||
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||
|
||||
codemirror-spell-checker@1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz#1c660f9089483ccb5113b9ba9ca19c3f4993371e"
|
||||
integrity sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==
|
||||
dependencies:
|
||||
typo-js "*"
|
||||
|
||||
codemirror@^5.63.1:
|
||||
version "5.65.15"
|
||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.15.tgz#66899278f44a7acde0eb641388cd563fe6dfbe19"
|
||||
integrity sha512-YC4EHbbwQeubZzxLl5G4nlbLc1T21QTrKGaOal/Pkm9dVDMZXMH7+ieSPEOZCtO9I68i8/oteJKOxzHC2zR+0g==
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
@ -1667,6 +1703,17 @@ dom-helpers@^5.0.1:
|
||||
"@babel/runtime" "^7.8.7"
|
||||
csstype "^3.0.2"
|
||||
|
||||
easymde@^2.18.0:
|
||||
version "2.18.0"
|
||||
resolved "https://registry.yarnpkg.com/easymde/-/easymde-2.18.0.tgz#ff1397d07329b1a7b9187d2d0c20766fa16b3b1b"
|
||||
integrity sha512-IxVVUxNWIoXLeqtBU4BLc+eS/ScYhT1Dcb6yF5Wchoj1iXAV+TIIDWx+NCaZhY7RcSHqDPKllbYq7nwGKILnoA==
|
||||
dependencies:
|
||||
"@types/codemirror" "^5.60.4"
|
||||
"@types/marked" "^4.0.7"
|
||||
codemirror "^5.63.1"
|
||||
codemirror-spell-checker "1.1.2"
|
||||
marked "^4.1.0"
|
||||
|
||||
electron-to-chromium@^1.4.477:
|
||||
version "1.4.508"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz#5641ff2f5ba11df4bd960fe6a2f9f70aa8b9af96"
|
||||
@ -2146,6 +2193,11 @@ mantine-datatable@^2.9.13:
|
||||
resolved "https://registry.yarnpkg.com/mantine-datatable/-/mantine-datatable-2.9.13.tgz#2c94a8f3b596216b794f1c7881acc20150ab1186"
|
||||
integrity sha512-k0Q+FKC3kx7IiNJxeLP2PXJHVxuL704U5OVvtVYP/rexlPW8tqZud3WIZDuqfDCkZ83VYoszSTzauCssW+7mLw==
|
||||
|
||||
marked@^4.1.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3"
|
||||
integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==
|
||||
|
||||
memoize-one@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
|
||||
@ -2497,6 +2549,13 @@ react-select@^5.7.4:
|
||||
react-transition-group "^4.3.0"
|
||||
use-isomorphic-layout-effect "^1.1.2"
|
||||
|
||||
react-simplemde-editor@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-simplemde-editor/-/react-simplemde-editor-5.2.0.tgz#7a4c8b97e4989cb129b45ba140145d71bdc0684e"
|
||||
integrity sha512-GkTg1MlQHVK2Rks++7sjuQr/GVS/xm6y+HchZ4GPBWrhcgLieh4CjK04GTKbsfYorSRYKa0n37rtNSJmOzEDkQ==
|
||||
dependencies:
|
||||
"@types/codemirror" "~5.60.5"
|
||||
|
||||
react-style-singleton@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4"
|
||||
@ -2751,6 +2810,11 @@ typescript@^5.2.2:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
|
||||
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||
|
||||
typo-js@*:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.2.3.tgz#aa7fab3cfcc3bba01746df06fceb93b7f786c6ac"
|
||||
integrity sha512-67Hyl94beZX8gmTap7IDPrG5hy2cHftgsCAcGvE1tzuxGT+kRB+zSBin0wIMwysYw8RUCBCvv9UfQl8TNM75dA==
|
||||
|
||||
unraw@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/unraw/-/unraw-2.0.1.tgz#7b51dcdfb1e43d59d5e52cdb44d349d029edbaba"
|
||||
|
Loading…
Reference in New Issue
Block a user