mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-25 15:17:28 -04:00
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:
parent
15949a7e21
commit
068500df84
33 changed files with 2017 additions and 504 deletions
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue