From 0a5f3e8fa1117c14fc9dd53d716f67ccfc91ddcc Mon Sep 17 00:00:00 2001 From: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:52:58 +0200 Subject: [PATCH] feat: use new appflowy-editor-plugins package (#5147) * feat: use new appflowy-editor-plugins package * fix: code block slash menu item * chore: update build runner dependency * chore: change dependency to pub.dev version * chore: revert generate: false in pubspec --- .../document_codeblock_paste_test.dart | 9 +- .../presentation/editor_configuration.dart | 8 + .../document/presentation/editor_page.dart | 29 +- .../base/format_arrow_character.dart | 2 +- .../code_block/code_block_component.dart | 700 ------------------ .../code_block/code_block_copy_button.dart | 53 ++ .../code_block_language_selector.dart | 193 +++++ .../code_block/code_block_shortcut_event.dart | 373 ---------- .../code_block/code_block_themes.dart | 116 --- .../code_block/code_language_screen.dart | 9 +- .../migration/editor_migration.dart | 1 + .../mobile_add_block_toolbar_item.dart | 4 +- .../mobile_convert_block_toolbar_item.dart | 4 +- .../add_block_toolbar_item.dart | 4 +- .../presentation/editor_plugins/plugins.dart | 3 +- .../shortcuts/settings_shortcuts_cubit.dart | 3 +- frontend/appflowy_flutter/pubspec.lock | 17 +- frontend/appflowy_flutter/pubspec.yaml | 9 +- 18 files changed, 311 insertions(+), 1226 deletions(-) delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_themes.dart diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart index b0d2aa0c21..f06a273fac 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_codeblock_paste_test.dart @@ -1,9 +1,10 @@ import 'dart:io'; +import 'package:flutter/services.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -23,11 +24,7 @@ void main() { // mock the clipboard const lines = 3; final text = List.generate(lines, (index) => 'line $index').join('\n'); - AppFlowyClipboard.mockSetData( - AppFlowyClipboardData( - text: text, - ), - ); + AppFlowyClipboard.mockSetData(AppFlowyClipboardData(text: text)); await insertCodeBlockInDocument(tester); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart index 88c77bd992..02f069e172 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; @@ -148,11 +149,18 @@ Map getEditorBuilderMap({ configuration: configuration, ), CodeBlockKeys.type: CodeBlockComponentBuilder( + editorState: editorState, configuration: configuration.copyWith( textStyle: (_) => styleCustomizer.codeBlockStyleBuilder(), placeholderTextStyle: (_) => styleCustomizer.codeBlockStyleBuilder(), ), + styleBuilder: () => CodeBlockStyle( + backgroundColor: AFThemeExtension.of(context).calloutBGColor, + foregroundColor: AFThemeExtension.of(context).textColor.withAlpha(155), + ), padding: const EdgeInsets.only(left: 20, right: 30, bottom: 34), + languagePickerBuilder: codeBlockLanguagePickerBuilder, + copyButtonBuilder: codeBlockCopyBuilder, ), AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(), SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 9dc429304e..fd4bb61188 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -1,6 +1,9 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_configuration.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart'; @@ -21,14 +24,32 @@ import 'package:appflowy/workspace/application/settings/shortcuts/settings_short import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +final codeBlockLocalization = CodeBlockLocalizations( + codeBlockNewParagraph: + LocaleKeys.settings_shortcuts_commands_codeBlockNewParagraph.tr(), + codeBlockAddTwoSpaces: + LocaleKeys.settings_shortcuts_commands_codeBlockAddTwoSpaces.tr(), + codeBlockDeleteTwoSpaces: + LocaleKeys.settings_shortcuts_commands_codeBlockDeleteTwoSpaces.tr(), + codeBlockSelectAll: + LocaleKeys.settings_shortcuts_commands_codeBlockSelectAll.tr(), + codeBlockPasteText: + LocaleKeys.settings_shortcuts_commands_codeBlockPasteText.tr(), +); + +final localizedCodeBlockCommands = + codeBlockCommands(localizations: codeBlockLocalization); + final List commandShortcutEvents = [ toggleToggleListCommand, - ...codeBlockCommands, + ...localizedCodeBlockCommands, customCopyCommand, customPasteCommand, customCutCommand, @@ -90,7 +111,7 @@ class _AppFlowyEditorPageState extends State { late final List commandShortcutEvents = [ toggleToggleListCommand, - ...codeBlockCommands, + ...localizedCodeBlockCommands, customCopyCommand, customPasteCommand, customCutCommand, @@ -264,7 +285,7 @@ class _AppFlowyEditorPageState extends State { final isRTL = context.read().state.layoutDirection == LayoutDirection.rtlLayout; - final textDirection = isRTL ? TextDirection.rtl : TextDirection.ltr; + final textDirection = isRTL ? ui.TextDirection.rtl : ui.TextDirection.ltr; _setRTLToolbarItems( context.read().state.enableRtlToolbarItems, @@ -371,7 +392,7 @@ class _AppFlowyEditorPageState extends State { calloutItem, outlineItem, mathEquationItem, - codeBlockItem, + codeBlockItem(LocaleKeys.document_selectionMenu_codeBlock.tr()), toggleListBlockItem, emojiMenuItem, autoGeneratorMenuItem, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart index 8ead5f9533..6e6e111df1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart @@ -1,5 +1,5 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; const _greater = '>'; const _equals = '='; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart deleted file mode 100644 index beaab3c714..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart +++ /dev/null @@ -1,700 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/presentation/home/toast.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:easy_localization/easy_localization.dart' hide TextDirection; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; -import 'package:go_router/go_router.dart'; -import 'package:highlight/highlight.dart' as highlight; -import 'package:highlight/languages/all.dart'; -import 'package:provider/provider.dart'; - -import 'code_block_themes.dart'; - -final supportedLanguages = [ - 'Assembly', - 'Bash', - 'BASIC', - 'C', - 'C#', - 'CPP', - 'Clojure', - 'CS', - 'CSS', - 'Dart', - 'Docker', - 'Elixir', - 'Elm', - 'Erlang', - 'Fortran', - 'Go', - 'GraphQL', - 'Haskell', - 'HTML', - 'Java', - 'JavaScript', - 'JSON', - 'Kotlin', - 'LaTeX', - 'Lisp', - 'Lua', - 'Markdown', - 'MATLAB', - 'Objective-C', - 'OCaml', - 'Perl', - 'PHP', - 'PowerShell', - 'Python', - 'R', - 'Ruby', - 'Rust', - 'Scala', - 'Shell', - 'SQL', - 'Swift', - 'TypeScript', - 'Visual Basic', - 'XML', - 'YAML', -]; - -final codeBlockSupportedLanguages = supportedLanguages - .map((e) => e.toLowerCase()) - .toSet() - .intersection(allLanguages.keys.toSet()) - .toList() - ..add('auto') - ..add('c') - ..sort(); - -class CodeBlockKeys { - const CodeBlockKeys._(); - - static const String type = 'code'; - - /// The content of a code block. - /// - /// The value is a String. - static const String delta = 'delta'; - - /// The language of a code block. - /// - /// The value is a String. - static const String language = 'language'; -} - -Node codeBlockNode({ - Delta? delta, - String? language, -}) { - final attributes = { - CodeBlockKeys.delta: (delta ?? Delta()).toJson(), - CodeBlockKeys.language: language, - }; - return Node( - type: CodeBlockKeys.type, - attributes: attributes, - ); -} - -// defining the callout block menu item for selection -SelectionMenuItem codeBlockItem = SelectionMenuItem.node( - getName: LocaleKeys.document_selectionMenu_codeBlock.tr, - iconData: Icons.abc, - keywords: ['code', 'codeblock'], - nodeBuilder: (editorState, _) => codeBlockNode(), - replace: (_, node) => node.delta?.isEmpty ?? false, -); - -const _interceptorKey = 'code-block-interceptor'; - -class CodeBlockComponentBuilder extends BlockComponentBuilder { - CodeBlockComponentBuilder({ - super.configuration, - this.padding = const EdgeInsets.all(0), - }); - - final EdgeInsets padding; - - @override - BlockComponentWidget build(BlockComponentContext blockComponentContext) { - final node = blockComponentContext.node; - return CodeBlockComponentWidget( - key: node.key, - node: node, - configuration: configuration, - padding: padding, - showActions: showActions(node), - actionBuilder: (context, state) => actionBuilder( - blockComponentContext, - state, - ), - ); - } - - @override - bool validate(Node node) => node.delta != null; -} - -class CodeBlockComponentWidget extends BlockComponentStatefulWidget { - const CodeBlockComponentWidget({ - super.key, - required super.node, - super.showActions, - super.actionBuilder, - super.configuration = const BlockComponentConfiguration(), - this.padding = const EdgeInsets.all(0), - }); - - final EdgeInsets padding; - - @override - State createState() => - _CodeBlockComponentWidgetState(); -} - -class _CodeBlockComponentWidgetState extends State - with - SelectableMixin, - DefaultSelectableMixin, - BlockComponentConfigurable, - BlockComponentTextDirectionMixin { - // the key used to forward focus to the richtext child - @override - final forwardKey = GlobalKey(debugLabel: 'code_flowy_rich_text'); - - @override - GlobalKey> blockComponentKey = - GlobalKey(debugLabel: CodeBlockKeys.type); - - @override - BlockComponentConfiguration get configuration => widget.configuration; - - @override - GlobalKey> get containerKey => node.key; - - @override - Node get node => widget.node; - - @override - late final editorState = context.read(); - - final popoverController = PopoverController(); - final scrollController = ScrollController(); - - // We use this to calculate the position of the cursor in the code block - // for automatic scrolling. - final codeBlockKey = GlobalKey(); - - String? get language => node.attributes[CodeBlockKeys.language] as String?; - String? autoDetectLanguage; - - bool isSelected = false; - bool isHovering = false; - bool canPanStart = true; - - late final interceptor = SelectionGestureInterceptor( - key: _interceptorKey, - canTap: (_) => canPanStart && !isSelected, - canPanStart: (_) => canPanStart && !isSelected, - ); - - late final StreamSubscription<(TransactionTime, Transaction)> - transactionSubscription; - - @override - void initState() { - super.initState(); - editorState.selectionService.registerGestureInterceptor(interceptor); - editorState.selectionNotifier.addListener(calculateScrollPosition); - transactionSubscription = editorState.transactionStream.listen((event) { - if (event.$2.operations.any((op) => op.path.equals(node.path))) { - calculateScrollPosition(); - } - }); - } - - @override - void dispose() { - scrollController.dispose(); - editorState.selectionService.currentSelection - .removeListener(calculateScrollPosition); - editorState.selectionService.unregisterGestureInterceptor(_interceptorKey); - transactionSubscription.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final textDirection = calculateTextDirection( - layoutDirection: Directionality.maybeOf(context), - ); - - Widget child = MouseRegion( - onEnter: (_) => setState(() => isHovering = true), - onExit: (_) => setState(() => isHovering = false), - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(8.0)), - color: AFThemeExtension.of(context).calloutBGColor, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - textDirection: textDirection, - children: [ - MouseRegion( - onEnter: (_) => setState(() => canPanStart = false), - onExit: (_) => setState(() => canPanStart = true), - child: Opacity( - opacity: isHovering || isSelected ? 1.0 : 0.0, - child: Row( - children: [ - _LanguageSelector( - controller: popoverController, - language: language, - isSelected: isSelected, - onLanguageSelected: updateLanguage, - onMenuOpen: () => isSelected = true, - onMenuClose: () => setState(() => isSelected = false), - ), - const Spacer(), - _CopyButton(node: node), - ], - ), - ), - ), - _buildCodeBlock(context, textDirection), - ], - ), - ), - ); - - child = Padding(key: blockComponentKey, padding: padding, child: child); - - child = BlockSelectionContainer( - node: node, - delegate: this, - listenable: editorState.selectionNotifier, - blockColor: editorState.editorStyle.selectionColor, - supportTypes: const [BlockSelectionType.block], - child: child, - ); - - if (PlatformExtension.isDesktopOrWeb) { - if (widget.showActions && widget.actionBuilder != null) { - child = BlockComponentActionWrapper( - node: widget.node, - actionBuilder: widget.actionBuilder!, - child: child, - ); - } - } else { - // show a fixed menu on mobile - child = MobileBlockActionButtons( - node: node, - editorState: editorState, - child: child, - ); - } - - return child; - } - - Widget _buildCodeBlock(BuildContext context, TextDirection textDirection) { - final isLightMode = Theme.of(context).brightness == Brightness.light; - final delta = node.delta ?? Delta(); - final content = delta.toPlainText(); - - final result = highlight.highlight.parse( - content, - language: language, - autoDetection: language == null, - ); - - autoDetectLanguage = language ?? result.language; - - final codeNodes = result.nodes; - if (codeNodes == null) { - throw Exception('Code block parse error.'); - } - - final codeTextSpans = _convert(codeNodes, isLightMode: isLightMode); - final linesOfCode = delta.toPlainText().split('\n').length; - - return Padding( - padding: widget.padding, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _LinesOfCodeNumbers( - linesOfCode: linesOfCode, - textStyle: textStyle, - ), - Flexible( - child: SingleChildScrollView( - key: codeBlockKey, - controller: scrollController, - physics: const ClampingScrollPhysics(), - scrollDirection: Axis.horizontal, - child: AppFlowyRichText( - key: forwardKey, - delegate: this, - node: widget.node, - editorState: editorState, - placeholderText: placeholderText, - lineHeight: 1.5, - textSpanDecorator: (_) => TextSpan( - style: textStyle, - children: codeTextSpans, - ), - placeholderTextSpanDecorator: (textSpan) => textSpan, - textDirection: textDirection, - cursorColor: editorState.editorStyle.cursorColor, - selectionColor: editorState.editorStyle.selectionColor, - ), - ), - ), - ], - ), - ); - } - - Future updateLanguage(String language) async { - final transaction = editorState.transaction - ..updateNode( - node, - {CodeBlockKeys.language: language == 'auto' ? null : language}, - ); - await editorState.apply(transaction); - } - - void calculateScrollPosition() { - WidgetsBinding.instance.addPostFrameCallback((_) { - final selection = editorState.selection; - if (!mounted || selection == null || !selection.isCollapsed) { - return; - } - - final nodes = editorState.getNodesInSelection(selection); - if (nodes.isEmpty || nodes.length > 1) { - return; - } - - final selectedNode = nodes.first; - if (selectedNode.path.equals(widget.node.path)) { - final renderBox = - codeBlockKey.currentContext?.findRenderObject() as RenderBox?; - final rects = editorState.selectionRects(); - if (renderBox == null || rects.isEmpty) { - return; - } - - final codeBlockOffset = renderBox.localToGlobal(Offset.zero); - final codeBlockSize = renderBox.size; - - final cursorRect = rects.first; - final cursorRelativeOffset = cursorRect.center - codeBlockOffset; - - // If the relative position of the cursor is less than 1, and the scrollController - // is not at offset 0, then we need to scroll to the left to make cursor visible. - if (cursorRelativeOffset.dx < 1 && scrollController.offset > 0) { - scrollController - .jumpTo(scrollController.offset + cursorRelativeOffset.dx - 1); - - // If the relative position of the cursor is greater than the width of the code block, - // then we need to scroll to the right to make cursor visible. - } else if (cursorRelativeOffset.dx > codeBlockSize.width - 1) { - scrollController.jumpTo( - scrollController.offset + - cursorRelativeOffset.dx - - codeBlockSize.width + - 1, - ); - } - } - }); - } - - // Copy from flutter.highlight package. - // https://github.com/git-touch/highlight.dart/blob/master/flutter_highlight/lib/flutter_highlight.dart - List _convert( - List nodes, { - bool isLightMode = true, - }) { - final List spans = []; - List currentSpans = spans; - final List> stack = []; - - final codeblockTheme = - isLightMode ? lightThemeInCodeblock : darkThemeInCodeBlock; - - void traverse(highlight.Node node) { - if (node.value != null) { - currentSpans.add( - node.className == null - ? TextSpan(text: node.value) - : TextSpan( - text: node.value, - style: codeblockTheme[node.className!], - ), - ); - } else if (node.children != null) { - final List tmp = []; - currentSpans.add( - TextSpan( - children: tmp, - style: codeblockTheme[node.className!], - ), - ); - stack.add(currentSpans); - currentSpans = tmp; - - for (final n in node.children!) { - traverse(n); - if (n == node.children!.last) { - currentSpans = stack.isEmpty ? spans : stack.removeLast(); - } - } - } - } - - for (final node in nodes) { - traverse(node); - } - - return spans; - } -} - -class _LinesOfCodeNumbers extends StatelessWidget { - const _LinesOfCodeNumbers({ - required this.linesOfCode, - required this.textStyle, - }); - - final int linesOfCode; - final TextStyle textStyle; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(right: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - for (int i = 1; i <= linesOfCode; i++) - Text( - i.toString(), - style: textStyle.copyWith( - color: AFThemeExtension.of(context).textColor.withAlpha(155), - ), - ), - ], - ), - ); - } -} - -class _CopyButton extends StatelessWidget { - const _CopyButton({required this.node}); - - final Node node; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(4), - child: FlowyTooltip( - message: LocaleKeys.document_codeBlock_copyTooltip.tr(), - child: FlowyIconButton( - onPressed: () async { - await getIt().setData( - ClipboardServiceData( - plainText: node.delta?.toPlainText(), - ), - ); - - if (context.mounted) { - showSnackBarMessage( - context, - LocaleKeys.document_codeBlock_codeCopiedSnackbar.tr(), - ); - } - }, - hoverColor: Theme.of(context).colorScheme.secondaryContainer, - icon: FlowySvg( - FlowySvgs.copy_s, - color: AFThemeExtension.of(context).textColor, - ), - ), - ), - ); - } -} - -class _LanguageSelector extends StatefulWidget { - const _LanguageSelector({ - required this.controller, - this.language, - required this.isSelected, - required this.onLanguageSelected, - this.onMenuOpen, - this.onMenuClose, - }); - - final PopoverController controller; - final String? language; - final bool isSelected; - final void Function(String) onLanguageSelected; - final VoidCallback? onMenuOpen; - final VoidCallback? onMenuClose; - - @override - State<_LanguageSelector> createState() => _LanguageSelectorState(); -} - -class _LanguageSelectorState extends State<_LanguageSelector> { - @override - Widget build(BuildContext context) { - Widget child = Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0), - child: FlowyTextButton( - widget.language?.capitalize() ?? - LocaleKeys.document_codeBlock_language_auto.tr(), - constraints: const BoxConstraints(minWidth: 50), - fontColor: Theme.of(context).colorScheme.onBackground, - fillColor: widget.isSelected - ? Theme.of(context).colorScheme.secondaryContainer - : Colors.transparent, - padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 4), - onPressed: () async { - if (PlatformExtension.isMobile) { - final language = await context - .push(MobileCodeLanguagePickerScreen.routeName); - if (language != null) { - widget.onLanguageSelected(language); - } - } - }, - ), - ), - ], - ); - - if (PlatformExtension.isDesktopOrWeb) { - child = AppFlowyPopover( - controller: widget.controller, - direction: PopoverDirection.bottomWithLeftAligned, - onOpen: widget.onMenuOpen, - constraints: const BoxConstraints(maxHeight: 300, maxWidth: 200), - onClose: widget.onMenuClose, - popupBuilder: (_) => _LanguageSelectionPopover( - editorState: context.read(), - language: widget.language, - onLanguageSelected: (language) { - widget.onLanguageSelected(language); - widget.controller.close(); - }, - ), - child: child, - ); - } - - return child; - } -} - -class _LanguageSelectionPopover extends StatefulWidget { - const _LanguageSelectionPopover({ - required this.editorState, - required this.language, - required this.onLanguageSelected, - }); - - final EditorState editorState; - final String? language; - final void Function(String) onLanguageSelected; - - @override - State<_LanguageSelectionPopover> createState() => - _LanguageSelectionPopoverState(); -} - -class _LanguageSelectionPopoverState extends State<_LanguageSelectionPopover> { - final searchController = TextEditingController(); - final focusNode = FocusNode(); - - List supportedLanguages = - codeBlockSupportedLanguages.map((e) => e.capitalize()).toList(); - late int selectedIndex = supportedLanguages.indexOf(widget.language ?? ''); - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback( - // This is a workaround because longer taps might break the - // focus, this might be an issue with the Flutter framework. - (_) => Future.delayed( - const Duration(milliseconds: 100), - () => focusNode.requestFocus(), - ), - ); - } - - @override - void dispose() { - focusNode.dispose(); - searchController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - FlowyTextField( - focusNode: focusNode, - autoFocus: false, - controller: searchController, - hintText: LocaleKeys.document_codeBlock_searchLanguageHint.tr(), - onChanged: (_) => setState(() { - supportedLanguages = codeBlockSupportedLanguages - .where((e) => e.contains(searchController.text.toLowerCase())) - .map((e) => e.capitalize()) - .toList(); - selectedIndex = - codeBlockSupportedLanguages.indexOf(widget.language ?? ''); - }), - ), - const VSpace(8), - Flexible( - child: SelectableItemListMenu( - shrinkWrap: true, - items: supportedLanguages, - selectedIndex: selectedIndex, - onSelected: (index) => - widget.onLanguageSelected(supportedLanguages[index]), - ), - ), - ], - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart new file mode 100644 index 0000000000..9c6ff01bf9 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; + +CodeBlockCopyBuilder codeBlockCopyBuilder = + (_, node) => _CopyButton(node: node); + +class _CopyButton extends StatelessWidget { + const _CopyButton({required this.node}); + + final Node node; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(4), + child: FlowyTooltip( + message: LocaleKeys.document_codeBlock_copyTooltip.tr(), + child: FlowyIconButton( + onPressed: () async { + await getIt().setData( + ClipboardServiceData( + plainText: node.delta?.toPlainText(), + ), + ); + + if (context.mounted) { + showSnackBarMessage( + context, + LocaleKeys.document_codeBlock_codeCopiedSnackbar.tr(), + ); + } + }, + hoverColor: Theme.of(context).colorScheme.secondaryContainer, + icon: FlowySvg( + FlowySvgs.copy_s, + color: AFThemeExtension.of(context).textColor, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart new file mode 100644 index 0000000000..323c15be1e --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.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:go_router/go_router.dart'; + +CodeBlockLanguagePickerBuilder codeBlockLanguagePickerBuilder = ( + editorState, + supportedLanguages, + onLanguageSelected, { + selectedLanguage, + onMenuClose, + onMenuOpen, +}) => + _CodeBlockLanguageSelector( + editorState: editorState, + language: selectedLanguage, + supportedLanguages: supportedLanguages, + onLanguageSelected: onLanguageSelected, + onMenuClose: onMenuClose, + onMenuOpen: onMenuOpen, + ); + +class _CodeBlockLanguageSelector extends StatefulWidget { + const _CodeBlockLanguageSelector({ + required this.editorState, + required this.supportedLanguages, + this.language, + required this.onLanguageSelected, + this.onMenuOpen, + this.onMenuClose, + }); + + final EditorState editorState; + final List supportedLanguages; + final String? language; + final void Function(String) onLanguageSelected; + final VoidCallback? onMenuOpen; + final VoidCallback? onMenuClose; + + @override + State<_CodeBlockLanguageSelector> createState() => + _CodeBlockLanguageSelectorState(); +} + +class _CodeBlockLanguageSelectorState + extends State<_CodeBlockLanguageSelector> { + final controller = PopoverController(); + + @override + void dispose() { + controller.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget child = Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 4.0), + child: FlowyTextButton( + widget.language?.capitalize() ?? + LocaleKeys.document_codeBlock_language_auto.tr(), + constraints: const BoxConstraints(minWidth: 50), + fontColor: Theme.of(context).colorScheme.onBackground, + padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 4), + fillColor: Colors.transparent, + hoverColor: Theme.of(context).colorScheme.secondaryContainer, + onPressed: () async { + if (PlatformExtension.isMobile) { + final language = await context + .push(MobileCodeLanguagePickerScreen.routeName); + if (language != null) { + widget.onLanguageSelected(language); + } + } + }, + ), + ), + ], + ); + + if (PlatformExtension.isDesktopOrWeb) { + child = AppFlowyPopover( + controller: controller, + direction: PopoverDirection.bottomWithLeftAligned, + onOpen: widget.onMenuOpen, + constraints: const BoxConstraints(maxHeight: 300, maxWidth: 200), + onClose: widget.onMenuClose, + popupBuilder: (_) => _LanguageSelectionPopover( + editorState: widget.editorState, + language: widget.language, + supportedLanguages: widget.supportedLanguages, + onLanguageSelected: (language) { + widget.onLanguageSelected(language); + controller.close(); + }, + ), + child: child, + ); + } + + return child; + } +} + +class _LanguageSelectionPopover extends StatefulWidget { + const _LanguageSelectionPopover({ + required this.editorState, + required this.language, + required this.supportedLanguages, + required this.onLanguageSelected, + }); + + final EditorState editorState; + final String? language; + final List supportedLanguages; + final void Function(String) onLanguageSelected; + + @override + State<_LanguageSelectionPopover> createState() => + _LanguageSelectionPopoverState(); +} + +class _LanguageSelectionPopoverState extends State<_LanguageSelectionPopover> { + final searchController = TextEditingController(); + final focusNode = FocusNode(); + late List filteredLanguages = + widget.supportedLanguages.map((e) => e.capitalize()).toList(); + late int selectedIndex = + widget.supportedLanguages.indexOf(widget.language ?? ''); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback( + // This is a workaround because longer taps might break the + // focus, this might be an issue with the Flutter framework. + (_) => Future.delayed( + const Duration(milliseconds: 100), + () => focusNode.requestFocus(), + ), + ); + } + + @override + void dispose() { + focusNode.dispose(); + searchController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + FlowyTextField( + focusNode: focusNode, + autoFocus: false, + controller: searchController, + hintText: LocaleKeys.document_codeBlock_searchLanguageHint.tr(), + onChanged: (_) => setState(() { + filteredLanguages = widget.supportedLanguages + .where((e) => e.contains(searchController.text.toLowerCase())) + .map((e) => e.capitalize()) + .toList(); + selectedIndex = + widget.supportedLanguages.indexOf(widget.language ?? ''); + }), + ), + const VSpace(8), + Flexible( + child: SelectableItemListMenu( + shrinkWrap: true, + items: filteredLanguages, + selectedIndex: selectedIndex, + onSelected: (index) => + widget.onLanguageSelected(filteredLanguages[index]), + ), + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart deleted file mode 100644 index 47b56bc599..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart +++ /dev/null @@ -1,373 +0,0 @@ -import 'package:flutter/material.dart'; - -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'; - -final List codeBlockCharacterEvents = [ - enterInCodeBlock, - ...ignoreKeysInCodeBlock, -]; - -final List codeBlockCommands = [ - insertNewParagraphNextToCodeBlockCommand, - pasteInCodeblock, - selectAllInCodeBlockCommand, - tabToInsertSpacesInCodeBlockCommand, - tabToDeleteSpacesInCodeBlockCommand, -]; - -/// press the enter key in code block to insert a new line in it. -/// -/// - support -/// - desktop -/// - web -/// - mobile -/// -final CharacterShortcutEvent enterInCodeBlock = CharacterShortcutEvent( - key: 'press enter in code block', - character: '\n', - handler: _enterInCodeBlockCommandHandler, -); - -/// ignore ' ', '/', '_', '*' in code block. -/// -/// - support -/// - desktop -/// - web -/// - mobile -/// -final List ignoreKeysInCodeBlock = - [' ', '/', '_', '*', '~', '-'] - .map( - (e) => CharacterShortcutEvent( - key: 'press enter in code block', - character: e, - handler: (editorState) => _ignoreKeysInCodeBlockCommandHandler( - editorState, - e, - ), - ), - ) - .toList(); - -/// shift + enter to insert a new node next to the code block. -/// -/// - support -/// - desktop -/// - web -/// -final CommandShortcutEvent insertNewParagraphNextToCodeBlockCommand = - CommandShortcutEvent( - key: 'insert a new paragraph next to the code block', - command: 'shift+enter', - getDescription: - LocaleKeys.settings_shortcuts_commands_codeBlockNewParagraph.tr, - handler: _insertNewParagraphNextToCodeBlockCommandHandler, -); - -/// tab to insert two spaces at the line start in code block. -/// -/// - support -/// - desktop -/// - web -final CommandShortcutEvent tabToInsertSpacesInCodeBlockCommand = - CommandShortcutEvent( - key: 'tab to insert two spaces at the line start in code block', - command: 'tab', - getDescription: - LocaleKeys.settings_shortcuts_commands_codeBlockAddTwoSpaces.tr, - handler: (editorState) => _indentationInCodeBlockCommandHandler( - editorState, - true, - ), -); - -/// shift+tab to delete two spaces at the line start in code block if needed. -/// -/// - support -/// - desktop -/// - web -final CommandShortcutEvent tabToDeleteSpacesInCodeBlockCommand = - CommandShortcutEvent( - key: 'shift + tab to delete two spaces at the line start in code block', - command: 'shift+tab', - getDescription: - LocaleKeys.settings_shortcuts_commands_codeBlockDeleteTwoSpaces.tr, - handler: (editorState) => _indentationInCodeBlockCommandHandler( - editorState, - false, - ), -); - -/// CTRL+A to select all content inside a Code Block, if cursor is inside one. -/// -/// - support -/// - desktop -/// - web -final CommandShortcutEvent selectAllInCodeBlockCommand = CommandShortcutEvent( - key: 'ctrl + a to select all content inside a code block', - command: 'ctrl+a', - macOSCommand: 'meta+a', - getDescription: LocaleKeys.settings_shortcuts_commands_codeBlockSelectAll.tr, - handler: _selectAllInCodeBlockCommandHandler, -); - -/// ctrl + v to paste text in code block. -/// -/// - support -/// - desktop -/// - web -final CommandShortcutEvent pasteInCodeblock = CommandShortcutEvent( - key: 'paste in codeblock', - command: 'ctrl+v', - macOSCommand: 'cmd+v', - getDescription: LocaleKeys.settings_shortcuts_commands_codeBlockPasteText.tr, - handler: _pasteInCodeBlock, -); - -CharacterShortcutEventHandler _enterInCodeBlockCommandHandler = - (editorState) async { - final selection = editorState.selection; - if (selection == null || !selection.isCollapsed) { - return false; - } - final node = editorState.getNodeAtPath(selection.end.path); - if (node == null || node.type != CodeBlockKeys.type) { - return false; - } - - final lines = node.delta?.toPlainText().split('\n'); - int spaces = 0; - if (lines?.isNotEmpty == true) { - int index = 0; - for (final line in lines!) { - if (index <= selection.endIndex && - selection.endIndex <= index + line.length) { - final lineSpaces = line.length - line.trimLeft().length; - spaces = lineSpaces; - break; - } - index += line.length + 1; - } - } - - final transaction = editorState.transaction - ..insertText( - node, - selection.end.offset, - '\n${' ' * spaces}', - ); - await editorState.apply(transaction); - return true; -}; - -Future _ignoreKeysInCodeBlockCommandHandler( - EditorState editorState, - String key, -) async { - final selection = editorState.selection; - if (selection == null || !selection.isCollapsed) { - return false; - } - final node = editorState.getNodeAtPath(selection.end.path); - if (node == null || node.type != CodeBlockKeys.type) { - return false; - } - await editorState.insertTextAtCurrentSelection(key); - return true; -} - -CommandShortcutEventHandler _insertNewParagraphNextToCodeBlockCommandHandler = - (editorState) { - final selection = editorState.selection; - if (selection == null || !selection.isCollapsed) { - return KeyEventResult.ignored; - } - final node = editorState.getNodeAtPath(selection.end.path); - final delta = node?.delta; - if (node == null || delta == null || node.type != CodeBlockKeys.type) { - return KeyEventResult.ignored; - } - final sliced = delta.slice(selection.startIndex); - final transaction = editorState.transaction - ..deleteText( - // delete the text after the cursor in the code block - node, - selection.startIndex, - delta.length - selection.startIndex, - ) - ..insertNode( - // insert a new paragraph node with the sliced delta after the code block - selection.end.path.next, - paragraphNode( - attributes: { - 'delta': sliced.toJson(), - }, - ), - ) - ..afterSelection = Selection.collapsed( - Position(path: selection.end.path.next), - ); - editorState.apply(transaction); - return KeyEventResult.handled; -}; - -KeyEventResult _indentationInCodeBlockCommandHandler( - EditorState editorState, - bool shouldIndent, -) { - final selection = editorState.selection; - if (selection == null) { - return KeyEventResult.ignored; - } - final node = editorState.getNodeAtPath(selection.end.path); - final delta = node?.delta; - if (node == null || delta == null || node.type != CodeBlockKeys.type) { - return KeyEventResult.ignored; - } - - const spaces = ' '; - final lines = delta.toPlainText().split('\n'); - int index = 0; - - // We store indexes to be indented in a list, because we should - // indent it in a reverse order to not mess up the offsets. - final List transactions = []; - - for (final line in lines) { - if (!shouldIndent && line.startsWith(spaces) || shouldIndent) { - bool shouldTransform = false; - if (selection.isCollapsed) { - shouldTransform = index <= selection.endIndex && - selection.endIndex <= index + line.length; - } else { - shouldTransform = index + line.length >= selection.startIndex && - selection.endIndex >= index; - - if (shouldIndent && line.trim().isEmpty) { - shouldTransform = false; - } - } - - if (shouldTransform) { - transactions.add(index); - } - } - - index += line.length + 1; - } - - if (transactions.isEmpty) { - return KeyEventResult.ignored; - } - - final transaction = editorState.transaction; - - for (final index in transactions.reversed) { - if (shouldIndent) { - transaction.insertText(node, index, spaces); - } else { - transaction.deleteText(node, index, spaces.length); - } - } - - // In case the selection is made backwards, we store the start - // and end here, we will adjust the order later - final start = !selection.isBackward ? selection.end : selection.start; - final end = !selection.isBackward ? selection.start : selection.end; - - final endOffset = shouldIndent - ? end.offset + (spaces.length * transactions.length) - : end.offset - (spaces.length * transactions.length); - - final endSelection = end.copyWith(offset: endOffset); - - final startOffset = shouldIndent - ? start.offset + spaces.length - : start.offset - spaces.length; - - final startSelection = selection.isCollapsed - ? endSelection - : start.copyWith(offset: startOffset); - - transaction.afterSelection = selection.copyWith( - start: selection.isBackward ? startSelection : endSelection, - end: selection.isBackward ? endSelection : startSelection, - ); - - editorState.apply(transaction); - - return KeyEventResult.handled; -} - -CommandShortcutEventHandler _selectAllInCodeBlockCommandHandler = - (editorState) { - final selection = editorState.selection; - if (selection == null || !selection.isSingle) { - return KeyEventResult.ignored; - } - - final node = editorState.getNodeAtPath(selection.end.path); - final delta = node?.delta; - if (node == null || delta == null || node.type != CodeBlockKeys.type) { - return KeyEventResult.ignored; - } - - editorState.service.selectionService.updateSelection( - Selection.single( - path: node.path, - startOffset: 0, - endOffset: delta.length, - ), - ); - - return KeyEventResult.handled; -}; - -CommandShortcutEventHandler _pasteInCodeBlock = (editorState) { - var selection = editorState.selection; - - if (selection == null) { - return KeyEventResult.ignored; - } - - if (editorState.getNodesInSelection(selection).length != 1) { - return KeyEventResult.ignored; - } - - final node = editorState.getNodeAtPath(selection.end.path); - if (node == null || node.type != CodeBlockKeys.type) { - return KeyEventResult.ignored; - } - - // delete the selection first. - if (!selection.isCollapsed) { - editorState.deleteSelection(selection); - } - - // fetch selection again. - selection = editorState.selection; - if (selection == null) { - return KeyEventResult.skipRemainingHandlers; - } - assert(selection.isCollapsed); - - () async { - final data = await AppFlowyClipboard.getData(); - final text = data.text; - if (text != null && text.isNotEmpty) { - final transaction = editorState.transaction - ..insertText( - node, - selection!.end.offset, - text, - ); - - await editorState.apply(transaction); - } - }(); - - return KeyEventResult.handled; -}; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_themes.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_themes.dart deleted file mode 100644 index 86fa2f99fe..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_themes.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'package:flutter/painting.dart'; - -const lightThemeInCodeblock = { - 'root': TextStyle( - backgroundColor: Color(0xfffbf1c7), - color: Color(0xff3c3836), - ), - 'subst': TextStyle(color: Color(0xff3c3836)), - 'deletion': TextStyle(color: Color(0xff9d0006)), - 'formula': TextStyle(color: Color(0xff9d0006)), - 'keyword': TextStyle(color: Color(0xff9d0006)), - 'link': TextStyle(color: Color(0xff9d0006)), - 'selector-tag': TextStyle(color: Color(0xff9d0006)), - 'built_in': TextStyle(color: Color(0xff076678)), - 'emphasis': TextStyle( - color: Color(0xff076678), - fontStyle: FontStyle.italic, - ), - 'name': TextStyle(color: Color(0xff076678)), - 'quote': TextStyle(color: Color(0xff076678)), - 'strong': TextStyle( - color: Color(0xff076678), - fontWeight: FontWeight.bold, - ), - 'title': TextStyle(color: Color(0xff076678)), - 'variable': TextStyle(color: Color(0xff076678)), - 'attr': TextStyle(color: Color(0xffb57614)), - 'params': TextStyle(color: Color(0xffb57614)), - 'template-tag': TextStyle(color: Color(0xffb57614)), - 'type': TextStyle(color: Color(0xffb57614)), - 'builtin-name': TextStyle(color: Color(0xff8f3f71)), - 'doctag': TextStyle(color: Color(0xff8f3f71)), - 'literal': TextStyle(color: Color(0xff8f3f71)), - 'number': TextStyle(color: Color(0xff8f3f71)), - 'code': TextStyle(color: Color(0xffaf3a03)), - 'meta': TextStyle(color: Color(0xffaf3a03)), - 'regexp': TextStyle(color: Color(0xffaf3a03)), - 'selector-id': TextStyle(color: Color(0xffaf3a03)), - 'template-variable': TextStyle(color: Color(0xffaf3a03)), - 'addition': TextStyle(color: Color(0xff79740e)), - 'meta-string': TextStyle(color: Color(0xff79740e)), - 'section': TextStyle( - color: Color(0xff79740e), - fontWeight: FontWeight.bold, - ), - 'selector-attr': TextStyle(color: Color(0xff79740e)), - 'selector-class': TextStyle(color: Color(0xff79740e)), - 'string': TextStyle(color: Color(0xff79740e)), - 'symbol': TextStyle(color: Color(0xff79740e)), - 'attribute': TextStyle(color: Color(0xff427b58)), - 'bullet': TextStyle(color: Color(0xff427b58)), - 'class': TextStyle(color: Color(0xff427b58)), - 'function': TextStyle(color: Color(0xff427b58)), - 'meta-keyword': TextStyle(color: Color(0xff427b58)), - 'selector-pseudo': TextStyle(color: Color(0xff427b58)), - 'tag': TextStyle( - color: Color(0xff427b58), - fontWeight: FontWeight.bold, - ), - 'comment': TextStyle( - color: Color(0xff928374), - fontStyle: FontStyle.italic, - ), - 'link_label': TextStyle(color: Color(0xff8f3f71)), -}; - -const darkThemeInCodeBlock = { - 'root': TextStyle( - backgroundColor: Color(0xff000000), - color: Color(0xfff8f8f8), - ), - 'comment': TextStyle( - color: Color(0xffaeaeae), - fontStyle: FontStyle.italic, - ), - 'quote': TextStyle( - color: Color(0xffaeaeae), - fontStyle: FontStyle.italic, - ), - 'keyword': TextStyle(color: Color(0xffe28964)), - 'selector-tag': TextStyle(color: Color(0xffe28964)), - 'type': TextStyle(color: Color(0xffe28964)), - 'string': TextStyle(color: Color(0xff65b042)), - 'subst': TextStyle(color: Color(0xffdaefa3)), - 'regexp': TextStyle(color: Color(0xffe9c062)), - 'link': TextStyle(color: Color(0xffe9c062)), - 'title': TextStyle(color: Color(0xff89bdff)), - 'section': TextStyle(color: Color(0xff89bdff)), - 'tag': TextStyle(color: Color(0xff89bdff)), - 'name': TextStyle(color: Color(0xff89bdff)), - 'symbol': TextStyle(color: Color(0xff3387cc)), - 'bullet': TextStyle(color: Color(0xff3387cc)), - 'number': TextStyle(color: Color(0xff3387cc)), - 'params': TextStyle(color: Color(0xff3e87e3)), - 'variable': TextStyle(color: Color(0xff3e87e3)), - 'template-variable': TextStyle(color: Color(0xff3e87e3)), - 'attribute': TextStyle(color: Color(0xffcda869)), - 'meta': TextStyle(color: Color(0xff8996a8)), - 'formula': TextStyle( - backgroundColor: Color(0xff0e2231), - color: Color(0xfff8f8f8), - fontStyle: FontStyle.italic, - ), - 'addition': TextStyle( - backgroundColor: Color(0xff253b22), - color: Color(0xfff8f8f8), - ), - 'deletion': TextStyle( - backgroundColor: Color(0xff420e09), - color: Color(0xfff8f8f8), - ), - 'selector-class': TextStyle(color: Color(0xff9b703f)), - 'selector-id': TextStyle(color: Color(0xff8b98ab)), - 'emphasis': TextStyle(fontStyle: FontStyle.italic), - 'strong': TextStyle(fontWeight: FontWeight.bold), -}; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart index 98013621cf..d08ef4cb39 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart @@ -1,10 +1,11 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class MobileCodeLanguagePickerScreen extends StatelessWidget { @@ -21,7 +22,7 @@ class MobileCodeLanguagePickerScreen extends StatelessWidget { body: SafeArea( child: ListView.separated( itemBuilder: (context, index) { - final language = codeBlockSupportedLanguages[index]; + final language = defaultCodeBlockSupportedLanguages[index]; return SizedBox( height: 48, child: FlowyTextButton( @@ -35,7 +36,7 @@ class MobileCodeLanguagePickerScreen extends StatelessWidget { ); }, separatorBuilder: (_, __) => const Divider(), - itemCount: codeBlockSupportedLanguages.length, + itemCount: defaultCodeBlockSupportedLanguages.length, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart index f2e3d4d427..015541e0a9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:collection/collection.dart'; class EditorMigration { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart index 30d344b04a..7b141f5351 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart @@ -1,10 +1,12 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_menu.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; // convert the current block to other block types // only show in single selection and text type diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_convert_block_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_convert_block_toolbar_item.dart index e3e8c80942..903fe3604e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_convert_block_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_convert_block_toolbar_item.dart @@ -1,10 +1,12 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_menu.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; // convert the current block to other block types // only show in single selection and text type diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart index 0b1ae151d3..d05f88da65 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart'; @@ -11,8 +13,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_too import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; final addBlockToolbarItem = AppFlowyMobileToolbarItem( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index f5f1331c94..bf092e08d6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -3,8 +3,7 @@ export 'actions/option_action.dart'; export 'align_toolbar_item/align_toolbar_item.dart'; export 'base/toolbar_extension.dart'; export 'callout/callout_block_component.dart'; -export 'code_block/code_block_component.dart'; -export 'code_block/code_block_shortcut_event.dart'; +export 'code_block/code_block_language_selector.dart'; export 'context_menu/custom_context_menu.dart'; export 'copy_and_paste/custom_copy_command.dart'; export 'copy_and_paste/custom_cut_command.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart index 70f73ccd3e..790375fc20 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart @@ -1,6 +1,5 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_block_shortcut_event.dart'; import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -122,5 +121,5 @@ class ShortcutsCubit extends Cubit { } extension on CommandShortcutEvent { - bool get isCodeBlockCommand => codeBlockCommands.contains(this); + bool get isCodeBlockCommand => localizedCodeBlockCommands.contains(this); } diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index c1401788ae..497c629321 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -61,11 +61,10 @@ packages: appflowy_editor_plugins: dependency: "direct main" description: - path: "." - ref: "8f238f2" - resolved-ref: "8f238f214de72e629fe2d90317518c5a0510cdc5" - url: "https://github.com/LucasXu0/appflowy_editor_plugins" - source: git + name: appflowy_editor_plugins + sha256: adfafde707a65ec9674bc3edaa8b8d865e065a08a4f653176d5db0c950521cbb + url: "https://pub.dev" + source: hosted version: "0.0.1" appflowy_popover: dependency: "direct main" @@ -173,10 +172,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.9" build_runner_core: dependency: transitive description: @@ -1965,10 +1964,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.2.6" url_launcher_android: dependency: transitive description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index f0ec285321..d91db514a7 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -46,11 +46,8 @@ dependencies: ref: 15a3a50 appflowy_result: path: packages/appflowy_result + appflowy_editor_plugins: ^0.0.1 appflowy_editor: - appflowy_editor_plugins: - git: - url: https://github.com/LucasXu0/appflowy_editor_plugins - ref: "8f238f2" appflowy_popover: path: packages/appflowy_popover @@ -142,10 +139,10 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter - build_runner: ^2.4.4 + build_runner: ^2.4.9 freezed: ^2.4.7 bloc_test: ^9.1.2 - json_serializable: ^6.7.0 + json_serializable: ^6.7.1 envied_generator: ^0.5.2 plugin_platform_interface: any