diff --git a/package.json b/package.json
index a67024fad1..30dcf2a2e6 100644
--- a/package.json
+++ b/package.json
@@ -123,8 +123,7 @@
"resolutions": {
"postcss": "8.4.40",
"parse-url": "8.1.0",
- "@types/react": "17",
- "@editorjs/editorjs": "2.27.2"
+ "@types/react": "17"
},
"msw": {
"workerDirectory": "public"
diff --git a/packages/prosess-vedtak/package.json b/packages/prosess-vedtak/package.json
index 9f1290c807..d6dcd8a6b8 100644
--- a/packages/prosess-vedtak/package.json
+++ b/packages/prosess-vedtak/package.json
@@ -5,7 +5,7 @@
"license": "MIT",
"private": true,
"dependencies": {
- "@editorjs/editorjs": "2.30.3",
+ "@editorjs/editorjs": "2.30.2",
"@editorjs/header": "2.8.7",
"@editorjs/list": "1.9.0",
"@editorjs/paragraph": "2.11.6",
diff --git a/packages/prosess-vedtak/src/components/FritekstBrevPanel.tsx b/packages/prosess-vedtak/src/components/FritekstBrevPanel.tsx
index 2e44358049..351d4a8ceb 100644
--- a/packages/prosess-vedtak/src/components/FritekstBrevPanel.tsx
+++ b/packages/prosess-vedtak/src/components/FritekstBrevPanel.tsx
@@ -1,6 +1,6 @@
import { Alert, Heading } from '@navikt/ds-react';
import { FormikProps, FormikValues } from 'formik';
-import React from 'react';
+import React, { useCallback } from 'react';
import { FormattedMessage, IntlShape, injectIntl } from 'react-intl';
import { TextAreaFormik, TextFieldFormik } from '@fpsak-frontend/form';
@@ -59,10 +59,14 @@ const FritekstBrevPanel = ({
const [featureToggles] = useFeatureToggles();
const kanRedigereFritekstbrev = kanHaManueltFritekstbrev(tilgjengeligeVedtaksbrev);
- const handleFritekstSubmit = async (html: string, request) => {
- formikProps.setFieldValue(fieldnames.REDIGERT_HTML, html);
- await lagreDokumentdata(request);
- };
+ // useCallback to avoid re-initializing FritekstRedigering editorjs on every re-render of this component
+ const handleFritekstSubmit = useCallback(
+ async (html: string, request) => {
+ await formikProps.setFieldValue(fieldnames.REDIGERT_HTML, html);
+ lagreDokumentdata(request);
+ },
+ [formikProps.setFieldValue, lagreDokumentdata],
+ );
return (
diff --git a/packages/prosess-vedtak/src/components/FritekstRedigering/EditorJSWrapper.ts b/packages/prosess-vedtak/src/components/FritekstRedigering/EditorJSWrapper.ts
index 4cc5db6829..5356f156b8 100644
--- a/packages/prosess-vedtak/src/components/FritekstRedigering/EditorJSWrapper.ts
+++ b/packages/prosess-vedtak/src/components/FritekstRedigering/EditorJSWrapper.ts
@@ -1,4 +1,4 @@
-import EditorJS, { API } from '@editorjs/editorjs';
+import EditorJS, { API, type EditorConfig } from '@editorjs/editorjs';
import Header from '@editorjs/header';
import Paragraph from '@editorjs/paragraph';
import List from '@editorjs/list';
@@ -7,8 +7,8 @@ import edjsHTML from 'editorjs-html';
export default class EditorJSWrapper {
private editor: EditorJS;
- public async init({ holder, onChange }: { holder: string; onChange: (api: API, event: CustomEvent
) => void }) {
- const tools = {
+ constructor({ holder, onChange }: { holder: string; onChange: (api: API, event: CustomEvent) => void }) {
+ const tools: EditorConfig['tools'] = {
paragraph: {
class: Paragraph,
inlineToolbar: true,
@@ -34,7 +34,6 @@ export default class EditorJSWrapper {
},
},
};
-
this.editor = new EditorJS({
holder,
minHeight: 0,
@@ -43,13 +42,6 @@ export default class EditorJSWrapper {
});
}
- public harEditor() {
- if (this.editor) {
- return true;
- }
- return false;
- }
-
public async importer(html) {
await this.editor.isReady;
await this.editor.blocks.renderFromHTML(html);
@@ -57,14 +49,12 @@ export default class EditorJSWrapper {
}
public async erKlar() {
- if (!this.editor) return false;
- return this.editor;
+ return this.editor.isReady;
}
public async lagre() {
- return this.editor.save().then(innhold => {
- const edjsParser = edjsHTML();
- return edjsParser.parse(innhold).join('');
- });
+ const innhold = await this.editor.save();
+ const edjsParser = edjsHTML();
+ return edjsParser.parse(innhold).join('');
}
}
diff --git a/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstEditor.stories.tsx b/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstEditor.stories.tsx
new file mode 100644
index 0000000000..479ca34531
--- /dev/null
+++ b/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstEditor.stories.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import type { Decorator, Meta, StoryObj } from '@storybook/react';
+import { fn, expect, userEvent, waitFor } from '@storybook/test';
+import { createIntl, IntlShape, RawIntlProvider } from 'react-intl';
+import FritekstEditor from './FritekstEditor.js';
+import messages from '../../../i18n/nb_NO.json';
+
+const withRawIntlProvider =
+ (intl: IntlShape): Decorator =>
+ Story => (
+
+
+
+ );
+
+const intl = createIntl({
+ locale: 'nb-NO',
+ messages,
+});
+
+const meta = {
+ title: 'prosess/prosess-vedtak/FritekstRedigering',
+ component: FritekstEditor,
+ decorators: [withRawIntlProvider(intl)],
+} satisfies Meta;
+
+export default meta;
+
+export const Default: StoryObj = {
+ args: {
+ kanInkludereKalender: true,
+ skalBrukeOverstyrendeFritekstBrev: false,
+ readOnly: false,
+ redigerbartInnholdKlart: true,
+ redigerbartInnhold: 'Storybook default scenario',
+ originalHtml: 'OriginalHtml',
+ prefiksInnhold: '',
+ suffiksInnhold: '',
+ brevStiler: '',
+ handleSubmit: fn(),
+ lukkEditor: fn(),
+ handleForhåndsvis: fn(),
+ setFieldValue: fn(),
+ },
+};
+
+const customizedContent = 'Storybook customized scenario';
+const addedContent = ' Added text';
+export const AvansertMedPlayTest: StoryObj = {
+ args: {
+ ...Default.args,
+ redigerbartInnhold: customizedContent,
+ originalHtml: 'Storybook customized scenario originalt innhold',
+ prefiksInnhold: 'Prefiks',
+ suffiksInnhold: 'Suffiks',
+ },
+ play: async ({ canvas, args, canvasElement }) => {
+ const prefixEl = canvas.getByText('Prefiks');
+ expect(prefixEl).toBeInTheDocument();
+ const suffixEl = canvas.getByText('Suffiks');
+ expect(suffixEl).toBeInTheDocument();
+ const contentBlock = canvasElement.querySelector('#rediger-brev');
+ await expect(contentBlock).toBeInTheDocument();
+
+ const submitBtn = canvas.getByRole('button', { name: 'Lagre og lukk' });
+ expect(submitBtn).toBeInTheDocument();
+ await userEvent.click(submitBtn, { delay: 100 });
+ await waitFor(() => expect(args.handleSubmit).toHaveBeenCalledWith(`${customizedContent}
`));
+ const para = contentBlock.querySelector('.ce-paragraph.cdx-block');
+ await userEvent.type(para, addedContent);
+ await userEvent.click(submitBtn);
+ await waitFor(() => expect(args.handleSubmit).toHaveBeenCalledWith(`${customizedContent}${addedContent}
`));
+ },
+};
diff --git a/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstEditor.tsx b/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstEditor.tsx
index f53f44c74b..afb63cc759 100644
--- a/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstEditor.tsx
+++ b/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstEditor.tsx
@@ -2,7 +2,7 @@
import { VerticalSpacer } from '@fpsak-frontend/shared-components';
import { Cancel } from '@navikt/ds-icons';
import { Alert, Button, HGrid, Heading, Modal } from '@navikt/ds-react';
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import { FormattedMessage, WrappedComponentProps, injectIntl } from 'react-intl';
import InkluderKalenderCheckbox from '../InkluderKalenderCheckbox';
import PreviewLink from '../PreviewLink';
@@ -16,7 +16,6 @@ interface ownProps {
handleSubmit: (value: string) => void;
lukkEditor: () => void;
handleForhåndsvis: (event: React.SyntheticEvent, html: string) => void;
- oppdaterFormFelt: (html: string) => void;
setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void;
kanInkludereKalender: boolean;
skalBrukeOverstyrendeFritekstBrev: boolean;
@@ -29,13 +28,22 @@ interface ownProps {
brevStiler: string;
}
-const editor = new EditorJSWrapper();
+const debounce = funksjon => {
+ let teller;
+ return function lagre(...args) {
+ const context = this;
+ if (teller) clearTimeout(teller);
+ teller = setTimeout(() => {
+ teller = null;
+ funksjon.apply(context, args);
+ }, 1000);
+ };
+};
const FritekstEditor = ({
handleSubmit,
lukkEditor,
handleForhåndsvis,
- oppdaterFormFelt,
setFieldValue,
kanInkludereKalender,
skalBrukeOverstyrendeFritekstBrev,
@@ -50,66 +58,87 @@ const FritekstEditor = ({
}: ownProps & WrappedComponentProps) => {
const [visAdvarsel, setVisAdvarsel] = useState(false);
const [visValideringsFeil, setVisValideringsFeil] = useState(false);
+ const editorRef = useRef(null);
+ const lastSubmitHtml = useRef(redigerbartInnhold);
+ const initImportNotDone = useRef(true);
+
+ // useCallback to avoid recreation of this on every re-render of component
+ const handleLagre = useCallback(async () => {
+ const editor = editorRef.current;
+ if (editor !== null) {
+ await editor.erKlar();
+ const html = await editor.lagre();
+ if (html !== lastSubmitHtml.current) {
+ handleSubmit(html);
+ lastSubmitHtml.current = html;
+ }
+ }
+ }, [handleSubmit]);
- const handleLagre = async () => {
- const html = await editor.lagre();
- handleSubmit(html);
- };
-
- const debounce = funksjon => {
- let teller;
- return function lagre(...args) {
- const context = this;
- if (teller) clearTimeout(teller);
- teller = setTimeout(() => {
- teller = null;
- funksjon.apply(context, args);
- }, 1000);
- };
- };
-
- const debouncedLagre = useCallback(debounce(handleLagre), []);
+ // useCallback to avoid recreation of this on every re-render of component
+ const debouncedLagre = useCallback(debounce(handleLagre), [handleLagre]);
- const onChange = () => {
+ // useCallback to avoid recreation of this on every re-render of component, since that would require recreating the editor on every re-render.
+ const onChange = useCallback(() => {
if (!readOnly) {
debouncedLagre();
}
- };
+ }, [readOnly, debouncedLagre]);
- const lastEditor = async () => {
- await editor.init({ holder: 'rediger-brev', onChange });
- await editor.importer(redigerbartInnhold);
- const html = await editor.lagre();
- oppdaterFormFelt(html);
- };
+ // Create new instance of editor (wrapper) when neccessary
+ useEffect(() => {
+ editorRef.current = new EditorJSWrapper({ holder: 'rediger-brev', onChange });
+ }, [onChange]);
+ // Last innhold inn i editor ved første initialisering, eller viss redigerbartInnhold har blir endra utanfrå.
useEffect(() => {
- if (redigerbartInnholdKlart && !readOnly) {
- lastEditor();
+ const lastEditor = async (editor: EditorJSWrapper) => {
+ if (initImportNotDone.current || lastSubmitHtml.current !== redigerbartInnhold) {
+ await editor.importer(redigerbartInnhold);
+ initImportNotDone.current = false;
+ }
+ };
+ const editor = editorRef.current;
+ if (editor !== null) {
+ if (redigerbartInnholdKlart && !readOnly) {
+ lastEditor(editor);
+ }
+ } else {
+ throw new Error(`Unexpectedly no editor instance available`);
}
- }, [redigerbartInnholdKlart, readOnly]);
+ }, [redigerbartInnhold, redigerbartInnholdKlart, readOnly]);
- const handleLagreOgLukk = () => {
- handleLagre();
+ const handleLagreOgLukk = async () => {
+ await handleLagre();
lukkEditor();
};
const onForhåndsvis = async e => {
- const html = await editor.lagre();
- const validert = await validerRedigertHtml.isValid(html);
-
- if (validert) {
- setVisValideringsFeil(false);
- handleForhåndsvis(e, html);
+ const editor = editorRef.current;
+ if (editor !== null) {
+ const html = await editor.lagre();
+ const validert = await validerRedigertHtml.isValid(html);
+
+ if (validert) {
+ setVisValideringsFeil(false);
+ handleForhåndsvis(e, html);
+ } else {
+ setVisValideringsFeil(true);
+ }
} else {
- setVisValideringsFeil(true);
+ throw new Error(`Fritekstredigering ikke initialisert. Kan ikke lage forhåndsvisning.`);
}
};
const handleTilbakestill = async () => {
- await editor.importer(originalHtml);
- setVisAdvarsel(false);
- handleLagre();
+ const editor = editorRef.current;
+ if (editor !== null) {
+ await editor.importer(originalHtml);
+ setVisAdvarsel(false);
+ await handleLagre();
+ } else {
+ console.warn('Fritekstredigering ikke initialisert. Kan ikke tilbakestille.');
+ }
};
return (
diff --git a/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstRedigering.tsx b/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstRedigering.tsx
index 3f0c614d7e..6d88ec1b16 100644
--- a/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstRedigering.tsx
+++ b/packages/prosess-vedtak/src/components/FritekstRedigering/FritekstRedigering.tsx
@@ -10,7 +10,7 @@ import {
import { DokumentDataType } from '@k9-sak-web/types/src/dokumentdata';
import { Edit } from '@navikt/ds-icons';
import { Alert, Button, Heading, Modal } from '@navikt/ds-react';
-import React, { useEffect, useRef, useState } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import { FormattedMessage, WrappedComponentProps, injectIntl } from 'react-intl';
import { fieldnames } from '../../konstanter';
import FritekstEditor from './FritekstEditor';
@@ -119,19 +119,31 @@ const FritekstRedigering = ({
const lukkEditor = () => setVisRedigering(false);
- const handleLagre = async html => {
- handleSubmit(
- html,
- lagLagreHtmlDokumentdataRequest({
- dokumentdata,
- redigerbarDokumentmal,
- redigertHtml: html,
- originalHtml,
- inkluderKalender,
- overstyrtMottaker,
- }),
- );
- };
+ // useCallback for å unngå unødvendig re-initialisering av editorjs i FritekstEditor
+ const handleLagre = useCallback(
+ async html => {
+ handleSubmit(
+ html,
+ lagLagreHtmlDokumentdataRequest({
+ dokumentdata,
+ redigerbarDokumentmal,
+ redigertHtml: html,
+ originalHtml,
+ inkluderKalender,
+ overstyrtMottaker,
+ }),
+ );
+ },
+ [
+ handleSubmit,
+ lagLagreHtmlDokumentdataRequest,
+ dokumentdata,
+ redigerbarDokumentmal,
+ originalHtml,
+ inkluderKalender,
+ overstyrtMottaker,
+ ],
+ );
useEffect(() => {
if (!firstRender.current && overstyrtMottaker && !henterMal) {
@@ -154,8 +166,6 @@ const FritekstRedigering = ({
const handleForhåndsvis = (e: React.SyntheticEvent, html: string) => previewBrev(e, html);
- const oppdaterFormFelt = (html: string) => setFieldValue(fieldnames.REDIGERT_HTML, html);
-
return (
<>
@@ -191,7 +201,6 @@ const FritekstRedigering = ({
handleSubmit={handleLagre}
lukkEditor={lukkEditor}
handleForhåndsvis={handleForhåndsvis}
- oppdaterFormFelt={oppdaterFormFelt}
setFieldValue={setFieldValue}
kanInkludereKalender={kanInkludereKalender}
skalBrukeOverstyrendeFritekstBrev={skalBrukeOverstyrendeFritekstBrev}
diff --git a/yarn.lock b/yarn.lock
index 30bc0402b7..004a124091 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1585,10 +1585,17 @@ __metadata:
languageName: node
linkType: hard
-"@editorjs/editorjs@npm:2.27.2":
- version: 2.27.2
- resolution: "@editorjs/editorjs@npm:2.27.2"
- checksum: fd484610d8ba8f04aff5ac5331c7a0ec75796af5dc0251e13cf5e64fdbe72d53f91e35b146cb3aca23db7091bd70da8b1abf9bcfc834c07dcc79e1299e010d7a
+"@editorjs/editorjs@npm:2.30.2":
+ version: 2.30.2
+ resolution: "@editorjs/editorjs@npm:2.30.2"
+ checksum: 658e10918390d18578e65d73e84f601b7eab8ba1a704142872a597ed0da6bb1ee407fc32b5b18a473a99c993729dc8959df408f10738a6150199acaf225474fc
+ languageName: node
+ linkType: hard
+
+"@editorjs/editorjs@npm:^2.29.1":
+ version: 2.30.3
+ resolution: "@editorjs/editorjs@npm:2.30.3"
+ checksum: 4cad89ba56abda96a00c74a3efe5d02d6cbbd44fda82793ad7b115ed0580d721100e98c972c9dcb0f15508064d8932f11a3b8dd6aa1ed60a28dc41b90ae847be
languageName: node
linkType: hard
@@ -2684,7 +2691,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@fpsak-frontend/prosess-vedtak@workspace:packages/prosess-vedtak"
dependencies:
- "@editorjs/editorjs": 2.30.3
+ "@editorjs/editorjs": 2.30.2
"@editorjs/header": 2.8.7
"@editorjs/list": 1.9.0
"@editorjs/paragraph": 2.11.6