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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 500 additions and 120 deletions

View File

@ -134,6 +134,6 @@ jobs:
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
attempt_limit: 5
attempt_limit: 20
attempt_delay: 10000

View File

@ -29,25 +29,31 @@ concurrency:
cancel-in-progress: true
jobs:
tests:
build:
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
target: x86_64-unknown-linux-gnu
- os: windows-latest
flutter_profile: development-windows-x86
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: "stable-2022-04-07"
- name: Checkout source code
uses: actions/checkout@v2
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
target: ${{ matrix.target }}
override: true
profile: minimal
@ -64,6 +70,7 @@ jobs:
prefix-key: ${{ matrix.os }}
workspaces: |
frontend/rust-lib
cache-all-crates: true
- uses: davidB/rust-cargo-make@v1
with:
@ -87,30 +94,24 @@ jobs:
cargo make appflowy-flutter-deps-tools
shell: bash
- name: Config Flutter
- name: Enable Flutter Desktop
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
flutter config --enable-linux-desktop
elif [ "$RUNNER_OS" == "macOS" ]; then
flutter config --enable-macos-desktop
elif [ "$RUNNER_OS" == "Windows" ]; then
git config --system core.longpaths true
flutter config --enable-windows-desktop
fi
shell: bash
- name: Build Test lib
- name: Build AppFlowy
working-directory: frontend
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
cargo make --profile development-linux-x86_64 appflowy-dev
elif [ "$RUNNER_OS" == "macOS" ]; then
cargo make --profile development-mac-x86_64 appflowy-dev
elif [ "$RUNNER_OS" == "Windows" ]; then
cargo make --profile development-windows-x86 appflowy-dev
fi
shell: bash
cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev
- name: Run AppFlowy tests
- name: Run Flutter integration tests
working-directory: frontend/appflowy_flutter
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
@ -135,5 +136,5 @@ jobs:
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
attempt_limit: 5
attempt_limit: 20
attempt_delay: 10000

View File

