Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

To-do lists in document lists #14700

Merged
merged 62 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
3fe0d1c
Initial PoC of to-do lists in document lists.
niegowski Jul 31, 2023
21448e3
Extend to-do list upcast.
niegowski Aug 4, 2023
d3d8e8c
The to-do list item should be properly upcasted even if wrapped with …
niegowski Aug 10, 2023
aa63392
Merge branch 'master' into ck/14663-to-do-lists-poc
niegowski Aug 17, 2023
4705bf3
Renamed Todo plugin to TodoDocumentList.
niegowski Aug 17, 2023
56b2251
Added command to toggle to-do list items.
niegowski Aug 17, 2023
76a59fb
Allow converting any block type to a to-do list item.
niegowski Aug 18, 2023
8205c7f
WiP.
niegowski Aug 18, 2023
f8799ab
Merge branch 'master' into ck/14663-to-do-lists-poc
niegowski Aug 25, 2023
3df3875
Introducing a generic list item marker downcast strategy (PoC).
niegowski Aug 25, 2023
6386b7c
Review fixes.
niegowski Aug 28, 2023
2045bea
Code cleanup and refactoring.
niegowski Aug 28, 2023
e71aeff
Generic strategies for downcast item marker.
niegowski Aug 28, 2023
b735dab
Reverted changes in GHS coupled attributes.
niegowski Aug 28, 2023
b5fb187
Code cleanup.
niegowski Aug 28, 2023
9c3eb52
Cleaning manual test.
niegowski Aug 28, 2023
3a0ffeb
Fixed Blink glitch.
niegowski Aug 28, 2023
f4a7309
Adding tests.
niegowski Aug 28, 2023
717e5bb
First block of to-do list item should be wrapped in description span …
niegowski Aug 29, 2023
2621462
Review fixes.
niegowski Aug 30, 2023
a1d530f
Checkbox should not be allowed between blocks of a single list item.
niegowski Aug 31, 2023
8e83b68
Code cleaning.
niegowski Sep 1, 2023
97bec24
Handle arrow keys before a checkbox.
niegowski Sep 1, 2023
e89e4c6
Added code comments and doclets.
niegowski Sep 1, 2023
05289c2
Updated manual tests.
niegowski Sep 4, 2023
d9ea416
Merge branch 'master' into ck/14663-to-do-lists-poc
niegowski Sep 4, 2023
0cb7df6
WiP css.
niegowski Sep 5, 2023
2fabd69
Unified to-do list styles.
niegowski Sep 6, 2023
aa9238e
Merge branch 'master' into ck/14663-to-do-lists-poc
niegowski Sep 7, 2023
75f0c75
The label element can't be rendered in the editing view because it in…
niegowski Sep 7, 2023
1be5de4
Fixed RTL styles.
niegowski Sep 8, 2023
b993cc7
Handling view positions inside the checkbox wrappers.
niegowski Sep 12, 2023
b9cfab7
Merge remote-tracking branch 'origin/ck/14663-to-do-lists-poc' into o…
mmotyczynska Sep 12, 2023
60c913d
Tests: todo document list conversion (wip).
mmotyczynska Sep 12, 2023
a62a386
Merge branch 'master' into ck/14663-to-do-lists-poc
niegowski Sep 12, 2023
b313a69
Merge branch 'origin/ck/14663-to-do-lists-poc-tests' into ck/14663-to…
mmotyczynska Sep 12, 2023
9fcd90e
Adding tests.
niegowski Sep 13, 2023
f163b70
Tests: todo document list command (wip).
mmotyczynska Sep 14, 2023
0ec1f70
Adding tests.
niegowski Sep 14, 2023
d641ec9
Adding tests.
niegowski Sep 15, 2023
b44ed26
Tests: check todo document list command.
mmotyczynska Sep 15, 2023
153877a
Tests: todo checkbox change observer.
mmotyczynska Sep 15, 2023
40c7511
Adding tests.
niegowski Sep 18, 2023
da290f5
Added tests.
niegowski Sep 18, 2023
2419eb2
Adding tests.
niegowski Sep 18, 2023
7b20945
Tests: todo list item postfixers and user interactions.
mmotyczynska Sep 18, 2023
00d0db9
Fixed checkbox toggling.
niegowski Sep 19, 2023
f13f328
Tests: arrow keys in todo list items.
mmotyczynska Sep 19, 2023
02a8e61
Added test.
niegowski Sep 19, 2023
636ec2c
Adding tests.
niegowski Sep 20, 2023
17c31ed
Adding tests.
niegowski Sep 20, 2023
f10cda7
Adding tests.
niegowski Sep 20, 2023
3795143
Added missing comments.
niegowski Sep 20, 2023
f00e6fc
Fixed test.
niegowski Sep 20, 2023
9724e17
Fixed white-list of allowed attributes on code-block.
niegowski Sep 20, 2023
23434fc
Fixed to-do list item alignment.
niegowski Sep 20, 2023
7b183f3
Adding tests.
niegowski Sep 20, 2023
0374893
Removed the checkbox toggling animation to avoid glitches while reusi…
niegowski Sep 20, 2023
aa4b2bf
Fixed tests.
niegowski Sep 20, 2023
960220d
The inline html elements should not be formatted in source editing (i…
niegowski Sep 20, 2023
4bb162e
Merge branch 'master' into ck/14663-to-do-lists-poc
niegowski Sep 20, 2023
7e4b6f6
Apply suggestions from code review.
arkflpc Sep 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions packages/ckeditor5-html-support/src/datafilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,11 @@ export default class DataFilter extends Plugin {
super( editor );

this._dataSchema = editor.plugins.get( 'DataSchema' );

this._allowedAttributes = new Matcher();

this._disallowedAttributes = new Matcher();

this._allowedElements = new Set();

this._disallowedElements = new Set();

this._dataInitialized = false;

this._coupledAttributes = null;

this._registerElementsAfterInit();
Expand Down
40 changes: 6 additions & 34 deletions packages/ckeditor5-html-support/src/integrations/documentlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,36 +107,8 @@ export default class DocumentListElementSupport extends Plugin {
} );

// Make sure that all items in a single list (items at the same level & listType) have the same properties.
// Note: This is almost an exact copy from DocumentListPropertiesEditing.
documentListEditing.on<DocumentListEditingPostFixerEvent>( '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;
}

for ( const { node, previousNodeInList } of listNodes ) {
// This is a first item of a nested list.
if ( !previousNodeInList ) {
continue;
Expand Down Expand Up @@ -174,7 +146,7 @@ export default class DocumentListElementSupport extends Plugin {
for ( const { node } of listNodes ) {
const listType = node.getAttribute( 'listType' );

if ( listType === 'bulleted' && node.getAttribute( 'htmlOlAttributes' ) ) {
if ( listType !== 'numbered' && node.getAttribute( 'htmlOlAttributes' ) ) {
writer.removeAttribute( 'htmlOlAttributes', node );
evt.return = true;
}
Expand Down Expand Up @@ -256,8 +228,8 @@ function viewToModelListAttributeConverter( attributeName: string, dataFilter: D
/**
* Returns HTML attribute name based on provided list type.
*/
function getAttributeFromListType( listType: 'bulleted' | 'numbered' ) {
return listType === 'bulleted' ?
'htmlUlAttributes' :
'htmlOlAttributes';
function getAttributeFromListType( listType: 'bulleted' | 'numbered' | 'todo' ) {
return listType === 'numbered' ?
'htmlOlAttributes' :
'htmlUlAttributes';
}
11 changes: 8 additions & 3 deletions packages/ckeditor5-list/src/augmentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import type {
TodoList,
TodoListEditing,
TodoListUI,
TodoDocumentList,
TodoDocumentListEditing,

ListCommand,
DocumentListCommand,
Expand All @@ -36,7 +38,8 @@ import type {
DocumentListStartCommand,
ListReversedCommand,
DocumentListReversedCommand,
CheckTodoListCommand
CheckTodoListCommand,
CheckTodoDocumentListCommand
} from '.';

declare module '@ckeditor/ckeditor5-core' {
Expand Down Expand Up @@ -69,6 +72,8 @@ declare module '@ckeditor/ckeditor5-core' {
[ TodoList.pluginName ]: TodoList;
[ TodoListEditing.pluginName ]: TodoListEditing;
[ TodoListUI.pluginName ]: TodoListUI;
[ TodoDocumentList.pluginName ]: TodoDocumentList;
[ TodoDocumentListEditing.pluginName ]: TodoDocumentListEditing;
}

interface CommandsMap {
Expand All @@ -83,7 +88,7 @@ declare module '@ckeditor/ckeditor5-core' {
listStyle: ListStyleCommand | DocumentListStyleCommand;
listStart: ListStartCommand | DocumentListStartCommand;
listReversed: ListReversedCommand | DocumentListReversedCommand;
todoList: ListCommand;
checkTodoList: CheckTodoListCommand;
todoList: ListCommand | DocumentListCommand;
checkTodoList: CheckTodoListCommand | CheckTodoDocumentListCommand;
}
}
76 changes: 66 additions & 10 deletions packages/ckeditor5-list/src/documentlist/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
getAllListItemBlocks,
getListItemBlocks,
isListItemBlock,
isFirstBlockOfListItem,
ListItemUid,
type ListElement
} from './utils/model';
Expand All @@ -48,8 +49,8 @@ import { findAndAddListHeadToMap } from './utils/postfixers';
import type {
default as DocumentListEditing,
DocumentListEditingCheckAttributesEvent,
DowncastStrategy,
ListItemAttributesMap
ListItemAttributesMap,
DowncastStrategy
} from './documentlistediting';

/**
Expand All @@ -72,15 +73,26 @@ export function listItemUpcastConverter(): GetCallback<UpcastElementEvent> {
return;
}

const listItemId = ListItemUid.next();
const listIndent = getIndent( data.viewItem );
let listType = data.viewItem.parent && data.viewItem.parent.is( 'element', 'ol' ) ? 'numbered' : 'bulleted';

// Preserve list type if was already set (for example by to-do list feature).
const firstItemListType = items[ 0 ].getAttribute( 'listType' ) as string;

if ( firstItemListType ) {
listType = firstItemListType;
}

const attributes = {
listItemId: ListItemUid.next(),
listIndent: getIndent( data.viewItem ),
listType: data.viewItem.parent && data.viewItem.parent.is( 'element', 'ol' ) ? 'numbered' : 'bulleted'
listItemId,
listIndent,
listType
};

for ( const item of items ) {
// Set list attributes only on same level items, those nested deeper are already handled by the recursive conversion.
if ( !isListItemBlock( item ) ) {
if ( !item.hasAttribute( 'listItemId' ) ) {
writer.setAttributes( attributes, item );
}
}
Expand Down Expand Up @@ -323,7 +335,8 @@ export function reconvertItemsOnDataChange(
export function listItemDowncastConverter(
attributeNames: Array<string>,
strategies: Array<DowncastStrategy>,
model: Model
model: Model,
{ dataPipeline }: { dataPipeline?: boolean } = {}
): GetCallback<DowncastAttributeEvent<ListElement>> {
const consumer = createAttributesConsumer( attributeNames );

Expand All @@ -345,11 +358,51 @@ export function listItemDowncastConverter(
// This is for cases when mapping is using inner view element like in the code blocks (pre > code).
const viewElement = findMappedViewElement( listItem, mapper, model )!;

// Remove custom item markers.
let previousSibling = viewElement.previousSibling;

while ( previousSibling ) {
if ( !previousSibling.is( 'element' ) || !previousSibling.getCustomProperty( 'listItemMarker' ) ) {
break;
}

writer.remove( previousSibling );
previousSibling = viewElement.previousSibling;
}

// Unwrap element from current list wrappers.
unwrapListItemBlock( viewElement, writer );

let viewRange = writer.createRangeOn( viewElement );

// Insert custom item markers.
if ( isFirstBlockOfListItem( listItem ) ) {
for ( const strategy of strategies ) {
if ( strategy.scope == 'itemMarker' && listItem.hasAttribute( strategy.attributeName ) ) {
const markerElement = strategy.createElement(
writer,
listItem.getAttribute( strategy.attributeName ),
listItem,
{ dataPipeline }
);

if ( !markerElement ) {
continue;
}

writer.setCustomProperty( 'listItemMarker', true, markerElement );
writer.insert( viewRange.start, markerElement );

viewRange = writer.createRange(
writer.createPositionBefore( markerElement ),
writer.createPositionAfter( viewElement )
);
}
}
}

// Then wrap them with the new list wrappers.
wrapListItemBlock( listItem, writer.createRangeOn( viewElement ), strategies, writer );
wrapListItemBlock( listItem, viewRange, strategies, writer );
};
}

Expand Down Expand Up @@ -395,7 +448,7 @@ export function findMappedViewElement( element: Element, mapper: Mapper, model:
const modelRange = model.createRangeOn( element );
const viewRange = mapper.toViewRange( modelRange ).getTrimmed();

return viewRange.getContainedElement();
return viewRange.end.nodeBefore as ViewElement | null;
}

// Unwraps all ol, ul, and li attribute elements that are wrapping the provided view element.
Expand Down Expand Up @@ -430,7 +483,10 @@ function wrapListItemBlock(
const listViewElement = createListElement( writer, indent, currentListItem.getAttribute( 'listType' ) );

for ( const strategy of strategies ) {
if ( currentListItem.hasAttribute( strategy.attributeName ) ) {
if (
( strategy.scope == 'list' || strategy.scope == 'item' ) &&
currentListItem.hasAttribute( strategy.attributeName )
) {
strategy.setAttributeOnDowncast(
writer,
currentListItem.getAttribute( strategy.attributeName ),
Expand Down
12 changes: 7 additions & 5 deletions packages/ckeditor5-list/src/documentlist/documentlistcommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default class DocumentListCommand extends Command {
/**
* The type of the list created by the command.
*/
public readonly type: 'numbered' | 'bulleted';
public readonly type: 'numbered' | 'bulleted' | 'todo';

/**
* A flag indicating whether the command is active, which means that the selection starts in a list of the same type.
Expand All @@ -45,7 +45,7 @@ export default class DocumentListCommand extends Command {
* @param editor The editor instance.
* @param type List type that will be handled by this command.
*/
constructor( editor: Editor, type: 'numbered' | 'bulleted' ) {
constructor( editor: Editor, type: 'numbered' | 'bulleted' | 'todo' ) {
super( editor );

this.type = type;
Expand Down Expand Up @@ -100,7 +100,7 @@ export default class DocumentListCommand extends Command {

this._fireAfterExecute( changedBlocks );
}
// Turning on the list items for a collapsed selection inside a list item.
// Changing type of list items for a collapsed selection inside a list item.
else if ( ( selectedBlockObject || document.selection.isCollapsed ) && isListItemBlock( blocks[ 0 ] ) ) {
const changedBlocks = getListItems( selectedBlockObject || blocks[ 0 ] );

Expand Down Expand Up @@ -178,8 +178,10 @@ export default class DocumentListCommand extends Command {
* @returns Whether the command should be enabled.
*/
private _checkEnabled(): boolean {
const selection = this.editor.model.document.selection;
const schema = this.editor.model.schema;
const model = this.editor.model;
const schema = model.schema;
const selection = model.document.selection;

const blocks = Array.from( selection.getSelectedBlocks() );

if ( !blocks.length ) {
Expand Down
Loading