Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make spellchecker work for multi line quotes #4518

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8592cc3
drop intermediate exports
tomaskikutis May 2, 2024
a89a7e7
v1
tomaskikutis May 7, 2024
1624796
move code to reload spellchecker warnings after action is performed
tomaskikutis May 7, 2024
87157f7
move spellcheck call componentDidUpdate
tomaskikutis May 7, 2024
c5d755f
enable accepting spellchecker suggestions inside table cell
tomaskikutis May 8, 2024
6e2620e
improve interface of getDecorators
tomaskikutis May 8, 2024
7f224c8
disable spellchecking for tables
tomaskikutis May 8, 2024
39b0ad4
when spellchecking action is performed - update UI without delay
tomaskikutis May 8, 2024
6ad6a66
drop spellchecker initialization in authoring-react
tomaskikutis May 8, 2024
b8ecc43
remove unused import
tomaskikutis May 8, 2024
1ba6944
fix unit tests
tomaskikutis May 9, 2024
1c3e597
replace removed unit tests with e2e tests
tomaskikutis May 9, 2024
1896dd4
fix component getting stuck in permanent loading state
tomaskikutis May 10, 2024
7055860
fix unit test
tomaskikutis May 12, 2024
b0d91ab
port broken e2e tests to playwright
tomaskikutis May 14, 2024
dbb6d20
fix record name
tomaskikutis May 14, 2024
65019be
fix lint
tomaskikutis May 14, 2024
1016a06
fix unit test
tomaskikutis May 14, 2024
f5ee8bd
make e2e test more stable
tomaskikutis May 14, 2024
e89226f
Merge branch 'develop' into make-spellchecker-work-for-multi-line-quotes
tomaskikutis May 14, 2024
8a6a715
make e2e tests more stable
tomaskikutis May 14, 2024
6706c6c
see if 100ms delay is enough
tomaskikutis May 14, 2024
5cc938e
Revert "see if 100ms delay is enough"
tomaskikutis May 14, 2024
23b5634
Merge branch 'develop' into make-spellchecker-work-for-multi-line-quotes
tomaskikutis May 15, 2024
cd80fc4
try lowering the delay again
tomaskikutis May 15, 2024
2f12177
fix unit test
tomaskikutis May 15, 2024
9167c28
fix unit test
tomaskikutis May 15, 2024
34d07a1
disable preference tests
tomaskikutis May 15, 2024
e42d312
use spec reported to be able to see which test throws
tomaskikutis May 15, 2024
dbd0987
Revert "disable preference tests"
tomaskikutis May 15, 2024
3434459
fix unit tests v10
tomaskikutis May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
v1
  • Loading branch information
