Skip to content

Commit

Permalink
IBX-6932: Fixed adding custom attrs to list (#139)
Browse files Browse the repository at this point in the history
* IBX-6932: Fixed adding custom attrs to list

* Corrected problem with listItem, Added remove custom classes and attributes on new item list

* Corrected clean custom classes

* fix

---------

Co-authored-by: matx132 <[email protected]>
  • Loading branch information
dew326 and mateuszdebinski authored Feb 14, 2024
1 parent efecf98 commit 058d349
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,24 @@ class IbexaCustomAttributesCommand extends Command {
refresh() {
const { selection } = this.editor.model.document;
const parentElement = selection.getSelectedElement() ?? selection.getFirstPosition().parent;
let parentElementName = parentElement.name;

if (this.editor.isListSelected) {
const mapping = {
bulleted: 'ul',
numbered: 'ol',
};
const listType = parentElement.getAttribute('listType');

if (mapping[listType]) {
parentElementName = mapping[listType];
}
}

const customAttributesConfig = getCustomAttributesConfig();
const customClassesConfig = getCustomClassesConfig();
const parentElementAttributesConfig = customAttributesConfig[parentElement.name];
const parentElementClassesConfig = customClassesConfig[parentElement.name];
const parentElementAttributesConfig = customAttributesConfig[parentElementName];
const parentElementClassesConfig = customClassesConfig[parentElementName];
const isEnabled = parentElementAttributesConfig || parentElementClassesConfig;

this.isEnabled = !!isEnabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,48 @@ class IbexaCustomAttributesEditing extends Plugin {
},
});

Object.values(customAttributesConfig).forEach((customAttributes) => {
Object.entries(customAttributesConfig).forEach(([element, customAttributes]) => {
const isList = element === 'ul' || element === 'ol';

Object.keys(customAttributes).forEach((customAttributeName) => {
if (isList) {
this.editor.conversion.for('dataDowncast').add((dispatcher) => {
dispatcher.on(`attribute:list-${customAttributeName}:listItem`, (event, data, conversionApi) => {
const viewItem = conversionApi.mapper.toViewElement(data.item);

conversionApi.writer.setAttribute(
`data-ezattribute-${customAttributeName}`,
data.attributeNewValue,
viewItem.parent,
);
});
});

this.editor.conversion.for('editingDowncast').add((dispatcher) => {
dispatcher.on(`attribute:list-${customAttributeName}:listItem`, (event, data, conversionApi) => {
const viewItem = conversionApi.mapper.toViewElement(data.item);

conversionApi.writer.setAttribute(
`data-ezattribute-${customAttributeName}`,
data.attributeNewValue,
viewItem.parent,
);
});
});

this.editor.conversion.for('upcast').add((dispatcher) => {
dispatcher.on('element:li', (event, data, conversionApi) => {
const listParent = data.viewItem.parent;
const listItem = data.modelRange.start.nodeAfter || data.modelRange.end.nodeBefore;
const attributeValue = listParent.getAttribute(`data-ezattribute-${customAttributeName}`);

conversionApi.writer.setAttribute(`list-${customAttributeName}`, attributeValue, listItem);
});
});

return;
}

conversion.attributeToAttribute({
model: {
key: customAttributeName,
Expand All @@ -34,6 +74,55 @@ class IbexaCustomAttributesEditing extends Plugin {
});
});
});

this.editor.conversion.for('dataDowncast').add((dispatcher) => {
dispatcher.on('attribute:list-custom-classes:listItem', (event, data, conversionApi) => {
if (data.attributeKey !== 'list-custom-classes' || data.attributeNewValue === '') {
return;
}

const viewItem = conversionApi.mapper.toViewElement(data.item);
const previousElement = viewItem.parent.previousSibling;

conversionApi.writer.setAttribute('class', data.attributeNewValue, viewItem.parent);

if (previousElement?.name === viewItem.parent.name) {
conversionApi.writer.mergeContainers(conversionApi.writer.createPositionAfter(previousElement));
}
});
});

this.editor.conversion.for('editingDowncast').add((dispatcher) => {
dispatcher.on('attribute:list-custom-classes:listItem', (event, data, conversionApi) => {
if (data.attributeKey !== 'list-custom-classes' || data.attributeNewValue === '') {
return;
}

const viewItem = conversionApi.mapper.toViewElement(data.item);
const previousElement = viewItem.parent.previousSibling;
const nextElement = viewItem.parent.nextSibling;

conversionApi.writer.setAttribute('class', data.attributeNewValue, viewItem.parent);

if (previousElement?.name === viewItem.parent.name) {
conversionApi.writer.mergeContainers(conversionApi.writer.createPositionAfter(previousElement));
}

if (nextElement?.name === viewItem.parent.name) {
conversionApi.writer.mergeContainers(conversionApi.writer.createPositionBefore(nextElement));
}
});
});

this.editor.conversion.for('upcast').add((dispatcher) => {
dispatcher.on('element:li', (event, data, conversionApi) => {
const listParent = data.viewItem.parent;
const listItem = data.modelRange.start.nodeAfter || data.modelRange.end.nodeBefore;
const classes = listParent.getAttribute('class');

conversionApi.writer.setAttribute('list-custom-classes', classes, listItem);
});
});
}

extendSchema(schema, element, definition) {
Expand All @@ -44,26 +133,63 @@ class IbexaCustomAttributesEditing extends Plugin {
}
}

init() {
cleanAttributes(element, customs) {
const { model } = this.editor;

Object.entries(customs).forEach(([elementName, config]) => {
if (elementName === element.name) {
return;
}

model.change((writer) => {
Object.keys(config).forEach((name) => {
writer.removeAttribute(name, element);
});
});
});
}

init() {
const { commands, model } = this.editor;
const customAttributesConfig = getCustomAttributesConfig();
const customClassesConfig = getCustomClassesConfig();
const elementsWithCustomAttributes = Object.keys(customAttributesConfig);
const elementsWithCustomClasses = Object.keys(customClassesConfig);

elementsWithCustomAttributes.forEach((element) => {
const isList = element === 'ul' || element === 'ol';
const prefix = isList ? 'list-' : '';
const elementName = isList ? 'listItem' : element;
const customAttributes = Object.keys(customAttributesConfig[element]);

this.extendSchema(model.schema, element, { allowAttributes: customAttributes });
customAttributes.forEach((customAttribute) => {
this.extendSchema(model.schema, elementName, { allowAttributes: `${prefix}${customAttribute}` });
});
});

elementsWithCustomClasses.forEach((element) => {
this.extendSchema(model.schema, element, { allowAttributes: 'custom-classes' });
const isList = element === 'ul' || element === 'ol';
const prefix = isList ? 'list-' : '';
const elementName = isList ? 'listItem' : element;

this.extendSchema(model.schema, elementName, { allowAttributes: `${prefix}custom-classes` });
});

this.defineConverters();

this.editor.commands.add('insertIbexaCustomAttributes', new IbexaCustomAttributesCommand(this.editor));
commands.get('enter').on('afterExecute', () => {
const blocks = model.document.selection.getSelectedBlocks();

for (const block of blocks) {
this.cleanAttributes(block, customAttributesConfig);

model.change((writer) => {
writer.removeAttribute('custom-classes', block);
});
}
});

commands.add('insertIbexaCustomAttributes', new IbexaCustomAttributesCommand(this.editor));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,26 @@ class IbexaAttributesUI extends Plugin {
return this.editor.model.document.selection.getSelectedElement() || this.editor.model.document.selection.anchor.parent;
}

getAttributePrefix() {
return this.editor.isListSelected ? 'list-' : '';
}

createFormView() {
const formView = new IbexaCustomAttributesFormView({ locale: this.editor.locale });

this.listenTo(formView, 'save-custom-attributes', () => {
const values = this.formView.getValues();
const modelElement = this.getModelElement();
const modelElements = this.editor.isListSelected
? Array.from(this.editor.model.document.selection.getSelectedBlocks())
: [this.getModelElement()];

this.editor.model.change((writer) => {
Object.entries(values).forEach(([name, value]) => {
writer.setAttribute(name, value, modelElement);
const prefix = this.getAttributePrefix();

modelElements.forEach((modelElement) => {
writer.setAttribute(`${prefix}${name}`, value, modelElement);
});
});
});

Expand All @@ -40,11 +50,17 @@ class IbexaAttributesUI extends Plugin {

this.listenTo(formView, 'remove-custom-attributes', () => {
const values = this.formView.getValues();
const modelElement = this.getModelElement();
const modelElements = this.editor.isListSelected
? Array.from(this.editor.model.document.selection.getSelectedBlocks())
: [this.getModelElement()];

this.editor.model.change((writer) => {
Object.keys(values).forEach((name) => {
writer.removeAttribute(name, modelElement);
const prefix = this.getAttributePrefix();

modelElements.forEach((modelElement) => {
writer.removeAttribute(`${prefix}${name}`, modelElement);
});
});
});

Expand All @@ -67,18 +83,33 @@ class IbexaAttributesUI extends Plugin {
const parentElement = this.getModelElement();
const customAttributesConfig = getCustomAttributesConfig();
const customClassesConfig = getCustomClassesConfig();
const customAttributes = customAttributesConfig[parentElement.name] ?? {};
const customClasses = customClassesConfig[parentElement.name];
const prefix = this.getAttributePrefix();
let parentElementName = parentElement.name;

if (this.editor.isListSelected) {
const mapping = {
bulleted: 'ul',
numbered: 'ol',
};
const listType = parentElement.getAttribute('listType');

if (mapping[listType]) {
parentElementName = mapping[listType];
}
}

const customAttributes = customAttributesConfig[parentElementName] ?? {};
const customClasses = customClassesConfig[parentElementName];
const areCustomAttributesSet =
parentElement.hasAttribute('custom-classes') ||
Object.keys(customAttributes).some((customAttributeName) => parentElement.hasAttribute(customAttributeName));
parentElement.hasAttribute(`${prefix}custom-classes`) ||
Object.keys(customAttributes).some((customAttributeName) => parentElement.hasAttribute(`${prefix}${customAttributeName}`));
const attributesValues = Object.entries(customAttributes).reduce((output, [name, config]) => {
output[name] = areCustomAttributesSet ? parentElement.getAttribute(name) : config.defaultValue;
output[name] = areCustomAttributesSet ? parentElement.getAttribute(`${prefix}${name}`) : config.defaultValue;

return output;
}, {});
const defaultCustomClasses = customClasses?.defaultValue ?? '';
const classesValue = areCustomAttributesSet ? parentElement.getAttribute('custom-classes') : defaultCustomClasses;
const classesValue = areCustomAttributesSet ? parentElement.getAttribute(`${prefix}custom-classes`) : defaultCustomClasses;

this.formView.destroy();
this.formView = this.createFormView();
Expand Down
47 changes: 47 additions & 0 deletions src/bundle/Resources/public/js/CKEditor/plugins/elements-path.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

const { Translator } = window;
class IbexaElementsPath extends Plugin {
constructor(props) {
super(props);
Expand All @@ -9,11 +10,57 @@ class IbexaElementsPath extends Plugin {
this.updatePath = this.updatePath.bind(this);
}

addListItem(element) {
const label = Translator.trans(/*@Desc("list")*/ 'elements_path.list.label', {}, 'ck_editor');
const pathItem = `<li class="ibexa-elements-path__item">${label}</li>`;
const container = document.createElement('ul');

container.insertAdjacentHTML('beforeend', pathItem);

const listItemNode = container.querySelector('li');

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 range = this.editor.model.createRange(
this.editor.model.createPositionBefore(firstElement),
this.editor.model.createPositionAfter(lastElement),
);

this.editor.isListSelected = true;
this.editor.model.change((writer) => writer.setSelection(range));
this.editor.focus();

this.editor.model.document.selection.once('change', () => {
this.editor.isListSelected = false;
});
},
false,
);

this.elementsPathWrapper.append(listItemNode);
}

updatePath(element) {
if (element.name === '$root') {
return;
}

if (element.name === 'listItem') {
this.addListItem(element);
}

const pathItem = `<li class="ibexa-elements-path__item">${element.name}</li>`;
const container = document.createElement('ul');

Expand Down
8 changes: 6 additions & 2 deletions src/bundle/Resources/public/scss/_elements-path.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
font-weight: bold;
flex-wrap: wrap;

&__item:not(:last-child) {
margin-right: calculateRem(16px);
&__item {
cursor: pointer;

&:not(:last-child) {
margin-right: calculateRem(16px);
}
}
}
5 changes: 5 additions & 0 deletions src/bundle/Resources/translations/ck_editor.en.xliff
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
<target state="new">Custom styles</target>
<note>key: custom_styles_btn.label</note>
</trans-unit>
<trans-unit id="3b257be2c6dd1f7bf0d5c70e64c22bf5b298dbbf" resname="elements_path.list.label">
<source>list</source>
<target state="new">list</target>
<note>key: elements_path.list.label</note>
</trans-unit>
<trans-unit id="5761446626d26974269cd5f7fb428e268928932b" resname="embed_btn.label">
<source>Embed</source>
<target state="new">Embed</target>
Expand Down

0 comments on commit 058d349

Please sign in to comment.