Merge pull request #1239 from LucasXu0/horizontal_rule

feat: implement horizontal rule
This commit is contained in:
Lucas.Xu 2022-10-07 12:01:18 +08:00 committed by GitHub
commit c0879e8445
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 222 additions and 6 deletions

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'package:example/plugin/code_block_node_widget.dart';
import 'package:example/plugin/horizontal_rule_node_widget.dart';
import 'package:example/plugin/tex_block_node_widget.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -121,15 +122,18 @@ class _MyHomePageState extends State<MyHomePage> {
customBuilders: {
'text/code_block': CodeBlockNodeWidgetBuilder(),
'tex': TeXBlockNodeWidgetBuidler(),
'horizontal_rule': HorizontalRuleWidgetBuilder(),
},
shortcutEvents: [
enterInCodeBlock,
ignoreKeysInCodeBlock,
underscoreToItalic,
insertHorizontalRule,
],
selectionMenuItems: [
codeBlockMenuItem,
teXBlockMenuItem,
horizontalRuleMenuItem,
],
),
);

View File

@ -46,7 +46,11 @@ ShortcutEventHandler _ignorekHandler = (editorState, event) {
SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
name: () => 'Code Block',
icon: const Icon(Icons.abc),
icon: const Icon(
Icons.abc,
color: Colors.black,
size: 18.0,
),
keywords: ['code block'],
handler: (editorState, _, __) {
final selection =

View File

@ -0,0 +1,167 @@
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.toRawString() == '--') {
TransactionBuilder(editorState)
..deleteText(textNode, 0, 2)
..insertNode(
textNode.path,
Node(
type: 'horizontal_rule',
children: LinkedList(),
attributes: {},
),
)
..afterSelection =
Selection.single(path: textNode.path.next, startOffset: 0)
..commit();
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.toRawString().isEmpty) {
TransactionBuilder(editorState)
..insertNode(
textNode.path,
Node(
type: 'horizontal_rule',
children: LinkedList(),
attributes: {},
),
)
..afterSelection =
Selection.single(path: textNode.path.next, startOffset: 0)
..commit();
} else {
TransactionBuilder(editorState)
..insertNode(
selection.end.path.next,
TextNode(
type: 'text',
children: LinkedList(),
attributes: {
'subtype': 'horizontal_rule',
},
delta: Delta()..insert('---'),
),
)
..afterSelection = selection
..commit();
}
},
);
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

@ -6,7 +6,11 @@ import 'package:flutter_math_fork/flutter_math.dart';
SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
name: () => 'Tex',
icon: const Icon(Icons.text_fields_rounded),
icon: const Icon(
Icons.text_fields_rounded,
color: Colors.black,
size: 18.0,
),
keywords: ['tex, latex, katex'],
handler: (editorState, _, __) {
final selection =

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:appflowy_editor/src/render/selection/selectable.dart';
import 'package:flutter/material.dart';
class CursorWidget extends StatefulWidget {
@ -9,9 +10,13 @@ class CursorWidget extends StatefulWidget {
required this.rect,
required this.color,
this.blinkingInterval = 0.5,
this.shouldBlink = true,
this.cursorStyle = CursorStyle.verticalLine,
}) : super(key: key);
final double blinkingInterval; // milliseconds
final bool shouldBlink;
final CursorStyle cursorStyle;
final Color color;
final Rect rect;
final LayerLink layerLink;
@ -67,11 +72,28 @@ class CursorWidgetState extends State<CursorWidget> {
// Ignore the gestures in cursor
// to solve the problem that cursor area cannot be selected.
child: IgnorePointer(
child: Container(
color: showCursor ? widget.color : Colors.transparent,
),
child: _buildCursor(context),
),
),
);
}
Widget _buildCursor(BuildContext context) {
var color = widget.color;
if (widget.shouldBlink && !showCursor) {
color = Colors.transparent;
}
switch (widget.cursorStyle) {
case CursorStyle.verticalLine:
return Container(
color: color,
);
case CursorStyle.borderLine:
return Container(
decoration: BoxDecoration(
border: Border.all(color: color, width: 2),
),
);
}
}
}

View File

@ -2,6 +2,11 @@ import 'package:appflowy_editor/src/document/position.dart';
import 'package:appflowy_editor/src/document/selection.dart';
import 'package:flutter/material.dart';
enum CursorStyle {
verticalLine,
borderLine,
}
/// [SelectableMixin] is used for the editor to calculate the position
/// and size of the selection.
///
@ -53,4 +58,8 @@ mixin SelectableMixin<T extends StatefulWidget> on State<T> {
Selection? getWorldBoundaryInOffset(Offset start) {
return null;
}
bool get shouldCursorBlink => true;
CursorStyle get cursorStyle => CursorStyle.verticalLine;
}

View File

@ -3,7 +3,6 @@ import 'package:appflowy_editor/src/service/internal_key_event_handlers/number_l
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
// Handle delete text.
ShortcutEventHandler deleteTextHandler = (editorState, event) {
@ -84,6 +83,11 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
}
} else {
if (textNodes.isEmpty) {
if (nonTextNodes.isNotEmpty) {
transactionBuilder.afterSelection =
Selection.collapsed(selection.start);
}
transactionBuilder.commit();
return KeyEventResult.handled;
}
final startPosition = selection.start;

View File

@ -457,6 +457,8 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
rect: cursorRect,
color: widget.cursorColor,
layerLink: node.layerLink,
shouldBlink: selectable.shouldCursorBlink,
cursorStyle: selectable.cursorStyle,
),
);