From c40ef35778203fdb2e515aff64d8f3c40d43ae0a Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:05:14 +0200 Subject: [PATCH 1/2] BUGFIX: 3839 show warn dialog before ckeditor formatting gets lost --- .../src/ckEditorApi.js | 44 ++++++++++- .../neos-ui-guest-frame/src/style.module.css | 5 ++ .../index.ts | 73 +++++++++++++++++++ packages/neos-ui-redux-store/src/UI/index.ts | 8 +- .../InlineEditorMarkupDerivationDialog.jsx | 71 ++++++++++++++++++ .../index.js | 2 + .../style.module.css | 9 +++ packages/neos-ui/src/manifest.containers.js | 2 + 8 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 packages/neos-ui-redux-store/src/UI/InlineEditorMarkupDerivationDialog/index.ts create mode 100644 packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/InlineEditorMarkupDerivationDialog.jsx create mode 100644 packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/index.js create mode 100644 packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/style.module.css diff --git a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js index 7579373ac1..7ee52076bf 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js +++ b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js @@ -37,6 +37,9 @@ 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, @@ -44,6 +47,8 @@ export const createEditor = store => async options => { propertyDomNode }); + const initialData = propertyDomNode.innerHTML; + class NeosEditor extends DecoupledEditor { constructor(...args) { super(...args); @@ -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) { diff --git a/packages/neos-ui-guest-frame/src/style.module.css b/packages/neos-ui-guest-frame/src/style.module.css index 2c6fed4f8b..04d37cac0f 100644 --- a/packages/neos-ui-guest-frame/src/style.module.css +++ b/packages/neos-ui-guest-frame/src/style.module.css @@ -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; diff --git a/packages/neos-ui-redux-store/src/UI/InlineEditorMarkupDerivationDialog/index.ts b/packages/neos-ui-redux-store/src/UI/InlineEditorMarkupDerivationDialog/index.ts new file mode 100644 index 0000000000..0a37c6e88f --- /dev/null +++ b/packages/neos-ui-redux-store/src/UI/InlineEditorMarkupDerivationDialog/index.ts @@ -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; + +// +// 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 = {}; diff --git a/packages/neos-ui-redux-store/src/UI/index.ts b/packages/neos-ui-redux-store/src/UI/index.ts index e96a020250..7ad37c1d40 100644 --- a/packages/neos-ui-redux-store/src/UI/index.ts +++ b/packages/neos-ui-redux-store/src/UI/index.ts @@ -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, @@ -37,7 +38,8 @@ const all = { SelectNodeTypeModal, NodeCreationDialog, NodeVariantCreationDialog, - ContentTree + ContentTree, + InlineEditorMarkupDerivationDialog }; function typedKeys(o: T) : Array { @@ -65,6 +67,7 @@ export interface State { nodeCreationDialog: NodeCreationDialog.State; nodeVariantCreationDialog: NodeVariantCreationDialog.State; contentTree: ContentTree.State; + inlineEditorMarkupDerivationDialog: InlineEditorMarkupDerivationDialog.State; } // @@ -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 // diff --git a/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/InlineEditorMarkupDerivationDialog.jsx b/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/InlineEditorMarkupDerivationDialog.jsx new file mode 100644 index 0000000000..7fec75b85e --- /dev/null +++ b/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/InlineEditorMarkupDerivationDialog.jsx @@ -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 ( + + + , + + ]} + title={
+ + + + +
} + onRequestClose={props.onCancel} + type="warn" + isOpen + autoFocus + > +
+ +
+
+ ); +}); diff --git a/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/index.js b/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/index.js new file mode 100644 index 0000000000..d351bfbb61 --- /dev/null +++ b/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/index.js @@ -0,0 +1,2 @@ + +export {InlineEditorMarkupDerivationDialog} from './InlineEditorMarkupDerivationDialog'; diff --git a/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/style.module.css b/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/style.module.css new file mode 100644 index 0000000000..a80b4a09df --- /dev/null +++ b/packages/neos-ui/src/Containers/Modals/InlineEditorMarkupDerivationDialog/style.module.css @@ -0,0 +1,9 @@ +.modalTitle { + margin-left: var(--spacing-Full); +} +.modalContents { + padding: var(--spacing-Full); +} +.buttonIcon { + margin-right: var(--spacing-Half); +} diff --git a/packages/neos-ui/src/manifest.containers.js b/packages/neos-ui/src/manifest.containers.js index a4feb712ba..b7e7cca223 100644 --- a/packages/neos-ui/src/manifest.containers.js +++ b/packages/neos-ui/src/manifest.containers.js @@ -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'; @@ -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); From 7a8a7238f1a4dddea86a638384c42babc29176aa Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:47:42 +0200 Subject: [PATCH 2/2] WIP: Fix neos ckeditor outline overlay https://github.com/neos/neos-ui/commit/95716f791b9be5c59e6de0a45d76eadd6e6dafbd https://github.com/neos/neos-ui/commit/e551201b582d604ca494b8251ee2f67abb3ba352 --- packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js | 2 -- packages/neos-ui-guest-frame/src/initializePropertyDomNode.js | 4 +--- packages/neos-ui-guest-frame/src/style.module.css | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js index 7ee52076bf..a685d4b095 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js +++ b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js @@ -69,7 +69,6 @@ export const createEditor = store => async options => { if (!hasMarkupDerivation) { propertyDomNode.replaceWith(clonedTemporaryPropertyDomNode) - clonedTemporaryPropertyDomNode.dataset.neosInlineEditorIsInitialized = true } else { const openMarkupDerivationDialogOnClickInText = () => { let cleanupSubscription = null; @@ -82,7 +81,6 @@ export const createEditor = store => async options => { debouncedOnChange() propertyDomNode.replaceWith(clonedTemporaryPropertyDomNode) - clonedTemporaryPropertyDomNode.dataset.neosInlineEditorIsInitialized = true editor.editing.view.focus(); break; diff --git a/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js b/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js index 94d527b5bb..b9c899b306 100644 --- a/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js +++ b/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js @@ -59,7 +59,7 @@ export default ({store, globalRegistry, nodeTypesRegistry, inlineEditorRegistry, } try { - if (!propertyDomNode.dataset.neosInlineEditorIsInitialized) { + if (!propertyDomNode.ckeditorInstance) { const userPreferences = $get('user.preferences', store.getState()); createInlineEditor({ @@ -96,8 +96,6 @@ export default ({store, globalRegistry, nodeTypesRegistry, inlineEditorRegistry, } } }); - - propertyDomNode.dataset.neosInlineEditorIsInitialized = true; } } catch (err) { // diff --git a/packages/neos-ui-guest-frame/src/style.module.css b/packages/neos-ui-guest-frame/src/style.module.css index 04d37cac0f..52b128c535 100644 --- a/packages/neos-ui-guest-frame/src/style.module.css +++ b/packages/neos-ui-guest-frame/src/style.module.css @@ -22,11 +22,11 @@ outline-color: var(--colors-Warn); } -:global(.neos-inline-editable:focus) { +:global([data-__neos-property].ck-content:focus) { outline: none; } -:global([data-neos-inline-editor-is-initialized]:hover) { +:global([data-__neos-property].ck-content:hover) { outline-offset: 5px; outline: 2px dashed var(--colors-PrimaryBlue); }