fix(core): wrap code in ai chat (#10108)

[BS-2540](https://linear.app/affine-design/issue/BS-2540/ai-chat-中-code-block-需要默认换行)
This commit is contained in:
donteatfriedrice
2025-02-12 09:43:52 +00:00
parent e350ba4a9c
commit 30612de1ad
12 changed files with 43 additions and 15 deletions
@@ -2,6 +2,7 @@ import { CodeBlockSchema } from '@blocksuite/affine-model';
import { import {
BlockHtmlAdapterExtension, BlockHtmlAdapterExtension,
type BlockHtmlAdapterMatcher, type BlockHtmlAdapterMatcher,
CODE_BLOCK_WRAP_KEY,
HastUtils, HastUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import type { DeltaInsert } from '@blocksuite/inline'; import type { DeltaInsert } from '@blocksuite/inline';
@@ -37,7 +38,8 @@ export const codeBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
? codeLang.replace('code-', '') ? codeLang.replace('code-', '')
: undefined; : undefined;
const { walkerContext, deltaConverter } = context; const { walkerContext, deltaConverter, configs } = context;
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
walkerContext walkerContext
.openNode( .openNode(
{ {
@@ -46,6 +48,7 @@ export const codeBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: codeLang ?? 'Plain Text', language: codeLang ?? 'Plain Text',
wrap,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: deltaConverter.astToDelta(codeText, { delta: deltaConverter.astToDelta(codeText, {
@@ -2,6 +2,7 @@ import { CodeBlockSchema } from '@blocksuite/affine-model';
import { import {
BlockMarkdownAdapterExtension, BlockMarkdownAdapterExtension,
type BlockMarkdownAdapterMatcher, type BlockMarkdownAdapterMatcher,
CODE_BLOCK_WRAP_KEY,
type MarkdownAST, type MarkdownAST,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import type { DeltaInsert } from '@blocksuite/inline'; import type { DeltaInsert } from '@blocksuite/inline';
@@ -19,7 +20,8 @@ export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
if (!isCodeNode(o.node)) { if (!isCodeNode(o.node)) {
return; return;
} }
const { walkerContext } = context; const { walkerContext, configs } = context;
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
walkerContext walkerContext
.openNode( .openNode(
{ {
@@ -28,6 +30,7 @@ export const codeBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: o.node.lang ?? 'Plain Text', language: o.node.lang ?? 'Plain Text',
wrap,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: [ delta: [
@@ -2,6 +2,7 @@ import { CodeBlockSchema } from '@blocksuite/affine-model';
import { import {
BlockNotionHtmlAdapterExtension, BlockNotionHtmlAdapterExtension,
type BlockNotionHtmlAdapterMatcher, type BlockNotionHtmlAdapterMatcher,
CODE_BLOCK_WRAP_KEY,
HastUtils, HastUtils,
} from '@blocksuite/affine-shared/adapters'; } from '@blocksuite/affine-shared/adapters';
import { nanoid } from '@blocksuite/store'; import { nanoid } from '@blocksuite/store';
@@ -20,7 +21,8 @@ export const codeBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatcher =
if (!code) { if (!code) {
return; return;
} }
const { walkerContext, deltaConverter } = context; const { walkerContext, deltaConverter, configs } = context;
const wrap = configs.get(CODE_BLOCK_WRAP_KEY) === 'true';
const codeText = const codeText =
code.children.length === 1 && code.children[0].type === 'text' code.children.length === 1 && code.children[0].type === 'text'
? code.children[0] ? code.children[0]
@@ -33,6 +35,7 @@ export const codeBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatcher =
flavour: CodeBlockSchema.model.flavour, flavour: CodeBlockSchema.model.flavour,
props: { props: {
language: 'Plain Text', language: 'Plain Text',
wrap,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: deltaConverter.astToDelta(codeText, { delta: deltaConverter.astToDelta(codeText, {
@@ -0,0 +1,11 @@
import type { TransformerMiddleware } from '@blocksuite/store';
export const CODE_BLOCK_WRAP_KEY = 'codeBlockWrap';
export const codeBlockWrapMiddleware = (
wrap: boolean
): TransformerMiddleware => {
return ({ adapterConfigs }) => {
adapterConfigs.set(CODE_BLOCK_WRAP_KEY, String(wrap));
};
};
@@ -1,2 +1,3 @@
export * from './code';
export * from './copy'; export * from './copy';
export * from './paste'; export * from './paste';
@@ -1980,6 +1980,7 @@ describe('html to snapshot', () => {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: 'python', language: 'python',
wrap: false,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: [ delta: [
@@ -2481,6 +2481,7 @@ describe('markdown to snapshot', () => {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: 'python', language: 'python',
wrap: false,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: [ delta: [
@@ -2526,6 +2527,7 @@ describe('markdown to snapshot', () => {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: 'python', language: 'python',
wrap: false,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: [ delta: [
@@ -2577,6 +2579,7 @@ describe('markdown to snapshot', () => {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: 'python', language: 'python',
wrap: false,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: [ delta: [
@@ -2628,6 +2631,7 @@ describe('markdown to snapshot', () => {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: 'python', language: 'python',
wrap: false,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: [ delta: [
@@ -51,6 +51,7 @@ describe('notion html to snapshot', () => {
flavour: 'affine:code', flavour: 'affine:code',
props: { props: {
language: 'Plain Text', language: 'Plain Text',
wrap: false,
text: { text: {
'$blocksuite:internal:text$': true, '$blocksuite:internal:text$': true,
delta: [ delta: [
+1
View File
@@ -102,6 +102,7 @@ export {
AttachmentAdapter, AttachmentAdapter,
AttachmentAdapterFactoryExtension, AttachmentAdapterFactoryExtension,
AttachmentAdapterFactoryIdentifier, AttachmentAdapterFactoryIdentifier,
codeBlockWrapMiddleware,
FetchUtils, FetchUtils,
HtmlAdapter, HtmlAdapter,
HtmlAdapterFactoryExtension, HtmlAdapterFactoryExtension,
@@ -9,7 +9,9 @@ import type {
} from '@blocksuite/affine/blocks'; } from '@blocksuite/affine/blocks';
import { import {
CodeBlockComponent, CodeBlockComponent,
codeBlockWrapMiddleware,
defaultBlockMarkdownAdapterMatchers, defaultBlockMarkdownAdapterMatchers,
defaultImageProxyMiddleware,
DividerBlockComponent, DividerBlockComponent,
InlineDeltaToMarkdownAdapterExtensions, InlineDeltaToMarkdownAdapterExtensions,
ListBlockComponent, ListBlockComponent,
@@ -213,12 +215,12 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
provider = container.provider(); provider = container.provider();
} }
if (latestAnswer && schema) { if (latestAnswer && schema) {
markDownToDoc( const middlewares = [
provider, defaultImageProxyMiddleware,
schema, codeBlockWrapMiddleware(true),
latestAnswer, ...(this.options.additionalMiddlewares ?? []),
this.options.additionalMiddlewares ];
) markDownToDoc(provider, schema, latestAnswer, middlewares)
.then(doc => { .then(doc => {
this.disposeDoc(); this.disposeDoc();
this._doc = doc.doc.getStore({ this._doc = doc.doc.getStore({
@@ -206,17 +206,13 @@ export async function markDownToDoc(
provider: ServiceProvider, provider: ServiceProvider,
schema: Schema, schema: Schema,
answer: string, answer: string,
additionalMiddlewares?: TransformerMiddleware[] middlewares?: TransformerMiddleware[]
) { ) {
// Should not create a new doc in the original collection // Should not create a new doc in the original collection
const collection = new WorkspaceImpl({ const collection = new WorkspaceImpl({
schema, schema,
}); });
collection.meta.initialize(); collection.meta.initialize();
const middlewares = [defaultImageProxyMiddleware];
if (additionalMiddlewares) {
middlewares.push(...additionalMiddlewares);
}
const transformer = new Transformer({ const transformer = new Transformer({
schema: collection.schema, schema: collection.schema,
blobCRUD: collection.blobSync, blobCRUD: collection.blobSync,
@@ -7,6 +7,7 @@ import {
} from '@blocksuite/affine/block-std'; } from '@blocksuite/affine/block-std';
import type { AffineAIPanelWidget } from '@blocksuite/affine/blocks'; import type { AffineAIPanelWidget } from '@blocksuite/affine/blocks';
import { import {
defaultImageProxyMiddleware,
deleteTextCommand, deleteTextCommand,
isInsideEdgelessEditor, isInsideEdgelessEditor,
} from '@blocksuite/affine/blocks'; } from '@blocksuite/affine/blocks';
@@ -151,7 +152,8 @@ export const copyText = async (host: EditorHost, text: string) => {
const previewDoc = await markDownToDoc( const previewDoc = await markDownToDoc(
host.std.provider, host.std.provider,
host.std.store.schema, host.std.store.schema,
text text,
[defaultImageProxyMiddleware]
); );
const models = previewDoc const models = previewDoc
.getBlocksByFlavour('affine:note') .getBlocksByFlavour('affine:note')