Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var editor = EditorJS({
| defaultStyle | `string` | default list style: `ordered`, `unordered` or `checklist`, default is `unordered` |
| maxLevel | `number` | maximum level of the list nesting, could be set to `1` to disable nesting, unlimited by default |
| counterTypes | `string[]` | specifies which counter types should be shown in the ordered list style, could be set to `['numeric','upper-roman']`, default is `undefined` which shows all counter types |
| styles | `string[]` | allows to specify which list styles user can choose from: `ordered`, `unordered` or `checklist`. For example, set to `['ordered','unordered']` to hide checklist option. By default all three styles are available |

## Output data

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@editorjs/list",
"version": "2.0.9",
"version": "2.1.0",
"keywords": [
"codex editor",
"list",
Expand Down
8 changes: 4 additions & 4 deletions src/ListRenderer/ListRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,27 @@ export interface ListCssClasses {
export interface ListRendererInterface<ItemMeta> {
/**
* Renders wrapper for list
* @param isRoot - boolean variable that represents level of the wrappre (root or childList)
* @param isRoot - boolean variable that represents level of the wrapper (root or childList)
* @returns - created html ul element
*/
renderWrapper: (isRoot: boolean) => HTMLElement;

/**
* Redners list item element
* Renders list item element
* @param content - content of the list item
* @returns - created html list item element
*/
renderItem: (content: string, meta: ItemMeta) => HTMLElement;

/**
* Return the item content
* Returns the item content
* @param {Element} item - item wrapper (<li>)
* @returns {string}
*/
getItemContent: (item: Element) => string;

/**
* Return meta object of certain element
* Returns meta object of certain element
* @param {Element} item - item of the list to get meta from
* @returns {ItemMeta} Item meta object
*/
Expand Down
39 changes: 34 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import stripNumbers from './utils/stripNumbers';
import normalizeData from './utils/normalizeData';
import type { PasteEvent } from './types';
import type { OrderedListItemMeta } from './types/ItemMeta';
import validateConfig from './utils/validateConfig';

/**
* Constructor Params for Editorjs List Tool, use to pass initial data and settings
Expand Down Expand Up @@ -52,7 +53,7 @@ export default class EditorjsList {
* title - title to show in toolbox
*/
public static get toolbox(): ToolboxConfig {
return [
const defaultToolbox = [
{
icon: IconListBulleted,
title: 'Unordered List',
Expand All @@ -75,6 +76,12 @@ export default class EditorjsList {
},
},
];

return EditorjsList.styles
? defaultToolbox.filter(
tool => EditorjsList.styles?.includes(tool.data.style as ListDataStyle)
)
: defaultToolbox;
Comment on lines +80 to +84
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'toolbox' getter is static and accesses the static 'styles' property, but there's a timing issue: this getter may be called before any instance is created (during tool registration), when 'EditorjsList.styles' is still undefined. This means the toolbox will always show all styles initially, even if a styles configuration is later provided.

Consider accepting configuration at tool registration time, or ensure the toolbox getter has access to the configuration without relying on instance creation.

Copilot uses AI. Check for mistakes.
}

/**
Expand Down Expand Up @@ -151,6 +158,11 @@ export default class EditorjsList {
this.listElement = newListElement;
}

/**
* Styles allowed to be used in list
*/
private static styles?: ListDataStyle[];

/**
* The Editor.js API
*/
Expand Down Expand Up @@ -210,11 +222,21 @@ export default class EditorjsList {
this.config = config;
this.block = block;

/**
* Validate user configuration
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation occurs during instance construction, after EditorJS tool registration. This means:

  1. Invalid configurations are only detected at runtime when an editor instance is created
  2. The error doesn't prevent tool registration, potentially causing confusing behavior
  3. Users won't discover configuration errors until they interact with the editor

While TypeScript provides compile-time type checking, runtime validation should happen as early as possible. Consider documenting this behavior or adding a note about when validation occurs.

Suggested change
* Validate user configuration
* Validate user configuration
*
* NOTE: Validation occurs during instance construction, after EditorJS tool registration.
* This means:
* 1. Invalid configurations are only detected at runtime when an editor instance is created.
* 2. The error doesn't prevent tool registration, potentially causing confusing behavior.
* 3. Users won't discover configuration errors until they interact with the editor.
*
* This is a limitation of the EditorJS tool API, as configuration is only available at this point.

Copilot uses AI. Check for mistakes.
*/
validateConfig(this.config);

/**
* Set the default list style from the config or presetted 'unordered'.
*/
this.defaultListStyle = this.config?.defaultStyle || 'unordered';

/**
* Set the style's list from the config
*/
EditorjsList.styles = this.config?.styles;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that setting to a static getter is a good idea. If you have several instances of Editor.js on the page with different configs, they can override each other.

Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a static property to store instance-level configuration creates issues in environments where multiple EditorjsList instances exist simultaneously. Each instance will overwrite the static 'styles' property with its own config, causing all instances to use the most recently instantiated configuration. This is particularly problematic when different editor instances need different list style configurations.

Consider making this an instance property instead, or use the instance's config directly where needed.

Copilot uses AI. Check for mistakes.

/**
* Set the default counter types for the ordered list
*/
Expand Down Expand Up @@ -284,7 +306,7 @@ export default class EditorjsList {
public renderSettings(): MenuConfigItem[] {
const defaultTunes: MenuConfigItem[] = [
{
label: this.api.i18n.t('Unordered'),
title: this.api.i18n.t('Unordered'),
icon: IconListBulleted,
closeOnActivate: true,
isActive: this.listStyle == 'unordered',
Expand All @@ -293,7 +315,7 @@ export default class EditorjsList {
},
},
{
label: this.api.i18n.t('Ordered'),
title: this.api.i18n.t('Ordered'),
icon: IconListNumbered,
closeOnActivate: true,
isActive: this.listStyle == 'ordered',
Expand All @@ -302,15 +324,22 @@ export default class EditorjsList {
},
},
{
label: this.api.i18n.t('Checklist'),
title: this.api.i18n.t('Checklist'),
icon: IconChecklist,
closeOnActivate: true,
isActive: this.listStyle == 'checklist',
onActivate: () => {
this.listStyle = 'checklist';
},
},
];
].filter((tune) => {
// If no styles config, keep all
if (!EditorjsList?.styles) {
return true;
};
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary semicolon after the return statement. This is inconsistent with the JavaScript style used in the rest of the file.

Suggested change
};
}

Copilot uses AI. Check for mistakes.

return (EditorjsList?.styles.includes(tune.title.toLowerCase() as ListDataStyle));
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filtering logic relies on comparing translated and lowercased tune titles ('Unordered', 'Ordered', 'Checklist') with ListDataStyle values ('unordered', 'ordered', 'checklist'). This approach is fragile because:

  1. It depends on the i18n translation returning specific English values
  2. If translations are localized to other languages, toLowerCase() won't produce the expected values
  3. The comparison will fail for non-English locales

Instead, filter by comparing tune.data.style or store the style value directly in each tune definition, similar to how it's done in the toolbox getter.

Copilot uses AI. Check for mistakes.
});

if (this.listStyle === 'ordered') {
const startWithElement = renderToolboxInput(
Expand Down
4 changes: 4 additions & 0 deletions src/types/ListParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,8 @@ export interface ListConfig {
* @default undefined // All counter types are available when not specified
*/
counterTypes?: OlCounterType[];
/**
* Styles allowed to be used in list
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation comment for the 'styles' config property is incomplete. It should include additional details like:

  • Default value behavior (all styles available when not specified)
  • Example usage matching the pattern used for other config properties
  • Mention that it filters both the toolbox and settings menu

Consider following the same documentation pattern as 'counterTypes' above it.

Suggested change
* Styles allowed to be used in list
* Specifies which list styles are available for selection in both the toolbox and settings menu.
* If not specified, all styles ('ordered', 'unordered', 'checklist') are available by default.
* @example ['ordered', 'unordered'] // Only these two styles will be available for selection
* @default undefined // All styles are available when not specified

Copilot uses AI. Check for mistakes.
*/
styles?: ListDataStyle[];
}
17 changes: 17 additions & 0 deletions src/utils/validateConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { ListConfig } from '../types/ListParams';

/**
* Method that will validate config object
* @param config - user configuration object for the List tool
*/
export default function validateConfig(config: ListConfig | undefined): void {
if (config === undefined) {
return;
}

const { styles, defaultStyle } = config;

if (defaultStyle !== undefined && Array.isArray(styles) && styles.length > 0 && !styles.includes(defaultStyle)) {
throw new Error(`Invalid config: defaultStyle '${defaultStyle}' must be included in 'styles' ${JSON.stringify(styles)}.`);
}
Comment on lines +14 to +16
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation only checks that 'defaultStyle' is included in 'styles' when both are defined and styles is non-empty. However, it doesn't validate:

  1. Whether 'defaultStyle' is a valid ListDataStyle value
  2. Whether 'styles' array contains only valid ListDataStyle values
  3. Whether 'styles' array contains duplicates

Consider adding validation to ensure all values are valid ListDataStyle types.

Copilot uses AI. Check for mistakes.
}
Comment on lines +14 to +17
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When 'defaultStyle' is configured but 'styles' is not, the validation passes and allows the defaultStyle to be set. However, this could lead to unexpected behavior where:

  • The default style might be used for new lists
  • But all three styles remain available in the toolbox and settings

Consider validating that if 'defaultStyle' is specified without 'styles', the defaultStyle should still be a valid ListDataStyle value, or document this behavior clearly.

Suggested change
if (defaultStyle !== undefined && Array.isArray(styles) && styles.length > 0 && !styles.includes(defaultStyle)) {
throw new Error(`Invalid config: defaultStyle '${defaultStyle}' must be included in 'styles' ${JSON.stringify(styles)}.`);
}
}
// List of allowed ListDataStyle values
const allowedStyles = ['unordered', 'ordered', 'checklist'];
if (defaultStyle !== undefined && Array.isArray(styles) && styles.length > 0 && !styles.includes(defaultStyle)) {
throw new Error(`Invalid config: defaultStyle '${defaultStyle}' must be included in 'styles' ${JSON.stringify(styles)}.`);
}
// If defaultStyle is set but styles is not, validate defaultStyle is a valid value
if (defaultStyle !== undefined && (!Array.isArray(styles) || styles.length === 0)) {
if (!allowedStyles.includes(defaultStyle)) {
throw new Error(`Invalid config: defaultStyle '${defaultStyle}' must be one of the allowed styles: ${JSON.stringify(allowedStyles)}.`);
}
}

Copilot uses AI. Check for mistakes.