feat(editor): support user provided role and role schema (#10939)

Let me analyze the key changes in this diff:

1. **Role System Changes**:
- Changed from a fixed enum of roles (`root`, `hub`, `content`) to a more flexible string-based system
- Removed strict role hierarchy validation rules (hub/content/root relationships)
- Added support for role-based matching using `@` prefix (e.g., `@root`, `@content`)

2. **Schema Validation Updates**:
- Added new `_matchFlavourOrRole` method to handle both flavour and role-based matching
- Updated `_validateParent` to consider both roles and flavours when validating parent-child relationships
- Simplified `_validateRole` by removing specific role hierarchy constraints

3. **Block Schema Changes**:
- Updated parent/children references in various block schemas to use the new `@` prefix notation
- Changed parent definitions from `['affine:page']` to `['@root']` in several blocks
- Updated children definitions to use role-based references (e.g., `['@content']`)

4. **Test Updates**:
- Added new test cases for role-based schema validation
- Introduced new test block schemas (`TestRoleBlockSchema`, `TestParagraphBlockSchema`) to verify role-based functionality

This appears to be a significant architectural change that makes the block schema system more flexible by:
1. Moving away from hardcoded role hierarchies
2. Introducing a more dynamic role-based relationship system
3. Supporting both flavour-based and role-based parent-child relationships
4. Using the `@` prefix convention to distinguish role references from flavour references

The changes make the system more extensible while maintaining backward compatibility with existing flavour-based relationships.
This commit is contained in:
Saul-Mirone
2025-03-18 01:58:59 +00:00
parent 4a3180ee04
commit 9f3cf271e3
5 changed files with 122 additions and 44 deletions

View File

@@ -54,6 +54,34 @@ const TestInvalidNoteBlockSchemaExtension = BlockSchemaExtension(
TestInvalidNoteBlockSchema
);
const TestRoleBlockSchema = defineBlockSchema({
flavour: 'affine:note-block-role-test',
metadata: {
version: 1,
role: 'content',
parent: ['affine:note'],
children: ['@test'],
},
props: internal => ({
text: internal.Text(),
}),
});
const TestRoleBlockSchemaExtension = BlockSchemaExtension(TestRoleBlockSchema);
const TestParagraphBlockSchema = defineBlockSchema({
flavour: 'affine:test-paragraph',
metadata: {
version: 1,
role: 'test',
parent: ['@content'],
},
});
const TestParagraphBlockSchemaExtension = BlockSchemaExtension(
TestParagraphBlockSchema
);
const extensions = [
RootBlockSchemaExtension,
ParagraphBlockSchemaExtension,
@@ -62,6 +90,8 @@ const extensions = [
DividerBlockSchemaExtension,
TestCustomNoteBlockSchemaExtension,
TestInvalidNoteBlockSchemaExtension,
TestRoleBlockSchemaExtension,
TestParagraphBlockSchemaExtension,
];
const defaultDocId = 'doc0';
@@ -128,4 +158,28 @@ describe('schema', () => {
return call[0] instanceof SchemaValidateError;
});
});
it('should be able to validate schema by role', () => {
const consoleMock = vi
.spyOn(console, 'error')
.mockImplementation(() => undefined);
const doc = createTestDoc();
const rootId = doc.addBlock('affine:page', {});
const noteId = doc.addBlock('affine:note', {}, rootId);
const roleId = doc.addBlock('affine:note-block-role-test', {}, noteId);
doc.addBlock('affine:paragraph', {}, roleId);
doc.addBlock('affine:paragraph', {}, roleId);
expect(consoleMock.mock.calls[1]).toSatisfy((call: unknown[]) => {
return call[0] instanceof SchemaValidateError;
});
consoleMock.mockClear();
doc.addBlock('affine:test-paragraph', {}, roleId);
doc.addBlock('affine:test-paragraph', {}, roleId);
expect(consoleMock).not.toBeCalled();
expect(doc.getBlocksByFlavour('affine:test-paragraph')).toHaveLength(2);
});
});