From f315398a72b952b82bedce4d0da2862f1f11678e Mon Sep 17 00:00:00 2001 From: Dariusz Szut Date: Tue, 19 Mar 2024 15:23:54 +0100 Subject: [PATCH] IBX-7503: Fixed custom attributes (#152) --- .../public/js/CKEditor/core/base-ckeditor.js | 2 +- .../custom-attributes-command.js | 43 +++++++++++--- .../custom-attributes-editing.js | 14 ++++- .../custom-attributes/custom-attributes-ui.js | 16 +++-- .../helpers/config-helper.js | 25 +++++++- .../js/CKEditor/plugins/elements-path.js | 59 +++++++++++++++---- 6 files changed, 128 insertions(+), 31 deletions(-) diff --git a/src/bundle/Resources/public/js/CKEditor/core/base-ckeditor.js b/src/bundle/Resources/public/js/CKEditor/core/base-ckeditor.js index 5df04ed3..3eac20cb 100644 --- a/src/bundle/Resources/public/js/CKEditor/core/base-ckeditor.js +++ b/src/bundle/Resources/public/js/CKEditor/core/base-ckeditor.js @@ -167,10 +167,10 @@ const VIEWPORT_TOP_OFFSET = 102; IbexaElementsPath, IbexaEmbed, IbexaCustomTags, + IbexaFormatted, IbexaCustomStylesInline, IbexaCustomAttributes, IbexaLink, - IbexaFormatted, IbexaAnchor, IbexaMove, IbexaRemoveElement, diff --git a/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-command.js b/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-command.js index 9a2eea40..1a3b4198 100644 --- a/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-command.js +++ b/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-command.js @@ -1,17 +1,25 @@ import Command from '@ckeditor/ckeditor5-core/src/command'; -import { getCustomAttributesConfig, getCustomClassesConfig } from './helpers/config-helper'; +import { + getCustomAttributesConfig, + getCustomClassesConfig, + getCustomAttributesElementConfig, + getCustomClassesElementConfig, + findConfigName, +} from './helpers/config-helper'; class IbexaCustomAttributesCommand extends Command { cleanAttributes(modelElement, attributes) { Object.entries(attributes).forEach(([elementName, config]) => { - if (elementName === modelElement.name) { + const configName = findConfigName(modelElement.name); + + if (elementName === configName) { return; } this.editor.model.change((writer) => { Object.keys(config).forEach((name) => { - if (attributes[modelElement.name]?.[name]) { + if (attributes[configName]?.[name]) { return; } @@ -23,15 +31,16 @@ class IbexaCustomAttributesCommand extends Command { cleanClasses(modelElement, classes) { Object.keys(classes).forEach((elementName) => { + const configName = findConfigName(modelElement.name); const selectedCustomClasses = modelElement.getAttribute('custom-classes') ?? ''; - const elementCustomClassesConfig = classes[modelElement.name]; + const elementCustomClassesConfig = classes[configName]; const hasOwnCustomClasses = elementCustomClassesConfig && selectedCustomClasses .split(' ') .every((selectedCustomClass) => elementCustomClassesConfig.choices.includes(selectedCustomClass)); - if (elementName === modelElement.name || hasOwnCustomClasses) { + if (elementName === configName || hasOwnCustomClasses) { return; } @@ -41,6 +50,24 @@ class IbexaCustomAttributesCommand extends Command { }); } + isTableColumnSelected(parentElementName) { + const selectedBlocks = [...this.editor.model.document.selection.getSelectedBlocks()]; + const isTableRow = parentElementName === 'tableRow'; + const areBlocksInSameRow = selectedBlocks.every((selectedBlock, index) => { + const nextBlock = selectedBlocks[index + 1]; + + if (!nextBlock) { + return true; + } + + const commonAncestor = selectedBlock.getCommonAncestor(nextBlock); + + return commonAncestor.name === 'tableRow'; + }); + + return !areBlocksInSameRow && isTableRow; + } + refresh() { const { selection } = this.editor.model.document; const parentElement = selection.getSelectedElement() ?? selection.getFirstPosition().parent; @@ -60,9 +87,9 @@ class IbexaCustomAttributesCommand extends Command { const customAttributesConfig = getCustomAttributesConfig(); const customClassesConfig = getCustomClassesConfig(); - const parentElementAttributesConfig = customAttributesConfig[parentElementName]; - const parentElementClassesConfig = customClassesConfig[parentElementName]; - const isEnabled = parentElementAttributesConfig || parentElementClassesConfig; + const parentElementAttributesConfig = getCustomAttributesElementConfig(parentElementName); + const parentElementClassesConfig = getCustomClassesElementConfig(parentElementName); + const isEnabled = !this.isTableColumnSelected(parentElementName) && (parentElementAttributesConfig || parentElementClassesConfig); this.isEnabled = !!isEnabled; diff --git a/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-editing.js b/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-editing.js index 059a22c5..eb71e362 100644 --- a/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-editing.js +++ b/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-editing.js @@ -4,6 +4,12 @@ import Widget from '@ckeditor/ckeditor5-widget/src/widget'; import IbexaCustomAttributesCommand from './custom-attributes-command'; import { getCustomAttributesConfig, getCustomClassesConfig } from './helpers/config-helper'; +const configElementsMapping = { + li: 'listItem', + tr: 'tableRow', + td: 'tableCell', +}; + class IbexaCustomAttributesEditing extends Plugin { static get requires() { return [Widget]; @@ -126,8 +132,10 @@ class IbexaCustomAttributesEditing extends Plugin { } extendSchema(schema, element, definition) { - if (schema.getDefinition(element)) { - schema.extend(element, definition); + const resolvedElement = configElementsMapping[element] ?? element; + + if (schema.getDefinition(resolvedElement)) { + schema.extend(resolvedElement, definition); } else { console.warn(`Schema does not have '${element}' element`); } @@ -193,4 +201,4 @@ class IbexaCustomAttributesEditing extends Plugin { } } -export default IbexaCustomAttributesEditing; +export { IbexaCustomAttributesEditing as default, configElementsMapping }; diff --git a/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-ui.js b/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-ui.js index a1c83670..33fba98c 100644 --- a/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-ui.js +++ b/src/bundle/Resources/public/js/CKEditor/custom-attributes/custom-attributes-ui.js @@ -4,7 +4,7 @@ import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsid import IbexaCustomAttributesFormView from './ui/custom-attributes-form-view'; import IbexaButtonView from '../common/button-view/button-view'; -import { getCustomAttributesConfig, getCustomClassesConfig } from './helpers/config-helper'; +import { getCustomAttributesElementConfig, getCustomClassesElementConfig } from './helpers/config-helper'; const { Translator } = window; @@ -40,6 +40,10 @@ class IbexaAttributesUI extends Plugin { const prefix = this.getAttributePrefix(); modelElements.forEach((modelElement) => { + if (this.editor.isListSelected && this.editor.listIndent !== modelElement.getAttribute('listIndent')) { + return; + } + writer.setAttribute(`${prefix}${name}`, value, modelElement); }); }); @@ -59,6 +63,10 @@ class IbexaAttributesUI extends Plugin { const prefix = this.getAttributePrefix(); modelElements.forEach((modelElement) => { + if (this.editor.isListSelected && this.editor.listIndent !== modelElement.getAttribute('listIndent')) { + return; + } + writer.removeAttribute(`${prefix}${name}`, modelElement); }); }); @@ -81,8 +89,6 @@ class IbexaAttributesUI extends Plugin { showForm() { const parentElement = this.getModelElement(); - const customAttributesConfig = getCustomAttributesConfig(); - const customClassesConfig = getCustomClassesConfig(); const prefix = this.getAttributePrefix(); let parentElementName = parentElement.name; @@ -98,8 +104,8 @@ class IbexaAttributesUI extends Plugin { } } - const customAttributes = customAttributesConfig[parentElementName] ?? {}; - const customClasses = customClassesConfig[parentElementName]; + const customAttributes = getCustomAttributesElementConfig(parentElementName) ?? {}; + const customClasses = getCustomClassesElementConfig(parentElementName); const areCustomAttributesSet = parentElement.hasAttribute(`${prefix}custom-classes`) || Object.keys(customAttributes).some((customAttributeName) => parentElement.hasAttribute(`${prefix}${customAttributeName}`)); diff --git a/src/bundle/Resources/public/js/CKEditor/custom-attributes/helpers/config-helper.js b/src/bundle/Resources/public/js/CKEditor/custom-attributes/helpers/config-helper.js index d323bf41..861a84b7 100644 --- a/src/bundle/Resources/public/js/CKEditor/custom-attributes/helpers/config-helper.js +++ b/src/bundle/Resources/public/js/CKEditor/custom-attributes/helpers/config-helper.js @@ -1,5 +1,11 @@ +import { configElementsMapping } from '../custom-attributes-editing'; + const headingsList = ['heading1', 'heading2', 'heading3', 'heading4', 'heading5', 'heading6']; +const findConfigName = (elementName) => { + const configName = Object.entries(configElementsMapping).find(([, value]) => value === elementName); + return configName?.[0] ?? elementName; +}; const getCustomAttributesConfig = () => { const attributes = { ...window.ibexa.richText.alloyEditor.attributes }; @@ -17,7 +23,12 @@ const getCustomAttributesConfig = () => { return attributes; }; +const getCustomAttributesElementConfig = (elementName) => { + const config = getCustomAttributesConfig(); + const configName = findConfigName(elementName); + return config[configName]; +}; const getCustomClassesConfig = () => { const classes = { ...window.ibexa.richText.alloyEditor.classes }; @@ -35,5 +46,17 @@ const getCustomClassesConfig = () => { return classes; }; +const getCustomClassesElementConfig = (elementName) => { + const config = getCustomClassesConfig(); + const configName = findConfigName(elementName); -export { getCustomAttributesConfig, getCustomClassesConfig }; + return config[configName]; +}; + +export { + getCustomAttributesConfig, + getCustomClassesConfig, + getCustomAttributesElementConfig, + getCustomClassesElementConfig, + findConfigName, +}; diff --git a/src/bundle/Resources/public/js/CKEditor/plugins/elements-path.js b/src/bundle/Resources/public/js/CKEditor/plugins/elements-path.js index 40752e4e..b662af92 100644 --- a/src/bundle/Resources/public/js/CKEditor/plugins/elements-path.js +++ b/src/bundle/Resources/public/js/CKEditor/plugins/elements-path.js @@ -6,11 +6,33 @@ class IbexaElementsPath extends Plugin { super(props); this.elementsPathWrapper = null; + this.isTableCellSelected = false; this.updatePath = this.updatePath.bind(this); } - addListItem(element) { + findElementSiblings(element, siblingsName) { + let iterator = element; + const elementSiblings = [element]; + + while (iterator?.previousSibling?.name === siblingsName) { + elementSiblings.unshift(iterator.previousSibling); + + iterator = iterator.previousSibling; + } + + iterator = element; + + while (iterator?.nextSibling?.name === siblingsName) { + elementSiblings.push(iterator.nextSibling); + + iterator = iterator.nextSibling; + } + + return elementSiblings; + } + + addListItem(element, index) { const label = Translator.trans(/*@Desc("list")*/ 'elements_path.list.label', {}, 'ck_editor'); const pathItem = `
  • ${label}
  • `; const container = document.createElement('ul'); @@ -22,16 +44,9 @@ class IbexaElementsPath extends Plugin { listItemNode.addEventListener( 'click', () => { - let firstElement = element; - let lastElement = element; - - while (firstElement?.previousSibling?.name === 'listItem') { - firstElement = firstElement.previousSibling; - } - - while (lastElement?.nextSibling?.name === 'listItem') { - lastElement = lastElement.nextSibling; - } + const elementSiblings = this.findElementSiblings(element, 'listItem'); + const firstElement = elementSiblings.find((elementSibling) => elementSibling.getAttribute('listIndent') === index); + const lastElement = elementSiblings.findLast((elementSibling) => elementSibling.getAttribute('listIndent') === index); const range = this.editor.model.createRange( this.editor.model.createPositionBefore(firstElement), @@ -39,11 +54,15 @@ class IbexaElementsPath extends Plugin { ); this.editor.isListSelected = true; + this.editor.listIndent = index; + this.editor.model.change((writer) => writer.setSelection(range)); this.editor.focus(); this.editor.model.document.selection.once('change', () => { this.editor.isListSelected = false; + + delete this.editor.listIndent; }); }, false, @@ -58,7 +77,11 @@ class IbexaElementsPath extends Plugin { } if (element.name === 'listItem') { - this.addListItem(element); + const listIndent = element.getAttribute('listIndent'); + + for (let i = 0; i <= listIndent; i++) { + this.addListItem(element, i); + } } const pathItem = `
  • ${element.name}
  • `; @@ -71,7 +94,11 @@ class IbexaElementsPath extends Plugin { listItemNode.addEventListener( 'click', () => { - this.editor.model.change((writer) => writer.setSelection(element, 'in')); + this.isTableCellSelected = element.name === 'tableCell'; + + const placement = this.isTableCellSelected ? 'on' : 'in'; + + this.editor.model.change((writer) => writer.setSelection(element, placement)); this.editor.focus(); }, false, @@ -87,6 +114,12 @@ class IbexaElementsPath extends Plugin { this.elementsPathWrapper.innerHTML = ''; this.editor.model.document.selection.getFirstPosition().getAncestors().forEach(this.updatePath); + + if (this.isTableCellSelected) { + this.updatePath(this.editor.model.document.selection.getSelectedElement()); + + this.isTableCellSelected = false; + } }); } }