tomaskikutis committed May 7, 2024
commit a89a7e7b91f8db40e8de7b948785fc3e49e52ec3
12 changes: 10 additions & 2 deletions scripts/apps/authoring-react/fields/editor3/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ interface IState {

export class Editor extends React.PureComponent<IProps, IState> {
private eventListenersToRemoveBeforeUnmounting: Array<() => void>;
private unmountAbortController: AbortController;

constructor(props: IProps) {
super(props);
Expand All @@ -74,6 +75,7 @@ export class Editor extends React.PureComponent<IProps, IState> {
};

this.eventListenersToRemoveBeforeUnmounting = [];
this.unmountAbortController = new AbortController();

this.getCharacterLimitPreference = this.getCharacterLimitPreference.bind(this);
this.syncPropsWithReduxStore = this.syncPropsWithReduxStore.bind(this);
Expand Down Expand Up @@ -128,7 +130,7 @@ export class Editor extends React.PureComponent<IProps, IState> {

Promise.all([
getAutocompleteSuggestions(this.props.editorId, this.props.language),
initializeSpellchecker(store, spellcheck),
initializeSpellchecker(store.dispatch, spellcheck),
]).then((res) => {
const [autocompleteSuggestions] = res;

Expand Down Expand Up @@ -290,12 +292,18 @@ export class Editor extends React.PureComponent<IProps, IState> {

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();
}
Expand Down
12 changes: 8 additions & 4 deletions scripts/core/editor3/actions/spellchecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -30,7 +30,11 @@ export function reloadSpellcheckerWarnings() {
return;
}

getSpellcheckWarningsByBlock(spellchecker, getState().editorState).then((spellcheckWarningsByBlock) => {
getSpellcheckWarningsByBlock(
spellchecker,
getState().editorState,
abortSignal,
).then((spellcheckWarningsByBlock) => {
dispatch(applySpellcheck(spellcheckWarningsByBlock));
});
};
Expand Down
9 changes: 6 additions & 3 deletions scripts/core/editor3/components/Editor3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<any, any> {
static defaultProps: any;
Expand All @@ -17,9 +18,11 @@ export class Editor3Base extends React.Component<any, any> {

render() {
return (
<MultipleHighlights {...this.props}>
<Editor3Component />
</MultipleHighlights>
<Editor3InitializeSpellchecker spellchecking={this.props.spellchecking} dispatch={this.props.dispatch}>
<MultipleHighlights {...this.props}>
<Editor3Component />
</MultipleHighlights>
</Editor3InitializeSpellchecker>
);
}
}
Expand Down
65 changes: 33 additions & 32 deletions scripts/core/editor3/components/Editor3Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -174,9 +175,10 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS

div: any;
editor: any;
spellcheckCancelFn: () => void;
onDragEnd: () => void;
removeListeners: Array<() => void> = [];
private removeListeners: Array<() => void> = [];

private spellcheckAbortController: AbortController;

constructor(props) {
super(props);
Expand All @@ -190,8 +192,9 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS
this.handleBeforeInput = this.handleBeforeInput.bind(this);
this.keyBindingFn = this.keyBindingFn.bind(this);
this.handleDropOnEditor = this.handleDropOnEditor.bind(this);
this.spellcheck = this.spellcheck.bind(this);
this.spellcheckCancelFn = noop;
this.spellcheck = debounce(this.spellcheck.bind(this), 1000);

this.spellcheckAbortController = new AbortController();

this.onDragEnd = () => {
if (this.state.draggingInProgress !== false) {
Expand All @@ -203,6 +206,8 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS
draggingInProgress: false,
contentChangesAfterLastFocus: 0,
};

this.removeListeners = [];
}

/**
Expand All @@ -215,35 +220,23 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS
}

spellcheck() {
this.spellcheckCancelFn();

this.spellcheckCancelFn = (() => {
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));
});
}

/**
Expand Down Expand Up @@ -518,6 +511,10 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS
if (this.props.spellchecking.enabled) {
this.spellcheck();
}

this.removeListeners.push(
addInternalEventListener('editor3SpellcheckerActionWasExecuted', this.spellcheck),
);
}

handleRefs(editor) {
Expand All @@ -536,6 +533,10 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS
$(this.div).off();

delete window[EDITOR_GLOBAL_REFS][this.editorKey];

for (const fn of this.removeListeners) {
fn();
}
}

componentDidUpdate(prevProps) {
Expand Down Expand Up @@ -670,7 +671,7 @@ export class Editor3Component extends React.Component<IPropsEditor3Component, IS
keyBindingFn={this.keyBindingFn}
handleBeforeInput={this.handleBeforeInput}
blockRenderMap={blockRenderMap}
blockRendererFn={blockRenderer}
blockRendererFn={getBlockRenderer(this.props.spellchecking)}
blockStyleFn={blockStyle}
customStyleMap={{...customStyleMap, ...this.props.highlightsManager.styleMap}}
onChange={(editorStateNext: EditorState) => {
Expand Down
64 changes: 64 additions & 0 deletions scripts/core/editor3/components/Editor3InitializeSpellchecker.tsx
Original file line number Diff line number Diff line change
@@ -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<IProps, IState> {
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<IProps>): 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;
}
}
}
19 changes: 13 additions & 6 deletions scripts/core/editor3/components/blockRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> = (props) => {
const {block, contentState} = props;
const entityKey = block.getEntityAt(0);
const spellchecking: IEditorStore['spellchecking'] | undefined | null = props.blockProps.spellchecking;

if (!entityKey) {
return null;
Expand All @@ -25,9 +27,9 @@ const BlockRendererComponent: React.StatelessComponent<any> = (props) => {
} else if (type === CustomEditor3Entity.EMBED) {
return <EmbedBlock {...props} />;
} else if (type === CustomEditor3Entity.TABLE) {
return <TableBlock {...props} toolbarStyle="table" />;
return <TableBlock {...props} toolbarStyle="table" spellchecking={spellchecking} />;
} else if (type === CustomEditor3Entity.MULTI_LINE_QUOTE) {
return <MultiLineQuote {...props} />;
return <MultiLineQuote {...props} spellchecking={spellchecking} />;
} else if (type === CustomEditor3Entity.ARTICLE_EMBED) {
return <ArticleEmbed {...props} />;
} else {
Expand All @@ -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,
},
};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ 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';

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<string>, selection: any) => void;
Expand All @@ -35,6 +37,7 @@ export class MultiLineQuoteComponent extends React.Component<IProps> {
editorState={this.props.editorState}
setActiveCell={this.props.setActiveCell}
parentOnChange={this.props.parentOnChange}
spellchecking={this.props.spellchecking}
/>
);
}
Expand Down
Loading