Skip to content

Commit

Permalink
Add code block copy button (fleather-editor#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
yzxh24 committed Oct 5, 2024
1 parent c391628 commit 114eb64
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 4 deletions.
49 changes: 45 additions & 4 deletions packages/fleather/lib/src/rendering/editable_text_line.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import 'editable_box.dart';

const double _kCursorHeightOffset = 2.0; // pixels

enum TextLineSlot { leading, body }
enum TextLineSlot { leading, body, trailing }

class RenderEditableTextLine extends RenderEditableBox {
/// Creates new editable paragraph render box.
Expand Down Expand Up @@ -167,6 +167,9 @@ class RenderEditableTextLine extends RenderEditableBox {
if (_body != null) {
yield _body!;
}
if (_trailing != null) {
yield _trailing!;
}
}

RenderBox? get leading => _leading;
Expand All @@ -176,6 +179,13 @@ class RenderEditableTextLine extends RenderEditableBox {
_leading = _updateChild(_leading, value, TextLineSlot.leading);
}

RenderBox? get trailing => _trailing;
RenderBox? _trailing;

set trailing(RenderBox? value) {
_trailing = _updateChild(_trailing, value, TextLineSlot.trailing);
}

RenderContentProxyBox? get body => _body;
RenderContentProxyBox? _body;

Expand Down Expand Up @@ -573,6 +583,7 @@ class RenderEditableTextLine extends RenderEditableBox {

add(leading, 'leading');
add(body, 'body');
add(trailing, 'trailing');
return value;
}

Expand All @@ -586,8 +597,9 @@ class RenderEditableTextLine extends RenderEditableBox {
final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
final effectiveHeight = math.max(0.0, height - verticalPadding);
final leadingWidth = leading?.getMinIntrinsicWidth(effectiveHeight) ?? 0;
final trailingWidth = trailing?.getMinIntrinsicWidth(effectiveHeight) ?? 0;
final bodyWidth = body?.getMinIntrinsicWidth(effectiveHeight) ?? 0;
return horizontalPadding + leadingWidth + bodyWidth;
return horizontalPadding + leadingWidth + bodyWidth + trailingWidth;
}

@override
Expand All @@ -597,8 +609,9 @@ class RenderEditableTextLine extends RenderEditableBox {
final verticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom;
final effectiveHeight = math.max(0.0, height - verticalPadding);
final leadingWidth = leading?.getMaxIntrinsicWidth(effectiveHeight) ?? 0;
final trailingWidth = trailing?.getMaxIntrinsicWidth(effectiveHeight) ?? 0;
final bodyWidth = body?.getMaxIntrinsicWidth(effectiveHeight) ?? 0;
return horizontalPadding + leadingWidth + bodyWidth;
return horizontalPadding + leadingWidth + bodyWidth + trailingWidth;
}

@override
Expand Down Expand Up @@ -642,7 +655,7 @@ class RenderEditableTextLine extends RenderEditableBox {
_resolvePadding();
assert(_resolvedPadding != null);

if (body == null && leading == null) {
if (body == null && leading == null && trailing == null) {
size = constraints.constrain(Size(
_resolvedPadding!.left + _resolvedPadding!.right,
_resolvedPadding!.top + _resolvedPadding!.bottom,
Expand Down Expand Up @@ -672,6 +685,18 @@ class RenderEditableTextLine extends RenderEditableBox {
parentData.offset = Offset(dxOffset, _resolvedPadding!.top);
}

if (trailing != null) {
final trailingConstraints = innerConstraints.copyWith(
minWidth: indentWidth,
maxWidth: indentWidth,
maxHeight: body!.size.height);
trailing!.layout(trailingConstraints, parentUsesSize: true);
final parentData = trailing!.parentData as BoxParentData;
final dxOffset =
textDirection == TextDirection.rtl ? 0.0 : body!.size.width;
parentData.offset = Offset(dxOffset, _resolvedPadding!.top);
}

size = constraints.constrain(Size(
_resolvedPadding!.left + body!.size.width + _resolvedPadding!.right,
_resolvedPadding!.top + body!.size.height + _resolvedPadding!.bottom,
Expand All @@ -697,6 +722,12 @@ class RenderEditableTextLine extends RenderEditableBox {
context.paintChild(leading!, effectiveOffset);
}

if (trailing != null) {
final parentData = trailing!.parentData as BoxParentData;
final effectiveOffset = offset + parentData.offset;
context.paintChild(trailing!, effectiveOffset);
}

if (body != null) {
final parentData = body!.parentData as BoxParentData;
final effectiveOffset = offset + parentData.offset;
Expand Down Expand Up @@ -806,6 +837,16 @@ class RenderEditableTextLine extends RenderEditableBox {
);
if (isHit) return true;
}
if (trailing != null) {
final childParentData = trailing!.parentData as BoxParentData;
final isHit = result.addWithPaintOffset(
offset: childParentData.offset,
position: position,
hitTest: (result, transformed) =>
trailing!.hitTest(result, position: transformed),
);
if (isHit) return true;
}
if (body == null) return false;
final parentData = body!.parentData as BoxParentData;
final offset = position - parentData.offset;
Expand Down
27 changes: 27 additions & 0 deletions packages/fleather/lib/src/widgets/editable_text_block.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:parchment/parchment.dart';
import 'package:flutter/services.dart';

import '../../util.dart';
import '../rendering/editable_text_block.dart';
Expand Down Expand Up @@ -74,6 +75,7 @@ class EditableTextBlock extends StatelessWidget {
node: line,
spacing: _getSpacingForLine(line, index, count, theme),
leading: leadingWidgets?[index],
trailing: index == 0 ? _buildCopyButton(context, lineNodes) : null,
indentWidth: _getIndentWidth(line),
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
body: TextLine(
Expand All @@ -96,6 +98,31 @@ class EditableTextBlock extends StatelessWidget {
return children.toList(growable: false);
}

Widget? _buildCopyButton(BuildContext context, List<LineNode> lineNodes) {
final block = node.style.get(ParchmentAttribute.block);
if (block != ParchmentAttribute.block.code) {
return null;
}
return InkWell(
onTap: () {
List<String> lines = [];
lines = lineNodes.map((e) => e.toPlainText().trimRight()).toList();
Clipboard.setData(ClipboardData(text: lines.join('\n')));
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(const SnackBar(
content: Text('Copy code successfully'),
duration: Duration(seconds: 1),
));
},
child: const Icon(
Icons.copy,
size: 16.0,
color: Colors.grey,
),
);
}

List<Widget>? _buildLeading(
FleatherThemeData theme, List<LineNode> children) {
final block = node.style.get(ParchmentAttribute.block);
Expand Down
9 changes: 9 additions & 0 deletions packages/fleather/lib/src/widgets/editable_text_line.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class EditableTextLine extends RenderObjectWidget {
/// A widget to display before the body.
final Widget? leading;

/// A widget to display after the body.
final Widget? trailing;

/// The primary rich text content of this widget. Usually [TextLine] widget.
final Widget body;

Expand Down Expand Up @@ -45,6 +48,7 @@ class EditableTextLine extends RenderObjectWidget {
required this.hasFocus,
required this.devicePixelRatio,
this.leading,
this.trailing, // 添加 trailing 参数
this.indentWidth = 0.0,
this.spacing = const VerticalSpacing(),
});
Expand Down Expand Up @@ -134,6 +138,7 @@ class _RenderEditableTextLineElement extends RenderObjectElement {
super.mount(parent, newSlot);
_mountChild(widget.leading, TextLineSlot.leading);
_mountChild(widget.body, TextLineSlot.body);
_mountChild(widget.trailing, TextLineSlot.trailing); // 添加 trailing
}

void _updateChild(Widget? widget, TextLineSlot slot) {
Expand All @@ -153,13 +158,17 @@ class _RenderEditableTextLineElement extends RenderObjectElement {
assert(widget == newWidget);
_updateChild(widget.leading, TextLineSlot.leading);
_updateChild(widget.body, TextLineSlot.body);
_updateChild(widget.trailing, TextLineSlot.trailing); // 添加 trailing
}

void _updateRenderObject(RenderObject? child, TextLineSlot? slot) {
switch (slot) {
case TextLineSlot.leading:
renderObject.leading = child as RenderBox?;
break;
case TextLineSlot.trailing:
renderObject.trailing = child as RenderBox?;
break;
case TextLineSlot.body:
renderObject.body = child as RenderContentProxyBox?;
break;
Expand Down

0 comments on commit 114eb64

Please sign in to comment.