Skip to content

Commit

Permalink
fix: depth issues in BulletedList, TodoList and NumberedList (#418)
Browse files Browse the repository at this point in the history
* fix: depth issues in BulletedList with md serialize/deserialize; closes #345

* chore: remove console.log

* fix: list item children as list-item Descendant type

* update email examples (#391)

## Description

Added the ability to add Loom & Wistia videos in the Video plugin. Note that I'm not used to Typescrpt at all, so I might need some help with this if you see issues.

Fixes # (issue)

## Type of change

Please tick the relevant option.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have performed a self-review of my own code
- [ ] I have commented on my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have checked my code and corrected any misspellings

* Improvements for loom and wistia videos (#402)

* update email examples
## Description

Added the ability to add Loom & Wistia videos in the Video plugin. Note that I'm not used to Typescrpt at all, so I might need some help with this if you see issues.

Fixes # (issue)

## Type of change

Please tick the relevant option.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have performed a self-review of my own code
- [ ] I have commented on my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have checked my code and corrected any misspellings

* fixes for loom and wistia providers

---------

Co-authored-by: 5andu <[email protected]>

* Update deserialize.ts (#415)

* fix types

* add smooth selection between block

* fix: changes after review plus clean empty nodes

* fix: nested TodoList items

---------

Co-authored-by: Alexandru Golovatenco <[email protected]>
Co-authored-by: Akhmed Ibragimov <[email protected]>
Co-authored-by: 5andu <[email protected]>
Co-authored-by: Vaishnav Deore <[email protected]>
  • Loading branch information
5 people authored Dec 30, 2024
1 parent 96fc42e commit 16e4625
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 84 deletions.
1 change: 1 addition & 0 deletions packages/core/editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export { getRootBlockElementType, getRootBlockElement } from './utils/blockEleme
export { findPluginBlockByPath } from './utils/findPluginBlockByPath';
export { findSlateBySelectionPath } from './utils/findSlateBySelectionPath';
export { deserializeTextNodes } from './parsers/deserializeTextNodes';
export { deserializeListNodes } from './parsers/deserializeListNodes';
export { serializeTextNodes, serializeTextNodesIntoMarkdown } from './parsers/serializeTextNodes';

export { createYooptaEditor } from './editor';
Expand Down
138 changes: 138 additions & 0 deletions packages/core/editor/src/parsers/deserializeListNodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {generateId} from '../utils/generateId';
import {YooEditor, YooptaBlockData} from '../editor/types';
import {deserializeTextNodes} from './deserializeTextNodes';
import {Descendant} from 'slate';

type ListHTMLElement = HTMLUListElement | HTMLOListElement | Element;

type DeserializeListBlockOptions = {
depth?: number;
type: 'TodoList' | 'NumberedList' | 'BulletedList';
align: YooptaBlockData['meta']['align'];
};

// recursive function to parse nested lists and return list of Blocks
export function deserializeListNodes(
editor: YooEditor,
listEl: ListHTMLElement,
options: DeserializeListBlockOptions,
): YooptaBlockData[] {
const deserializedListBlocks: YooptaBlockData[] = [];
const depth = typeof options.depth === 'number' ? options.depth : 0;

if (listEl.nodeName === 'UL' || listEl.nodeName === 'OL') {
const listItems = Array.from(listEl.children).filter((el) => el.nodeName === 'LI');

// check if the list is a TodoList or not
const isTodoList = listItems.some((listItem) => {
const hasCheckbox = listItem.querySelector('input[type="checkbox"]');
const textContent = listItem.textContent || '';
const hasBrackets = /\[\s*[xX\s]?\s*\]/.test(textContent);
return hasCheckbox || hasBrackets;
});

if (isTodoList && options.type !== 'TodoList') {
return deserializedListBlocks;
}

if (!isTodoList && options.type === 'TodoList') {
return deserializedListBlocks;
}

listItems.forEach((listItem) => {
const textContent = listItem.textContent || '';
let blockData: YooptaBlockData | null = null;

if (options.type === 'TodoList') {
// check if the list item has a checkbox input or brackets `[]` in the text content
const checkbox = listItem.querySelector('input[type="checkbox"]') as HTMLInputElement | null;
const checked = checkbox ? checkbox.checked : /\[\s*[xX]\s*\]/.test(textContent);

const cleanText = textContent
.replace(/\[\s*[xX\s]?\s*\]/, '')
.replace(/\u00a0/g, ' ')
.trim();

blockData = {
id: generateId(),
type: 'TodoList',
value: [
{
id: generateId(),
type: 'todo-list',
children: deserializeTextNodes(editor, listItem.childNodes),
props: { nodeType: 'block', checked },
},
],
meta: { order: 0, depth, align: options.align },
};
} else {
const listType = options.type === 'NumberedList' ? 'numbered-list' : 'bulleted-list';
blockData = {
id: generateId(),
type: options.type,
value: [
{
id: generateId(),
type: listType,
children: deserializeTextNodes(editor, listItem.childNodes),
props: { nodeType: 'block' },
},
],
meta: { order: 0, depth, align: options.align },
};
}

if (blockData) {
const cleanedData = {
...blockData,
value: sanitizeTextNodes(blockData.value),
}

deserializedListBlocks.push(cleanedData);
}

const nestedLists = Array.from(listItem.children).filter((el) => (el.nodeName === 'UL' || el.nodeName === 'OL'));

// if the list item has nested lists, then parse them recursively by increasing the depth
if (nestedLists.length > 0) {
nestedLists.forEach((nestedList) => {
const nestedBlocks = deserializeListNodes(editor, nestedList as ListHTMLElement, {
...options,
depth: depth + 1,
});
deserializedListBlocks.push(...nestedBlocks);
});
}
});
}

return deserializedListBlocks;
}

function sanitizeTextNodes(descendants: Descendant[]): Descendant[] {
return descendants
.map((descendant) => {
if ('children' in descendant) {
// Recursively clean nested children
return {
...descendant,
children: sanitizeTextNodes(descendant.children),
};
}

// Only include text nodes that has content
if ('text' in descendant) {
// Clean text content for todo lists or empty text nodes
const cleanText = descendant.text
.replace(/\[\s*[xX\s]?\s*\]/, '') // Remove [] for todo lists
.replace(/\u00a0/g, ' ') // Replace non-breaking spaces
.trim();

return cleanText ? { ...descendant, text: cleanText } : null; // Remove if empty
}

return descendant;
})
.filter(Boolean) as Descendant[];
}
33 changes: 6 additions & 27 deletions packages/plugins/lists/src/plugin/BulletedList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
buildBlockData,
deserializeTextNodes,
deserializeListNodes,
generateId,
serializeTextNodes,
serializeTextNodesIntoMarkdown,
Expand Down Expand Up @@ -36,35 +36,13 @@ const BulletedList = new YooptaPlugin<Pick<ListElementMap, 'bulleted-list'>>({
nodeNames: ['UL'],
parse(el, editor) {
if (el.nodeName === 'UL') {
const listItems = el.querySelectorAll('li');

const align = (el.getAttribute('data-meta-align') || 'left') as YooptaBlockData['meta']['align'];
const depth = parseInt(el.getAttribute('data-meta-depth') || '0', 10);

const bulletListBlocks: YooptaBlockData[] = Array.from(listItems)
.filter((listItem) => {
const textContent = listItem.textContent || '';
const isTodoListItem = /\[\s*\S?\s*\]/.test(textContent);

return !isTodoListItem;
})
.map((listItem) => {
return buildBlockData({
id: generateId(),
type: 'BulletedList',
value: [
{
id: generateId(),
type: 'bulleted-list',
children: deserializeTextNodes(editor, listItem.childNodes),
props: { nodeType: 'block' },
},
],
meta: { order: 0, depth: depth, align },
});
});

if (bulletListBlocks.length > 0) return bulletListBlocks;
const deserializedList = deserializeListNodes(editor, el, { type: 'BulletedList', depth, align });
if (deserializedList.length > 0) {
return deserializedList;
}
}
},
},
Expand All @@ -78,6 +56,7 @@ const BulletedList = new YooptaPlugin<Pick<ListElementMap, 'bulleted-list'>>({
},
markdown: {
serialize: (element, text) => {
console.log({element})
return `- ${serializeTextNodesIntoMarkdown(element.children)}`;
},
},
Expand Down
32 changes: 5 additions & 27 deletions packages/plugins/lists/src/plugin/NumberedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
deserializeTextNodes,
serializeTextNodes,
serializeTextNodesIntoMarkdown,
Blocks,
Blocks, deserializeListNodes,
} from '@yoopta/editor';
import { NumberedListCommands } from '../commands';
import { NumberedListRender } from '../elements/NumberedList';
Expand Down Expand Up @@ -39,35 +39,13 @@ const NumberedList = new YooptaPlugin<Pick<ListElementMap, 'numbered-list'>>({
nodeNames: ['OL'],
parse(el, editor) {
if (el.nodeName === 'OL') {
const listItems = el.querySelectorAll('li');

const align = (el.getAttribute('data-meta-align') || 'left') as YooptaBlockData['meta']['align'];
const depth = parseInt(el.getAttribute('data-meta-depth') || '0', 10);

const numberedListBlocks: YooptaBlockData[] = Array.from(listItems)
.filter((listItem) => {
const textContent = listItem.textContent || '';
const isTodoListItem = /\[\s*\S?\s*\]/.test(textContent);

return !isTodoListItem;
})
.map((listItem, i) => {
return Blocks.buildBlockData({
id: generateId(),
type: 'NumberedList',
value: [
{
id: generateId(),
type: 'numbered-list',
children: deserializeTextNodes(editor, listItem.childNodes),
props: { nodeType: 'block' },
},
],
meta: { order: 0, depth, align },
});
});

if (numberedListBlocks.length > 0) return numberedListBlocks;
const deserializedList = deserializeListNodes(editor, el, { type: 'NumberedList', depth, align });
if (deserializedList.length > 0) {
return deserializedList;
}
}
},
},
Expand Down
35 changes: 5 additions & 30 deletions packages/plugins/lists/src/plugin/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
buildBlockData,
buildBlockData, deserializeListNodes,
deserializeTextNodes,
generateId,
serializeTextNodes,
Expand Down Expand Up @@ -39,38 +39,13 @@ const TodoList = new YooptaPlugin<Pick<ListElementMap, 'todo-list'>>({
nodeNames: ['OL', 'UL'],
parse(el, editor) {
if (el.nodeName === 'OL' || el.nodeName === 'UL') {
const listItems = el.querySelectorAll('li');

const align = (el.getAttribute('data-meta-align') || 'left') as YooptaBlockData['meta']['align'];
const depth = parseInt(el.getAttribute('data-meta-depth') || '0', 10);

const todoListBlocks: YooptaBlockData[] = Array.from(listItems)
.filter((listItem) => {
const textContent = listItem.textContent || '';
const isTodoListItem = /\[\s*\S?\s*\]/.test(textContent);

return isTodoListItem;
})
.map((listItem) => {
const textContent = listItem.textContent || '';
const checked = /\[\s*x\s*\]/i.test(textContent);

return buildBlockData({
id: generateId(),
type: 'TodoList',
value: [
{
id: generateId(),
type: 'todo-list',
children: deserializeTextNodes(editor, listItem.childNodes),
props: { nodeType: 'block', checked: checked },
},
],
meta: { order: 0, depth, align },
});
});

if (todoListBlocks.length > 0) return todoListBlocks;
const deserializedList = deserializeListNodes(editor, el, { type: 'TodoList', depth, align });
if (deserializedList.length > 0) {
return deserializedList;
}
}
},
},
Expand Down

0 comments on commit 16e4625

Please sign in to comment.