feat(ui): add BlockEditor component with block types, slash menu, and drag-and-drop
- add BlockEditor orchestrator with controlled block list and keyboard navigation - add Block client component with contentEditable sync, drag handles, and markdown shortcuts - add SlashMenu for inserting block types via `/` command - add blockRegistry and block type definitions (paragraph, heading, bullet list, numbered list, quote, code, divider) - add caret and id utility helpers - export BlockEditor from shared components index - add BlockEditor demo to admin devkit ComponentsPage - add README documenting usage and architecture
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// Registre extensible des types de blocs.
|
||||
// Les blocs built-in s'enregistrent dans defaultBlocks.js (chargé par index.js).
|
||||
// Les consommateurs peuvent appeler `registerBlock` pour ajouter leurs propres types.
|
||||
//
|
||||
// Forme d'une définition de bloc :
|
||||
// {
|
||||
// type: string, // id unique (ex: 'paragraph', 'my_custom')
|
||||
// label: string, // libellé affiché dans le slash menu
|
||||
// icon: string, // glyphe court (emoji ou caractère)
|
||||
// keywords: string[], // termes de recherche pour le slash menu
|
||||
// shortcut?: string, // préfixe markdown qui convertit (ex: '# ', '- ')
|
||||
// shortcutTransform?: (block, match) => block, // optionnel : transforme un bloc existant
|
||||
// create: (init?) => Block, // construit un nouveau bloc
|
||||
// isText: boolean, // true si le bloc a un contentEditable de texte
|
||||
// textTag?: string, // pour info / rendu en mode display
|
||||
// textClassName?: string, // classes appliquées au contentEditable
|
||||
// placeholder?: string, // texte fantôme quand le bloc est vide et focus
|
||||
// renderPrefix?: (ctx) => ReactNode, // pour les listes (puce, numéro)
|
||||
// Component?: ReactComponent, // pour blocs non-texte ; reçoit { block, onChange, disabled }
|
||||
// }
|
||||
|
||||
const registry = new Map();
|
||||
|
||||
export function registerBlock(def) {
|
||||
if (!def || typeof def.type !== 'string') {
|
||||
throw new Error('registerBlock: `type` is required');
|
||||
}
|
||||
if (typeof def.create !== 'function') {
|
||||
throw new Error(`registerBlock(${def.type}): \`create\` is required`);
|
||||
}
|
||||
registry.set(def.type, def);
|
||||
}
|
||||
|
||||
export function getBlockDef(type) {
|
||||
return registry.get(type) || null;
|
||||
}
|
||||
|
||||
export function listBlocks() {
|
||||
return Array.from(registry.values());
|
||||
}
|
||||
|
||||
export function isBlockText(type) {
|
||||
const def = registry.get(type);
|
||||
return Boolean(def?.isText);
|
||||
}
|
||||
|
||||
export const DEFAULT_BLOCK_TYPE = 'paragraph';
|
||||
Reference in New Issue
Block a user