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 { try {
// If the type exists in tools, add that block type, otherwise treat as paragraph // 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 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(); await this.syncState();
} catch (err) { } catch (err) {
console.error('Error adding block:', err); console.error('Error adding block:', err);

View File

@ -1,5 +1,6 @@
import { API, BlockTool, BlockToolData } from '@editorjs/editorjs'; import { API, BlockTool, BlockToolData } from '@editorjs/editorjs';
import './checklist.css'; import './checklist.css';
import { EditorJSPlugin, CustomEditorJSPlugin, EditorJSPluginData } from './types';
interface ChecklistItem { interface ChecklistItem {
text: string; text: string;
@ -20,8 +21,8 @@ interface ChecklistConfig {
} }
class ChecklistTool implements BlockTool { class ChecklistTool implements EditorJSPlugin {
private data: ChecklistData; protected data: ChecklistData;
private wrapper: HTMLElement; private wrapper: HTMLElement;
private api: API; private api: API;
private readOnly: boolean; private readOnly: boolean;
@ -29,6 +30,11 @@ class ChecklistTool implements BlockTool {
private itemElements: HTMLElement[] = []; private itemElements: HTMLElement[] = [];
private pendingUpdate = false; private pendingUpdate = false;
static = {
toolbox: ChecklistTool.toolbox,
sanitize: ChecklistTool.sanitize
};
static get toolbox() { static get toolbox() {
return { return {
title: 'Checklist', title: 'Checklist',
@ -346,21 +352,23 @@ class ChecklistTool implements BlockTool {
void this.notifyChange(); void this.notifyChange();
} }
save(): BlockToolData<ChecklistData> { save(): { data: ChecklistData } {
return { return {
data: {
items: this.data.items.map(item => ({ items: this.data.items.map(item => ({
text: item.text || '', text: item.text || '',
checked: Boolean(item.checked) checked: Boolean(item.checked)
})) }))
}
}; };
} }
validate(savedData: BlockToolData<ChecklistData>): boolean { validate(savedData: { data: ChecklistData }): boolean {
try { try {
const { items } = savedData; const { data } = savedData;
if (!Array.isArray(items)) return false; if (!data || !Array.isArray(data.items)) return false;
return items.every(item => return data.items.every(item =>
item && item &&
typeof item === 'object' && typeof item === 'object' &&
'text' in item && '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 }; 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) { constructor(config: ChecklistConfig) {
super(config); 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 { public renderSettings(): HTMLElement {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.innerHTML = ` wrapper.innerHTML = `
@ -411,24 +454,8 @@ class ReactChecklistTool extends ChecklistTool {
return wrapper; return wrapper;
} }
public static renderNewElementTile(onAdd?: () => void): React.ReactElement { public renderNewElementTile(onAdd?: () => void): React.ReactElement {
return ( return ReactChecklistTool.renderNewElementTile(onAdd);
<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>
);
} }
} }

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;
}