mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-08-04 14:57:27 +00:00

* feat: support columns block in editor * feat: upgrade simple columns block * fix: build error * feat: add column width resizer * fix: drag visual border * fix: drag button position issue * feat: add rule to check if the column is empty * fix: flutter analyze * feat: add document rules to delete the columns if its children are empty * feat: support adding image in columns block * feat: integrate block actions in columns block * feat: support dragging to create a columns block * feat: drag a block into an existing columns block * feat: add delete columns and delete column rules * feat: dragging the block to the left side of another block to create a columns block * feat: support 2-4 columns block in slash menu * chore: disable debug flag in columns block * chore: update pubspec.yaml * chore: update translations and icons * fix: cloud integration test * fix: integration test
119 lines
4.0 KiB
Dart
119 lines
4.0 KiB
Dart
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
|
|
/// Apply rules to the document
|
|
///
|
|
/// 1. ensure there is at least one paragraph in the document, otherwise the user will be blocked from typing
|
|
/// 2. remove columns block if its children are empty
|
|
class DocumentRules {
|
|
DocumentRules({
|
|
required this.editorState,
|
|
});
|
|
|
|
final EditorState editorState;
|
|
|
|
Future<void> applyRules({
|
|
required EditorTransactionValue value,
|
|
}) async {
|
|
await Future.wait([
|
|
_ensureAtLeastOneParagraphExists(value: value),
|
|
_removeColumnIfItIsEmpty(value: value),
|
|
]);
|
|
}
|
|
|
|
Future<void> _ensureAtLeastOneParagraphExists({
|
|
required EditorTransactionValue value,
|
|
}) async {
|
|
final document = editorState.document;
|
|
if (document.root.children.isEmpty) {
|
|
final transaction = editorState.transaction;
|
|
transaction
|
|
..insertNode([0], paragraphNode())
|
|
..afterSelection = Selection.collapsed(
|
|
Position(path: [0]),
|
|
);
|
|
await editorState.apply(transaction);
|
|
}
|
|
}
|
|
|
|
Future<void> _removeColumnIfItIsEmpty({
|
|
required EditorTransactionValue value,
|
|
}) async {
|
|
final transaction = value.$2;
|
|
final options = value.$3;
|
|
|
|
if (options.inMemoryUpdate) {
|
|
return;
|
|
}
|
|
|
|
for (final operation in transaction.operations) {
|
|
final deleteColumnsTransaction = editorState.transaction;
|
|
if (operation is DeleteOperation) {
|
|
final path = operation.path;
|
|
final column = editorState.document.nodeAtPath(path.parent);
|
|
if (column != null && column.type == SimpleColumnBlockKeys.type) {
|
|
// check if the column is empty
|
|
final children = column.children;
|
|
if (children.isEmpty) {
|
|
// delete the column or the columns
|
|
final columns = column.parent;
|
|
|
|
if (columns != null &&
|
|
columns.type == SimpleColumnsBlockKeys.type) {
|
|
final nonEmptyColumnCount = columns.children.fold(
|
|
0,
|
|
(p, c) => c.children.isEmpty ? p : p + 1,
|
|
);
|
|
|
|
// Example:
|
|
// columns
|
|
// - column 1
|
|
// - paragraph 1-1
|
|
// - paragraph 1-2
|
|
// - column 2
|
|
// - paragraph 2
|
|
// - column 3
|
|
// - paragraph 3
|
|
//
|
|
// case 1: delete the paragraph 3 from column 3.
|
|
// because there is only one child in column 3, we should delete the column 3 as well.
|
|
// the result should be:
|
|
// columns
|
|
// - column 1
|
|
// - paragraph 1-1
|
|
// - paragraph 1-2
|
|
// - column 2
|
|
// - paragraph 2
|
|
//
|
|
// case 2: delete the paragraph 3 from column 3 and delete the paragraph 2 from column 2.
|
|
// in this case, there will be only one column left, so we should delete the columns block and flatten the children.
|
|
// the result should be:
|
|
// paragraph 1-1
|
|
// paragraph 1-2
|
|
|
|
// if there is only one empty column left, delete the columns block and flatten the children
|
|
if (nonEmptyColumnCount <= 1) {
|
|
// move the children in columns out of the column
|
|
final children = columns.children
|
|
.map((e) => e.children)
|
|
.expand((e) => e)
|
|
.map((e) => e.deepCopy())
|
|
.toList();
|
|
deleteColumnsTransaction.insertNodes(columns.path, children);
|
|
deleteColumnsTransaction.deleteNode(columns);
|
|
} else {
|
|
// otherwise, delete the column
|
|
deleteColumnsTransaction.deleteNode(column);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deleteColumnsTransaction.operations.isNotEmpty) {
|
|
await editorState.apply(deleteColumnsTransaction);
|
|
}
|
|
}
|
|
}
|
|
}
|