Skip to content

Commit

Permalink
feat: added divider to work with * (#118)
Browse files Browse the repository at this point in the history
* feat: Added divider to work with *

* chore: Resolved the suggested changes

* refactor: removed the check for star character

* feat:added  character variable to insert divider .

* feat: support converting stars ** to divider

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
  • Loading branch information
glunkad and LucasXu0 authored Jun 7, 2023
1 parent 77e0f6c commit eeae6fe
Show file tree
Hide file tree
Showing 8 changed files with 371 additions and 34 deletions.
5 changes: 5 additions & 0 deletions lib/src/editor/block_component/block_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export 'heading_block_component/heading_character_shortcut.dart';
// image
export 'image_block_component/image_block_component.dart';

// divider
export 'divider_block_component/divider_block_component.dart';
export 'divider_block_component/divider_character_shortcut.dart';
export 'divider_block_component/divider_menu_item.dart';

// base
export 'base_component/convert_to_paragraph_command.dart';
export 'base_component/insert_newline_in_type_command.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';

class DividerBlockKeys {
const DividerBlockKeys._();

static const String type = 'divider';
}

// creating a new callout node
Node dividerNode() {
return Node(
type: DividerBlockKeys.type,
);
}

class DividerBlockComponentBuilder extends BlockComponentBuilder {
DividerBlockComponentBuilder({
this.configuration = const BlockComponentConfiguration(),
this.lineColor = Colors.grey,
});

@override
final BlockComponentConfiguration configuration;

final Color lineColor;

@override
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
final node = blockComponentContext.node;
return DividerBlockComponentWidget(
key: node.key,
node: node,
configuration: configuration,
lineColor: lineColor,
showActions: showActions(node),
actionBuilder: (context, state) => actionBuilder(
blockComponentContext,
state,
),
);
}

@override
bool validate(Node node) => node.children.isEmpty;
}

class DividerBlockComponentWidget extends BlockComponentStatefulWidget {
const DividerBlockComponentWidget({
super.key,
required super.node,
super.showActions,
super.actionBuilder,
super.configuration = const BlockComponentConfiguration(),
this.lineColor = Colors.grey,
});

final Color lineColor;

@override
State<DividerBlockComponentWidget> createState() =>
_DividerBlockComponentWidgetState();
}

class _DividerBlockComponentWidgetState
extends State<DividerBlockComponentWidget> with SelectableMixin {
RenderBox get _renderBox => context.findRenderObject() as RenderBox;

@override
Widget build(BuildContext context) {
Widget child = Container(
height: 10,
alignment: Alignment.center,
child: Divider(
color: widget.lineColor,
thickness: 1,
),
);

if (widget.showActions && widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: widget.node,
actionBuilder: widget.actionBuilder!,
child: child,
);
}

return child;
}

@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.cover;

@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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:appflowy_editor/appflowy_editor.dart';

/// insert divider into a document by typing three minuses(-).
///
/// - support
/// - desktop
/// - web
/// - mobile
///
final CharacterShortcutEvent convertMinusesToDivider = CharacterShortcutEvent(
key: 'convert minuses to a divider',
character: '-',
handler: (editorState) => _convertSyntaxToDivider(editorState, '--'),
);

final CharacterShortcutEvent convertStarsToDivider = CharacterShortcutEvent(
key: 'convert starts to a divider',
character: '*',
handler: (editorState) => _convertSyntaxToDivider(editorState, '**'),
);

Future<bool> _convertSyntaxToDivider(
EditorState editorState,
String syntax,
) async {
final selection = editorState.selection;
if (selection == null || !selection.isCollapsed) {
return false;
}
final path = selection.end.path;
final node = editorState.getNodeAtPath(path);
final delta = node?.delta;
if (node == null || delta == null) {
return false;
}
if (delta.toPlainText() != syntax) {
return false;
}
final transaction = editorState.transaction
..insertNode(path, dividerNode())
..insertNode(path, paragraphNode())
..deleteNode(node)
..afterSelection = Selection.collapse(path, 0);
editorState.apply(transaction);
return true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_icon.dart';
import 'package:flutter/material.dart';

SelectionMenuItem dividerMenuItem = SelectionMenuItem(
name: 'Divider',
icon: (editorState, isSelected, style) => SelectionMenuIconWidget(
icon: Icons.horizontal_rule,
isSelected: isSelected,
style: style,
),
keywords: ['horizontal rule', 'divider'],
handler: (editorState, _, __) {
final selection = editorState.selection;
if (selection == null || !selection.isCollapsed) {
return;
}
final path = selection.end.path;
final node = editorState.getNodeAtPath(path);
final delta = node?.delta;
if (node == null || delta == null) {
return;
}
final insertedPath = delta.isEmpty ? path : path.next;
final transaction = editorState.transaction
..insertNode(insertedPath, dividerNode())
..insertNode(insertedPath, paragraphNode())
..afterSelection = Selection.collapse(insertedPath.next, 0);
editorState.apply(transaction);
},
);
42 changes: 42 additions & 0 deletions lib/src/render/selection_menu/selection_menu_icon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';

class SelectionMenuIconWidget extends StatelessWidget {
SelectionMenuIconWidget({
super.key,
this.name,
this.icon,
required this.isSelected,
required this.style,
}) {
assert((name == null && icon != null) || ((name != null && icon == null)));
}

final String? name;
final IconData? icon;
final bool isSelected;
final SelectionMenuStyle style;

@override
Widget build(BuildContext context) {
if (icon != null) {
return Icon(
icon,
size: 18.0,
color: isSelected
? style.selectionMenuItemSelectedIconColor
: style.selectionMenuItemIconColor,
);
} else if (name != null) {
return FlowySvg(
name: 'selection_menu/$name',
width: 18.0,
height: 18.0,
color: isSelected
? style.selectionMenuItemSelectedIconColor
: style.selectionMenuItemIconColor,
);
}
throw UnimplementedError();
}
}
Loading

0 comments on commit eeae6fe

Please sign in to comment.