diff --git a/package-lock.json b/package-lock.json index 658883f..76635fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "editorjs-blocks-react-renderer", - "version": "1.2.4", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "editorjs-blocks-react-renderer", - "version": "1.2.4", + "version": "1.3.0", "license": "MIT", "dependencies": { "html-react-parser": "^1.4.14" diff --git a/src/renderers/list/__snapshots__/index.test.tsx.snap b/src/renderers/list/__snapshots__/index.test.tsx.snap index 88b9adf..ca8fe2b 100644 --- a/src/renderers/list/__snapshots__/index.test.tsx.snap +++ b/src/renderers/list/__snapshots__/index.test.tsx.snap @@ -1,5 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` backward compatibility renders ordered list without meta property (default behavior) 1`] = ` +
    +
  1. + Item without meta +
  2. +
  3. + Another item +
  4. +
+`; + +exports[` backward compatibility renders unordered list without meta property 1`] = ` + +`; + exports[` when receives a list "ordered" block renders a
    block 1`] = `
    1. @@ -36,6 +58,137 @@ exports[` when receives a list "unordered" block renders a
        block 1`
      `; +exports[` when receives a list with meta property with custom start value renders an
        block with start attribute 1`] = ` +
          +
        1. + First item +
        2. +
        3. + Second item +
        4. +
        5. + Third item +
        6. +
        +`; + +exports[` when receives a list with meta property with lower-alpha counter type renders an
          block with lower-alpha list style 1`] = ` +
            +
          1. + First item +
          2. +
          3. + Second item +
          4. +
          5. + Third item +
          6. +
          +`; + +exports[` when receives a list with meta property with lower-roman counter type renders an
            block with lower-roman list style 1`] = ` +
              +
            1. + First item +
            2. +
            3. + Second item +
            4. +
            5. + Third item +
            6. +
            +`; + +exports[` when receives a list with meta property with nested lists and meta properties renders nested
              blocks with inherited meta properties 1`] = ` +
                +
              1. + First level item 1 +
              2. +
              3. + First level item 2 +
                  +
                1. + Nested item 1 +
                2. +
                3. + Nested item 2 +
                4. +
                +
              4. +
              +`; + +exports[` when receives a list with meta property with upper-alpha counter type renders an
                block with upper-alpha list style and custom start 1`] = ` +
                  +
                1. + First item +
                2. +
                3. + Second item +
                4. +
                5. + Third item +
                6. +
                +`; + +exports[` when receives a list with meta property with upper-roman counter type renders an
                  block with upper-roman list style and custom start 1`] = ` +
                    +
                  1. + First item +
                  2. +
                  3. + Second item +
                  4. +
                  5. + Third item +
                  6. +
                  +`; + exports[` when receives a nested list "ordered" block and when className is provided renders className to all
                    blocks 1`] = `
                      ', () => { }); }); }); -}); + + describe('when receives a list with meta property', () => { + describe('with custom start value', () => { + const data: ListBlockData = { + style: 'ordered', + items: [ + 'First item', + 'Second item', + 'Third item', + ], + meta: { + start: 5, + counterType: 'numeric', + }, + }; + + it('renders an
                        block with start attribute', () => { + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + // @ts-expect-error - accessing props for testing + expect(tree.props.start).toBe(5); + }); + }); + + describe('with lower-roman counter type', () => { + const data: ListBlockData = { + style: 'ordered', + items: [ + 'First item', + 'Second item', + 'Third item', + ], + meta: { + start: 1, + counterType: 'lower-roman', + }, + }; + + it('renders an
                          block with lower-roman list style', () => { + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + // @ts-expect-error - accessing props for testing + expect(tree.props.style.listStyleType).toBe('lower-roman'); + }); + }); + + describe('with upper-roman counter type', () => { + const data: ListBlockData = { + style: 'ordered', + items: [ + 'First item', + 'Second item', + 'Third item', + ], + meta: { + start: 3, + counterType: 'upper-roman', + }, + }; + + it('renders an
                            block with upper-roman list style and custom start', () => { + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + // @ts-expect-error - accessing props for testing + expect(tree.props.start).toBe(3); + // @ts-expect-error - accessing props for testing + expect(tree.props.style.listStyleType).toBe('upper-roman'); + }); + }); + + describe('with lower-alpha counter type', () => { + const data: ListBlockData = { + style: 'ordered', + items: [ + 'First item', + 'Second item', + 'Third item', + ], + meta: { + start: 1, + counterType: 'lower-alpha', + }, + }; + + it('renders an
                              block with lower-alpha list style', () => { + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + // @ts-expect-error - accessing props for testing + expect(tree.props.style.listStyleType).toBe('lower-alpha'); + }); + }); + + describe('with upper-alpha counter type', () => { + const data: ListBlockData = { + style: 'ordered', + items: [ + 'First item', + 'Second item', + 'Third item', + ], + meta: { + start: 2, + counterType: 'upper-alpha', + }, + }; + + it('renders an
                                block with upper-alpha list style and custom start', () => { + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + // @ts-expect-error - accessing props for testing + expect(tree.props.start).toBe(2); + // @ts-expect-error - accessing props for testing + expect(tree.props.style.listStyleType).toBe('upper-alpha'); + }); + }); + + describe('with nested lists and meta properties', () => { + const data: ListBlockData = { + style: 'ordered', + items: [ + { + content: 'First level item 1', + items: [], + }, + { + content: 'First level item 2', + items: [ + { + content: 'Nested item 1', + items: [], + }, + { + content: 'Nested item 2', + items: [], + }, + ], + }, + ], + meta: { + start: 3, + counterType: 'upper-roman', + }, + }; + + it('renders nested
                                  blocks with inherited meta properties', () => { + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + }); + }); + + describe('backward compatibility', () => { + it('renders ordered list without meta property (default behavior)', () => { + const data: ListBlockData = { + style: 'ordered', + items: [ + 'Item without meta', + 'Another item', + ], + }; + + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + // Should not have start attribute when start is 1 (default) + // @ts-expect-error - accessing props for testing + expect(tree.props.start).toBeUndefined(); + }); + + it('renders unordered list without meta property', () => { + const data: ListBlockData = { + style: 'unordered', + items: [ + 'Bullet item', + 'Another bullet', + ], + }; + + const tree = create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + }); +}); \ No newline at end of file diff --git a/src/renderers/list/index.tsx b/src/renderers/list/index.tsx index f2435a2..bf0d088 100644 --- a/src/renderers/list/index.tsx +++ b/src/renderers/list/index.tsx @@ -5,6 +5,10 @@ import { RenderFn } from '../..'; export interface ListBlockData { style: 'ordered' | 'unordered'; items: NestedListItem[]; + meta?: { + start?: number; + counterType?: 'numeric' | 'lower-roman' | 'upper-roman' | 'lower-alpha' | 'upper-alpha'; + }; } export type NestedListItem = @@ -20,22 +24,61 @@ const Group: FC<{ Tag: keyof JSX.IntrinsicElements; items: NestedListItem[]; className?: string; -}> = ({ Tag, items, ...props }) => ( - - {items.map((item, i) => ( - - {typeof item === 'string' ? ( - HTMLReactParser(item) - ) : ( - <> - {HTMLReactParser(item?.content)} - {item?.items?.length > 0 && } - - )} - - ))} - -); + start?: number; + counterType?: 'numeric' | 'lower-roman' | 'upper-roman' | 'lower-alpha' | 'upper-alpha'; +}> = ({ Tag, items, className, start = 1, counterType = 'numeric', ...props }) => { + const listProps: { + [key: string]: any; + } = { ...props }; + + if (className) { + listProps.className = className; + } + + // Handle ordered list attributes + if (Tag === 'ol') { + if (start && start !== 1) { + listProps.start = start; + } + // Apply counter type styling + if (counterType && counterType !== 'numeric') { + const counterTypeMap: Record = { + 'lower-roman': 'lower-roman', + 'upper-roman': 'upper-roman', + 'lower-alpha': 'lower-alpha', + 'upper-alpha': 'upper-alpha', + }; + const styleType = counterTypeMap[counterType] || counterType; + listProps.style = { listStyleType: styleType }; + } + } + + return ( + + {items.map((item, i) => ( + + {typeof item === 'string' ? ( + HTMLReactParser(item) + ) : ( + <> + {HTMLReactParser(item?.content)} + {item?.items?.length > 0 && ( + + )} + + )} + + ))} + + ); +}; const List: RenderFn = ({ data, className = '' }) => { const props: { @@ -46,8 +89,20 @@ const List: RenderFn = ({ data, className = '' }) => { props.className = className; } + const { start = 1, counterType = 'numeric' } = data?.meta || {}; const Tag = (data?.style === 'ordered' ? `ol` : `ul`) as keyof JSX.IntrinsicElements; - return data && ; + + return ( + data && ( + + ) + ); }; -export default List; +export default List; \ No newline at end of file