Improve types
This commit is contained in:
parent
544a1614a0
commit
78dd4f5189
@ -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);
|
||||||
|
@ -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
41
src/types.ts
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user