From 8592cc36cb1ebeba3b5554912b0187cabc4abeb9 Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Thu, 2 May 2024 14:04:33 +0200 Subject: [PATCH 01/29] drop intermediate exports --- scripts/core/editor3/components/blockRenderer.tsx | 2 +- scripts/core/editor3/components/tables/TableBlock.tsx | 2 +- scripts/core/editor3/components/tables/index.tsx | 2 -- scripts/core/editor3/components/tests/tables.spec.tsx | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 scripts/core/editor3/components/tables/index.tsx diff --git a/scripts/core/editor3/components/blockRenderer.tsx b/scripts/core/editor3/components/blockRenderer.tsx index 4943e35cb3..0c54ed93ad 100644 --- a/scripts/core/editor3/components/blockRenderer.tsx +++ b/scripts/core/editor3/components/blockRenderer.tsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {MediaBlock} from './media'; import {EmbedBlock} from './embeds'; -import {TableBlock} from './tables'; +import {TableBlock} from './tables/TableBlock'; import {ContentBlock} from 'draft-js'; import {DragableEditor3Block} from './media/dragable-editor3-block'; import {MultiLineQuote} from './multi-line-quote'; diff --git a/scripts/core/editor3/components/tables/TableBlock.tsx b/scripts/core/editor3/components/tables/TableBlock.tsx index 845d5269a4..d4f6f4da77 100644 --- a/scripts/core/editor3/components/tables/TableBlock.tsx +++ b/scripts/core/editor3/components/tables/TableBlock.tsx @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import * as actions from '../../actions'; import {connect} from 'react-redux'; -import {TableCell} from '.'; +import {TableCell} from './TableCell'; import {EditorState, SelectionState, ContentBlock} from 'draft-js'; import {getCell, setCell, getData, setData} from '../../helpers/table'; import {IActiveCell, ISetActiveCellReturnType} from 'superdesk-api'; diff --git a/scripts/core/editor3/components/tables/index.tsx b/scripts/core/editor3/components/tables/index.tsx deleted file mode 100644 index 5197e1455d..0000000000 --- a/scripts/core/editor3/components/tables/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export {TableBlock} from './TableBlock'; -export {TableCell} from './TableCell'; diff --git a/scripts/core/editor3/components/tests/tables.spec.tsx b/scripts/core/editor3/components/tests/tables.spec.tsx index 1d56e4d8d5..0ce229a16c 100644 --- a/scripts/core/editor3/components/tests/tables.spec.tsx +++ b/scripts/core/editor3/components/tests/tables.spec.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {EditorState, ContentState} from 'draft-js'; import {shallow, mount} from 'enzyme'; import {tableBlockAndContent} from './utils'; -import {TableCell} from '../tables'; +import {TableCell} from '../tables/TableCell'; import {TableBlockComponent as TableBlock} from '../tables/TableBlock'; describe('editor3.component.table-block', () => { From a89a7e7b91f8db40e8de7b948785fc3e49e52ec3 Mon Sep 17 00:00:00 2001 From: Tomas Kikutis Date: Tue, 7 May 2024 14:06:13 +0200 Subject: [PATCH 02/29] v1 --- .../authoring-react/fields/editor3/editor.tsx | 12 +- scripts/core/editor3/actions/spellchecker.tsx | 12 +- scripts/core/editor3/components/Editor3.tsx | 9 +- .../editor3/components/Editor3Component.tsx | 65 +++++----- .../Editor3InitializeSpellchecker.tsx | 64 ++++++++++ .../core/editor3/components/blockRenderer.tsx | 19 ++- .../multi-line-quote/MultiLineQuote.tsx | 3 + .../spellchecker/SpellcheckerContextMenu.tsx | 23 +++- .../spellchecker/SpellcheckerDecorator.tsx | 3 +- .../spellchecker/default-spellcheckers.tsx | 17 ++- .../components/spellchecker/interfaces.ts | 2 +- .../editor3/components/tables/TableBlock.tsx | 2 + .../editor3/components/tables/TableCell.tsx | 119 ++++++++++++++++-- .../editor3/components/tests/editor3.spec.tsx | 12 +- .../editor3/components/tests/tables.spec.tsx | 11 ++ scripts/core/editor3/helpers/table.ts | 12 +- scripts/core/editor3/reducers/editor3.tsx | 2 +- scripts/core/editor3/service.ts | 15 ++- scripts/core/editor3/store/index.ts | 6 +- scripts/core/internal-events.ts | 2 + scripts/core/spellcheck/spellcheck.ts | 10 +- .../PlainTextEditor/PlainTextEditor.tsx | 8 ++ 22 files changed, 339 insertions(+), 89 deletions(-) create mode 100644 scripts/core/editor3/components/Editor3InitializeSpellchecker.tsx diff --git a/scripts/apps/authoring-react/fields/editor3/editor.tsx b/scripts/apps/authoring-react/fields/editor3/editor.tsx index d34b63106f..d105af4c63 100644 --- a/scripts/apps/authoring-react/fields/editor3/editor.tsx +++ b/scripts/apps/authoring-react/fields/editor3/editor.tsx @@ -63,6 +63,7 @@ interface IState { export class Editor extends React.PureComponent { private eventListenersToRemoveBeforeUnmounting: Array<() => void>; + private unmountAbortController: AbortController; constructor(props: IProps) { super(props); @@ -74,6 +75,7 @@ export class Editor extends React.PureComponent { }; this.eventListenersToRemoveBeforeUnmounting = []; + this.unmountAbortController = new AbortController(); this.getCharacterLimitPreference = this.getCharacterLimitPreference.bind(this); this.syncPropsWithReduxStore = this.syncPropsWithReduxStore.bind(this); @@ -128,7 +130,7 @@ export class Editor extends React.PureComponent { Promise.all([ getAutocompleteSuggestions(this.props.editorId, this.props.language), - initializeSpellchecker(store, spellcheck), + initializeSpellchecker(store.dispatch, spellcheck), ]).then((res) => { const [autocompleteSuggestions] = res; @@ -290,12 +292,18 @@ export class Editor extends React.PureComponent { this.eventListenersToRemoveBeforeUnmounting.push( addEditorEventListener('spellchecker__set_status', (event) => { - this.props.value.store.dispatch(setSpellcheckerStatus(event.detail)); + this.props.value.store.dispatch( + setSpellcheckerStatus( + event.detail, + this.unmountAbortController.signal, + ), + ); }), ); } componentWillUnmount() { + this.unmountAbortController.abort(); for (const fn of this.eventListenersToRemoveBeforeUnmounting) { fn(); } diff --git a/scripts/core/editor3/actions/spellchecker.tsx b/scripts/core/editor3/actions/spellchecker.tsx index 1cd92479f2..296a9c6eeb 100644 --- a/scripts/core/editor3/actions/spellchecker.tsx +++ b/scripts/core/editor3/actions/spellchecker.tsx @@ -13,15 +13,15 @@ export function replaceWord(data: IReplaceWordData) { }; } -export function setSpellcheckerStatus(enabled: boolean): any { +export function setSpellcheckerStatus(enabled: boolean, abortSignal: AbortSignal): any { if (enabled) { - return reloadSpellcheckerWarnings(); + return reloadSpellcheckerWarnings(abortSignal); } else { return disableSpellchecker(); } } -export function reloadSpellcheckerWarnings() { +export function reloadSpellcheckerWarnings(abortSignal: AbortSignal) { return function(dispatch, getState) { const state: IEditorStore = getState(); const spellchecker = getSpellchecker(state.spellchecking.language); @@ -30,7 +30,11 @@ export function reloadSpellcheckerWarnings() { return; } - getSpellcheckWarningsByBlock(spellchecker, getState().editorState).then((spellcheckWarningsByBlock) => { + getSpellcheckWarningsByBlock( + spellchecker, + getState().editorState, + abortSignal, + ).then((spellcheckWarningsByBlock) => { dispatch(applySpellcheck(spellcheckWarningsByBlock)); }); }; diff --git a/scripts/core/editor3/components/Editor3.tsx b/scripts/core/editor3/components/Editor3.tsx index 018d411c49..926de3fdc7 100644 --- a/scripts/core/editor3/components/Editor3.tsx +++ b/scripts/core/editor3/components/Editor3.tsx @@ -5,6 +5,7 @@ import {Editor3Component} from './Editor3Component'; import {MultipleHighlights} from './MultipleHighlights'; import * as actions from '../actions'; import {EditorState} from 'draft-js'; +import {Editor3InitializeSpellchecker} from './Editor3InitializeSpellchecker'; export class Editor3Base extends React.Component { static defaultProps: any; @@ -17,9 +18,11 @@ export class Editor3Base extends React.Component { render() { return ( - - - + + + + + ); } } diff --git a/scripts/core/editor3/components/Editor3Component.tsx b/scripts/core/editor3/components/Editor3Component.tsx index 1b19ec854a..241e939f82 100644 --- a/scripts/core/editor3/components/Editor3Component.tsx +++ b/scripts/core/editor3/components/Editor3Component.tsx @@ -16,7 +16,7 @@ import {getVisibleSelectionRect} from 'draft-js'; import {Map} from 'immutable'; import Toolbar from './toolbar'; -import {blockRenderer} from './blockRenderer'; +import {getBlockRenderer} from './blockRenderer'; import {customStyleMap} from './customStyleMap'; import classNames from 'classnames'; import {handlePastedText} from './handlePastedText'; @@ -27,7 +27,7 @@ import UnstyledWrapper from './UnstyledWrapper'; import * as Suggestions from '../helpers/suggestions'; import {getCurrentAuthor} from '../helpers/author'; import {setSpellcheckerProgress, applySpellcheck, PopupTypes} from '../actions'; -import {noop} from 'lodash'; +import {debounce} from 'lodash'; import {getSpellcheckWarningsByBlock} from './spellchecker/SpellcheckerDecorator'; import {getSpellchecker} from './spellchecker/default-spellcheckers'; import {IEditorStore} from '../store'; @@ -42,6 +42,7 @@ import {querySelectorParent} from 'core/helpers/dom/querySelectorParent'; import {MEDIA_TYPES_TRIGGER_DROP_ZONE} from 'core/constants'; import {isMacOS} from 'core/utils'; import {canAddArticleEmbed} from './article-embed/can-add-article-embed'; +import {addInternalEventListener} from 'core/internal-events'; export const EVENT_TYPES_TRIGGER_DROP_ZONE = [ ...MEDIA_TYPES_TRIGGER_DROP_ZONE, @@ -174,9 +175,10 @@ export class Editor3Component extends React.Component void; onDragEnd: () => void; - removeListeners: Array<() => void> = []; + private removeListeners: Array<() => void> = []; + + private spellcheckAbortController: AbortController; constructor(props) { super(props); @@ -190,8 +192,9 @@ export class Editor3Component extends React.Component { if (this.state.draggingInProgress !== false) { @@ -203,6 +206,8 @@ export class Editor3Component extends React.Component { - let canceled = false; + this.spellcheckAbortController.abort(); + this.spellcheckAbortController = new AbortController(); - setTimeout(() => { - if (!canceled) { - if (this.props.spellchecking.inProgress !== true) { - this.props.dispatch(setSpellcheckerProgress(true)); - } - - const spellchecker = getSpellchecker(this.props.spellchecking.language); + if (this.props.spellchecking.inProgress !== true) { + this.props.dispatch(setSpellcheckerProgress(true)); + } - if (spellchecker == null) { - return; - } + const spellchecker = getSpellchecker(this.props.spellchecking.language); - getSpellcheckWarningsByBlock(spellchecker, this.props.editorState) - .then((spellcheckWarningsByBlock) => { - if (!canceled) { - this.props.dispatch(applySpellcheck(spellcheckWarningsByBlock)); - this.spellcheckCancelFn = noop; - } - }); - } - }, 500); + if (spellchecker == null) { + return; + } - return () => canceled = true; - })(); + getSpellcheckWarningsByBlock(spellchecker, this.props.editorState, this.spellcheckAbortController.signal) + .then((spellcheckWarningsByBlock) => { + this.props.dispatch(applySpellcheck(spellcheckWarningsByBlock)); + }); } /** @@ -518,6 +511,10 @@ export class Editor3Component extends React.Component { diff --git a/scripts/core/editor3/components/Editor3InitializeSpellchecker.tsx b/scripts/core/editor3/components/Editor3InitializeSpellchecker.tsx new file mode 100644 index 0000000000..336d433ccc --- /dev/null +++ b/scripts/core/editor3/components/Editor3InitializeSpellchecker.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import {IEditorStore, initializeSpellchecker} from '../store'; +import ng from 'core/services/ng'; +import {isEqual} from 'lodash'; + +interface IProps { + spellchecking: IEditorStore['spellchecking']; + dispatch(): void; +} + +interface IState { + loading: boolean; +} + +export class Editor3InitializeSpellchecker extends React.PureComponent { + constructor(props: IProps) { + super(props); + + this.state = { + loading: props.spellchecking.enabled === true, + }; + + this.load = this.load.bind(this); + } + + private load() { + if (this.props.spellchecking.enabled === true) { + const spellcheck = ng.get('spellcheck'); + const language = this.props.spellchecking.language; + + spellcheck.getDictionary(language).then((dict) => { + spellcheck.isActiveDictionary = !!dict.length; + spellcheck.setLanguage(language); + spellcheck.setSpellcheckerStatus(true); + + initializeSpellchecker(this.props.dispatch, spellcheck).then(() => { + this.setState({loading: false}); + }); + }); + } + } + + componentDidMount(): void { + this.load(); + } + + componentDidUpdate(prevProps: Readonly): void { + if ( + this.props.spellchecking.enabled !== prevProps.spellchecking.enabled + || this.props.spellchecking.language !== prevProps.spellchecking.language + ) { + // eslint-disable-next-line react/no-did-update-set-state + this.setState({loading: true}, this.load); + } + } + + render() { + if (this.state.loading) { + return null; + } else { + return this.props.children; + } + } +} diff --git a/scripts/core/editor3/components/blockRenderer.tsx b/scripts/core/editor3/components/blockRenderer.tsx index 0c54ed93ad..80c497e2ee 100644 --- a/scripts/core/editor3/components/blockRenderer.tsx +++ b/scripts/core/editor3/components/blockRenderer.tsx @@ -8,10 +8,12 @@ import {DragableEditor3Block} from './media/dragable-editor3-block'; import {MultiLineQuote} from './multi-line-quote'; import {CustomEditor3Entity} from '../constants'; import {ArticleEmbed} from './article-embed/article-embed'; +import {IEditorStore} from '../store'; const BlockRendererComponent: React.StatelessComponent = (props) => { const {block, contentState} = props; const entityKey = block.getEntityAt(0); + const spellchecking: IEditorStore['spellchecking'] | undefined | null = props.blockProps.spellchecking; if (!entityKey) { return null; @@ -25,9 +27,9 @@ const BlockRendererComponent: React.StatelessComponent = (props) => { } else if (type === CustomEditor3Entity.EMBED) { return ; } else if (type === CustomEditor3Entity.TABLE) { - return ; + return ; } else if (type === CustomEditor3Entity.MULTI_LINE_QUOTE) { - return ; + return ; } else if (type === CustomEditor3Entity.ARTICLE_EMBED) { return ; } else { @@ -53,9 +55,14 @@ BlockRendererComponent.propTypes = { contentState: PropTypes.object.isRequired, }; -export function blockRenderer(contentBlock: ContentBlock) { - return contentBlock.getType() !== 'atomic' ? null : { - component: BlockRendererComponent, - editable: false, +export function getBlockRenderer(spellchecking: IEditorStore['spellchecking']) { + return (contentBlock: ContentBlock) => { + return contentBlock.getType() !== 'atomic' ? null : { + component: BlockRendererComponent, + editable: false, + props: { + spellchecking, + }, + }; }; } diff --git a/scripts/core/editor3/components/multi-line-quote/MultiLineQuote.tsx b/scripts/core/editor3/components/multi-line-quote/MultiLineQuote.tsx index 8f7c47998b..9da79e4872 100644 --- a/scripts/core/editor3/components/multi-line-quote/MultiLineQuote.tsx +++ b/scripts/core/editor3/components/multi-line-quote/MultiLineQuote.tsx @@ -4,6 +4,7 @@ import {connect} from 'react-redux'; import {EditorState, ContentBlock} from 'draft-js'; import {TableBlock} from '../tables/TableBlock'; import {IActiveCell} from 'superdesk-api'; +import {IEditorStore} from 'core/editor3/store'; export const MULTI_LINE_QUOTE_CLASS = 'multi-line-quote'; @@ -11,6 +12,7 @@ interface IProps { block: ContentBlock; readOnly: boolean; editorState: EditorState; + spellchecking: IEditorStore['spellchecking']; parentOnChange: (newEditorState: EditorState, force: boolean) => void; activeCell?: IActiveCell; setActiveCell: (row: number, col: number, blockKey: string, currentStyle: Array, selection: any) => void; @@ -35,6 +37,7 @@ export class MultiLineQuoteComponent extends React.Component { editorState={this.props.editorState} setActiveCell={this.props.setActiveCell} parentOnChange={this.props.parentOnChange} + spellchecking={this.props.spellchecking} /> ); } diff --git a/scripts/core/editor3/components/spellchecker/SpellcheckerContextMenu.tsx b/scripts/core/editor3/components/spellchecker/SpellcheckerContextMenu.tsx index 28c31b2eb9..6578529059 100644 --- a/scripts/core/editor3/components/spellchecker/SpellcheckerContextMenu.tsx +++ b/scripts/core/editor3/components/spellchecker/SpellcheckerContextMenu.tsx @@ -21,11 +21,20 @@ export class SpellcheckerContextMenuComponent extends React.Component { stickyElementTracker: any; dropdownElement: any; + private reloadSpellcheckerAbortController: AbortController; + + constructor(props: IProps) { + super(props); + + this.reloadSpellcheckerAbortController = new AbortController(); + } + componentDidMount() { this.stickyElementTracker = new StickElementsWithTracking(this.props.targetElement, this.dropdownElement); } componentWillUnmount() { this.stickyElementTracker.destroy(); + this.reloadSpellcheckerAbortController.abort(); } onSuggestionClick(suggestion: ISpellcheckerSuggestion) { @@ -95,11 +104,15 @@ export class SpellcheckerContextMenuComponent extends React.Component { return (