Contentful SDK, React funktionella komponenter, TypeScript och react-quill - går det inte att komma åt quill-historik?


Jag försöker ändra ångra/omgör-beteendet hos react-quill när det implementeras som en fältmodifiering i Contentfuls SDK. Implementeringen beskrivs av mallen på https://github.com/contentful/create-contentful-app/tree/master/template.

När det implementeras naivt rensar Ctrl-Z-tangentbordsgenvägen helt react-quill-redigeraren. Att implementera userOnly: true i historikmodulen åtgärdar detta, men jag skulle vilja använda knappar i verktygsfältet för att kontrollera ångra/omåtgärderbeteendet genom att direkt komma åt react-quills delta- och historikfunktionalitet.

Jag har ändrat https://github.com/contentful/create-contentful-app/blob/master/template/src/components/Field.tsx på följande sätt:

import React, {useState, useEffect, useRef, createRef} from 'react';
import { Paragraph } from '@contentful/forma-36-react-components';
import { FieldExtensionSDK } from 'contentful-ui-extensions-sdk';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';

const formats = ['header','bold', 'italic', 'underline', 'strike', 'blockquote','list', 'bullet', 'indent','link', 'image', 'undo', 'redo'];

let icons = ReactQuill.Quill.import("ui/icons");

interface FieldProps { sdk: FieldExtensionSDK; }

let mainRef = React.createRef<ReactQuill>();

const Field = (props: FieldProps) => {
  const [value, setValue] = useState('');

  useEffect (()=> {
    // CMS update code here - removed for clarity
  }, [])

  const modules = {
    toolbar: {
      container: [
        [
          { 'header': [1, 2, false] }
        ],
        ['bold', 'italic'],
        [
          {'list': 'ordered'},
          {'list': 'bullet'}
        ],
        ['link'],
        ['image'],
        ['undo'],
        ['redo']
      ],
      handlers: {
        'undo': function () {
          console.log(mainRef.current)
        },
        'redo': function () {
          console.log(mainRef.current)
        }
      }
    },
    history: {
      delay: 200,
      maxStack: 500,
      userOnly: true
    }
  };

  return <div>
        <ReactQuill id='quillmain' modules={modules} formats={formats} theme="snow" value={value} onChange={setValue} ref={mainRef}/>
    </div>
  ;
};

export default Field;

De ångra- och göra-om-handlers som definierats ovan kommer att utlösas framgångsrikt - när jag klickar på ångra- eller göra-om-ikonerna kommer jag att få konsolutdata av det aktuella tillståndet för ReactQuill-instansen, enligt referensen jag har skapat:

konsolutdata

Jag skulle vilja få tillgång till historikmodulens kontroller som definieras på https://quilljs.com/docs/modules/history/, så att jag effektivt kan kalla på något som mainRef.current.history.undo() och redo(). Men om jag försöker kalla på dem i min modulers const-deklaration, stöter jag på problem med tillståndet för ReactQuill-instansen:

console.log(mainRef.current.history)
>>
Object is possibly 'null'.  TS2531

Att försöka tvinga fram problemet hjälper inte:

console.log(mainRef.current!.history)
>>
Property 'history' does not exist on type 'ReactQuill'.  TS2339

Men enligt skärmdumpen ovan visar utskrift av den aktuella instansen att egenskapen history är tillgänglig, jag antar bara att den inte är det vid den punkt där modulens const deklareras. Att flytta deklarationen utanför modulens const hjälper inte - samma TS-fel uppstår.

Detta känns som om det borde vara möjligt enligt detta svar: Hur man skapar ångra/ göra om-knappar i Quill JS (react-quill)?, men jag känner som om kombinationen av att vara begränsad till en funktionell komponent, TypeScript och hooks alla samverkar för att hindra mig från att använda react-quill på det sätt jag behöver.

Kan någon föreslå hur jag kan få tillgång till ReactQuill-instansen så att jag kan anropa funktionerna undo() och redo() vid behov, eller alternativt, hur man implementerar react-quill under dessa begränsningar så att ångra-funktionen fungerar som avsett?

Svar #1

Svaret här är useCallback. Som misstänkt orsakade inline-funktioner problem med komponentens tillstånd, men useCallback gör det möjligt att deklarera en inline-funktion samtidigt som önskat beteende i react-quill bibehålls. Följande kod tillåter den kontrollerade inline-funktionen att köras samtidigt som önskat ångra/redo-beteende bibehålls:

const modules = {
  toolbar: {
    container: [
      [
        { 'header': [1, 2, false] }
      ],
      ['bold', 'italic'],
      [
        {'list': 'ordered'},
        {'list': 'bullet'}
      ],
      ['link'],
      ['image'],
      ['undo'],
      ['redo']
    ],
    handlers: {
      'undo': React.useCallback(undoFunc => {
        console.log(mainRef.current) // DOES NOT BREAK QUILL STATE!
      }, [])
      'redo': function () {
        console.log(mainRef.current) // BREAKS QUILL STATE!
      }
    }
  },
  history: {
    delay: 200,
    maxStack: 500,
    userOnly: true
  }
};

Att deklarera modulerna som const med useCallback istället för inline-funktioner uppnår önskat beteende.

Svar #2

import ReactQuill, { Quill } from 'react-quill';

let mainRef = React.createRef<ReactQuill>();
.....
....
..
.
const modules = {
  toolbar: {
    container: [
      [
        { 'header': [1, 2, false] }
      ],
      ['bold', 'italic'],
      [
        {'list': 'ordered'},
        {'list': 'bullet'}
      ],
      ['link'],
      ['image'],
      ['undo'],
      ['redo']
    ],
    handlers: {
      'undo': () => {
        let myEditor: any = mainRef?.current?.getEditor();
        return myEditor?.history?.undo();
      },
      'redo': function () {
        let myEditor: any = mainRef?.current?.getEditor();
        return myEditor?.history?.redo();
      }
    }
  },
  history: {
    delay: 200,
    maxStack: 500,
    userOnly: true
  }
};

return (
    <ReactQuill
      theme="snow"
      value={props.value}
      onChange={props.onChange}
      placeholder={'Write something awesome...'}
      modules={modules}
      ref={mainRef}
      formats={formats}
    />
  );

Försök att göra detta, det fungerar för mig !!