Skip to content

Commit

Permalink
Merge pull request #11391 from ckeditor/ck/11284-document-list-ghs
Browse files Browse the repository at this point in the history
Feature (html-support): Adds support for document list in GHS. Closes #11454. Closes #11359. Closes #11358.
  • Loading branch information
arkflpc authored Mar 30, 2022
2 parents ff38725 + c45f85d commit 24f77dc
Show file tree
Hide file tree
Showing 36 changed files with 1,483 additions and 573 deletions.
4 changes: 3 additions & 1 deletion packages/ckeditor5-html-support/src/generalhtmlsupport.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import MediaEmbedElementSupport from './integrations/mediaembed';
import ScriptElementSupport from './integrations/script';
import TableElementSupport from './integrations/table';
import StyleElementSupport from './integrations/style';
import DocumentListElementSupport from './integrations/documentlist';

/**
* The General HTML Support feature.
Expand Down Expand Up @@ -48,7 +49,8 @@ export default class GeneralHtmlSupport extends Plugin {
MediaEmbedElementSupport,
ScriptElementSupport,
TableElementSupport,
StyleElementSupport
StyleElementSupport,
DocumentListElementSupport
];
}

Expand Down
189 changes: 189 additions & 0 deletions packages/ckeditor5-html-support/src/integrations/documentlist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module html-support/integrations/documentlist
*/

import { isEqual } from 'lodash-es';
import { Plugin } from 'ckeditor5/src/core';
import { setViewAttributes } from '../conversionutils.js';

import DataFilter from '../datafilter';

/**
* Provides the General HTML Support integration with {@link module:list/documentlist~DocumentList Document List} feature.
*
* @extends module:core/plugin~Plugin
*/
export default class DocumentListElementSupport extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ DataFilter ];
}

/**
* @inheritDoc
*/
init() {
const editor = this.editor;

if ( !editor.plugins.has( 'DocumentListEditing' ) ) {
return;
}

const schema = editor.model.schema;
const conversion = editor.conversion;
const dataFilter = editor.plugins.get( DataFilter );
const documentListEditing = editor.plugins.get( 'DocumentListEditing' );

// Register downcast strategy.
// Note that this must be done before document list editing registers conversion in afterInit.
documentListEditing.registerDowncastStrategy( {
scope: 'item',
attributeName: 'htmlLiAttributes',

setAttributeOnDowncast( writer, attributeValue, viewElement ) {
setViewAttributes( writer, attributeValue, viewElement );
}
} );

documentListEditing.registerDowncastStrategy( {
scope: 'list',
attributeName: 'htmlListAttributes',

setAttributeOnDowncast( writer, viewAttributes, viewElement ) {
setViewAttributes( writer, viewAttributes, viewElement );
}
} );

dataFilter.on( 'register', ( evt, definition ) => {
if ( ![ 'ul', 'ol', 'li' ].includes( definition.view ) ) {
return;
}

evt.stop();

// Do not register same converters twice.
if ( schema.checkAttribute( '$block', 'htmlListAttributes' ) ) {
return;
}

schema.extend( '$block', { allowAttributes: [ 'htmlListAttributes', 'htmlLiAttributes' ] } );
schema.extend( '$blockObject', { allowAttributes: [ 'htmlListAttributes', 'htmlLiAttributes' ] } );
schema.extend( '$container', { allowAttributes: [ 'htmlListAttributes', 'htmlLiAttributes' ] } );

conversion.for( 'upcast' ).add( dispatcher => {
dispatcher.on( 'element:ul', viewToModelListAttributeConverter( 'htmlListAttributes', dataFilter ), { priority: 'low' } );
dispatcher.on( 'element:ol', viewToModelListAttributeConverter( 'htmlListAttributes', dataFilter ), { priority: 'low' } );
dispatcher.on( 'element:li', viewToModelListAttributeConverter( 'htmlLiAttributes', dataFilter ), { priority: 'low' } );
} );
} );

// Reset list attributes after indenting list items.
this.listenTo( editor.commands.get( 'indentList' ), 'afterExecute', ( evt, changedBlocks ) => {
editor.model.change( writer => {
for ( const node of changedBlocks ) {
// Just reset the attribute.
// If there is a previous indented list that this node should be merged into,
// the postfixer will unify all the attributes of both sub-lists.
writer.setAttribute( 'htmlListAttributes', {}, node );
}
} );
} );

// Make sure that all items in a single list (items at the same level & listType) have the same properties.
// Note: This is almost exact copy from DocumentListPropertiesEditing.
documentListEditing.on( 'postFixer', ( evt, { listNodes, writer } ) => {
const previousNodesByIndent = []; // Last seen nodes of lower indented lists.

for ( const { node, previous } of listNodes ) {
// For the first list block there is nothing to compare with.
if ( !previous ) {
continue;
}

const nodeIndent = node.getAttribute( 'listIndent' );
const previousNodeIndent = previous.getAttribute( 'listIndent' );

let previousNodeInList = null; // It's like `previous` but has the same indent as current node.

// Let's find previous node for the same indent.
// We're going to need that when we get back to previous indent.
if ( nodeIndent > previousNodeIndent ) {
previousNodesByIndent[ previousNodeIndent ] = previous;
}
// Restore the one for given indent.
else if ( nodeIndent < previousNodeIndent ) {
previousNodeInList = previousNodesByIndent[ nodeIndent ];
previousNodesByIndent.length = nodeIndent;
}
// Same indent.
else {
previousNodeInList = previous;
}

// This is a first item of a nested list.
if ( !previousNodeInList ) {
continue;
}

if ( previousNodeInList.getAttribute( 'listType' ) == node.getAttribute( 'listType' ) ) {
const value = previousNodeInList.getAttribute( 'htmlListAttributes' );

if ( !isEqual( node.getAttribute( 'htmlListAttributes' ), value ) ) {
writer.setAttribute( 'htmlListAttributes', value, node );
evt.return = true;
}
}

if ( previousNodeInList.getAttribute( 'listItemId' ) == node.getAttribute( 'listItemId' ) ) {
const value = previousNodeInList.getAttribute( 'htmlLiAttributes' );

if ( !isEqual( node.getAttribute( 'htmlLiAttributes' ), value ) ) {
writer.setAttribute( 'htmlLiAttributes', value, node );
evt.return = true;
}
}
}
} );
}
}

// View-to-model conversion helper preserving allowed attributes on {@link TODO}
// feature model element.
//
// @private
// @param {String} attributeName
// @param {module:html-support/datafilter~DataFilter} dataFilter
// @returns {Function} Returns a conversion callback.
function viewToModelListAttributeConverter( attributeName, dataFilter ) {
return ( evt, data, conversionApi ) => {
const viewElement = data.viewItem;

if ( !data.modelRange ) {
Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
}

const viewAttributes = dataFilter._consumeAllowedAttributes( viewElement, conversionApi );

for ( const item of data.modelRange.getItems( { shallow: true } ) ) {
// Apply only to list item blocks.
if ( !item.hasAttribute( 'listItemId' ) ) {
continue;
}

// Set list attributes only on same level items, those nested deeper are already handled
// by the recursive conversion.
if ( item.hasAttribute( attributeName ) ) {
continue;
}

conversionApi.writer.setAttribute( attributeName, viewAttributes || {}, item );
}
};
}
Loading

0 comments on commit 24f77dc

Please sign in to comment.