Skip to content

Commit

Permalink
Merge pull request #13837 from ckeditor/ck/11578-remove-style-classes…
Browse files Browse the repository at this point in the history
…-when-creating-new-paragraph

Fix (html-support): Remove HTML classes from paragraph created after pressing enter in heading. Closes #11578.
  • Loading branch information
arkflpc authored Apr 17, 2023
2 parents 97e7815 + 7da9f8a commit 77091ec
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 81 deletions.
2 changes: 1 addition & 1 deletion packages/ckeditor5-engine/src/model/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ export default class Writer {
* @param element The element to rename.
* @param newName New element name.
*/
public rename( element: Element, newName: string ): void {
public rename( element: Element | DocumentFragment, newName: string ): void {
this._assertWriterUsedCorrectly();

if ( !( element instanceof Element ) ) {
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-enter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
export { default as Enter } from './enter';
export { default as ShiftEnter } from './shiftenter';
export type { ViewDocumentEnterEvent } from './enterobserver';
export type { default as EnterCommand } from './entercommand';
export type { default as EnterCommand, EnterCommandAfterExecuteEvent } from './entercommand';
export type { default as ShiftEnterCommand } from './shiftentercommand';

import './augmentation';
3 changes: 2 additions & 1 deletion packages/ckeditor5-heading/src/headingediting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { Plugin, type Editor } from 'ckeditor5/src/core';
import { Paragraph } from 'ckeditor5/src/paragraph';
import { priorities } from 'ckeditor5/src/utils';
import type { EnterCommandAfterExecuteEvent } from 'ckeditor5/src/enter';
import type { HeadingOption } from './headingconfig';

import HeadingCommand from './headingcommand';
Expand Down Expand Up @@ -94,7 +95,7 @@ export default class HeadingEditing extends Plugin {
const options: Array<HeadingOption> = editor.config.get( 'heading.options' )!;

if ( enterCommand ) {
this.listenTo( enterCommand, 'afterExecute', ( evt, data ) => {
this.listenTo<EnterCommandAfterExecuteEvent>( enterCommand, 'afterExecute', ( evt, data ) => {
const positionParent = editor.model.document.selection.getFirstPosition()!.parent;
const isHeading = options.some( option => positionParent.is( 'element', option.model ) );

Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-html-support/src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
mergeViewElementAttributes,
updateViewAttributes,
type GHSViewAttributes
} from './conversionutils';
} from './utils';
import type DataFilter from './datafilter';
import type { DataSchemaBlockElementDefinition, DataSchemaDefinition, DataSchemaInlineElementDefinition } from './dataschema';

Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-html-support/src/datafilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
type DataSchemaDefinition,
type DataSchemaInlineElementDefinition
} from './dataschema';
import type { GHSViewAttributes } from './conversionutils';
import type { GHSViewAttributes } from './utils';

import { isPlainObject, pull as removeItemFromArray } from 'lodash-es';

Expand Down
62 changes: 1 addition & 61 deletions packages/ckeditor5-html-support/src/generalhtmlsupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import DocumentListElementSupport from './integrations/documentlist';
import CustomElementSupport from './integrations/customelement';
import type { DataSchemaInlineElementDefinition } from './dataschema';
import type { DocumentSelection, Item, Model, Range, Selectable, Writer } from 'ckeditor5/src/engine';
import type { GHSViewAttributes } from './conversionutils';
import { modifyGhsAttribute } from './utils';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { GeneralHtmlSupportConfig } from './generalhtmlsupportconfig';

Expand Down Expand Up @@ -267,63 +267,3 @@ function getValidRangesForSelectable(
return model.schema.getValidRanges( model.createSelection( selectable ).getRanges(), ghsAttributeName );
}
}

interface CallbackMap {
classes: ( t: Set<string> ) => void;
attributes: ( t: Map<string, unknown> ) => void;
styles: ( t: Map<string, string> ) => void;
}

/**
* Updates a GHS attribute on a specified item.
* @param callback That receives a map or set as an argument and should modify it (add or remove entries).
*/
function modifyGhsAttribute<T extends keyof GHSViewAttributes>(
writer: Writer,
item: Item | DocumentSelection,
ghsAttributeName: string,
subject: T,
callback: CallbackMap[T]
) {
const oldValue = item.getAttribute( ghsAttributeName ) as Record<string, any>;
const newValue: Record<string, any> = {};

for ( const kind of [ 'attributes', 'styles', 'classes' ] as Array<T> ) {
// Properties other than `subject` should be assigned from `oldValue`.
if ( kind != subject ) {
if ( oldValue && oldValue[ kind ] ) {
newValue[ kind ] = oldValue[ kind ];
}
continue;
}
// `callback` should be applied on property [`subject`].
if ( subject == 'classes' ) {
const values = new Set<string>( oldValue && oldValue.classes || [] );
( callback as CallbackMap['classes'] )( values );
if ( values.size ) {
newValue[ kind ] = Array.from( values ) as GHSViewAttributes[T];
}
continue;
}

const values = new Map<string, unknown>( Object.entries( oldValue && oldValue[ kind ] || {} ) );
( callback as CallbackMap['attributes'] )( values );
if ( values.size ) {
newValue[ kind ] = Object.fromEntries( values ) as GHSViewAttributes[T];
}
}

if ( Object.keys( newValue ).length ) {
if ( item.is( 'documentSelection' ) ) {
writer.setSelectionAttribute( ghsAttributeName, newValue );
} else {
writer.setAttribute( ghsAttributeName, newValue, item );
}
} else if ( oldValue ) {
if ( item.is( 'documentSelection' ) ) {
writer.removeSelectionAttribute( ghsAttributeName );
} else {
writer.removeAttribute( ghsAttributeName, item );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
} from 'ckeditor5/src/engine';
import { Plugin } from 'ckeditor5/src/core';

import { updateViewAttributes, type GHSViewAttributes } from '../conversionutils';
import { updateViewAttributes, type GHSViewAttributes } from '../utils';
import DataFilter, { type DataFilterRegisterEvent } from '../datafilter';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { UpcastWriter, type ViewDocumentFragment, type ViewNode } from 'ckeditor

import DataSchema from '../dataschema';
import DataFilter, { type DataFilterRegisterEvent } from '../datafilter';
import { type GHSViewAttributes, setViewAttributes } from '../conversionutils';
import { type GHSViewAttributes, setViewAttributes } from '../utils';

/**
* Provides the General HTML Support for custom elements (not registered in the {@link module:html-support/dataschema~DataSchema}).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {
DocumentListIndentCommand
} from '@ckeditor/ckeditor5-list';

import { type GHSViewAttributes, setViewAttributes } from '../conversionutils';
import { type GHSViewAttributes, setViewAttributes } from '../utils';
import DataFilter, { type DataFilterRegisterEvent } from '../datafilter';

/**
Expand Down
48 changes: 42 additions & 6 deletions packages/ckeditor5-html-support/src/integrations/heading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@
* @module html-support/integrations/heading
*/

import { Plugin } from 'ckeditor5/src/core';
import { Plugin, type Editor } from 'ckeditor5/src/core';
import type { Item } from 'ckeditor5/src/engine';
import type { HeadingOption } from '@ckeditor/ckeditor5-heading';
import {
Enter,
type EnterCommand,
type EnterCommandAfterExecuteEvent
} from 'ckeditor5/src/enter';

import DataSchema from '../dataschema';
import { modifyGhsAttribute } from '../utils';

/**
* Provides the General HTML Support integration with {@link module:heading/heading~Heading Heading} feature.
Expand All @@ -20,7 +27,7 @@ export default class HeadingElementSupport extends Plugin {
* @inheritDoc
*/
public static get requires() {
return [ DataSchema ] as const;
return [ DataSchema, Enter ] as const;
}

/**
Expand All @@ -40,12 +47,19 @@ export default class HeadingElementSupport extends Plugin {
return;
}

const dataSchema = editor.plugins.get( DataSchema );
const options: Array<HeadingOption> = editor.config.get( 'heading.options' )!;
const headerModels = [];

// We are registering all elements supported by HeadingEditing
// to enable custom attributes for those elements.
this.registerHeadingElements( editor, options );
this.removeClassesOnEnter( editor, options );
}

/**
* Registers all elements supported by HeadingEditing to enable custom attributes for those elements.
*/
private registerHeadingElements( editor: Editor, options: Array<HeadingOption> ) {
const dataSchema = editor.plugins.get( DataSchema );

const headerModels = [];
for ( const option of options ) {
if ( 'model' in option && 'view' in option ) {
dataSchema.registerBlockElement( {
Expand All @@ -64,4 +78,26 @@ export default class HeadingElementSupport extends Plugin {
}
} );
}

/**
* Removes css classes from "htmlAttributes" of new paragraph created when hitting "enter" in heading.
*/
private removeClassesOnEnter( editor: Editor, options: Array<HeadingOption> ): void {
const enterCommand: EnterCommand = editor.commands.get( 'enter' )!;

this.listenTo<EnterCommandAfterExecuteEvent>( enterCommand, 'afterExecute', ( evt, data ) => {
const positionParent = editor.model.document.selection.getFirstPosition()!.parent;
const isHeading = options.some( option => positionParent.is( 'element', option.model ) );

if ( isHeading && positionParent.childCount === 0 ) {
modifyGhsAttribute(
data.writer,
positionParent as Item,
'htmlAttributes',
'classes',
classes => classes.clear()
);
}
} );
}
}
2 changes: 1 addition & 1 deletion packages/ckeditor5-html-support/src/integrations/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
ViewElement } from 'ckeditor5/src/engine';

import DataFilter, { type DataFilterRegisterEvent } from '../datafilter';
import { type GHSViewAttributes, setViewAttributes, updateViewAttributes } from '../conversionutils';
import { type GHSViewAttributes, setViewAttributes, updateViewAttributes } from '../utils';
import { getDescendantElement } from './integrationutils';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Plugin } from 'ckeditor5/src/core';

import DataFilter, { type DataFilterRegisterEvent } from '../datafilter';
import DataSchema from '../dataschema';
import { updateViewAttributes, type GHSViewAttributes } from '../conversionutils';
import { updateViewAttributes, type GHSViewAttributes } from '../utils';
import type {
DowncastAttributeEvent,
DowncastDispatcher,
Expand Down
2 changes: 1 addition & 1 deletion packages/ckeditor5-html-support/src/integrations/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
UpcastElementEvent,
ViewElement } from 'ckeditor5/src/engine';
import { Plugin } from 'ckeditor5/src/core';
import { setViewAttributes, type GHSViewAttributes } from '../conversionutils';
import { setViewAttributes, type GHSViewAttributes } from '../utils';
import DataFilter, { type DataFilterRegisterEvent } from '../datafilter';
import { getDescendantElement } from './integrationutils';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
* @module html-support/conversionutils
*/

import type { DowncastWriter, ViewElement } from 'ckeditor5/src/engine';
import type {
DocumentSelection,
DowncastWriter,
Item,
ViewElement,
Writer
} from 'ckeditor5/src/engine';
import { cloneDeep } from 'lodash-es';

export interface GHSViewAttributes {
Expand Down Expand Up @@ -107,3 +113,94 @@ export function mergeViewElementAttributes( target: GHSViewAttributes, source: G

return result;
}

type ModifyGhsAttributesCallback = ( t: Map<string, unknown> ) => void;
type ModifyGhsClassesCallback = ( t: Set<string> ) => void;
type ModifyGhsStylesCallback = ( t: Map<string, string> ) => void;

/**
* Updates a GHS attribute on a specified item.
* @param callback That receives a map as an argument and should modify it (add or remove entries).
*/
export function modifyGhsAttribute(
writer: Writer,
item: Item | DocumentSelection,
ghsAttributeName: string,
subject: 'attributes',
callback: ModifyGhsAttributesCallback
): void;

/**
* Updates a GHS attribute on a specified item.
* @param callback That receives a set as an argument and should modify it (add or remove entries).
*/
export function modifyGhsAttribute(
writer: Writer,
item: Item | DocumentSelection,
ghsAttributeName: string,
subject: 'classes',
callback: ModifyGhsClassesCallback
): void;

/**
* Updates a GHS attribute on a specified item.
* @param callback That receives a map as an argument and should modify it (add or remove entries).
*/
export function modifyGhsAttribute(
writer: Writer,
item: Item | DocumentSelection,
ghsAttributeName: string,
subject: 'styles',
callback: ModifyGhsStylesCallback
): void;

export function modifyGhsAttribute(
writer: Writer,
item: Item | DocumentSelection,
ghsAttributeName: string,
subject: 'attributes' | 'styles' | 'classes',
callback: ModifyGhsClassesCallback | ModifyGhsAttributesCallback | ModifyGhsStylesCallback
): void {
const oldValue = item.getAttribute( ghsAttributeName ) as Record<string, any>;
const newValue: Record<string, any> = {};

for ( const kind of [ 'attributes', 'styles', 'classes' ] ) {
// Properties other than `subject` should be assigned from `oldValue`.
if ( kind != subject ) {
if ( oldValue && oldValue[ kind ] ) {
newValue[ kind ] = oldValue[ kind ];
}
continue;
}

// `callback` should be applied on property [`subject`].
if ( subject == 'classes' ) {
const values = new Set<string>( oldValue && oldValue.classes || [] );
( callback as ModifyGhsClassesCallback )( values );
if ( values.size ) {
newValue[ kind ] = Array.from( values );
}
continue;
}

const values = new Map<string, any>( Object.entries( oldValue && oldValue[ kind ] || {} ) );
( callback as ( ModifyGhsAttributesCallback | ModifyGhsStylesCallback ) )( values );
if ( values.size ) {
newValue[ kind ] = Object.fromEntries( values );
}
}

if ( Object.keys( newValue ).length ) {
if ( item.is( 'documentSelection' ) ) {
writer.setSelectionAttribute( ghsAttributeName, newValue );
} else {
writer.setAttribute( ghsAttributeName, newValue, item );
}
} else if ( oldValue ) {
if ( item.is( 'documentSelection' ) ) {
writer.removeSelectionAttribute( ghsAttributeName );
} else {
writer.removeAttribute( ghsAttributeName, item );
}
}
}
2 changes: 1 addition & 1 deletion packages/ckeditor5-html-support/tests/_utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-util
* @returns {String} result.data The stringified data.
* @returns {Object} result.attributes Indexed data attributes.
*/
export function getModelDataWithAttributes( model, options ) {
export function getModelDataWithAttributes( model, options = {} ) {
// Simplify GHS attributes as they are not very readable at this point due to object structure.
let counter = 1;
const data = getModelData( model, options ).replace( /(html.*?)="{.*?}"/g, ( fullMatch, attributeName ) => {
Expand Down
Loading

0 comments on commit 77091ec

Please sign in to comment.