fix: numbered list generated by ai should keep the same index as the input (#7622)

Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
This commit is contained in:
Lucas 2025-03-26 13:31:32 +08:00 committed by GitHub
parent 24bb1b58a0
commit 9115e208ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 12 deletions

View File

@ -519,7 +519,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
editorState,
aiWriterNode!,
);
_textRobot.start(position: position);
_textRobot.start(position: position, previousSelection: selection);
records.add(
AiWriterRecord.user(
content: prompt,

View File

@ -1,8 +1,10 @@
import 'dart:convert';
import 'package:appflowy/plugins/document/presentation/editor_plugins/numbered_list/numbered_list_icon.dart';
import 'package:appflowy/shared/markdown_to_document.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.dart';
import 'package:synchronized/synchronized.dart';
const _enableDebug = false;
@ -28,6 +30,9 @@ class MarkdownTextRobot {
/// Only for debug via [_enableDebug].
final List<String> _debugMarkdownTexts = [];
/// Selection before the refresh.
Selection? _previousSelection;
bool get hasAnyResult => _markdownText.isNotEmpty;
String get markdownText => _markdownText;
@ -56,9 +61,11 @@ class MarkdownTextRobot {
}
void start({
Selection? previousSelection,
Position? position,
}) {
_insertPosition = position ?? editorState.selection?.start;
_previousSelection = previousSelection ?? editorState.selection;
if (_enableDebug) {
Log.info(
@ -175,11 +182,40 @@ class MarkdownTextRobot {
tableWidth: 250.0,
).root.children;
// check if the first selected node before the refresh is a numbered list node
final previousSelection = _previousSelection;
final previousSelectedNode = previousSelection == null
? null
: editorState.getNodeAtPath(previousSelection.start.path);
final firstNodeIsNumberedList = previousSelectedNode != null &&
previousSelectedNode.type == NumberedListBlockKeys.type;
final newNodes = attributes == null
? documentNodes
: documentNodes
.map((node) => _styleDelta(node: node, attributes: attributes))
.toList();
: documentNodes.mapIndexed((index, node) {
final n = _styleDelta(node: node, attributes: attributes);
n.externalValues = AINodeExternalValues(
isAINode: true,
);
if (index == 0 && n.type == NumberedListBlockKeys.type) {
if (firstNodeIsNumberedList) {
final builder = NumberedListIndexBuilder(
editorState: editorState,
node: previousSelectedNode,
);
final firstIndex = builder.indexInSameLevel;
n.updateAttributes({
NumberedListBlockKeys.number: firstIndex,
});
}
n.externalValues = AINodeExternalValues(
isAINode: true,
isFirstNumberedListNode: true,
);
}
return n;
}).toList();
if (newNodes.isEmpty) {
return;
@ -247,3 +283,13 @@ class MarkdownTextRobot {
);
}
}
class AINodeExternalValues extends NodeExternalValues {
const AINodeExternalValues({
this.isAINode = false,
this.isFirstNumberedListNode = false,
});
final bool isAINode;
final bool isFirstNumberedListNode;
}

View File

@ -1,3 +1,4 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -31,7 +32,7 @@ class NumberedListIcon extends StatelessWidget {
return Padding(
padding: const EdgeInsets.only(left: 6.0, right: 10.0),
child: Text(
node.levelString,
node.buildLevelString(context),
style: adjustedTextStyle,
strutStyle: StrutStyle.fromTextStyle(combinedTextStyle),
textHeightBehavior: TextHeightBehavior(
@ -47,9 +48,12 @@ class NumberedListIcon extends StatelessWidget {
}
}
extension on Node {
String get levelString {
final builder = _NumberedListIconBuilder(node: this);
extension NumberedListNodeIndex on Node {
String buildLevelString(BuildContext context) {
final builder = NumberedListIndexBuilder(
editorState: context.read<EditorState>(),
node: this,
);
final indexInRootLevel = builder.indexInRootLevel;
final indexInSameLevel = builder.indexInSameLevel;
final level = indexInRootLevel % 3;
@ -62,11 +66,13 @@ extension on Node {
}
}
class _NumberedListIconBuilder {
_NumberedListIconBuilder({
class NumberedListIndexBuilder {
NumberedListIndexBuilder({
required this.editorState,
required this.node,
});
final EditorState editorState;
final Node node;
// the level of the current node
@ -88,7 +94,13 @@ class _NumberedListIconBuilder {
Node? previous = node.previous;
// if the previous one is not a numbered list, then it is the first one
if (previous == null || previous.type != NumberedListBlockKeys.type) {
final aiNodeExternalValues =
node.externalValues?.unwrapOrNull<AINodeExternalValues>();
if (previous == null ||
previous.type != NumberedListBlockKeys.type ||
(aiNodeExternalValues != null &&
aiNodeExternalValues.isFirstNumberedListNode)) {
return node.attributes[NumberedListBlockKeys.number] ?? level;
}
@ -97,10 +109,17 @@ class _NumberedListIconBuilder {
startNumber = previous.attributes[NumberedListBlockKeys.number] as int?;
level++;
previous = previous.previous;
// break the loop if the start number is found when the current node is an AI node
if (aiNodeExternalValues != null && startNumber != null) {
return startNumber + level - 1;
}
}
if (startNumber != null) {
return startNumber + level - 1;
level = startNumber + level - 1;
}
return level;
}
}