Skip to content

Commit

Permalink
Editorjs fix (#6434)
Browse files Browse the repository at this point in the history
* Fjerner resolution på editorjs, oppgraderer til versjon 2.30.2.

Fikser typescript problem med versjone 2.30.2.

* Litt forbetring av EditorJSWrapper og FritekstEditor.

Laga story som tester FritekstEditor komponent, og generelt forbetra EditorJSWrapper og FritekstEditor litt.

* Fikser unødvendig re-initialisering av editorjs.

La til useCallback med korrekte deps rundt litt funksjoner i FritekstRedigering.tsx og FritekstBrevPanel.tsx slik at vi unngår at callback funksjoner sendt som props ned til FritekstEditor endrer seg ved kvar re-render og dermed fører til re-initialisering av editorjs.

* Fikser looping/feil ved innlasting av editorjs i FritekstEditor.tsx

Virka som det var unødvendig å kalle lagre og oppdaterFormFelt etter kvar initialisering av editor. oppdaterFormFelt blir uansett kalla i handleSubmit i ovanliggande komponent, så formik verdi blir oppdatert der.

Trur og dette forårsaka ein race condition eller noko anna rart som førte til feilmeldinger frå editorjs (Block has invalid content). Ville tru det gjerne kom i konflikt med at submithandler vart kalla ca samtidig.

Med denne endring kan lastEditor køyre på kvar endring av redigerbartInnhold prop, slik at komponenten sin reaktivitet er inntakt. Ein kan dermed bruke den utan å unmounte + remounte som ein måtte gjere før for å få korrekt oppførsel viss redigerbartInnhold prop endra seg utanfrå.

* Fiks FritekstEditor.stories.tsx etter forrige endring i FritekstEditor.

* Fjerner ts-expect-error som visstnok ikkje var nødvendig likevel.

---------

Co-authored-by: Thomas H. Wiberg <thomas@thawi.no>
  • Loading branch information
josstn and thomashwi committed Aug 13, 2024
1 parent bfbdc59 commit 2811c4a
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 92 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion packages/prosess-vedtak/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 9 additions & 5 deletions packages/prosess-vedtak/src/components/FritekstBrevPanel.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 (
<div className={styles.fritekstbrevPanel}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<any>) => void }) {
const tools = {
constructor({ holder, onChange }: { holder: string; onChange: (api: API, event: CustomEvent<any>) => void }) {
const tools: EditorConfig['tools'] = {
paragraph: {
class: Paragraph,
inlineToolbar: true,
Expand All @@ -34,7 +34,6 @@ export default class EditorJSWrapper {
},
},
};

this.editor = new EditorJS({
holder,
minHeight: 0,
Expand All @@ -43,28 +42,19 @@ 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);
return true;
}

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('');
}
}
Original file line number Diff line number Diff line change
@@ -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 => (
<RawIntlProvider value={intl}>
<Story />
</RawIntlProvider>
);

const intl = createIntl({
locale: 'nb-NO',
messages,
});

const meta = {
title: 'prosess/prosess-vedtak/FritekstRedigering',
component: FritekstEditor,
decorators: [withRawIntlProvider(intl)],
} satisfies Meta<typeof FritekstEditor>;

export default meta;

export const Default: StoryObj<typeof meta> = {
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<typeof FritekstEditor> = {
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(`<p>${customizedContent}</p>`));
const para = contentBlock.querySelector('.ce-paragraph.cdx-block');
await userEvent.type(para, addedContent);
await userEvent.click(submitBtn);
await waitFor(() => expect(args.handleSubmit).toHaveBeenCalledWith(`<p>${customizedContent}${addedContent}</p>`));
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -50,66 +58,87 @@ const FritekstEditor = ({
}: ownProps & WrappedComponentProps) => {
const [visAdvarsel, setVisAdvarsel] = useState<boolean>(false);
const [visValideringsFeil, setVisValideringsFeil] = useState<boolean>(false);
const editorRef = useRef<EditorJSWrapper | null>(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 (
Expand Down
Loading

0 comments on commit 2811c4a

Please sign in to comment.