diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart index a81b823f37..bdb7f5d701 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -111,6 +111,47 @@ class _MyHomePageState extends State { if (!darkMode) ...lightEditorStyleExtension, if (!darkMode) ...lightPlguinStyleExtension, ]); + final delta = Delta(); + delta.add(TextInsert('Hello ')); + delta.add( + TextInsert( + 'World', + attributes: { + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.italic: true, + }, + ), + ); + delta.add( + TextInsert( + ' ', + ), + ); + delta.add( + TextInsert( + 'Again', + attributes: { + BuiltInAttributeKey.italic: true, + }, + ), + ); + delta.add( + TextInsert( + ' ', + ), + ); + delta.add( + TextInsert( + 'Again', + attributes: { + BuiltInAttributeKey.href: 'https://google.com', + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.strikethrough: true, + }, + ), + ); + final result = DeltaMarkdownEncoder().convert(delta); return Container( color: darkMode ? Colors.black : Colors.white, width: MediaQuery.of(context).size.width, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart index 29cb9f87f6..f646577a8a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart @@ -33,3 +33,4 @@ export 'src/render/selection_menu/selection_menu_widget.dart'; export 'src/l10n/l10n.dart'; export 'src/render/style/plugin_styles.dart'; export 'src/render/style/editor_style.dart'; +export 'src/plugins/markdown/delta_markdown_encoder.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/delta_markdown_encoder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/delta_markdown_encoder.dart new file mode 100644 index 0000000000..5c8bd9ebf9 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/delta_markdown_encoder.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/appflowy_editor.dart'; + +/// A [Delta] encoder that encodes a [Delta] to Markdown. +/// +/// Only support inline styles, like bold, italic, underline, strike, code. +class DeltaMarkdownEncoder extends Converter { + @override + String convert(Delta input) { + final buffer = StringBuffer(); + final iterator = input.iterator; + while (iterator.moveNext()) { + final op = iterator.current; + if (op is TextInsert) { + final attributes = op.attributes; + if (attributes != null) { + buffer.write(_prefixSyntax(attributes)); + buffer.write(op.text); + buffer.write(_suffixSyntax(attributes)); + } else { + buffer.write(op.text); + } + } + } + return buffer.toString(); + } + + String _prefixSyntax(Attributes attributes) { + var syntax = ''; + + if (attributes[BuiltInAttributeKey.bold] == true && + attributes[BuiltInAttributeKey.italic] == true) { + syntax += '***'; + } else if (attributes[BuiltInAttributeKey.bold] == true) { + syntax += '**'; + } else if (attributes[BuiltInAttributeKey.italic] == true) { + syntax += '_'; + } + + if (attributes[BuiltInAttributeKey.strikethrough] == true) { + syntax += '~~'; + } + if (attributes[BuiltInAttributeKey.underline] == true) { + syntax += ''; + } + if (attributes[BuiltInAttributeKey.code] == true) { + syntax += '`'; + } + + if (attributes[BuiltInAttributeKey.href] != null) { + syntax += '['; + } + + return syntax; + } + + String _suffixSyntax(Attributes attributes) { + var syntax = ''; + + if (attributes[BuiltInAttributeKey.href] != null) { + syntax += '](${attributes[BuiltInAttributeKey.href]})'; + } + + if (attributes[BuiltInAttributeKey.code] == true) { + syntax += '`'; + } + + if (attributes[BuiltInAttributeKey.underline] == true) { + syntax += ''; + } + + if (attributes[BuiltInAttributeKey.strikethrough] == true) { + syntax += '~~'; + } + + if (attributes[BuiltInAttributeKey.bold] == true && + attributes[BuiltInAttributeKey.italic] == true) { + syntax += '***'; + } else if (attributes[BuiltInAttributeKey.bold] == true) { + syntax += '**'; + } else if (attributes[BuiltInAttributeKey.italic] == true) { + syntax += '_'; + } + + return syntax; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/document_markdown_encoder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/document_markdown_encoder.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/markdown_encoder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/markdown_encoder.dart new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/markdown_encoder.dart @@ -0,0 +1 @@ + diff --git a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml index 574757cb79..04a31d29e5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: intl: flutter_localizations: sdk: flutter + markdown: ^6.0.1 dev_dependencies: flutter_test: diff --git a/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/delta_markdown_encoder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/delta_markdown_encoder_test.dart new file mode 100644 index 0000000000..831a449d02 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/delta_markdown_encoder_test.dart @@ -0,0 +1,100 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + group('delta_markdown_encoder.dart', () { + test('bold', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.bold: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to **AppFlowy**'); + }); + + test('italic', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.italic: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to _AppFlowy_'); + }); + + test('underline', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.underline: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to AppFlowy'); + }); + + test('strikethrough', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.strikethrough: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to ~~AppFlowy~~'); + }); + + test('href', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.href: 'https://appflowy.io', + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to [AppFlowy](https://appflowy.io)'); + }); + + test('code', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.code: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to `AppFlowy`'); + }); + + test('composition', () { + final delta = Delta(operations: [ + TextInsert('Welcome', attributes: { + BuiltInAttributeKey.code: true, + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.underline: true, + }), + TextInsert(' '), + TextInsert('to', attributes: { + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.strikethrough: true, + }), + TextInsert(' '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.href: 'https://appflowy.io', + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.italic: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect( + result, + '***`Welcome`*** ***~~to~~*** ***[AppFlowy](https://appflowy.io)***', + ); + }); + }); +}