mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-26 02:35:58 +08:00
fix(editor): should preserve format in <p> when importing html (#12275)
Closes: [BS-3485](https://linear.app/affine-design/issue/BS-3485/粘贴-html-格式的内容时,紧邻着-bold-text-的普通文本会丢失空格) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved handling of spaces and whitespace in paragraphs when converting HTML with inline formatting, ensuring spaces are preserved as in the original content. - **Tests** - Added a new test to verify that spaces are correctly preserved in paragraphs containing bold and italic formatting during HTML conversion. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2360,6 +2360,65 @@ describe('html to snapshot', () => {
|
|||||||
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should preserve space in p', async () => {
|
||||||
|
const html = template(
|
||||||
|
`<p>A <b>bold text</b> followed by a <i>italic text</i></p>`
|
||||||
|
);
|
||||||
|
|
||||||
|
const blockSnapshot: BlockSnapshot = {
|
||||||
|
type: 'block',
|
||||||
|
id: 'matchesReplaceMap[0]',
|
||||||
|
flavour: 'affine:note',
|
||||||
|
props: {
|
||||||
|
xywh: '[0,0,800,95]',
|
||||||
|
background: DefaultTheme.noteBackgrounColor,
|
||||||
|
index: 'a0',
|
||||||
|
hidden: false,
|
||||||
|
displayMode: NoteDisplayMode.DocAndEdgeless,
|
||||||
|
},
|
||||||
|
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: ' followed by a ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
insert: 'italic text',
|
||||||
|
attributes: {
|
||||||
|
italic: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const htmlAdapter = new HtmlAdapter(createJob(), provider);
|
||||||
|
const rawBlockSnapshot = await htmlAdapter.toBlockSnapshot({
|
||||||
|
file: html,
|
||||||
|
});
|
||||||
|
expect(nanoidReplacement(rawBlockSnapshot)).toEqual(blockSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
test('span nested in p', async () => {
|
test('span nested in p', async () => {
|
||||||
const html = template(
|
const html = template(
|
||||||
`<p><span>aaa</span><span>bbb</span><span>ccc</span></p>`
|
`<p><span>aaa</span><span>bbb</span><span>ccc</span></p>`
|
||||||
|
|||||||
@@ -5,6 +5,24 @@ import {
|
|||||||
import { collapseWhiteSpace } from 'collapse-white-space';
|
import { collapseWhiteSpace } from 'collapse-white-space';
|
||||||
import type { Element } from 'hast';
|
import type { Element } from 'hast';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle empty text nodes created by HTML parser for styling purposes.
|
||||||
|
* These nodes typically contain only whitespace/newlines, for example:
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "type": "text",
|
||||||
|
* "value": "\n\n \n \n "
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* We collapse and trim the whitespace to check if the node is truly empty,
|
||||||
|
* and return an empty array in that case.
|
||||||
|
*/
|
||||||
|
const isEmptyText = (ast: HtmlAST): boolean => {
|
||||||
|
return (
|
||||||
|
ast.type === 'text' && collapseWhiteSpace(ast.value, { trim: true }) === ''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const isElement = (ast: HtmlAST): ast is Element => {
|
const isElement = (ast: HtmlAST): ast is Element => {
|
||||||
return ast.type === 'element';
|
return ast.type === 'element';
|
||||||
};
|
};
|
||||||
@@ -22,12 +40,16 @@ export const htmlTextToDeltaMatcher = HtmlASTToDeltaExtension({
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const { options } = context;
|
const { options } = context;
|
||||||
options.trim ??= true;
|
options.trim ??= false;
|
||||||
|
|
||||||
if (options.pre) {
|
if (options.pre) {
|
||||||
return [{ insert: ast.value }];
|
return [{ insert: ast.value }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isEmptyText(ast)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const value = options.trim
|
const value = options.trim
|
||||||
? collapseWhiteSpace(ast.value, { trim: options.trim })
|
? collapseWhiteSpace(ast.value, { trim: options.trim })
|
||||||
: collapseWhiteSpace(ast.value);
|
: collapseWhiteSpace(ast.value);
|
||||||
|
|||||||
Reference in New Issue
Block a user