Improve types

This commit is contained in:
Caleb Braaten 2025-04-23 23:30:47 -07:00
parent 544a1614a0
commit 78dd4f5189
3 changed files with 105 additions and 32 deletions

View File

@ -69,10 +69,15 @@ class EditorJSWrapper {
try {
// If the type exists in tools, add that block type, otherwise treat as paragraph
// TODO: Make more generic and allow for plugin to define this behavior
const blockType = this.tools[type] ? type : 'paragraph';
const data = blockType === 'paragraph' ? { text: type } : undefined;
const data = blockType === 'paragraph' ?
{ text: type } :
blockType === 'checklist' ?
{ items: [{ text: '', checked: false }] } :
undefined;
await this.editor.blocks.insert(blockType, data);
await this.editor.blocks.insert(blockType, { data });
await this.syncState();
} catch (err) {
console.error('Error adding block:', err);

View File

@ -1,5 +1,6 @@
import { API, BlockTool, BlockToolData } from '@editorjs/editorjs';
import './checklist.css';
import { EditorJSPlugin, CustomEditorJSPlugin, EditorJSPluginData } from './types';
interface ChecklistItem {
text: string;
@ -20,8 +21,8 @@ interface ChecklistConfig {
}
class ChecklistTool implements BlockTool {
private data: ChecklistData;
class ChecklistTool implements EditorJSPlugin {
protected data: ChecklistData;
private wrapper: HTMLElement;
private api: API;
private readOnly: boolean;
@ -29,6 +30,11 @@ class ChecklistTool implements BlockTool {
private itemElements: HTMLElement[] = [];
private pendingUpdate = false;
static = {
toolbox: ChecklistTool.toolbox,
sanitize: ChecklistTool.sanitize
};
static get toolbox() {
return {
title: 'Checklist',
@ -346,21 +352,23 @@ class ChecklistTool implements BlockTool {
void this.notifyChange();
}
save(): BlockToolData<ChecklistData> {
save(): { data: ChecklistData } {
return {
items: this.data.items.map(item => ({
text: item.text || '',
checked: Boolean(item.checked)
}))
data: {
items: this.data.items.map(item => ({
text: item.text || '',
checked: Boolean(item.checked)
}))
}
};
}
validate(savedData: BlockToolData<ChecklistData>): boolean {
validate(savedData: { data: ChecklistData }): boolean {
try {
const { items } = savedData;
if (!Array.isArray(items)) return false;
const { data } = savedData;
if (!data || !Array.isArray(data.items)) return false;
return items.every(item =>
return data.items.every(item =>
item &&
typeof item === 'object' &&
'text' in item &&
@ -382,8 +390,32 @@ class ChecklistTool implements BlockTool {
}
}
class ReactChecklistTool extends ChecklistTool {
class ReactChecklistTool extends ChecklistTool implements CustomEditorJSPlugin {
private settings: { title: string };
static = {
toolbox: ReactChecklistTool.toolbox,
sanitize: ReactChecklistTool.sanitize
};
static renderNewElementTile(onAdd?: () => void): React.ReactElement {
return (
<button
className="w-full border rounded-md p-4 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-left"
role="button"
aria-label="Add new checklist block"
onClick={onAdd}
>
<div className="flex items-center gap-2 mb-2">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 11L12 14L20 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M20 12V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V6C4 4.89543 4.89543 4 6 4H15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
<h3 className="text-base font-medium">Checklist</h3>
<p className="text-sm text-gray-500 mt-1">Add a new checklist</p>
</button>
);
}
constructor(config: ChecklistConfig) {
super(config);
@ -392,6 +424,17 @@ class ReactChecklistTool extends ChecklistTool {
};
}
save(): { data: ChecklistData } {
return {
data: {
items: this.data.items.map(item => ({
text: item.text || '',
checked: Boolean(item.checked)
}))
}
};
}
public renderSettings(): HTMLElement {
const wrapper = document.createElement('div');
wrapper.innerHTML = `
@ -411,24 +454,8 @@ class ReactChecklistTool extends ChecklistTool {
return wrapper;
}
public static renderNewElementTile(onAdd?: () => void): React.ReactElement {
return (
<button
className="w-full border rounded-md p-4 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors text-left"
role="button"
aria-label="Add new checklist block"
onClick={onAdd}
>
<div className="flex items-center gap-2 mb-2">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 11L12 14L20 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M20 12V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V6C4 4.89543 4.89543 4 6 4H15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
<h3 className="text-base font-medium">Checklist</h3>
<p className="text-sm text-gray-500 mt-1">Add a new checklist</p>
</button>
);
public renderNewElementTile(onAdd?: () => void): React.ReactElement {
return ReactChecklistTool.renderNewElementTile(onAdd);
}
}

41
src/types.ts Normal file
View File

@ -0,0 +1,41 @@
import { BlockTool, PasteEvent } from '@editorjs/editorjs';
export interface EditorJSPluginConfig<T = any> {
data?: T;
config?: {
placeholder?: string;
[key: string]: any;
};
}
export interface EditorJSPluginData<T = any> {
data: T;
}
export interface EditorJSPlugin<T = any> extends BlockTool {
// Required methods from BlockTool
save(): EditorJSPluginData<T>;
validate(savedData: T): boolean;
render(): HTMLElement;
// Optional methods
renderSettings?(): HTMLElement;
onPaste?(event: PasteEvent): void;
merge?(data: T): T;
// Static properties
static: {
toolbox: {
title: string;
icon: string;
};
sanitize: {
[key: string]: boolean | { [key: string]: boolean };
};
}
}
export interface CustomEditorJSPlugin<T = any> extends EditorJSPlugin<T> {
renderNewElementTile(onAdd?: () => void): React.ReactElement;
renderSettings(): HTMLElement;
}