fix(editor): notion text adapter should handle text without styles correctly (#12248)

Closes: [BS-3486](https://linear.app/affine-design/issue/BS-3486/粘贴从-notion-复制的内容出错)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Bug Fixes**
  - Improved handling of Notion text segments with empty or invalid style arrays, ensuring plain text and styled text are both processed correctly and preventing errors from malformed input.
- **Tests**
  - Added a test case to verify correct conversion of Notion text with empty styles arrays.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
donteatfriedrice
2025-05-13 08:05:30 +00:00
parent b4dbc5b6e7
commit 5dbe6ff68b
2 changed files with 100 additions and 18 deletions

View File

@@ -106,4 +106,65 @@ describe('notion-text to snapshot', () => {
});
expect(nanoidReplacement(target!)).toEqual(sliceSnapshot);
});
test('notion text with empty styles array', () => {
const notionText =
'{"blockType":"text","editing":[["a "],["bold text",[["b"]]],[" hello world"]],"selection":{"startIndex":0,"endIndex":23},"action":"copy"}';
const sliceSnapshot: SliceSnapshot = {
type: 'slice',
content: [
{
type: 'block',
id: 'matchesReplaceMap[0]',
flavour: 'affine:note',
props: {
xywh: '[0,0,800,95]',
background: DefaultTheme.noteBackgrounColor,
index: 'a0',
hidden: false,
displayMode: 'both',
},
children: [
{
type: 'block',
id: 'matchesReplaceMap[1]',
flavour: 'affine:paragraph',
props: {
type: 'text',
text: {
'$blocksuite:internal:text$': true,
delta: [
{
insert: 'a ',
},
{
insert: 'bold text',
attributes: {
bold: true,
},
},
{
insert: ' hello world',
},
],
},
},
children: [],
},
],
},
],
workspaceId: '',
pageId: '',
};
const ntAdapter = new NotionTextAdapter(createJob(), provider);
const target = ntAdapter.toSliceSnapshot({
file: notionText,
workspaceId: '',
pageId: '',
});
expect(nanoidReplacement(target!)).toEqual(sliceSnapshot);
});
});

View File

@@ -94,28 +94,49 @@ export class NotionTextAdapter extends BaseAdapter<NotionText> {
const notionText = JSON.parse(payload.file) as NotionTextSerialized;
const content: SliceSnapshot['content'] = [];
const deltas: DeltaInsert<AffineTextAttributes>[] = [];
// Check if the notionText.editing is an array
if (!Array.isArray(notionText.editing)) {
return null;
}
for (const editing of notionText.editing) {
const delta: DeltaInsert<AffineTextAttributes> = {
insert: editing[0],
attributes: Object.create(null),
};
for (const styleElement of editing[1]) {
switch (styleElement[0]) {
case 'b':
delta.attributes!.bold = true;
break;
case 'i':
delta.attributes!.italic = true;
break;
case '_':
delta.attributes!.underline = true;
break;
case 'c':
delta.attributes!.code = true;
break;
case 's':
delta.attributes!.strike = true;
break;
// Check if the stylesArray of editing[1] is an array
const stylesArray = editing[1];
if (Array.isArray(stylesArray)) {
for (const styleElement of stylesArray) {
// Skip invalid style entries
if (!styleElement || typeof styleElement[0] !== 'string') {
continue;
}
// Check if the delta.attributes exists, if not, create a new object
if (!delta.attributes) {
delta.attributes = Object.create(null);
}
// Add the style to the delta.attributes
switch (styleElement[0]) {
case 'b':
delta.attributes!.bold = true;
break;
case 'i':
delta.attributes!.italic = true;
break;
case '_':
delta.attributes!.underline = true;
break;
case 'c':
delta.attributes!.code = true;
break;
case 's':
delta.attributes!.strike = true;
break;
}
}
}
deltas.push(delta);