nested-list icon indicating copy to clipboard operation
nested-list copied to clipboard

Max level depth implementation

Open ccppoo opened this issue 1 year ago • 0 comments

import { BlockToolConstructorOptions } from '@editorjs/editorjs/types/tools';
import type { NestedListParams } from '@editorjs/nested-list';
import NestedList from '@editorjs/nested-list';


// copied from original code == start ==
type ListDataStyle = 'ordered' | 'unordered';

interface ListItem {
  /**
   * list item text content
   */
  content: string;
  /**
   * sublist items
   */
  items: ListItem[];
}
interface ListData {
  /**
   * list type 'ordered' or 'unordered'
   */
  style: ListDataStyle;
  /**
   * list of first-level elements
   */
  items: ListItem[];
}
interface NestedListConfig {
  /**
   * default list style: ordered or unordered
   * default is unordered
   */
  defaultStyle?: ListDataStyle;
}

// copied from original code == end ==

// this is also implemented in package but couldn't import from dist source, so I just copied it
function isHtmlElement(node: Node): boolean {
  return node instanceof HTMLElement;
}

// interface for config, 
interface NestedListCustomParams {
  maxDepth?: number;
}

// extending constructor params
export type _NestedListParams = BlockToolConstructorOptions<
  ListData,
  NestedListConfig & NestedListCustomParams
>;

export default class LimitedNestedList extends NestedList {
  constructor({ data, config: _config, api, readOnly }: _NestedListParams) {
    const { maxDepth, ...config } = _config;
    // need to cast to original NestedListParams interface 
    super({ data, config, api, readOnly } as NestedListParams);

    if (!maxDepth || maxDepth < 1) {
      // min : 1 (could nest like : 1.1, 1.2, 2.1, 2.2, ... )
      // default : 2 (could nest like : 1.1.1, 1.2.3, ... )
      this.maxDepth = 2;
      return;
    }
    this.maxDepth = maxDepth;
  }

  private maxDepth: number;

  addTab(event: KeyboardEvent): void {
    // same as original code == start ==
    event.stopPropagation();
    event.preventDefault();

    const currentItem = this.currentItem;
    if (!currentItem) {
      return;
    }
    const prevItem = currentItem.previousSibling;
    if (!prevItem) {
      return;
    }
    if (!isHtmlElement(prevItem)) {
      return;
    }
    const isFirstChild = !prevItem;

    if (isFirstChild) {
      return;
    }
    // same as original code == end ==

    // find until element class name is  : `ce-block__content`
    const maxHTMLDepth = 2 + 3 * (this.maxDepth - 1); // 0, 2, 5, 8, 11, ...
    const rootName = 'ce-block__content';
    let HTMLDepth = 0;
    let element: Element | null = currentItem;
    while (element?.className !== rootName) {
      element = element?.parentElement || null;
      HTMLDepth += 1;
      if (HTMLDepth > maxHTMLDepth) return;
    }

    return super.addTab(event);
  }
}
const nestedListTool = {
  class: LimitedNestedList,
  inlineToolbar: true,
  config: {
    defaultStyle: 'ordered',
  },
};

....

const editorConfig = {
    tools: {
        list : nestedListTool 
        ....
    }
}

I tried to make as finding root element, or Block by using block data

but it was quite hard to override methods, and accessing private variables.

ccppoo avatar Aug 11 '24 07:08 ccppoo