Skip to content

Commit

Permalink
BUGFIX: 3839 show warn dialog before ckeditor formatting gets lost
Browse files Browse the repository at this point in the history
  • Loading branch information
mhsdesign committed Sep 5, 2024
1 parent 1711617 commit c40ef35
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 3 deletions.
44 changes: 43 additions & 1 deletion packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ export const bootstrap = _editorConfig => {

export const createEditor = store => async options => {
const {propertyDomNode, propertyName, editorOptions, globalRegistry, userPreferences, onChange} = options;

const clonedTemporaryPropertyDomNode = propertyDomNode.cloneNode(true);

const ckEditorConfig = editorConfig.configRegistry.getCkeditorConfig({
editorOptions,
userPreferences,
globalRegistry,
propertyDomNode
});

const initialData = propertyDomNode.innerHTML;

class NeosEditor extends DecoupledEditor {
constructor(...args) {
super(...args);
Expand All @@ -55,9 +60,46 @@ export const createEditor = store => async options => {
}

return NeosEditor
.create(propertyDomNode, ckEditorConfig)
.create(clonedTemporaryPropertyDomNode, {...ckEditorConfig, initialData})
.then(editor => {
const debouncedOnChange = debounce(() => onChange(cleanupContentBeforeCommit(editor.getData())), 1500, {maxWait: 5000});

const firstUpcastedData = cleanupContentBeforeCommit(editor.getData());
const hasMarkupDerivation = firstUpcastedData !== initialData;

if (!hasMarkupDerivation) {
propertyDomNode.replaceWith(clonedTemporaryPropertyDomNode)
clonedTemporaryPropertyDomNode.dataset.neosInlineEditorIsInitialized = true
} else {
const openMarkupDerivationDialogOnClickInText = () => {
let cleanupSubscription = null;
const waitForConfirmation = () => {
const {acknowledgement} = store.getState().ui.inlineEditorMarkupDerivationDialog;
switch (acknowledgement) {
case 'CONFIRMED':
cleanupSubscription?.()
propertyDomNode.removeEventListener('click', openMarkupDerivationDialogOnClickInText)

debouncedOnChange()
propertyDomNode.replaceWith(clonedTemporaryPropertyDomNode)
clonedTemporaryPropertyDomNode.dataset.neosInlineEditorIsInitialized = true

editor.editing.view.focus();
break;
case 'CANCELED':
cleanupSubscription?.()
break;
}
};
cleanupSubscription = store.subscribe(waitForConfirmation)
store.dispatch(actions.UI.InlineEditorMarkupDerivationDialog.open())
console.warn('ckeditor formatting derivation', initialData, firstUpcastedData)
};

propertyDomNode.dataset.neosInlineEditorMarkupDerivation = true
propertyDomNode.addEventListener('click', openMarkupDerivationDialogOnClickInText)
}

editor.model.document.on('change:data', debouncedOnChange);
editor.ui.focusTracker.on('change:isFocused', event => {
if (!event.source.isFocused) {
Expand Down
5 changes: 5 additions & 0 deletions packages/neos-ui-guest-frame/src/style.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
outline: 2px dashed var(--colors-PrimaryBlue);
}

:global([data-neos-inline-editor-markup-derivation]) {
outline-offset: 5px;
outline: 2px dashed var(--colors-Warn);
}

.notInlineEditableOverlay {
position: absolute;
top: 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import produce from 'immer';
import {action as createAction, ActionType} from 'typesafe-actions';

import {InitAction} from '../../System';

export interface State extends Readonly<{
isOpen: boolean;
acknowledgement?: 'CANCELED' | 'CONFIRMED'
}> {}

export const defaultState: State = {
isOpen: false
};

//
// Export the action types
//
export enum actionTypes {
OPEN = '@neos/neos-ui/UI/InlineEditorMarkupDerivationDialog/OPEN',
CANCEL = '@neos/neos-ui/UI/InlineEditorMarkupDerivationDialog/CANCEL',
CONFIRM = '@neos/neos-ui/UI/InlineEditorMarkupDerivationDialog/CONFIRM'
}

/**
* Opens the modal
*/
const open = () => createAction(actionTypes.OPEN);

/**
* Closes the modal
*/
const cancel = () => createAction(actionTypes.CANCEL);

/**
* Confirms the modal
*/
const confirm = () => createAction(actionTypes.CONFIRM);

//
// Export the actions
//
export const actions = {
open,
cancel,
confirm
};

export type Action = ActionType<typeof actions>;

//
// Export the reducer
//
export const reducer = (state: State = defaultState, action: InitAction | Action) => produce(state, draft => {
switch (action.type) {
case actionTypes.OPEN: {
draft.isOpen = true;
draft.acknowledgement = undefined;
break;
}
case actionTypes.CANCEL: {
draft.isOpen = false;
draft.acknowledgement = 'CANCELED';
break;
}
case actionTypes.CONFIRM: {
draft.isOpen = false;
draft.acknowledgement = 'CONFIRMED';
break;
}
}
});

export const selectors = {};
8 changes: 6 additions & 2 deletions packages/neos-ui-redux-store/src/UI/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as SelectNodeTypeModal from './SelectNodeTypeModal';
import * as NodeCreationDialog from './NodeCreationDialog';
import * as NodeVariantCreationDialog from './NodeVariantCreationDialog';
import * as ContentTree from './ContentTree';
import * as InlineEditorMarkupDerivationDialog from './InlineEditorMarkupDerivationDialog';

const all = {
FlashMessages,
Expand All @@ -37,7 +38,8 @@ const all = {
SelectNodeTypeModal,
NodeCreationDialog,
NodeVariantCreationDialog,
ContentTree
ContentTree,
InlineEditorMarkupDerivationDialog
};

function typedKeys<T>(o: T) : Array<keyof T> {
Expand Down Expand Up @@ -65,6 +67,7 @@ export interface State {
nodeCreationDialog: NodeCreationDialog.State;
nodeVariantCreationDialog: NodeVariantCreationDialog.State;
contentTree: ContentTree.State;
inlineEditorMarkupDerivationDialog: InlineEditorMarkupDerivationDialog.State;
}

//
Expand Down Expand Up @@ -97,7 +100,8 @@ export const reducer = combineReducers({
selectNodeTypeModal: SelectNodeTypeModal.reducer,
nodeCreationDialog: NodeCreationDialog.reducer,
nodeVariantCreationDialog: NodeVariantCreationDialog.reducer,
contentTree: ContentTree.reducer
contentTree: ContentTree.reducer,
inlineEditorMarkupDerivationDialog: InlineEditorMarkupDerivationDialog.reducer
} as any); // TODO: when we update redux, this shouldn't be necessary https://github.com/reduxjs/redux/issues/2709#issuecomment-357328709

//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import {connect} from 'react-redux';

import {actions} from '@neos-project/neos-ui-redux-store';
import {Button, Dialog, Icon} from '@neos-project/react-ui-components';
import I18n from '@neos-project/neos-ui-i18n';

import style from './style.module.css';

const withReduxState = connect((state) => ({
isOpen: state?.ui?.inlineEditorMarkupDerivationDialog?.isOpen
}), {
onCancel: actions.UI.InlineEditorMarkupDerivationDialog.cancel,
onConfirm: actions.UI.InlineEditorMarkupDerivationDialog.confirm
});

export const InlineEditorMarkupDerivationDialog = withReduxState((props) => {
if (!props.isOpen) {
return null;
}

return (
<Dialog
actions={[
<Button
key="cancel"
style="lighter"
hoverStyle="brand"
onClick={props.onCancel}
>
<I18n
id="Neos.Neos.Ui:InlineEditorMarkupDerivationDialog:cancel"
fallback="Cancel"
/>
</Button>,
<Button
key="confirm"
style="warn"
hoverStyle="warn"
onClick={props.onConfirm}
>
<Icon icon="i-cursor" className={style.buttonIcon} />
<I18n
id="Neos.Neos.Ui:InlineEditorMarkupDerivationDialog:confirm"
fallback="Edit"
/>
</Button>
]}
title={<div>
<Icon icon="highlighter"/>
<span className={style.modalTitle}>
<I18n
id="Neos.Neos.Ui:InlineEditorMarkupDerivationDialog:title"
fallback="Unexpected formatting in text"
/>
</span>
</div>}
onRequestClose={props.onCancel}
type="warn"
isOpen
autoFocus
>
<div className={style.modalContents}>
<I18n
id="Neos.Neos.Ui:InlineEditorMarkupDerivationDialog:message"
fallback="By editing this text its formatting might partially be lost."
/>
</div>
</Dialog>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export {InlineEditorMarkupDerivationDialog} from './InlineEditorMarkupDerivationDialog';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.modalTitle {
margin-left: var(--spacing-Full);
}
.modalContents {
padding: var(--spacing-Full);
}
.buttonIcon {
margin-right: var(--spacing-Half);
}
2 changes: 2 additions & 0 deletions packages/neos-ui/src/manifest.containers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import NodeVariantCreationDialog from './Containers/Modals/NodeVariantCreationDi
import ReloginDialog from './Containers/Modals/ReloginDialog/index';
import KeyboardShortcutModal from './Containers/Modals/KeyboardShortcutModal/index';
import UnappliedChangesDialog from './Containers/Modals/UnappliedChangesDialog/index';
import {InlineEditorMarkupDerivationDialog} from './Containers/Modals/InlineEditorMarkupDerivationDialog/index';

import PrimaryToolbar from './Containers/PrimaryToolbar/index';
import DimensionSwitcher from './Containers/PrimaryToolbar/DimensionSwitcher/index';
Expand Down Expand Up @@ -55,6 +56,7 @@ manifest('main.containers', {}, globalRegistry => {
containerRegistry.set('Modals/ReloginDialog', ReloginDialog);
containerRegistry.set('Modals/KeyboardShortcutModal', KeyboardShortcutModal);
containerRegistry.set('Modals/UnappliedChangesDialog', UnappliedChangesDialog);
containerRegistry.set('Modals/InlineEditorMarkupDerivationDialog', InlineEditorMarkupDerivationDialog);

containerRegistry.set('PrimaryToolbar', PrimaryToolbar);
containerRegistry.set('PrimaryToolbar/Left/MenuToggler', MenuToggler);
Expand Down

0 comments on commit c40ef35

Please sign in to comment.