feat:IInline math equation (#2949)

This commit is contained in:
Lucas.Xu
2023-07-09 10:03:22 +07:00
committed by GitHub
parent 4c17298432
commit ff9b3c56c5
43 changed files with 500 additions and 120 deletions

View File

@ -49,9 +49,10 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
quoteItem,
bulletedListItem,
numberedListItem,
inlineMathEquationItem,
linkItem,
textColorItem,
highlightColorItem,
buildTextColorItem(),
buildHighlightColorItem(),
];
late final List<SelectionMenuItem> slashMenuItems;

View File

@ -1,7 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';

View File

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
@ -43,20 +43,6 @@ enum CoverType {
}
}
class DocumentHeaderNodeWidgetBuilder implements NodeWidgetBuilder {
@override
Widget build(NodeWidgetContext<Node> context) {
return DocumentHeaderNodeWidget(
key: context.node.key,
node: context.node,
editorState: context.editorState,
);
}
@override
NodeValidator<Node> get nodeValidator => (_) => true;
}
class DocumentHeaderNodeWidget extends StatefulWidget {
const DocumentHeaderNodeWidget({
required this.node,

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';

View File

@ -1,5 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';

View File

@ -0,0 +1,173 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text_input.dart';
import 'package:flutter/material.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:provider/provider.dart';
class InlineMathEquationKeys {
const InlineMathEquationKeys._();
static const formula = 'formula';
}
class InlineMathEquation extends StatefulWidget {
const InlineMathEquation({
super.key,
required this.formula,
required this.node,
required this.index,
this.textStyle,
});
final Node node;
final int index;
final String formula;
final TextStyle? textStyle;
@override
State<InlineMathEquation> createState() => _InlineMathEquationState();
}
class _InlineMathEquationState extends State<InlineMathEquation> {
final popoverController = PopoverController();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return _IgnoreParentPointer(
child: AppFlowyPopover(
controller: popoverController,
direction: PopoverDirection.bottomWithLeftAligned,
popupBuilder: (_) {
return MathInputTextField(
initialText: widget.formula,
onSubmit: (value) async {
popoverController.close();
if (value == widget.formula) {
return;
}
final editorState = context.read<EditorState>();
final transaction = editorState.transaction
..formatText(widget.node, widget.index, 1, {
InlineMathEquationKeys.formula: value,
});
await editorState.apply(transaction);
},
);
},
offset: const Offset(0, 10),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const HSpace(2),
Math.tex(
widget.formula,
options: MathOptions(
style: MathStyle.text,
mathFontOptions: const FontOptions(
fontShape: FontStyle.italic,
),
fontSize: 14.0,
color: widget.textStyle?.color ??
theme.colorScheme.onBackground,
),
),
const HSpace(2),
],
),
),
),
),
);
}
}
class MathInputTextField extends StatefulWidget {
const MathInputTextField({
super.key,
required this.initialText,
required this.onSubmit,
});
final String initialText;
final void Function(String value) onSubmit;
@override
State<MathInputTextField> createState() => _MathInputTextFieldState();
}
class _MathInputTextFieldState extends State<MathInputTextField> {
late final TextEditingController textEditingController;
@override
void initState() {
super.initState();
textEditingController = TextEditingController(
text: widget.initialText,
);
textEditingController.selection = TextSelection(
baseOffset: 0,
extentOffset: widget.initialText.length,
);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: 240,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: FlowyFormTextInput(
autoFocus: true,
textAlign: TextAlign.left,
controller: textEditingController,
contentPadding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 4.0,
),
onEditingComplete: () =>
widget.onSubmit(textEditingController.text),
),
),
const HSpace(4.0),
FlowyButton(
text: FlowyText(LocaleKeys.button_Done.tr()),
useIntrinsicWidth: true,
onTap: () => widget.onSubmit(textEditingController.text),
),
],
),
);
}
}
class _IgnoreParentPointer extends StatelessWidget {
const _IgnoreParentPointer({
required this.child,
});
final Widget child;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {},
onTapDown: (_) {},
onDoubleTap: () {},
onLongPress: () {},
child: child,
);
}
}

View File

@ -0,0 +1,52 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart';
final ToolbarItem inlineMathEquationItem = ToolbarItem(
id: 'editor.inline_math_equation',
group: 2,
isActive: onlyShowInSingleSelectionAndTextType,
builder: (context, editorState) {
final selection = editorState.selection!;
final nodes = editorState.getNodesInSelection(selection);
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
return delta.everyAttributes(
(attributes) => attributes[InlineMathEquationKeys.formula] != null,
);
});
return IconItemWidget(
iconBuilder: (_) => svgWidget(
'editor/math',
size: const Size.square(16),
color: Colors.white,
),
isHighlight: isHighlight,
tooltip: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
onPressed: () async {
final selection = editorState.selection;
if (selection == null || selection.isCollapsed) {
return;
}
final node = editorState.getNodeAtPath(selection.start.path);
if (node == null) {
return;
}
final text = editorState.getTextInSelection(selection).join();
final transaction = editorState.transaction
..replaceText(
node,
selection.startIndex,
selection.length,
'\$',
attributes: {
InlineMathEquationKeys.formula: text,
},
);
await editorState.apply(transaction);
},
);
},
);

View File

@ -24,8 +24,6 @@ class MentionBlockKeys {
static const mention = 'mention';
static const type = 'type'; // MentionType, String
static const pageId = 'page_id';
static const pageType = 'page_type';
static const pageName = 'page_name';
}
class InlinePageReferenceService {

View File

@ -1,22 +1,24 @@
export 'actions/block_action_list.dart';
export 'actions/option_action.dart';
export 'callout/callout_block_component.dart';
export 'code_block/code_block_component.dart';
export 'code_block/code_block_shortcut_event.dart';
export 'header/cover_editor_bloc.dart';
export 'header/document_header_node_widget.dart';
export 'header/custom_cover_picker.dart';
export 'emoji_picker/emoji_menu_item.dart';
export 'extensions/flowy_tint_extension.dart';
export 'database/database_view_block_component.dart';
export 'database/inline_database_menu_item.dart';
export 'database/referenced_database_menu_item.dart';
export 'database/database_view_block_component.dart';
export 'emoji_picker/emoji_menu_item.dart';
export 'extensions/flowy_tint_extension.dart';
export 'header/cover_editor_bloc.dart';
export 'header/custom_cover_picker.dart';
export 'header/document_header_node_widget.dart';
export 'image/image_menu.dart';
export 'image/image_selection_menu.dart';
export 'inline_math_equation/inline_math_equation.dart';
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
export 'math_equation/math_equation_block_component.dart';
export 'openai/widgets/auto_completion_node_widget.dart';
export 'openai/widgets/smart_edit_node_widget.dart';
export 'openai/widgets/smart_edit_toolbar_item.dart';
export 'outline/outline_block_component.dart';
export 'toggle/toggle_block_component.dart';
export 'toggle/toggle_block_shortcut_event.dart';
export 'outline/outline_block_component.dart';
export 'image/image_menu.dart';
export 'image/image_selection_menu.dart';
export 'actions/option_action.dart';
export 'actions/block_action_list.dart';

View File

@ -1,7 +1,8 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg, Log;
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -173,13 +174,17 @@ class EditorStyleCustomizer {
}
InlineSpan customizeAttributeDecorator(
TextInsert textInsert,
Node node,
int index,
TextInsert text,
TextSpan textSpan,
) {
final attributes = textInsert.attributes;
final attributes = text.attributes;
if (attributes == null) {
return textSpan;
}
// customize the inline mention block, like inline page
final mention = attributes[MentionBlockKeys.mention] as Map?;
if (mention != null) {
final type = mention[MentionBlockKeys.type];
@ -193,6 +198,21 @@ class EditorStyleCustomizer {
);
}
}
// customize the inline math equation block
final formula = attributes[InlineMathEquationKeys.formula] as String?;
if (formula != null) {
return WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: InlineMathEquation(
node: node,
index: index,
formula: formula,
textStyle: style().textStyleConfiguration.text,
),
);
}
return textSpan;
}
}