mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat:IInline math equation (#2949)
This commit is contained in:
@ -49,9 +49,10 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
quoteItem,
|
||||
bulletedListItem,
|
||||
numberedListItem,
|
||||
inlineMathEquationItem,
|
||||
linkItem,
|
||||
textColorItem,
|
||||
highlightColorItem,
|
||||
buildTextColorItem(),
|
||||
buildHighlightColorItem(),
|
||||
];
|
||||
|
||||
late final List<SelectionMenuItem> slashMenuItems;
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
@ -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 {
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user