feat: inline sub page mention (#6567)

* feat: inline sub page mention

* fix: disable editing documents in trash

* fix: duplicate block behavior

* refactor: clean up code

* feat: use formatText function instead of modify delta manually

* fix: paste behavior format mention

* fix: default icon for mentioned pages

* fix: view new parent turn into page reference

* test: add base test

* chore: add feature flag

* chore: default flag to on

* fix: minor fixes to behavior

* fix: review and code cleanup

* fix: dart linter

* fix: content is required

* test: use doc title to rename page

* test: add test coverage

* test: fix wrong expect

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
Mathias Mogensen 2024-10-21 10:34:30 +02:00 committed by GitHub
parent 15949a7e21
commit 068500df84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 2017 additions and 504 deletions

View file

@ -1,15 +1,13 @@
import 'dart:async';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
import 'package:appflowy/startup/startup.dart';
@ -50,25 +48,16 @@ class _DocumentPageState extends State<DocumentPage>
late final documentBloc = DocumentBloc(documentId: widget.view.id)
..add(const DocumentEvent.initial());
StreamSubscription<(TransactionTime, Transaction)>? transactionSubscription;
bool isUndoRedo = false;
bool isPaste = false;
bool isDraggingNode = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
EditorNotification.addListener(onEditorNotification);
}
@override
void dispose() {
EditorNotification.removeListener(onEditorNotification);
WidgetsBinding.instance.removeObserver(this);
documentBloc.close();
transactionSubscription?.cancel();
super.dispose();
}
@ -109,8 +98,6 @@ class _DocumentPageState extends State<DocumentPage>
return const SizedBox.shrink();
}
editorState.transactionStream.listen(onEditorTransaction);
return BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: onNotificationAction,
@ -170,11 +157,15 @@ class _DocumentPageState extends State<DocumentPage>
return Provider(
create: (_) => SharedEditorContext(),
child: Column(
children: [
if (state.isDeleted) buildBanner(context),
Expanded(child: child),
],
child: EditorTransactionService(
viewId: widget.view.id,
editorState: state.editorState!,
child: Column(
children: [
if (state.isDeleted) buildBanner(context),
Expanded(child: child),
],
),
),
);
}
@ -217,33 +208,6 @@ class _DocumentPageState extends State<DocumentPage>
);
}
void onEditorNotification(EditorNotificationType type) {
final editorState = this.editorState;
if (editorState == null) {
return;
}
if ([EditorNotificationType.undo, EditorNotificationType.redo]
.contains(type)) {
isUndoRedo = true;
} else if (type == EditorNotificationType.paste) {
isPaste = true;
} else if (type == EditorNotificationType.dragStart) {
isDraggingNode = true;
} else if (type == EditorNotificationType.dragEnd) {
isDraggingNode = false;
}
if (type == EditorNotificationType.undo) {
undoCommand.execute(editorState);
} else if (type == EditorNotificationType.redo) {
redoCommand.execute(editorState);
} else if (type == EditorNotificationType.exitEditing &&
editorState.selection != null) {
editorState.selection = null;
}
}
void onNotificationAction(
BuildContext context,
ActionNavigationState state,
@ -325,88 +289,6 @@ class _DocumentPageState extends State<DocumentPage>
return false;
}
List<Node> collectMatchingNodes(Node node, String type) {
final List<Node> matchingNodes = [];
if (node.type == type) {
matchingNodes.add(node);
}
for (final child in node.children) {
matchingNodes.addAll(collectMatchingNodes(child, type));
}
return matchingNodes;
}
void onEditorTransaction((TransactionTime, Transaction) event) {
if (editorState == null || event.$1 == TransactionTime.before) {
return;
}
final Map<String, List<Node>> addedNodes = {
for (final handler in SharedEditorContext.transactionHandlers)
handler.blockType: [],
};
final Map<String, List<Node>> removedNodes = {
for (final handler in SharedEditorContext.transactionHandlers)
handler.blockType: [],
};
final transactionHandlerTypes = SharedEditorContext.transactionHandlers
.map((h) => h.blockType)
.toList();
// Collect all matching nodes in a performant way for each handler type.
for (final op in event.$2.operations) {
if (op is InsertOperation) {
for (final n in op.nodes) {
for (final handlerType in transactionHandlerTypes) {
if (n.type == handlerType) {
addedNodes[handlerType]!
.addAll(collectMatchingNodes(n, handlerType));
}
}
}
} else if (op is DeleteOperation) {
for (final n in op.nodes) {
for (final handlerType in transactionHandlerTypes) {
if (n.type == handlerType) {
removedNodes[handlerType]!
.addAll(collectMatchingNodes(n, handlerType));
}
}
}
}
}
if (removedNodes.isEmpty && addedNodes.isEmpty) {
return;
}
for (final handler in SharedEditorContext.transactionHandlers) {
final added = addedNodes[handler.blockType] ?? [];
final removed = removedNodes[handler.blockType] ?? [];
if (added.isEmpty && removed.isEmpty) {
continue;
}
handler.onTransaction(
context,
editorState!,
added,
removed,
isUndoRedo: isUndoRedo,
isPaste: isPaste,
isDraggingNode: isDraggingNode,
parentViewId: widget.view.id,
);
isUndoRedo = false;
isPaste = false;
}
}
Selection? _calculateInitialSelection(EditorState editorState) {
if (widget.initialSelection != null) {
return widget.initialSelection;