Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ckeditor/ckeditor5-list

Package Overview
Dependencies
Maintainers
1
Versions
707
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-list - npm Package Compare versions

Comparing version 12.0.4 to 12.1.0

lang/translations/en-gb.po

22

CHANGELOG.md
Changelog
=========
## [12.1.0](https://github.com/ckeditor/ckeditor5-list/compare/v12.0.4...v12.1.0) (2019-08-26)
### Features
* Introduced to-do lists. Closes [ckeditor/ckeditor5#1434](https://github.com/ckeditor/ckeditor5/issues/1434). ([56a7a7a](https://github.com/ckeditor/ckeditor5-list/commit/56a7a7a))
### Bug fixes
* The UI buttons should be marked as toggleable for better assistive technologies support (see [ckeditor/ckeditor5#1403](https://github.com/ckeditor/ckeditor5/issues/1403)). ([bb12325](https://github.com/ckeditor/ckeditor5-list/commit/bb12325))
### Other changes
* The issue tracker for this package was moved to https://github.com/ckeditor/ckeditor5/issues. See [ckeditor/ckeditor5#1988](https://github.com/ckeditor/ckeditor5/issues/1988). ([5507ac6](https://github.com/ckeditor/ckeditor5-list/commit/5507ac6))
* Updated translations. ([10e296d](https://github.com/ckeditor/ckeditor5-list/commit/10e296d))
## [12.0.4](https://github.com/ckeditor/ckeditor5-list/compare/v12.0.3...v12.0.4) (2019-07-10)

@@ -16,3 +32,3 @@

The `@ckeditor/ckeditor5-indent` feature introduces the "indent" and "outdent" buttons which can be used to manipulate lists and other blocks.
* Updated translations. ([6c4b520](https://github.com/ckeditor/ckeditor5-list/commit/6c4b520))
* Updated translations. ([6c4b520](https://github.com/ckeditor/ckeditor5-list/commit/6c4b520))

@@ -24,3 +40,3 @@

* Updated translations. ([b7f3abc](https://github.com/ckeditor/ckeditor5-list/commit/b7f3abc))
* Updated translations. ([b7f3abc](https://github.com/ckeditor/ckeditor5-list/commit/b7f3abc))

@@ -32,3 +48,3 @@

* Updated translations. ([d595449](https://github.com/ckeditor/ckeditor5-list/commit/d595449))
* Updated translations. ([d595449](https://github.com/ckeditor/ckeditor5-list/commit/d595449))

@@ -35,0 +51,0 @@

5

lang/contexts.json
{
"Numbered List": "Toolbar button tooltip for the Numbered List feature.",
"Bulleted List": "Toolbar button tooltip for the Bulleted List feature."
}
"Bulleted List": "Toolbar button tooltip for the Bulleted List feature.",
"To-do List": "Toolbar button tooltip for the To-do List feature."
}
{
"name": "@ckeditor/ckeditor5-list",
"version": "12.0.4",
"version": "12.1.0",
"description": "Ordered and unordered lists feature to CKEditor 5.",

@@ -13,19 +13,21 @@ "keywords": [

"dependencies": {
"@ckeditor/ckeditor5-core": "^12.2.1",
"@ckeditor/ckeditor5-engine": "^13.2.1",
"@ckeditor/ckeditor5-paragraph": "^11.0.4",
"@ckeditor/ckeditor5-ui": "^13.0.2",
"@ckeditor/ckeditor5-utils": "^13.0.1"
"@ckeditor/ckeditor5-core": "^12.3.0",
"@ckeditor/ckeditor5-engine": "^14.0.0",
"@ckeditor/ckeditor5-paragraph": "^11.0.5",
"@ckeditor/ckeditor5-ui": "^14.0.0",
"@ckeditor/ckeditor5-utils": "^14.0.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "^11.1.3",
"@ckeditor/ckeditor5-block-quote": "^11.1.2",
"@ckeditor/ckeditor5-clipboard": "^12.0.1",
"@ckeditor/ckeditor5-editor-classic": "^12.1.3",
"@ckeditor/ckeditor5-enter": "^11.0.4",
"@ckeditor/ckeditor5-heading": "^11.0.4",
"@ckeditor/ckeditor5-indent": "^10.0.1",
"@ckeditor/ckeditor5-link": "^11.1.1",
"@ckeditor/ckeditor5-typing": "^12.1.1",
"@ckeditor/ckeditor5-undo": "^11.0.4",
"@ckeditor/ckeditor5-basic-styles": "^11.1.4",
"@ckeditor/ckeditor5-block-quote": "^11.1.3",
"@ckeditor/ckeditor5-clipboard": "^12.0.2",
"@ckeditor/ckeditor5-editor-classic": "^12.1.4",
"@ckeditor/ckeditor5-enter": "^11.1.0",
"@ckeditor/ckeditor5-heading": "^11.0.5",
"@ckeditor/ckeditor5-highlight": "^11.0.5",
"@ckeditor/ckeditor5-indent": "^10.1.0",
"@ckeditor/ckeditor5-link": "^11.1.2",
"@ckeditor/ckeditor5-table": "^14.0.0",
"@ckeditor/ckeditor5-typing": "^12.2.0",
"@ckeditor/ckeditor5-undo": "^11.0.5",
"eslint": "^5.5.0",

@@ -43,3 +45,3 @@ "eslint-config-ckeditor5": "^2.0.0",

"homepage": "https://ckeditor.com/ckeditor-5",
"bugs": "https://github.com/ckeditor/ckeditor5-list/issues",
"bugs": "https://github.com/ckeditor/ckeditor5/issues",
"repository": {

@@ -46,0 +48,0 @@ "type": "git",

@@ -12,3 +12,3 @@ CKEditor 5 list feature

This package implements bulleted and numbered list feature for CKEditor 5.
This package implements bulleted, numbered and to-do list feature for CKEditor 5.

@@ -15,0 +15,0 @@ ## Documentation

@@ -10,7 +10,13 @@ /**

import { createViewListItemElement } from './utils';
import {
generateLiInUl,
injectViewList,
mergeViewLists,
getSiblingListItem,
positionAfterUiElements
} from './utils';
import TreeWalker from '@ckeditor/ckeditor5-engine/src/model/treewalker';
/**
* A model-to-view converter for `listItem` model element insertion.
* A model-to-view converter for the `listItem` model element insertion.
*

@@ -47,3 +53,3 @@ * It creates a `<ul><li></li><ul>` (or `<ol>`) view structure out of a `listItem` model element, inserts it at the correct

/**
* A model-to-view converter for `listItem` model element removal.
* A model-to-view converter for the `listItem` model element removal.
*

@@ -91,7 +97,11 @@ * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:remove

/**
* A model-to-view converter for `type` attribute change on `listItem` model element.
* A model-to-view converter for the `type` attribute change on the `listItem` model element.
*
* This change means that `<li>` elements parent changes from `<ul>` to `<ol>` (or vice versa). This is accomplished
* by breaking view elements, changing their name and merging them.
* This change means that the `<li>` element parent changes from `<ul>` to `<ol>` (or vice versa). This is accomplished
* by breaking view elements and changing their name. The next {@link module:list/converters~modelViewMergeAfterChangeType}
* converter will attempt to merge split nodes.
*
* Splitting this conversion into 2 steps makes it possible to add an additional conversion in the middle.
* Check {@link module:list/todolistconverters~modelViewChangeType} to see an example of it.
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute

@@ -110,3 +120,3 @@ * @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.

// 1. Break the container after and before the list item.
// Break the container after and before the list item.
// This will create a view list with one view list item -- the one that changed type.

@@ -116,13 +126,28 @@ viewWriter.breakContainer( viewWriter.createPositionBefore( viewItem ) );

// 2. Change name of the view list that holds the changed view item.
// Change name of the view list that holds the changed view item.
// We cannot just change name property, because that would not render properly.
let viewList = viewItem.parent;
const viewList = viewItem.parent;
const listName = data.attributeNewValue == 'numbered' ? 'ol' : 'ul';
viewList = viewWriter.rename( listName, viewList );
// 3. Merge the changed view list with other lists, if possible.
viewWriter.rename( listName, viewList );
}
/**
* A model-to-view converter that attempts to merge nodes split by {@link module:list/converters~modelViewChangeType}.
*
* @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute
* @param {module:utils/eventinfo~EventInfo} evt An object containing information about the fired event.
* @param {Object} data Additional information about the change.
* @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi Conversion interface.
*/
export function modelViewMergeAfterChangeType( evt, data, conversionApi ) {
const viewItem = conversionApi.mapper.toViewElement( data.item );
const viewList = viewItem.parent;
const viewWriter = conversionApi.writer;
// Merge the changed view list with other lists, if possible.
mergeViewLists( viewWriter, viewList, viewList.nextSibling );
mergeViewLists( viewWriter, viewList.previousSibling, viewList );
// 4. Consumable insertion of children inside the item. They are already handled by re-building the item in view.
// Consumable insertion of children inside the item. They are already handled by re-building the item in view.
for ( const child of data.item.getChildren() ) {

@@ -134,3 +159,3 @@ conversionApi.consumable.consume( child, 'insert' );

/**
* A model-to-view converter for `listIndent` attribute change on `listItem` model element.
* A model-to-view converter for the `listIndent` attribute change on the `listItem` model element.
*

@@ -335,3 +360,3 @@ * @see module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:attribute

/**
* A view-to-model converter that converts `<li>` view elements into `listItem` model elements.
* A view-to-model converter that converts the `<li>` view elements into the `listItem` model elements.
*

@@ -396,4 +421,4 @@ * To set correct values of the `listType` and `listIndent` attributes the converter:

/**
* A view-to-model converter for `<ul>` and `<ol>` view elements that cleans the input view of garbage.
* This is mostly to clean whitespaces from between `<li>` view elements inside the view list element, however, also
* A view-to-model converter for the `<ul>` and `<ol>` view elements that cleans the input view of garbage.
* This is mostly to clean whitespaces from between the `<li>` view elements inside the view list element, however, also
* incorrect data can be cleared if the view was incorrect.

@@ -420,3 +445,3 @@ *

/**
* A view-to-model converter for `<li>` elements that cleans whitespace formatting from the input view.
* A view-to-model converter for the `<li>` elements that cleans whitespace formatting from the input view.
*

@@ -465,5 +490,5 @@ * @see module:engine/conversion/upcastdispatcher~UpcastDispatcher#event:element

/**
* Returns callback for model position to view position mapping for {@link module:engine/conversion/mapper~Mapper}. The callback fixes
* positions between `listItem` elements that would be incorrectly mapped because of how list items are represented in model
* and view.
* Returns a callback for model position to view position mapping for {@link module:engine/conversion/mapper~Mapper}. The callback fixes
* positions between the `listItem` elements that would be incorrectly mapped because of how list items are represented in the model
* and in the view.
*

@@ -504,4 +529,4 @@ * @see module:engine/conversion/mapper~Mapper#event:modelToViewPosition

* The callback for view position to model position mapping for {@link module:engine/conversion/mapper~Mapper}. The callback fixes
* positions between `<li>` elements that would be incorrectly mapped because of how list items are represented in model
* and view.
* positions between the `<li>` elements that would be incorrectly mapped because of how list items are represented in the model
* and in the view.
*

@@ -569,4 +594,4 @@ * @see module:engine/conversion/mapper~Mapper#event:viewToModelPosition

*
* In an example below, there is a correct list structure.
* Then the middle element will be removed so the list structure will become incorrect:
* In the example below, there is a correct list structure.
* Then the middle element is removed so the list structure will become incorrect:
*

@@ -577,3 +602,3 @@ * <listItem listType="bulleted" listIndent=0>Item 1</listItem>

*
* List structure after the middle element removed:
* The list structure after the middle element is removed:
*

@@ -800,19 +825,2 @@ * <listItem listType="bulleted" listIndent=0>Item 1</listItem>

// Helper function that creates a `<ul><li></li></ul>` or (`<ol>`) structure out of given `modelItem` model `listItem` element.
// Then, it binds created view list item (<li>) with model `listItem` element.
// The function then returns created view list item (<li>).
function generateLiInUl( modelItem, conversionApi ) {
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
const listType = modelItem.getAttribute( 'listType' ) == 'numbered' ? 'ol' : 'ul';
const viewItem = createViewListItemElement( viewWriter );
const viewList = viewWriter.createContainerElement( listType, null );
viewWriter.insert( viewWriter.createPositionAt( viewList, 0 ), viewItem );
mapper.bindElements( modelItem, viewItem );
return viewItem;
}
// Helper function that converts children of a given `<li>` view element into corresponding model elements.

@@ -905,130 +913,2 @@ // The function maintains proper order of elements if model `listItem` is split during the conversion

// Helper function that seeks for a previous list item sibling of given model item which meets given criteria.
// `options` object may contain one or more of given values (by default they are `false`):
// `options.sameIndent` - whether sought sibling should have same indent (default = no),
// `options.smallerIndent` - whether sought sibling should have smaller indent (default = no).
// `options.listIndent` - the reference indent.
// Either `options.sameIndent` or `options.smallerIndent` should be set to `true`.
function getSiblingListItem( modelItem, options ) {
const sameIndent = !!options.sameIndent;
const smallerIndent = !!options.smallerIndent;
const indent = options.listIndent;
let item = modelItem;
while ( item && item.name == 'listItem' ) {
const itemIndent = item.getAttribute( 'listIndent' );
if ( ( sameIndent && indent == itemIndent ) || ( smallerIndent && indent > itemIndent ) ) {
return item;
}
item = item.previousSibling;
}
return null;
}
// Helper function that takes two parameters, that are expected to be view list elements, and merges them.
// The merge happen only if both parameters are UL or OL elements.
function mergeViewLists( viewWriter, firstList, secondList ) {
if ( firstList && secondList && ( firstList.name == 'ul' || firstList.name == 'ol' ) && firstList.name == secondList.name ) {
return viewWriter.mergeContainers( viewWriter.createPositionAfter( firstList ) );
}
return null;
}
// Helper function that takes model list item element `modelItem`, corresponding view list item element `injectedItem`
// that is not added to the view and is inside a view list element (`ul` or `ol`) and is that's list only child.
// The list is inserted at correct position (element breaking may be needed) and then merged with it's siblings.
// See comments below to better understand the algorithm.
function injectViewList( modelItem, injectedItem, conversionApi, model ) {
const injectedList = injectedItem.parent;
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
// Position where view list will be inserted.
let insertPosition = mapper.toViewPosition( model.createPositionBefore( modelItem ) );
// 1. Find previous list item that has same or smaller indent. Basically we are looking for a first model item
// that is "parent" or "sibling" of injected model item.
// If there is no such list item, it means that injected list item is the first item in "its list".
const refItem = getSiblingListItem( modelItem.previousSibling, {
sameIndent: true,
smallerIndent: true,
listIndent: modelItem.getAttribute( 'listIndent' )
} );
const prevItem = modelItem.previousSibling;
if ( refItem && refItem.getAttribute( 'listIndent' ) == modelItem.getAttribute( 'listIndent' ) ) {
// There is a list item with same indent - we found same-level sibling.
// Break the list after it. Inserted view item will be inserted in the broken space.
const viewItem = mapper.toViewElement( refItem );
insertPosition = viewWriter.breakContainer( viewWriter.createPositionAfter( viewItem ) );
} else {
// There is no list item with same indent. Check previous model item.
if ( prevItem && prevItem.name == 'listItem' ) {
// If it is a list item, it has to have lower indent.
// It means that inserted item should be added to it as its nested item.
insertPosition = mapper.toViewPosition( model.createPositionAt( prevItem, 'end' ) );
} else {
// Previous item is not a list item (or does not exist at all).
// Just map the position and insert the view item at mapped position.
insertPosition = mapper.toViewPosition( model.createPositionBefore( modelItem ) );
}
}
insertPosition = positionAfterUiElements( insertPosition );
// Insert the view item.
viewWriter.insert( insertPosition, injectedList );
// 2. Handle possible children of injected model item.
if ( prevItem && prevItem.name == 'listItem' ) {
const prevView = mapper.toViewElement( prevItem );
const walkerBoundaries = viewWriter.createRange( viewWriter.createPositionAt( prevView, 0 ), insertPosition );
const walker = walkerBoundaries.getWalker( { ignoreElementEnd: true } );
for ( const value of walker ) {
if ( value.item.is( 'li' ) ) {
const breakPosition = viewWriter.breakContainer( viewWriter.createPositionBefore( value.item ) );
const viewList = value.item.parent;
const targetPosition = viewWriter.createPositionAt( injectedItem, 'end' );
mergeViewLists( viewWriter, targetPosition.nodeBefore, targetPosition.nodeAfter );
viewWriter.move( viewWriter.createRangeOn( viewList ), targetPosition );
walker.position = breakPosition;
}
}
} else {
const nextViewList = injectedList.nextSibling;
if ( nextViewList && ( nextViewList.is( 'ul' ) || nextViewList.is( 'ol' ) ) ) {
let lastSubChild = null;
for ( const child of nextViewList.getChildren() ) {
const modelChild = mapper.toModelElement( child );
if ( modelChild && modelChild.getAttribute( 'listIndent' ) > modelItem.getAttribute( 'listIndent' ) ) {
lastSubChild = child;
} else {
break;
}
}
if ( lastSubChild ) {
viewWriter.breakContainer( viewWriter.createPositionAfter( lastSubChild ) );
viewWriter.move( viewWriter.createRangeOn( lastSubChild.parent ), viewWriter.createPositionAt( injectedItem, 'end' ) );
}
}
}
// Merge inserted view list with its possible neighbour lists.
mergeViewLists( viewWriter, injectedList, injectedList.nextSibling );
mergeViewLists( viewWriter, injectedList.previousSibling, injectedList );
}
// Helper function that takes all children of given `viewRemovedItem` and moves them in a correct place, according

@@ -1130,10 +1010,1 @@ // to other given parameters.

}
// Helper function that for given `view.Position`, returns a `view.Position` that is after all `view.UIElement`s that
// are after given position.
// For example:
// <container:p>foo^<ui:span></ui:span><ui:span></ui:span>bar</contain:p>
// For position ^, a position before "bar" will be returned.
function positionAfterUiElements( viewPosition ) {
return viewPosition.getLastMatchingPosition( value => value.item.is( 'uiElement' ) );
}

@@ -47,3 +47,3 @@ /**

/**
* Indents or outdents (depends on the {@link #constructor}'s `indentDirection` parameter) selected list items.
* Indents or outdents (depending on the {@link #constructor}'s `indentDirection` parameter) selected list items.
*

@@ -50,0 +50,0 @@ * @fires execute

@@ -18,3 +18,3 @@ /**

*
* This is a "glue" plugin which loads the {@link module:list/listediting~ListEditing list editing feature}
* This is a "glue" plugin that loads the {@link module:list/listediting~ListEditing list editing feature}
* and {@link module:list/listui~ListUI list UI feature}.

@@ -21,0 +21,0 @@ *

@@ -32,5 +32,5 @@ /**

* @readonly
* @member {'numbered'|'bulleted'}
* @member {'numbered'|'bulleted'|'todo'}
*/
this.type = type == 'bulleted' ? 'bulleted' : 'numbered';
this.type = type;

@@ -37,0 +37,0 @@ /**

@@ -21,2 +21,3 @@ /**

modelViewChangeType,
modelViewMergeAfterChangeType,
modelViewMergeAfter,

@@ -81,11 +82,8 @@ modelViewRemove,

editing.downcastDispatcher.on( 'attribute:listType:listItem', modelViewChangeType );
data.downcastDispatcher.on( 'attribute:listType:listItem', modelViewChangeType );
editing.downcastDispatcher.on( 'attribute:listType:listItem', modelViewChangeType, { priority: 'high' } );
editing.downcastDispatcher.on( 'attribute:listType:listItem', modelViewMergeAfterChangeType, { priority: 'low' } );
editing.downcastDispatcher.on( 'attribute:listIndent:listItem', modelViewChangeIndent( editor.model ) );
data.downcastDispatcher.on( 'attribute:listIndent:listItem', modelViewChangeIndent( editor.model ) );
editing.downcastDispatcher.on( 'remove:listItem', modelViewRemove( editor.model ) );
editing.downcastDispatcher.on( 'remove', modelViewMergeAfter, { priority: 'low' } );
data.downcastDispatcher.on( 'remove:listItem', modelViewRemove( editor.model ) );
data.downcastDispatcher.on( 'remove', modelViewMergeAfter, { priority: 'low' } );

@@ -108,3 +106,3 @@ data.upcastDispatcher.on( 'element:ul', cleanList, { priority: 'high' } );

const viewDocument = this.editor.editing.view.document;
const viewDocument = editing.view.document;

@@ -178,2 +176,5 @@ // Overwrite default Enter key behavior.

/**
* @inheritDoc
*/
afterInit() {

@@ -180,0 +181,0 @@ const commands = this.editor.commands;

@@ -10,2 +10,4 @@ /**

import { createUIComponent } from './utils';
import numberedListIcon from '../theme/icons/numberedlist.svg';

@@ -15,3 +17,2 @@ import bulletedListIcon from '../theme/icons/bulletedlist.svg';

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';

@@ -29,39 +30,8 @@ /**

init() {
// Create two buttons and link them with numberedList and bulletedList commands.
const t = this.editor.t;
this._addButton( 'numberedList', t( 'Numbered List' ), numberedListIcon );
this._addButton( 'bulletedList', t( 'Bulleted List' ), bulletedListIcon );
}
/**
* Helper method for initializing a button and linking it with an appropriate command.
*
* @private
* @param {String} commandName The name of the command.
* @param {Object} label The button label.
* @param {String} icon The source of the icon.
*/
_addButton( commandName, label, icon ) {
const editor = this.editor;
editor.ui.componentFactory.add( commandName, locale => {
const command = editor.commands.get( commandName );
const buttonView = new ButtonView( locale );
buttonView.set( {
label,
icon,
tooltip: true
} );
// Bind button model to command.
buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute command.
this.listenTo( buttonView, 'execute', () => editor.execute( commandName ) );
return buttonView;
} );
// Create two buttons and link them with numberedList and bulletedList commands.
createUIComponent( this.editor, 'numberedList', t( 'Numbered List' ), numberedListIcon );
createUIComponent( this.editor, 'bulletedList', t( 'Bulleted List' ), bulletedListIcon );
}
}

@@ -11,5 +11,6 @@ /**

import { getFillerOffset } from '@ckeditor/ckeditor5-engine/src/view/containerelement';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
/**
* Creates list item {@link module:engine/view/containerelement~ContainerElement}.
* Creates a list item {@link module:engine/view/containerelement~ContainerElement}.
*

@@ -21,2 +22,3 @@ * @param {module:engine/view/downcastwriter~DowncastWriter} writer The writer instance.

const viewItem = writer.createContainerElement( 'li' );
viewItem.getFillerOffset = getListItemFillerOffset;

@@ -27,2 +29,235 @@

/**
* Helper function that creates a `<ul><li></li></ul>` or (`<ol>`) structure out of the given `modelItem` model `listItem` element.
* Then, it binds the created view list item (<li>) with the model `listItem` element.
* The function then returns the created view list item (<li>).
*
* @param {module:engine/model/item~Item} modelItem Model list item.
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface.
* @returns {module:engine/view/containerelement~ContainerElement} View list element.
*/
export function generateLiInUl( modelItem, conversionApi ) {
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
const listType = modelItem.getAttribute( 'listType' ) == 'numbered' ? 'ol' : 'ul';
const viewItem = createViewListItemElement( viewWriter );
const viewList = viewWriter.createContainerElement( listType, null );
viewWriter.insert( viewWriter.createPositionAt( viewList, 0 ), viewItem );
mapper.bindElements( modelItem, viewItem );
return viewItem;
}
/**
* Helper function that inserts a view list at a correct place and merges it with its siblings.
* It takes a model list item element (`modelItem`) and a corresponding view list item element (`injectedItem`). The view list item
* should be in a view list element (`<ul>` or `<ol>`) and should be its only child.
* See comments below to better understand the algorithm.
*
* @param {module:engine/view/item~Item} modelItem Model list item.
* @param {module:engine/view/containerelement~ContainerElement} injectedItem
* @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion interface.
* @param {module:engine/model/model~Model} model The model instance.
*/
export function injectViewList( modelItem, injectedItem, conversionApi, model ) {
const injectedList = injectedItem.parent;
const mapper = conversionApi.mapper;
const viewWriter = conversionApi.writer;
// The position where the view list will be inserted.
let insertPosition = mapper.toViewPosition( model.createPositionBefore( modelItem ) );
// 1. Find the previous list item that has the same or smaller indent. Basically we are looking for the first model item
// that is a "parent" or "sibling" of the injected model item.
// If there is no such list item, it means that the injected list item is the first item in "its list".
const refItem = getSiblingListItem( modelItem.previousSibling, {
sameIndent: true,
smallerIndent: true,
listIndent: modelItem.getAttribute( 'listIndent' )
} );
const prevItem = modelItem.previousSibling;
if ( refItem && refItem.getAttribute( 'listIndent' ) == modelItem.getAttribute( 'listIndent' ) ) {
// There is a list item with the same indent - we found the same-level sibling.
// Break the list after it. The inserted view item will be added in the broken space.
const viewItem = mapper.toViewElement( refItem );
insertPosition = viewWriter.breakContainer( viewWriter.createPositionAfter( viewItem ) );
} else {
// There is no list item with the same indent. Check the previous model item.
if ( prevItem && prevItem.name == 'listItem' ) {
// If it is a list item, it has to have a lower indent.
// It means that the inserted item should be added to it as its nested item.
insertPosition = mapper.toViewPosition( model.createPositionAt( prevItem, 'end' ) );
} else {
// The previous item is not a list item (or does not exist at all).
// Just map the position and insert the view item at the mapped position.
insertPosition = mapper.toViewPosition( model.createPositionBefore( modelItem ) );
}
}
insertPosition = positionAfterUiElements( insertPosition );
// Insert the view item.
viewWriter.insert( insertPosition, injectedList );
// 2. Handle possible children of the injected model item.
if ( prevItem && prevItem.name == 'listItem' ) {
const prevView = mapper.toViewElement( prevItem );
const walkerBoundaries = viewWriter.createRange( viewWriter.createPositionAt( prevView, 0 ), insertPosition );
const walker = walkerBoundaries.getWalker( { ignoreElementEnd: true } );
for ( const value of walker ) {
if ( value.item.is( 'li' ) ) {
const breakPosition = viewWriter.breakContainer( viewWriter.createPositionBefore( value.item ) );
const viewList = value.item.parent;
const targetPosition = viewWriter.createPositionAt( injectedItem, 'end' );
mergeViewLists( viewWriter, targetPosition.nodeBefore, targetPosition.nodeAfter );
viewWriter.move( viewWriter.createRangeOn( viewList ), targetPosition );
walker.position = breakPosition;
}
}
} else {
const nextViewList = injectedList.nextSibling;
if ( nextViewList && ( nextViewList.is( 'ul' ) || nextViewList.is( 'ol' ) ) ) {
let lastSubChild = null;
for ( const child of nextViewList.getChildren() ) {
const modelChild = mapper.toModelElement( child );
if ( modelChild && modelChild.getAttribute( 'listIndent' ) > modelItem.getAttribute( 'listIndent' ) ) {
lastSubChild = child;
} else {
break;
}
}
if ( lastSubChild ) {
viewWriter.breakContainer( viewWriter.createPositionAfter( lastSubChild ) );
viewWriter.move( viewWriter.createRangeOn( lastSubChild.parent ), viewWriter.createPositionAt( injectedItem, 'end' ) );
}
}
}
// Merge the inserted view list with its possible neighbor lists.
mergeViewLists( viewWriter, injectedList, injectedList.nextSibling );
mergeViewLists( viewWriter, injectedList.previousSibling, injectedList );
}
/**
* Helper function that takes two parameters that are expected to be view list elements, and merges them.
* The merge happens only if both parameters are list elements of the same type (the same element name and the same class attributes).
*
* @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter The writer instance.
* @param {module:engine/view/item~Item} firstList The first element to compare.
* @param {module:engine/view/item~Item} secondList The second element to compare.
* @returns {module:engine/view/position~Position|null} The position after merge or `null` when there was no merge.
*/
export function mergeViewLists( viewWriter, firstList, secondList ) {
// Check if two lists are going to be merged.
if ( !firstList || !secondList || ( firstList.name != 'ul' && firstList.name != 'ol' ) ) {
return null;
}
// Both parameters are list elements, so compare types now.
if ( firstList.name != secondList.name || firstList.getAttribute( 'class' ) !== secondList.getAttribute( 'class' ) ) {
return null;
}
return viewWriter.mergeContainers( viewWriter.createPositionAfter( firstList ) );
}
/**
* Helper function that for a given `view.Position`, returns a `view.Position` that is after all `view.UIElement`s that
* are after the given position.
*
* For example:
* `<container:p>foo^<ui:span></ui:span><ui:span></ui:span>bar</container:p>`
* For position ^, the position before "bar" will be returned.
*
* @param {module:engine/view/position~Position} viewPosition
* @returns {module:engine/view/position~Position}
*/
export function positionAfterUiElements( viewPosition ) {
return viewPosition.getLastMatchingPosition( value => value.item.is( 'uiElement' ) );
}
/**
* Helper function that searches for a previous list item sibling of a given model item that meets the given criteria
* passed by the options object.
*
* @param {module:engine/model/item~Item} modelItem
* @param {Object} options Search criteria.
* @param {Boolean} [options.sameIndent=false] Whether the sought sibling should have the same indentation.
* @param {Boolean} [options.smallerIndent=false] Whether the sought sibling should have a smaller indentation.
* @param {Number} [options.listIndent] The reference indentation.
* @returns {module:engine/model/item~Item|null}
*/
export function getSiblingListItem( modelItem, options ) {
const sameIndent = !!options.sameIndent;
const smallerIndent = !!options.smallerIndent;
const indent = options.listIndent;
let item = modelItem;
while ( item && item.name == 'listItem' ) {
const itemIndent = item.getAttribute( 'listIndent' );
if ( ( sameIndent && indent == itemIndent ) || ( smallerIndent && indent > itemIndent ) ) {
return item;
}
item = item.previousSibling;
}
return null;
}
export function findInRange( range, comparator ) {
for ( const item of range.getItems() ) {
const result = comparator( item );
if ( result ) {
return result;
}
}
}
/**
* Helper method for creating a UI button and linking it with an appropriate command.
*
* @private
* @param {module:core/editor/editor~Editor} editor The editor instance to which the UI component will be added.
* @param {String} commandName The name of the command.
* @param {Object} label The button label.
* @param {String} icon The source of the icon.
*/
export function createUIComponent( editor, commandName, label, icon ) {
editor.ui.componentFactory.add( commandName, locale => {
const command = editor.commands.get( commandName );
const buttonView = new ButtonView( locale );
buttonView.set( {
label,
icon,
tooltip: true,
isToggleable: true
} );
// Bind button model to command.
buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
// Execute command.
buttonView.on( 'execute', () => editor.execute( commandName ) );
return buttonView;
} );
}
// Implementation of getFillerOffset for view list item element.

@@ -29,0 +264,0 @@ //

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc