feat: move divider plugin to appflowy editor plugins directory

This commit is contained in:
Lucas.Xu 2022-12-01 11:14:35 +08:00
parent 7ba638268b
commit 157f929ff9
8 changed files with 176 additions and 183 deletions

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:flutter/material.dart';
class SimpleEditor extends StatelessWidget {
@ -30,10 +31,20 @@ class SimpleEditor extends StatelessWidget {
),
);
onEditorStateChange(editorState);
return AppFlowyEditor(
editorState: editorState,
themeData: themeData,
autoFocus: editorState.document.isEmpty,
customBuilders: {
kDividerType: DividerWidgetBuilder(),
},
shortcutEvents: [
insertDividerEvent,
],
selectionMenuItems: [
dividerMenuItem,
],
);
} else {
return const Center(

View File

@ -1,166 +0,0 @@
import 'dart:collection';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
ShortcutEvent insertHorizontalRule = ShortcutEvent(
key: 'Horizontal rule',
command: 'Minus',
handler: _insertHorzaontalRule,
);
ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
final selection = editorState.service.selectionService.currentSelection.value;
final textNodes = editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
if (textNodes.length != 1 || selection == null) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
if (textNode.toPlainText() == '--') {
final transaction = editorState.transaction
..deleteText(textNode, 0, 2)
..insertNode(
textNode.path,
Node(
type: 'horizontal_rule',
children: LinkedList(),
attributes: {},
),
)
..afterSelection =
Selection.single(path: textNode.path.next, startOffset: 0);
editorState.apply(transaction);
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
};
SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
name: () => 'Horizontal rule',
icon: (_, __) => const Icon(
Icons.horizontal_rule,
color: Colors.black,
size: 18.0,
),
keywords: ['horizontal rule'],
handler: (editorState, _, __) {
final selection =
editorState.service.selectionService.currentSelection.value;
final textNodes = editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
if (selection == null || textNodes.isEmpty) {
return;
}
final textNode = textNodes.first;
if (textNode.toPlainText().isEmpty) {
final transaction = editorState.transaction
..insertNode(
textNode.path,
Node(
type: 'horizontal_rule',
children: LinkedList(),
attributes: {},
),
)
..afterSelection =
Selection.single(path: textNode.path.next, startOffset: 0);
editorState.apply(transaction);
} else {
final transaction = editorState.transaction
..insertNode(
selection.end.path.next,
TextNode(
children: LinkedList(),
attributes: {
'subtype': 'horizontal_rule',
},
delta: Delta()..insert('---'),
),
)
..afterSelection = selection;
editorState.apply(transaction);
}
},
);
class HorizontalRuleWidgetBuilder extends NodeWidgetBuilder<Node> {
@override
Widget build(NodeWidgetContext<Node> context) {
return _HorizontalRuleWidget(
key: context.node.key,
node: context.node,
editorState: context.editorState,
);
}
@override
NodeValidator<Node> get nodeValidator => (node) {
return true;
};
}
class _HorizontalRuleWidget extends StatefulWidget {
const _HorizontalRuleWidget({
Key? key,
required this.node,
required this.editorState,
}) : super(key: key);
final Node node;
final EditorState editorState;
@override
State<_HorizontalRuleWidget> createState() => __HorizontalRuleWidgetState();
}
class __HorizontalRuleWidgetState extends State<_HorizontalRuleWidget>
with SelectableMixin {
RenderBox get _renderBox => context.findRenderObject() as RenderBox;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Container(
height: 1,
color: Colors.grey,
),
);
}
@override
Position start() => Position(path: widget.node.path, offset: 0);
@override
Position end() => Position(path: widget.node.path, offset: 1);
@override
Position getPositionInOffset(Offset start) => end();
@override
bool get shouldCursorBlink => false;
@override
CursorStyle get cursorStyle => CursorStyle.borderLine;
@override
Rect? getCursorRectInPosition(Position position) {
final size = _renderBox.size;
return Rect.fromLTWH(-size.width / 2.0, 0, size.width, size.height);
}
@override
List<Rect> getRectsInSelection(Selection selection) =>
[Offset.zero & _renderBox.size];
@override
Selection getSelectionInRange(Offset start, Offset end) => Selection.single(
path: widget.node.path,
startOffset: 0,
endOffset: 1,
);
@override
Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset);
}

View File

@ -45,6 +45,8 @@ dependencies:
universal_html: ^2.0.8
highlight: ^0.7.0
flutter_math_fork: ^0.6.3+1
appflowy_editor_plugins:
path: ../../../packages/appflowy_editor_plugins
dev_dependencies:
flutter_test:

View File

@ -1,7 +1,4 @@
library appflowy_editor_plugins;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}
export 'src/divider/divider_node_widget.dart';
export 'src/divider/divider_shortcut_event.dart';

View File

@ -0,0 +1,84 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
const String kDividerType = 'divider';
class DividerWidgetBuilder extends NodeWidgetBuilder<Node> {
@override
Widget build(NodeWidgetContext<Node> context) {
return _DividerWidget(
key: context.node.key,
node: context.node,
editorState: context.editorState,
);
}
@override
NodeValidator<Node> get nodeValidator => (node) {
return true;
};
}
class _DividerWidget extends StatefulWidget {
const _DividerWidget({
Key? key,
required this.node,
required this.editorState,
}) : super(key: key);
final Node node;
final EditorState editorState;
@override
State<_DividerWidget> createState() => _DividerWidgetState();
}
class _DividerWidgetState extends State<_DividerWidget> with SelectableMixin {
RenderBox get _renderBox => context.findRenderObject() as RenderBox;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Container(
height: 1,
color: Colors.grey,
),
);
}
@override
Position start() => Position(path: widget.node.path, offset: 0);
@override
Position end() => Position(path: widget.node.path, offset: 1);
@override
Position getPositionInOffset(Offset start) => end();
@override
bool get shouldCursorBlink => false;
@override
CursorStyle get cursorStyle => CursorStyle.borderLine;
@override
Rect? getCursorRectInPosition(Position position) {
final size = _renderBox.size;
return Rect.fromLTWH(-size.width / 2.0, 0, size.width, size.height);
}
@override
List<Rect> getRectsInSelection(Selection selection) =>
[Offset.zero & _renderBox.size];
@override
Selection getSelectionInRange(Offset start, Offset end) => Selection.single(
path: widget.node.path,
startOffset: 0,
endOffset: 1,
);
@override
Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset);
}

View File

@ -0,0 +1,72 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/src/divider/divider_node_widget.dart';
import 'package:flutter/material.dart';
// insert divider into a document by typing three minuses.
// ---
ShortcutEvent insertDividerEvent = ShortcutEvent(
key: 'Divider',
command: 'Minus',
handler: _insertDividerHandler,
);
ShortcutEventHandler _insertDividerHandler = (editorState, event) {
final selection = editorState.service.selectionService.currentSelection.value;
final textNodes = editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
if (textNodes.length != 1 || selection == null) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
if (textNode.toPlainText() != '--') {
return KeyEventResult.ignored;
}
final transaction = editorState.transaction
..deleteText(textNode, 0, 2) // remove the existing minuses.
..insertNode(textNode.path, Node(type: kDividerType)) // insert the divder
..afterSelection = Selection.single(
// update selection to the next text node.
path: textNode.path.next,
startOffset: 0,
);
editorState.apply(transaction);
return KeyEventResult.handled;
};
SelectionMenuItem dividerMenuItem = SelectionMenuItem(
name: () => 'Divider',
icon: (editorState, onSelected) => Icon(
Icons.horizontal_rule,
color: onSelected
? editorState.editorStyle.selectionMenuItemSelectedIconColor
: editorState.editorStyle.selectionMenuItemIconColor,
size: 18.0,
),
keywords: ['horizontal rule', 'divider'],
handler: (editorState, _, __) {
final selection =
editorState.service.selectionService.currentSelection.value;
final textNodes = editorState.service.selectionService.currentSelectedNodes
.whereType<TextNode>();
if (textNodes.length != 1 || selection == null) {
return;
}
final textNode = textNodes.first;
// insert the divider at current path if the text node is empty.
if (textNode.toPlainText().isEmpty) {
final transaction = editorState.transaction
..insertNode(textNode.path, Node(type: kDividerType))
..afterSelection = Selection.single(
path: textNode.path.next,
startOffset: 0,
);
editorState.apply(transaction);
} else {
// insert the divider at the path next to current path if the text node is not empty.
final transaction = editorState.transaction
..insertNode(selection.end.path.next, Node(type: kDividerType))
..afterSelection = selection;
editorState.apply(transaction);
}
},
);

View File

@ -1,7 +1,9 @@
name: appflowy_editor_plugins
description: A new Flutter package project.
version: 0.0.1
homepage:
homepage: https://github.com/AppFlowy-IO/AppFlowy
publish_to: none
environment:
sdk: ">=2.17.6 <3.0.0"
@ -10,6 +12,8 @@ environment:
dependencies:
flutter:
sdk: flutter
appflowy_editor:
path: ../appflowy_editor
dev_dependencies:
flutter_test:

View File

@ -1,12 +1 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}