@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" style="width: 16px; height: 16px; display: block; fill: inherit; flex-shrink: 0; backface-visibility: hidden;" width="30" height="30" ><path d="M6.04883 27.3232C7.24219 27.3232 7.86426 26.4854 8.20703 25.3936L13.9834 6.79492H27.5039C28.2275 6.79492 28.7227 6.35059 28.7227 5.65234C28.7227 4.97949 28.2275 4.53516 27.5039 4.53516H13.958C12.6631 4.53516 12.0791 5.01758 11.7363 6.13477L6.18848 24.3525H5.97266L3.58594 15.9355C3.38281 15.2373 3.00195 14.9072 2.40527 14.9072C1.73242 14.9072 1.2373 15.3896 1.2373 16.0117C1.2373 16.2656 1.30078 16.4941 1.35156 16.6846L4.04297 25.5332C4.36035 26.5615 4.93164 27.3232 6.04883 27.3232ZM16.3955 24.7334C16.8652 24.7334 17.1064 24.5684 17.4619 24.0732L20.4707 19.8203H20.5215L23.5049 24.0732C23.873 24.5684 24.1143 24.7334 24.5713 24.7334C25.2061 24.7334 25.6758 24.3018 25.6758 23.7051C25.6758 23.4258 25.6123 23.1973 25.4219 22.9561L21.9307 18.208L25.4473 13.4346C25.6377 13.168 25.7139 12.9395 25.7139 12.6855C25.7139 12.127 25.2568 11.6953 24.6475 11.6953C24.2031 11.6953 23.9365 11.8477 23.6064 12.3174L20.7246 16.5957H20.6611L17.6904 12.3047C17.373 11.8477 17.0938 11.6953 16.624 11.6953C16.0146 11.6953 15.5068 12.165 15.5068 12.7363C15.5068 13.0537 15.583 13.2568 15.8115 13.5488L19.1504 18.1445L15.6084 23.0322C15.418 23.2861 15.3672 23.4639 15.3672 23.7559C15.3672 24.3018 15.8115 24.7334 16.3955 24.7334Z" fill="#2F3030"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -426,6 +426,7 @@
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
"smartEditDisabled": "Connect OpenAI in Settings",
"discardResponse": "Do you want to discard the AI responses?",
"createInlineMathEquation": "Create equation",
"cover": {
"changeCover": "Change Cover",
"colors": "Colors",

View File

@ -107,7 +107,7 @@ void main() {
// Make sure that the event is edited
tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
tester.assertNumberofEventsOnSpecificDay(2, DateTime.now());
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
// Click on the event
await tester.openCalendarEvent(index: 1);
@ -119,7 +119,7 @@ void main() {
// Check that there are 2 events
tester.assertNumberOfEventsInCalendar(2, title: 'hello world');
tester.assertNumberofEventsOnSpecificDay(3, DateTime.now());
tester.assertNumberOfEventsOnSpecificDay(3, DateTime.now());
// Delete an event
await tester.openCalendarEvent(index: 1);
@ -127,7 +127,7 @@ void main() {
// Check that there is 1 event
tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
tester.assertNumberofEventsOnSpecificDay(2, DateTime.now());
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
});
testWidgets('rescheduling events', (tester) async {
@ -150,7 +150,7 @@ void main() {
// Make sure that the event has been rescheduled to the new date
final sameDayNextWeek = firstOfThisMonth.add(const Duration(days: 7));
tester.assertNumberOfEventsInCalendar(1);
tester.assertNumberofEventsOnSpecificDay(1, sameDayNextWeek);
tester.assertNumberOfEventsOnSpecificDay(1, sameDayNextWeek);
// Delete the event
await tester.openCalendarEvent(index: 0, date: sameDayNextWeek);
@ -162,7 +162,7 @@ void main() {
await tester.dismissRowDetailPage();
// Make sure that the event is today
tester.assertNumberofEventsOnSpecificDay(1, today);
tester.assertNumberOfEventsOnSpecificDay(1, today);
// Click on the event
await tester.openCalendarEvent(index: 0);
@ -183,7 +183,7 @@ void main() {
// Make sure that the event is edited
tester.assertNumberOfEventsInCalendar(1);
tester.assertNumberofEventsOnSpecificDay(1, newDate);
tester.assertNumberOfEventsOnSpecificDay(1, newDate);
});
});
}

View File

@ -172,9 +172,10 @@ void main() {
// Focus on the editor
final textBlock = find.byType(TextBlockComponentWidget);
await tester.tapAt(tester.getCenter(textBlock));
await tester.pumpAndSettle();
// Input some text
const inputText = 'Hello world';
const inputText = 'Hello World';
await tester.ime.insertText(inputText);
expect(
find.textContaining(inputText, findRichText: true),

View File

@ -10,6 +10,8 @@ void main() {
group('database', () {
testWidgets('import v0.2.0 database data', (tester) async {
await tester.openV020database();
// wait the database data is loaded
await tester.pumpAndSettle(const Duration(microseconds: 500));
// check the text cell
final textCells = <String>['A', 'B', 'C', 'D', 'E', '', '', '', '', ''];

View File

@ -9,7 +9,7 @@ import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('document', () {
group('create and delete the document', () {
testWidgets('create a new document when launching app in first time',
(tester) async {
await tester.initializeAppFlowy();

View File

@ -0,0 +1,22 @@
import 'package:integration_test/integration_test.dart';
import 'document_create_and_delete_test.dart'
as document_create_and_delete_test;
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
import 'document_with_database_test.dart' as document_with_database_test;
import 'document_with_inline_math_equation_test.dart'
as document_with_inline_math_equation_test;
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
import 'edit_document_test.dart' as document_edit_test;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
document_create_and_delete_test.main();
document_edit_test.main();
document_with_database_test.main();
document_with_inline_page_test.main();
document_with_inline_math_equation_test.main();
document_with_cover_image_test.main();
}

View File

@ -0,0 +1,65 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
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:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/ime.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
TestWidgetsFlutterBinding.ensureInitialized();
group('inline math equation in document', () {
testWidgets('insert an inline math equation', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a inline page
const formula = 'E = MC ^ 2';
await tester.ime.insertText(formula);
await tester.editor.updateSelection(
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
// tap the inline math equation button
final inlineMathEquationButton = find.byTooltip(
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
);
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block
final inlineMathEquation = find.byType(InlineMathEquation);
expect(inlineMathEquation, findsOneWidget);
// tap it and update the content
await tester.tapButton(inlineMathEquation);
final textFormField = find.descendant(
of: find.byType(MathInputTextField),
matching: find.byType(TextFormField),
);
const newFormula = 'E = MC ^ 3';
await tester.enterText(textFormField, newFormula);
await tester.tapButton(
find.descendant(
of: find.byType(MathInputTextField),
matching: find.byType(FlowyButton),
),
);
await tester.pumpAndSettle();
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -1,23 +1,19 @@
import 'package:integration_test/integration_test.dart';
import 'switch_folder_test.dart' as switch_folder_test;
import 'document/document_test.dart' as document_test;
import 'document/cover_image_test.dart' as cover_image_test;
import 'share_markdown_test.dart' as share_markdown_test;
import 'import_files_test.dart' as import_files_test;
import 'document/document_with_database_test.dart'
as document_with_database_test;
import 'document/edit_document_test.dart' as edit_document_test;
import 'database_calendar_test.dart' as database_calendar_test;
import 'database_cell_test.dart' as database_cell_test;
import 'database_field_test.dart' as database_field_test;
import 'database_share_test.dart' as database_share_test;
import 'database_filter_test.dart' as database_filter_test;
import 'database_row_page_test.dart' as database_row_page_test;
import 'database_row_test.dart' as database_row_test;
import 'database_setting_test.dart' as database_setting_test;
import 'database_filter_test.dart' as database_filter_test;
import 'database_view_test.dart' as database_view_test;
import 'database_calendar_test.dart' as database_calendar_test;
import 'database_share_test.dart' as database_share_test;
import 'database_sort_test.dart' as database_sort_test;
import 'database_view_test.dart' as database_view_test;
import 'document/document_test_runner.dart' as document_test_runner;
import 'import_files_test.dart' as import_files_test;
import 'share_markdown_test.dart' as share_markdown_test;
import 'switch_folder_test.dart' as switch_folder_test;
/// The main task runner for all integration tests in AppFlowy.
///
@ -33,10 +29,7 @@ void main() {
import_files_test.main();
// Document integration tests
cover_image_test.main();
document_test.main();
document_with_database_test.main();
edit_document_test.main();
document_test_runner.startTesting();
// Database integration tests
database_cell_test.main();

View File

@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
class FlowyTestContext {
FlowyTestContext({
@ -23,7 +24,10 @@ extension AppFlowyTestBase on WidgetTester {
Future<FlowyTestContext> initializeAppFlowy({
// use to append after the application data directory
String? pathExtension,
Size windowsSize = const Size(1600, 1200),
}) async {
binding.setSurfaceSize(windowsSize);
mockHotKeyManagerHandlers();
final directory = await mockApplicationDataStorage(
pathExtension: pathExtension,
@ -60,7 +64,7 @@ extension AppFlowyTestBase on WidgetTester {
final dir = await getTemporaryDirectory();
// Use a random uuid to avoid conflict.
String path = '${dir.path}/appflowy_integration_test/${uuid()}';
String path = p.join(dir.path, 'appflowy_integration_test', uuid());
if (pathExtension != null && pathExtension.isNotEmpty) {
path = '$path/$pathExtension';
}
@ -78,7 +82,7 @@ extension AppFlowyTestBase on WidgetTester {
Finder finder, {
int? pointer,
int buttons = kPrimaryButton,
bool warnIfMissed = true,
bool warnIfMissed = false,
int milliseconds = 500,
}) async {
await tap(
@ -123,6 +127,18 @@ extension AppFlowyTestBase on WidgetTester {
return;
}
Future<void> doubleTapAt(
Offset location, {
int? pointer,
int buttons = kPrimaryButton,
int milliseconds = 500,
}) async {
await tapAt(location, pointer: pointer, buttons: buttons);
await pump(kDoubleTapMinTime);
await tapAt(location, pointer: pointer, buttons: buttons);
await pumpAndSettle(Duration(milliseconds: milliseconds));
}
Future<void> doubleTapButton(
Finder finder, {
int? pointer,
@ -130,20 +146,22 @@ extension AppFlowyTestBase on WidgetTester {
bool warnIfMissed = true,
int milliseconds = 500,
}) async {
await tapButton(
await tap(
finder,
pointer: pointer,
buttons: buttons,
warnIfMissed: warnIfMissed,
milliseconds: kDoubleTapMinTime.inMilliseconds,
);
await tapButton(
await pump(kDoubleTapMinTime);
await tap(
finder,
pointer: pointer,
buttons: buttons,
pointer: pointer,
warnIfMissed: warnIfMissed,
milliseconds: milliseconds,
);
await pumpAndSettle(Duration(milliseconds: milliseconds));
}
Future<void> wait(int milliseconds) async {

View File

@ -453,8 +453,15 @@ extension AppFlowyDatabaseTest on WidgetTester {
}
Future<void> dismissRowDetailPage() async {
await sendKeyEvent(LogicalKeyboardKey.escape);
// use tap empty area instead of clicking ESC to dismiss the row detail page
// sometimes, the ESC key is not working.
await simulateKeyEvent(LogicalKeyboardKey.escape);
await pumpAndSettle();
final findRowDetailPage = find.byType(RowDetailPage);
if (findRowDetailPage.evaluate().isNotEmpty) {
await tapAt(const Offset(0, 0));
await pumpAndSettle();
}
}
Future<void> editTitleInRowDetailPage(String title) async {
@ -1031,7 +1038,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
expect(findEvents, findsNWidgets(number));
}
void assertNumberofEventsOnSpecificDay(
void assertNumberOfEventsOnSpecificDay(
int number,
DateTime date, {
String? title,
@ -1058,8 +1065,8 @@ extension AppFlowyDatabaseTest on WidgetTester {
final todayCell = find.byWidgetPredicate(
(widget) => widget is CalendarDayCard && isSameDay(date, widget.date),
);
await doubleTapButton(todayCell);
final location = getTopLeft(todayCell).translate(10, 10);
await doubleTapAt(location);
}
Future<void> openCalendarEvent({required index, DateTime? date}) async {

View File

@ -165,4 +165,14 @@ class EditorOperations {
);
await tester.tapButton(atMenuItem);
}
/// Update the editor's selection
Future<void> updateSelection(Selection selection) async {
final editorState = getCurrentEditorState();
editorState.updateSelectionWithReason(
selection,
reason: SelectionUpdateReason.uiEvent,
);
await tester.pumpAndSettle(const Duration(milliseconds: 200));
}
}

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;
}
}

View File

@ -46,8 +46,9 @@ class FlowyRunner {
final launcher = getIt<AppLauncher>();
launcher.addTasks(
[
// handle platform errors.
const PlatformErrorCatcherTask(),
// this task should be first task, for handling platform errors.
// don't catch errors in test mode
if (!mode.isUnitTest) const PlatformErrorCatcherTask(),
// localization
const InitLocalizationTask(),
// init the app window

View File

@ -19,17 +19,14 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
@override
Future<void> initialize(LaunchContext context) async {
// Don't initialize on mobile or web.
if (!defaultTargetPlatform.isDesktop) {
if (!defaultTargetPlatform.isDesktop || context.env.isIntegrationTest) {
return;
}
await windowManager.ensureInitialized();
windowManager.addListener(this);
Size windowSize = await WindowSizeManager().getSize();
if (context.env.isIntegrationTest) {
windowSize = const Size(1600, 1200);
}
final windowSize = await WindowSizeManager().getSize();
final windowOptions = WindowOptions(
size: windowSize,

View File

@ -96,7 +96,7 @@ class SplashScreen extends StatelessWidget {
}
Future<void> _registerIfNeeded() async {
final result = await UserEventCheckUser().send();
final result = await UserEventGetUserProfile().send();
if (!result.isLeft()) {
await getIt<AuthService>().signUpAsGuest();
}

View File

@ -98,6 +98,7 @@ class MockApplicationDataStorage extends ApplicationDataStorage {
final path = initialPath;
if (path != null) {
initialPath = null;
await super.setPath(path);
return Future.value(path);
}
return super.getPath();

View File

@ -5,16 +5,23 @@ import 'package:flutter_svg/flutter_svg.dart';
///
/// Get the hover color from ThemeData
class FlowySvg extends StatelessWidget {
const FlowySvg({super.key, this.size, required this.name});
const FlowySvg({
super.key,
required this.name,
this.size,
this.color,
});
final String name;
final Size? size;
final Color? color;
@override
Widget build(BuildContext context) {
return svgWidget(
name,
size: size,
color: Theme.of(context).iconTheme.color,
color: color ?? Theme.of(context).iconTheme.color,
);
}
}

View File

@ -53,11 +53,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "572a174"
resolved-ref: "572a174892267e2f78f9c3d7f1fe4ca71c9be0db"
ref: c5b5e64
resolved-ref: c5b5e641fe11ae634f02db112e71f40a119e9c44
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "1.0.4"
version: "1.1.0"
appflowy_popover:
dependency: "direct main"
description:

View File

@ -46,7 +46,7 @@ dependencies:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: 572a174
ref: c5b5e64
appflowy_popover:
path: packages/appflowy_popover

View File

@ -7,7 +7,7 @@ mod init;
mod notification;
mod request;
use flowy_notification::register_notification_sender;
use flowy_notification::{register_notification_sender, unregister_all_notification_sender};
use init::*;
use notification::*;
use request::*;
@ -22,6 +22,8 @@ fn main() {
.on_menu_event(|_menu| {})
.on_page_load(|window, _payload| {
let app_handler = window.app_handle();
// Make sure hot reload won't register the notification sender twice
unregister_all_notification_sender();
register_notification_sender(TSNotificationSender::new(app_handler.clone()));
// tauri::async_runtime::spawn(async move {});
window.listen_global(AF_EVENT, move |event| {

View File

@ -85,6 +85,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "appflowy-integrate"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"anyhow",
"collab",
@ -896,6 +897,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"anyhow",
"bytes",
@ -913,6 +915,7 @@ dependencies = [
[[package]]
name = "collab-client-ws"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"bytes",
"collab-sync",
@ -930,6 +933,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"anyhow",
"async-trait",
@ -956,6 +960,7 @@ dependencies = [
[[package]]
name = "collab-derive"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"proc-macro2",
"quote",
@ -967,6 +972,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"anyhow",
"collab",
@ -985,6 +991,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"anyhow",
"chrono",
@ -1004,6 +1011,7 @@ dependencies = [
[[package]]
name = "collab-persistence"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"bincode",
"chrono",
@ -1023,6 +1031,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"anyhow",
"async-trait",
@ -1056,6 +1065,7 @@ dependencies = [
[[package]]
name = "collab-sync"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
dependencies = [
"bytes",
"collab",

View File

@ -6,7 +6,7 @@ use lazy_static::lazy_static;
use parking_lot::RwLock;
use flowy_core::*;
use flowy_notification::register_notification_sender;
use flowy_notification::{register_notification_sender, unregister_all_notification_sender};
use lib_dispatch::prelude::ToBytes;
use lib_dispatch::prelude::*;
@ -90,6 +90,8 @@ pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 {
#[no_mangle]
pub extern "C" fn set_stream_port(port: i64) -> i32 {
// Make sure hot reload won't register the notification sender twice
unregister_all_notification_sender();
register_notification_sender(DartNotificationSender::new(port));
0
}

View File

@ -14,6 +14,10 @@ lazy_static! {
static ref NOTIFICATION_SENDER: RwLock<Vec<Box<dyn NotificationSender>>> = RwLock::new(vec![]);
}
/// Register a notification sender. The sender will be alive until the process exits.
/// Flutter integration test or Tauri hot reload might cause register multiple times.
/// So before register a new sender, you might need to unregister the old one. Currently,
/// Just remove all senders by calling `unregister_all_notification_sender`.
pub fn register_notification_sender<T: NotificationSender>(sender: T) {
let box_sender = Box::new(sender);
match NOTIFICATION_SENDER.write() {
@ -22,6 +26,13 @@ pub fn register_notification_sender<T: NotificationSender>(sender: T) {
}
}
pub fn unregister_all_notification_sender() {
match NOTIFICATION_SENDER.write() {
Ok(mut write_guard) => write_guard.clear(),
Err(err) => tracing::error!("Failed to remove all notification senders: {:?}", err),
}
}
pub trait NotificationSender: Send + Sync + 'static {
fn send_subject(&self, subject: SubscribeObject) -> Result<(), String>;
}

View File

@ -10,7 +10,9 @@ cd ../../../appflowy_flutter
# Navigate to the appflowy_flutter directory and generate files
echo "Generating files for appflowy_flutter"
flutter clean >/dev/null 2>&1 && flutter packages pub get >/dev/null 2>&1 && dart run build_runner clean && dart run build_runner build -d
# flutter clean >/dev/null 2>&1 && flutter packages pub get >/dev/null 2>&1 && dart run build_runner clean &&
flutter packages pub get >/dev/null 2>&1
dart run build_runner build -d
echo "Done generating files for appflowy_flutter"
echo "Generating files for packages"
@ -23,7 +25,8 @@ for d in */ ; do
if [ -f "pubspec.yaml" ]; then
echo "Generating freezed files in $d..."
echo "Please wait while we clean the project and fetch the dependencies."
flutter clean >/dev/null 2>&1 && flutter packages pub get >/dev/null 2>&1 && dart run build_runner clean && dart run build_runner build -d
flutter packages pub get >/dev/null 2>&1
dart run build_runner build -d
echo "Done running build command in $d"
else
echo "No pubspec.yaml found in $d, it can\'t be a Dart project. Skipping."

0
frontend/scripts/code_generation/generate.sh Normal file → Executable file
View File

View File

@ -10,8 +10,6 @@ cd /d "%~dp0"
cd ..\..\..\appflowy_flutter
call flutter clean
call flutter packages pub get
echo Specifying source directory for AppFlowy Localizations.

View File

@ -10,8 +10,6 @@ cd "$(dirname "$0")"
# Navigate to the project root
cd ../../../appflowy_flutter
flutter clean
flutter packages pub get
echo "Specifying source directory for AppFlowy Localizations."

View File

@ -167,7 +167,6 @@ script = [
[tasks.flutter-build]
script = ["""
cd appflowy_flutter/
flutter clean
flutter pub get
flutter build ${TARGET_OS} --${BUILD_FLAG}
"""]
@ -176,7 +175,6 @@ script_runner = "@shell"
[tasks.flutter-build.windows]
script = ["""
cd appflowy_flutter
exec cmd.exe /c flutter clean
exec cmd.exe /c flutter pub get
exec cmd.exe /c flutter build ${TARGET_OS} --${BUILD_FLAG} --build-name=${APP_VERSION}
"""]
@ -186,16 +184,15 @@ script_runner = "@duckscript"
script_runner = "@shell"
script = [
"""
chmod +x scripts/code_generation/generate.sh
""",
"scripts/code_generation/generate.sh"
sh scripts/code_generation/generate.sh
"""
]
[tasks.code_generation.windows]
script_runner = "@duckscript"
script = [
"""
exec "scripts/code_generation/generate.cmd"
exec scripts/code_generation/generate.cmd
""",
]