diff --git a/README.md b/README.md index ceaac09643..f2b2031667 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,121 @@ -[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) -![Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) +

+ + AppFlowy.IO
+
+ ⭐️ The Open Source Notion Alternative ⭐️
+

+ +

+You are in charge of your data and customizations. +

-# what is AppFlowy? +
+ +![GitHub stars](https://img.shields.io/github/stars/{AppFlowy-IO}/{appflowy}.svg) -## Features +
-## Documentation +

+ Website • + Discord • + Twitter • +

-* [**Getting Started**](doc/GETTING_STARTED.md) -* [**Roadmap**](doc/ROADMAP.md) -* [**Deep Dive AppFlowy**](doc/APPFLOWY_SYSTEM_DESIGN.md) +

The Open Source Notion Alternative.

+ +## Built With + +* [Flutter](https://flutter.dev/) + +* [Rust](https://www.rust-lang.org/) + +## Stay Up-to-Date + +GIF (how to star) + +## Getting Started + +To get a local copy up and running, please follow these simple steps. + +Development environment setup: + +**Step 1:** + +* git clone [https://github.com/AppFlowy-IO/appflowy.git](https://github.com/AppFlowy-IO/appflowy.git) + +**Step 2:** + +- `cd appflowy` +- `make install_rust` + +> skip if you already installed it +> +- `make nstall_cargo_make` + +> AppFlowy uses [https://github.com/sagiegurari/cargo-make](https://github.com/sagiegurari/cargo-make) to construct the build scripts +> +- `cargo make flowy_dev` +> It's located at xxx/appflowy/scripts/makefile/env.toml. `flowy_dev` consists of three tasks: +> * `install_targets` +> * `install_diesel` +> * `install_protobuf` + +**Step 3:** + +* Follow the instructions [here](https://flutter.dev/docs/get-started/install) to install Flutter. As AppFlowy uses the `dev` channel, you need to switch the channel. Just type: + +`flutter channel dev` + +**Step 4:** + +* Open the `app_flowy` folder located at xx/appflowy/app_flowy with Visual Studio or other IDEs at your disposal +* Go to the Run and Debug tab and then click the run button. +![Run the project](https://github.com/AppFlowy-IO/appflowy/blob/main/doc/imgs/run.png) + +## Roadmap + +[AppFlowy Roadmap](https://trello.com/b/NCyXCXXh/appflowy-roadmap) + +If you'd like to propose a feature, submit an issue [here](https://github.com/AppFlowy-IO/appflowy/issues) + +## **Releases** + +Please see the [changelog](https://www.appflowy.io/whatsnew) for more details about a given release. + ## Contributing -Read the [Contributing Doc](doc/CONTRIBUTING.md) before you want to contribute. -## Social Media -* Slack (to be determined) +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. Please look at [CONTRIBUTING.md](https://github.com/AppFlowy-IO/appflowy/blob/main/doc/CONTRIBUTING.md) for details. + +## Why Are We Building This? + +Notion has been our favorite project and knowledge management tool in recent years because of its aesthetic appeal and functionality. Our team uses it daily, and we are on its paid plan. However, as we all know Notion has its limitations. These include weak data security and poor compatibility with mobile devices. Likewise, alternative collaborative workplace management tools also have their constraints. + +The limitations we encountered using these tools rooted in our past work experience with collaborative productivity tools lead to our firm belief that there is, and will be a glass ceiling on what's possible in the future for tools like Notion. This emanates from these tools probable struggles to scale horizontally at some point. It implies that they will likely be forced to prioritize for a proportion of customers whose needs can be quite different from the rest. While decision-makers want a workplace OS, the truth is that it is not very possible to come up with a one-size fits all solution in such a fragmented market. + +When a customer's evolving core needs are not satisfied, they either switch to another or build one from the ground up, in-house. Consequently, they either go under another ceiling or buy an expensive ticket to learn a hard lesson. This is a requirement for many resources and expertise, building a reliable and easy-to-use collaborative tool, not to mention the speed and native experience. The same may apply to individual users as well. + +All these restrictions necessitate our mission - to make it possible for anyone to create apps that suit their needs well. + +- To individuals, we would like to offer Notion's functionality along with data security and cross-platform native experience. +- To enterprises and hackers, AppFlowy is dedicated to offering building blocks, that is, collaboration infra services to enable you to make apps on your own. Moreover, you have 100% control of your data. You can design and modify AppFlowy your way, with a single codebase written in Flutter and Rust supporting multiple platforms armed with long-term maintainability. + +We decided to achieve this mission by upholding the three most fundamental values: + +- Data privacy first +- Reliable native experience +- Community-driven extensibility + +To be honest, we do not claim to outperform Notion in terms of functionality and design, at least for now. Besides, our priority doesn't lie in more functionality at the moment. Instead, we would like to cultivate a community to democratize the knowledge and wheels of making complex workplace management tools, while enabling people and businesses to create beautiful things on their own by equipping them with a versatile toolbox of building blocks. ## License -AppFlowy is under the Apache 2.0 license. See the [LICENSE](/LICENSE) file for details. + +Distributed under the AGPLv3 License. See `LICENSE.md` for more information. + +## Acknowledgements + +Special thanks to these amazing projects which help power AppFlowy.IO: + +- [flutter-quill](https://github.com/singerdmx/flutter-quill) diff --git a/app_flowy/lib/startup/tasks/application_task.dart b/app_flowy/lib/startup/tasks/application_task.dart index e3833c8ca6..c0e68ba8ee 100644 --- a/app_flowy/lib/startup/tasks/application_task.dart +++ b/app_flowy/lib/startup/tasks/application_task.dart @@ -33,7 +33,7 @@ class ApplicationWidget extends StatelessWidget { @override Widget build(BuildContext context) { const ratio = 1.73; - const minWidth = 1200.0; + const minWidth = 800.0; setWindowMinSize(const Size(minWidth, minWidth / ratio)); // const launchWidth = 1310.0; // setWindowFrame(const Rect.fromLTWH(0, 0, launchWidth, launchWidth / ratio)); diff --git a/app_flowy/lib/user/application/sign_in_bloc.freezed.dart b/app_flowy/lib/user/application/sign_in_bloc.freezed.dart index c2add8b145..2f557809d5 100644 --- a/app_flowy/lib/user/application/sign_in_bloc.freezed.dart +++ b/app_flowy/lib/user/application/sign_in_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -46,6 +47,13 @@ mixin _$SignInEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signedInWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? signedInWithUserEmailAndPassword, TResult Function(String email)? emailChanged, @@ -62,6 +70,14 @@ mixin _$SignInEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignedInWithUserEmailAndPassword value)? + signedInWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(SignedInWithUserEmailAndPassword value)? signedInWithUserEmailAndPassword, @@ -140,6 +156,16 @@ class _$SignedInWithUserEmailAndPassword return signedInWithUserEmailAndPassword(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signedInWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + }) { + return signedInWithUserEmailAndPassword?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -165,6 +191,17 @@ class _$SignedInWithUserEmailAndPassword return signedInWithUserEmailAndPassword(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignedInWithUserEmailAndPassword value)? + signedInWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + }) { + return signedInWithUserEmailAndPassword?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -257,6 +294,16 @@ class _$EmailChanged implements EmailChanged { return emailChanged(email); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signedInWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + }) { + return emailChanged?.call(email); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -282,6 +329,17 @@ class _$EmailChanged implements EmailChanged { return emailChanged(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignedInWithUserEmailAndPassword value)? + signedInWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + }) { + return emailChanged?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -380,6 +438,16 @@ class _$PasswordChanged implements PasswordChanged { return passwordChanged(password); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signedInWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + }) { + return passwordChanged?.call(password); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -405,6 +473,17 @@ class _$PasswordChanged implements PasswordChanged { return passwordChanged(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignedInWithUserEmailAndPassword value)? + signedInWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + }) { + return passwordChanged?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/user/application/sign_up_bloc.freezed.dart b/app_flowy/lib/user/application/sign_up_bloc.freezed.dart index 0953ed45d5..ec7b4d9dac 100644 --- a/app_flowy/lib/user/application/sign_up_bloc.freezed.dart +++ b/app_flowy/lib/user/application/sign_up_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -53,6 +54,14 @@ mixin _$SignUpEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signUpWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? repeatPasswordChanged, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? signUpWithUserEmailAndPassword, TResult Function(String email)? emailChanged, @@ -72,6 +81,15 @@ mixin _$SignUpEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignUpWithUserEmailAndPassword value)? + signUpWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(SignUpWithUserEmailAndPassword value)? signUpWithUserEmailAndPassword, @@ -151,6 +169,17 @@ class _$SignUpWithUserEmailAndPassword return signUpWithUserEmailAndPassword(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signUpWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? repeatPasswordChanged, + }) { + return signUpWithUserEmailAndPassword?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -179,6 +208,18 @@ class _$SignUpWithUserEmailAndPassword return signUpWithUserEmailAndPassword(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignUpWithUserEmailAndPassword value)? + signUpWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged, + }) { + return signUpWithUserEmailAndPassword?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -273,6 +314,17 @@ class _$EmailChanged implements EmailChanged { return emailChanged(email); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signUpWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? repeatPasswordChanged, + }) { + return emailChanged?.call(email); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -301,6 +353,18 @@ class _$EmailChanged implements EmailChanged { return emailChanged(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignUpWithUserEmailAndPassword value)? + signUpWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged, + }) { + return emailChanged?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -401,6 +465,17 @@ class _$PasswordChanged implements PasswordChanged { return passwordChanged(password); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signUpWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? repeatPasswordChanged, + }) { + return passwordChanged?.call(password); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -429,6 +504,18 @@ class _$PasswordChanged implements PasswordChanged { return passwordChanged(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignUpWithUserEmailAndPassword value)? + signUpWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged, + }) { + return passwordChanged?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -530,6 +617,17 @@ class _$RepeatPasswordChanged implements RepeatPasswordChanged { return repeatPasswordChanged(password); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? signUpWithUserEmailAndPassword, + TResult Function(String email)? emailChanged, + TResult Function(String password)? passwordChanged, + TResult Function(String password)? repeatPasswordChanged, + }) { + return repeatPasswordChanged?.call(password); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -558,6 +656,18 @@ class _$RepeatPasswordChanged implements RepeatPasswordChanged { return repeatPasswordChanged(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(SignUpWithUserEmailAndPassword value)? + signUpWithUserEmailAndPassword, + TResult Function(EmailChanged value)? emailChanged, + TResult Function(PasswordChanged value)? passwordChanged, + TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged, + }) { + return repeatPasswordChanged?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/user/application/splash_bloc.freezed.dart b/app_flowy/lib/user/application/splash_bloc.freezed.dart index b46aee0482..51e8a7fe3a 100644 --- a/app_flowy/lib/user/application/splash_bloc.freezed.dart +++ b/app_flowy/lib/user/application/splash_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -32,6 +33,11 @@ mixin _$SplashEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? getUser, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? getUser, required TResult orElse(), @@ -43,6 +49,11 @@ mixin _$SplashEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_GetUser value)? getUser, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(_GetUser value)? getUser, required TResult orElse(), @@ -108,6 +119,14 @@ class _$_GetUser implements _GetUser { return getUser(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? getUser, + }) { + return getUser?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -128,6 +147,14 @@ class _$_GetUser implements _GetUser { return getUser(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_GetUser value)? getUser, + }) { + return getUser?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/user/domain/auth_state.freezed.dart b/app_flowy/lib/user/domain/auth_state.freezed.dart index af6f7b385f..85e8fc3822 100644 --- a/app_flowy/lib/user/domain/auth_state.freezed.dart +++ b/app_flowy/lib/user/domain/auth_state.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -46,6 +47,13 @@ mixin _$AuthState { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(UserProfile userProfile)? authenticated, + TResult Function(UserError error)? unauthenticated, + TResult Function()? initial, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function(UserProfile userProfile)? authenticated, TResult Function(UserError error)? unauthenticated, @@ -61,6 +69,13 @@ mixin _$AuthState { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Authenticated value)? authenticated, + TResult Function(Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(Authenticated value)? authenticated, TResult Function(Unauthenticated value)? unauthenticated, @@ -157,6 +172,16 @@ class _$Authenticated implements Authenticated { return authenticated(userProfile); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(UserProfile userProfile)? authenticated, + TResult Function(UserError error)? unauthenticated, + TResult Function()? initial, + }) { + return authenticated?.call(userProfile); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -181,6 +206,16 @@ class _$Authenticated implements Authenticated { return authenticated(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Authenticated value)? authenticated, + TResult Function(Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + }) { + return authenticated?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -276,6 +311,16 @@ class _$Unauthenticated implements Unauthenticated { return unauthenticated(error); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(UserProfile userProfile)? authenticated, + TResult Function(UserError error)? unauthenticated, + TResult Function()? initial, + }) { + return unauthenticated?.call(error); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -300,6 +345,16 @@ class _$Unauthenticated implements Unauthenticated { return unauthenticated(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Authenticated value)? authenticated, + TResult Function(Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + }) { + return unauthenticated?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -368,6 +423,16 @@ class _$_Initial implements _Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(UserProfile userProfile)? authenticated, + TResult Function(UserError error)? unauthenticated, + TResult Function()? initial, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -392,6 +457,16 @@ class _$_Initial implements _Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Authenticated value)? authenticated, + TResult Function(Unauthenticated value)? unauthenticated, + TResult Function(_Initial value)? initial, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/app/app_bloc.freezed.dart b/app_flowy/lib/workspace/application/app/app_bloc.freezed.dart index ad75353628..d795abad45 100644 --- a/app_flowy/lib/workspace/application/app/app_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/app/app_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -68,6 +69,16 @@ mixin _$AppEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc, ViewType viewType)? createView, + TResult Function()? delete, + TResult Function(String newName)? rename, + TResult Function(List views)? didReceiveViews, + TResult Function(App app)? appDidUpdate, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? initial, TResult Function(String name, String desc, ViewType viewType)? createView, @@ -89,6 +100,16 @@ mixin _$AppEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateView value)? createView, + TResult Function(Delete value)? delete, + TResult Function(Rename value)? rename, + TResult Function(ReceiveViews value)? didReceiveViews, + TResult Function(AppDidUpdate value)? appDidUpdate, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(Initial value)? initial, TResult Function(CreateView value)? createView, @@ -164,6 +185,19 @@ class _$Initial implements Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc, ViewType viewType)? createView, + TResult Function()? delete, + TResult Function(String newName)? rename, + TResult Function(List views)? didReceiveViews, + TResult Function(App app)? appDidUpdate, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -194,6 +228,19 @@ class _$Initial implements Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateView value)? createView, + TResult Function(Delete value)? delete, + TResult Function(Rename value)? rename, + TResult Function(ReceiveViews value)? didReceiveViews, + TResult Function(AppDidUpdate value)? appDidUpdate, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -312,6 +359,19 @@ class _$CreateView implements CreateView { return createView(name, desc, viewType); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc, ViewType viewType)? createView, + TResult Function()? delete, + TResult Function(String newName)? rename, + TResult Function(List views)? didReceiveViews, + TResult Function(App app)? appDidUpdate, + }) { + return createView?.call(name, desc, viewType); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -342,6 +402,19 @@ class _$CreateView implements CreateView { return createView(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateView value)? createView, + TResult Function(Delete value)? delete, + TResult Function(Rename value)? rename, + TResult Function(ReceiveViews value)? didReceiveViews, + TResult Function(AppDidUpdate value)? appDidUpdate, + }) { + return createView?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -420,6 +493,19 @@ class _$Delete implements Delete { return delete(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc, ViewType viewType)? createView, + TResult Function()? delete, + TResult Function(String newName)? rename, + TResult Function(List views)? didReceiveViews, + TResult Function(App app)? appDidUpdate, + }) { + return delete?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -450,6 +536,19 @@ class _$Delete implements Delete { return delete(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateView value)? createView, + TResult Function(Delete value)? delete, + TResult Function(Rename value)? rename, + TResult Function(ReceiveViews value)? didReceiveViews, + TResult Function(AppDidUpdate value)? appDidUpdate, + }) { + return delete?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -545,6 +644,19 @@ class _$Rename implements Rename { return rename(newName); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc, ViewType viewType)? createView, + TResult Function()? delete, + TResult Function(String newName)? rename, + TResult Function(List views)? didReceiveViews, + TResult Function(App app)? appDidUpdate, + }) { + return rename?.call(newName); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -575,6 +687,19 @@ class _$Rename implements Rename { return rename(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateView value)? createView, + TResult Function(Delete value)? delete, + TResult Function(Rename value)? rename, + TResult Function(ReceiveViews value)? didReceiveViews, + TResult Function(AppDidUpdate value)? appDidUpdate, + }) { + return rename?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -676,6 +801,19 @@ class _$ReceiveViews implements ReceiveViews { return didReceiveViews(views); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc, ViewType viewType)? createView, + TResult Function()? delete, + TResult Function(String newName)? rename, + TResult Function(List views)? didReceiveViews, + TResult Function(App app)? appDidUpdate, + }) { + return didReceiveViews?.call(views); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -706,6 +844,19 @@ class _$ReceiveViews implements ReceiveViews { return didReceiveViews(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateView value)? createView, + TResult Function(Delete value)? delete, + TResult Function(Rename value)? rename, + TResult Function(ReceiveViews value)? didReceiveViews, + TResult Function(AppDidUpdate value)? appDidUpdate, + }) { + return didReceiveViews?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -808,6 +959,19 @@ class _$AppDidUpdate implements AppDidUpdate { return appDidUpdate(app); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc, ViewType viewType)? createView, + TResult Function()? delete, + TResult Function(String newName)? rename, + TResult Function(List views)? didReceiveViews, + TResult Function(App app)? appDidUpdate, + }) { + return appDidUpdate?.call(app); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -838,6 +1002,19 @@ class _$AppDidUpdate implements AppDidUpdate { return appDidUpdate(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateView value)? createView, + TResult Function(Delete value)? delete, + TResult Function(Rename value)? rename, + TResult Function(ReceiveViews value)? didReceiveViews, + TResult Function(AppDidUpdate value)? appDidUpdate, + }) { + return appDidUpdate?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart b/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart index a2f83ca8db..02a1f50d94 100644 --- a/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -52,6 +53,15 @@ mixin _$DocEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? deleted, + TResult Function()? restore, + TResult Function()? restorePage, + TResult Function()? deletePermanently, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? initial, TResult Function()? deleted, @@ -71,6 +81,15 @@ mixin _$DocEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(Deleted value)? deleted, + TResult Function(Restore value)? restore, + TResult Function(RestorePage value)? restorePage, + TResult Function(DeletePermanently value)? deletePermanently, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(Initial value)? initial, TResult Function(Deleted value)? deleted, @@ -143,6 +162,18 @@ class _$Initial implements Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? deleted, + TResult Function()? restore, + TResult Function()? restorePage, + TResult Function()? deletePermanently, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -171,6 +202,18 @@ class _$Initial implements Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(Deleted value)? deleted, + TResult Function(Restore value)? restore, + TResult Function(RestorePage value)? restorePage, + TResult Function(DeletePermanently value)? deletePermanently, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -238,6 +281,18 @@ class _$Deleted implements Deleted { return deleted(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? deleted, + TResult Function()? restore, + TResult Function()? restorePage, + TResult Function()? deletePermanently, + }) { + return deleted?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -266,6 +321,18 @@ class _$Deleted implements Deleted { return deleted(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(Deleted value)? deleted, + TResult Function(Restore value)? restore, + TResult Function(RestorePage value)? restorePage, + TResult Function(DeletePermanently value)? deletePermanently, + }) { + return deleted?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -333,6 +400,18 @@ class _$Restore implements Restore { return restore(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? deleted, + TResult Function()? restore, + TResult Function()? restorePage, + TResult Function()? deletePermanently, + }) { + return restore?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -361,6 +440,18 @@ class _$Restore implements Restore { return restore(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(Deleted value)? deleted, + TResult Function(Restore value)? restore, + TResult Function(RestorePage value)? restorePage, + TResult Function(DeletePermanently value)? deletePermanently, + }) { + return restore?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -430,6 +521,18 @@ class _$RestorePage implements RestorePage { return restorePage(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? deleted, + TResult Function()? restore, + TResult Function()? restorePage, + TResult Function()? deletePermanently, + }) { + return restorePage?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -458,6 +561,18 @@ class _$RestorePage implements RestorePage { return restorePage(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(Deleted value)? deleted, + TResult Function(Restore value)? restore, + TResult Function(RestorePage value)? restorePage, + TResult Function(DeletePermanently value)? deletePermanently, + }) { + return restorePage?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -527,6 +642,18 @@ class _$DeletePermanently implements DeletePermanently { return deletePermanently(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? deleted, + TResult Function()? restore, + TResult Function()? restorePage, + TResult Function()? deletePermanently, + }) { + return deletePermanently?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -555,6 +682,18 @@ class _$DeletePermanently implements DeletePermanently { return deletePermanently(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(Deleted value)? deleted, + TResult Function(Restore value)? restore, + TResult Function(RestorePage value)? restorePage, + TResult Function(DeletePermanently value)? deletePermanently, + }) { + return deletePermanently?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -790,6 +929,12 @@ mixin _$DocLoadState { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? loading, TResult Function(Either successOrFail)? finish, @@ -803,6 +948,12 @@ mixin _$DocLoadState { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(_Loading value)? loading, TResult Function(_Finish value)? finish, @@ -871,6 +1022,15 @@ class _$_Loading implements _Loading { return loading(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + }) { + return loading?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -893,6 +1053,15 @@ class _$_Loading implements _Loading { return loading(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + }) { + return loading?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -981,6 +1150,15 @@ class _$_Finish implements _Finish { return finish(successOrFail); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + }) { + return finish?.call(successOrFail); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -1003,6 +1181,15 @@ class _$_Finish implements _Finish { return finish(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + }) { + return finish?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/doc/share_bloc.dart b/app_flowy/lib/workspace/application/doc/share_bloc.dart new file mode 100644 index 0000000000..f81495a15a --- /dev/null +++ b/app_flowy/lib/workspace/application/doc/share_bloc.dart @@ -0,0 +1,52 @@ +import 'package:app_flowy/workspace/domain/i_share.dart'; +import 'package:app_flowy/workspace/infrastructure/markdown/delta_markdown.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace-infra/export.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace-infra/view_create.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:dartz/dartz.dart'; +part 'share_bloc.freezed.dart'; + +class DocShareBloc extends Bloc { + IShare shareManager; + View view; + DocShareBloc({required this.view, required this.shareManager}) : super(const DocShareState.initial()) { + on((event, emit) async { + await event.map( + shareMarkdown: (ShareMarkdown value) async { + await shareManager.exportMarkdown(view.id).then((result) { + result.fold( + (value) => emit(DocShareState.finish(left(_convertDeltaToMarkdown(value)))), + (error) => emit(DocShareState.finish(right(error))), + ); + }); + + emit(const DocShareState.loading()); + }, + shareLink: (ShareLink value) {}, + shareText: (ShareText value) {}, + ); + }); + } + + ExportData _convertDeltaToMarkdown(ExportData value) { + final result = deltaToMarkdown(value.data); + value.data = result; + return value; + } +} + +@freezed +class DocShareEvent with _$DocShareEvent { + const factory DocShareEvent.shareMarkdown() = ShareMarkdown; + const factory DocShareEvent.shareText() = ShareText; + const factory DocShareEvent.shareLink() = ShareLink; +} + +@freezed +class DocShareState with _$DocShareState { + const factory DocShareState.initial() = _Initial; + const factory DocShareState.loading() = _Loading; + const factory DocShareState.finish(Either successOrFail) = _Finish; +} diff --git a/app_flowy/lib/workspace/application/doc/share_bloc.freezed.dart b/app_flowy/lib/workspace/application/doc/share_bloc.freezed.dart new file mode 100644 index 0000000000..ce72e660f2 --- /dev/null +++ b/app_flowy/lib/workspace/application/doc/share_bloc.freezed.dart @@ -0,0 +1,867 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target + +part of 'share_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +class _$DocShareEventTearOff { + const _$DocShareEventTearOff(); + + ShareMarkdown shareMarkdown() { + return const ShareMarkdown(); + } + + ShareText shareText() { + return const ShareText(); + } + + ShareLink shareLink() { + return const ShareLink(); + } +} + +/// @nodoc +const $DocShareEvent = _$DocShareEventTearOff(); + +/// @nodoc +mixin _$DocShareEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() shareMarkdown, + required TResult Function() shareText, + required TResult Function() shareLink, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(ShareMarkdown value) shareMarkdown, + required TResult Function(ShareText value) shareText, + required TResult Function(ShareLink value) shareLink, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DocShareEventCopyWith<$Res> { + factory $DocShareEventCopyWith( + DocShareEvent value, $Res Function(DocShareEvent) then) = + _$DocShareEventCopyWithImpl<$Res>; +} + +/// @nodoc +class _$DocShareEventCopyWithImpl<$Res> + implements $DocShareEventCopyWith<$Res> { + _$DocShareEventCopyWithImpl(this._value, this._then); + + final DocShareEvent _value; + // ignore: unused_field + final $Res Function(DocShareEvent) _then; +} + +/// @nodoc +abstract class $ShareMarkdownCopyWith<$Res> { + factory $ShareMarkdownCopyWith( + ShareMarkdown value, $Res Function(ShareMarkdown) then) = + _$ShareMarkdownCopyWithImpl<$Res>; +} + +/// @nodoc +class _$ShareMarkdownCopyWithImpl<$Res> + extends _$DocShareEventCopyWithImpl<$Res> + implements $ShareMarkdownCopyWith<$Res> { + _$ShareMarkdownCopyWithImpl( + ShareMarkdown _value, $Res Function(ShareMarkdown) _then) + : super(_value, (v) => _then(v as ShareMarkdown)); + + @override + ShareMarkdown get _value => super._value as ShareMarkdown; +} + +/// @nodoc + +class _$ShareMarkdown implements ShareMarkdown { + const _$ShareMarkdown(); + + @override + String toString() { + return 'DocShareEvent.shareMarkdown()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || (other is ShareMarkdown); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() shareMarkdown, + required TResult Function() shareText, + required TResult Function() shareLink, + }) { + return shareMarkdown(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + }) { + return shareMarkdown?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + required TResult orElse(), + }) { + if (shareMarkdown != null) { + return shareMarkdown(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(ShareMarkdown value) shareMarkdown, + required TResult Function(ShareText value) shareText, + required TResult Function(ShareLink value) shareLink, + }) { + return shareMarkdown(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + }) { + return shareMarkdown?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + required TResult orElse(), + }) { + if (shareMarkdown != null) { + return shareMarkdown(this); + } + return orElse(); + } +} + +abstract class ShareMarkdown implements DocShareEvent { + const factory ShareMarkdown() = _$ShareMarkdown; +} + +/// @nodoc +abstract class $ShareTextCopyWith<$Res> { + factory $ShareTextCopyWith(ShareText value, $Res Function(ShareText) then) = + _$ShareTextCopyWithImpl<$Res>; +} + +/// @nodoc +class _$ShareTextCopyWithImpl<$Res> extends _$DocShareEventCopyWithImpl<$Res> + implements $ShareTextCopyWith<$Res> { + _$ShareTextCopyWithImpl(ShareText _value, $Res Function(ShareText) _then) + : super(_value, (v) => _then(v as ShareText)); + + @override + ShareText get _value => super._value as ShareText; +} + +/// @nodoc + +class _$ShareText implements ShareText { + const _$ShareText(); + + @override + String toString() { + return 'DocShareEvent.shareText()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || (other is ShareText); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() shareMarkdown, + required TResult Function() shareText, + required TResult Function() shareLink, + }) { + return shareText(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + }) { + return shareText?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + required TResult orElse(), + }) { + if (shareText != null) { + return shareText(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(ShareMarkdown value) shareMarkdown, + required TResult Function(ShareText value) shareText, + required TResult Function(ShareLink value) shareLink, + }) { + return shareText(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + }) { + return shareText?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + required TResult orElse(), + }) { + if (shareText != null) { + return shareText(this); + } + return orElse(); + } +} + +abstract class ShareText implements DocShareEvent { + const factory ShareText() = _$ShareText; +} + +/// @nodoc +abstract class $ShareLinkCopyWith<$Res> { + factory $ShareLinkCopyWith(ShareLink value, $Res Function(ShareLink) then) = + _$ShareLinkCopyWithImpl<$Res>; +} + +/// @nodoc +class _$ShareLinkCopyWithImpl<$Res> extends _$DocShareEventCopyWithImpl<$Res> + implements $ShareLinkCopyWith<$Res> { + _$ShareLinkCopyWithImpl(ShareLink _value, $Res Function(ShareLink) _then) + : super(_value, (v) => _then(v as ShareLink)); + + @override + ShareLink get _value => super._value as ShareLink; +} + +/// @nodoc + +class _$ShareLink implements ShareLink { + const _$ShareLink(); + + @override + String toString() { + return 'DocShareEvent.shareLink()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || (other is ShareLink); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() shareMarkdown, + required TResult Function() shareText, + required TResult Function() shareLink, + }) { + return shareLink(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + }) { + return shareLink?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? shareMarkdown, + TResult Function()? shareText, + TResult Function()? shareLink, + required TResult orElse(), + }) { + if (shareLink != null) { + return shareLink(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(ShareMarkdown value) shareMarkdown, + required TResult Function(ShareText value) shareText, + required TResult Function(ShareLink value) shareLink, + }) { + return shareLink(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + }) { + return shareLink?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(ShareMarkdown value)? shareMarkdown, + TResult Function(ShareText value)? shareText, + TResult Function(ShareLink value)? shareLink, + required TResult orElse(), + }) { + if (shareLink != null) { + return shareLink(this); + } + return orElse(); + } +} + +abstract class ShareLink implements DocShareEvent { + const factory ShareLink() = _$ShareLink; +} + +/// @nodoc +class _$DocShareStateTearOff { + const _$DocShareStateTearOff(); + + _Initial initial() { + return const _Initial(); + } + + _Loading loading() { + return const _Loading(); + } + + _Finish finish(Either successOrFail) { + return _Finish( + successOrFail, + ); + } +} + +/// @nodoc +const $DocShareState = _$DocShareStateTearOff(); + +/// @nodoc +mixin _$DocShareState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Either successOrFail) + finish, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Finish value) finish, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DocShareStateCopyWith<$Res> { + factory $DocShareStateCopyWith( + DocShareState value, $Res Function(DocShareState) then) = + _$DocShareStateCopyWithImpl<$Res>; +} + +/// @nodoc +class _$DocShareStateCopyWithImpl<$Res> + implements $DocShareStateCopyWith<$Res> { + _$DocShareStateCopyWithImpl(this._value, this._then); + + final DocShareState _value; + // ignore: unused_field + final $Res Function(DocShareState) _then; +} + +/// @nodoc +abstract class _$InitialCopyWith<$Res> { + factory _$InitialCopyWith(_Initial value, $Res Function(_Initial) then) = + __$InitialCopyWithImpl<$Res>; +} + +/// @nodoc +class __$InitialCopyWithImpl<$Res> extends _$DocShareStateCopyWithImpl<$Res> + implements _$InitialCopyWith<$Res> { + __$InitialCopyWithImpl(_Initial _value, $Res Function(_Initial) _then) + : super(_value, (v) => _then(v as _Initial)); + + @override + _Initial get _value => super._value as _Initial; +} + +/// @nodoc + +class _$_Initial implements _Initial { + const _$_Initial(); + + @override + String toString() { + return 'DocShareState.initial()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || (other is _Initial); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Either successOrFail) + finish, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Finish value) finish, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements DocShareState { + const factory _Initial() = _$_Initial; +} + +/// @nodoc +abstract class _$LoadingCopyWith<$Res> { + factory _$LoadingCopyWith(_Loading value, $Res Function(_Loading) then) = + __$LoadingCopyWithImpl<$Res>; +} + +/// @nodoc +class __$LoadingCopyWithImpl<$Res> extends _$DocShareStateCopyWithImpl<$Res> + implements _$LoadingCopyWith<$Res> { + __$LoadingCopyWithImpl(_Loading _value, $Res Function(_Loading) _then) + : super(_value, (v) => _then(v as _Loading)); + + @override + _Loading get _value => super._value as _Loading; +} + +/// @nodoc + +class _$_Loading implements _Loading { + const _$_Loading(); + + @override + String toString() { + return 'DocShareState.loading()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || (other is _Loading); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Either successOrFail) + finish, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Finish value) finish, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements DocShareState { + const factory _Loading() = _$_Loading; +} + +/// @nodoc +abstract class _$FinishCopyWith<$Res> { + factory _$FinishCopyWith(_Finish value, $Res Function(_Finish) then) = + __$FinishCopyWithImpl<$Res>; + $Res call({Either successOrFail}); +} + +/// @nodoc +class __$FinishCopyWithImpl<$Res> extends _$DocShareStateCopyWithImpl<$Res> + implements _$FinishCopyWith<$Res> { + __$FinishCopyWithImpl(_Finish _value, $Res Function(_Finish) _then) + : super(_value, (v) => _then(v as _Finish)); + + @override + _Finish get _value => super._value as _Finish; + + @override + $Res call({ + Object? successOrFail = freezed, + }) { + return _then(_Finish( + successOrFail == freezed + ? _value.successOrFail + : successOrFail // ignore: cast_nullable_to_non_nullable + as Either, + )); + } +} + +/// @nodoc + +class _$_Finish implements _Finish { + const _$_Finish(this.successOrFail); + + @override + final Either successOrFail; + + @override + String toString() { + return 'DocShareState.finish(successOrFail: $successOrFail)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other is _Finish && + (identical(other.successOrFail, successOrFail) || + const DeepCollectionEquality() + .equals(other.successOrFail, successOrFail))); + } + + @override + int get hashCode => + runtimeType.hashCode ^ const DeepCollectionEquality().hash(successOrFail); + + @JsonKey(ignore: true) + @override + _$FinishCopyWith<_Finish> get copyWith => + __$FinishCopyWithImpl<_Finish>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Either successOrFail) + finish, + }) { + return finish(successOrFail); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + }) { + return finish?.call(successOrFail); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Either successOrFail)? finish, + required TResult orElse(), + }) { + if (finish != null) { + return finish(successOrFail); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Finish value) finish, + }) { + return finish(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + }) { + return finish?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Finish value)? finish, + required TResult orElse(), + }) { + if (finish != null) { + return finish(this); + } + return orElse(); + } +} + +abstract class _Finish implements DocShareState { + const factory _Finish(Either successOrFail) = + _$_Finish; + + Either get successOrFail => + throw _privateConstructorUsedError; + @JsonKey(ignore: true) + _$FinishCopyWith<_Finish> get copyWith => throw _privateConstructorUsedError; +} diff --git a/app_flowy/lib/workspace/application/edit_pannel/edit_pannel_bloc.freezed.dart b/app_flowy/lib/workspace/application/edit_pannel/edit_pannel_bloc.freezed.dart index 43d859d4df..5b49609f58 100644 --- a/app_flowy/lib/workspace/application/edit_pannel/edit_pannel_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/edit_pannel/edit_pannel_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -43,6 +44,12 @@ mixin _$EditPannelEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(EditPannelContext context)? startEdit, + TResult Function(EditPannelContext context)? endEdit, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function(EditPannelContext context)? startEdit, TResult Function(EditPannelContext context)? endEdit, @@ -56,6 +63,12 @@ mixin _$EditPannelEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_StartEdit value)? startEdit, + TResult Function(_EndEdit value)? endEdit, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(_StartEdit value)? startEdit, TResult Function(_EndEdit value)? endEdit, @@ -169,6 +182,15 @@ class _$_StartEdit implements _StartEdit { return startEdit(context); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(EditPannelContext context)? startEdit, + TResult Function(EditPannelContext context)? endEdit, + }) { + return startEdit?.call(context); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -191,6 +213,15 @@ class _$_StartEdit implements _StartEdit { return startEdit(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_StartEdit value)? startEdit, + TResult Function(_EndEdit value)? endEdit, + }) { + return startEdit?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -286,6 +317,15 @@ class _$_EndEdit implements _EndEdit { return endEdit(context); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(EditPannelContext context)? startEdit, + TResult Function(EditPannelContext context)? endEdit, + }) { + return endEdit?.call(context); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -308,6 +348,15 @@ class _$_EndEdit implements _EndEdit { return endEdit(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_StartEdit value)? startEdit, + TResult Function(_EndEdit value)? endEdit, + }) { + return endEdit?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/home/home_bloc.freezed.dart b/app_flowy/lib/workspace/application/home/home_bloc.freezed.dart index bdc2e91786..b3967c13c9 100644 --- a/app_flowy/lib/workspace/application/home/home_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/home/home_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -53,6 +54,14 @@ mixin _$HomeEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(bool isLoading)? showLoading, + TResult Function(bool forceCollapse)? forceCollapse, + TResult Function(EditPannelContext editContext)? setEditPannel, + TResult Function()? dismissEditPannel, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function(bool isLoading)? showLoading, TResult Function(bool forceCollapse)? forceCollapse, @@ -70,6 +79,14 @@ mixin _$HomeEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_ShowLoading value)? showLoading, + TResult Function(_ForceCollapse value)? forceCollapse, + TResult Function(_ShowEditPannel value)? setEditPannel, + TResult Function(_DismissEditPannel value)? dismissEditPannel, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(_ShowLoading value)? showLoading, TResult Function(_ForceCollapse value)? forceCollapse, @@ -168,6 +185,17 @@ class _$_ShowLoading implements _ShowLoading { return showLoading(isLoading); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(bool isLoading)? showLoading, + TResult Function(bool forceCollapse)? forceCollapse, + TResult Function(EditPannelContext editContext)? setEditPannel, + TResult Function()? dismissEditPannel, + }) { + return showLoading?.call(isLoading); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -194,6 +222,17 @@ class _$_ShowLoading implements _ShowLoading { return showLoading(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_ShowLoading value)? showLoading, + TResult Function(_ForceCollapse value)? forceCollapse, + TResult Function(_ShowEditPannel value)? setEditPannel, + TResult Function(_DismissEditPannel value)? dismissEditPannel, + }) { + return showLoading?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -292,6 +331,17 @@ class _$_ForceCollapse implements _ForceCollapse { return forceCollapse(this.forceCollapse); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(bool isLoading)? showLoading, + TResult Function(bool forceCollapse)? forceCollapse, + TResult Function(EditPannelContext editContext)? setEditPannel, + TResult Function()? dismissEditPannel, + }) { + return forceCollapse?.call(this.forceCollapse); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -318,6 +368,17 @@ class _$_ForceCollapse implements _ForceCollapse { return forceCollapse(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_ShowLoading value)? showLoading, + TResult Function(_ForceCollapse value)? forceCollapse, + TResult Function(_ShowEditPannel value)? setEditPannel, + TResult Function(_DismissEditPannel value)? dismissEditPannel, + }) { + return forceCollapse?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -416,6 +477,17 @@ class _$_ShowEditPannel implements _ShowEditPannel { return setEditPannel(editContext); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(bool isLoading)? showLoading, + TResult Function(bool forceCollapse)? forceCollapse, + TResult Function(EditPannelContext editContext)? setEditPannel, + TResult Function()? dismissEditPannel, + }) { + return setEditPannel?.call(editContext); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -442,6 +514,17 @@ class _$_ShowEditPannel implements _ShowEditPannel { return setEditPannel(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_ShowLoading value)? showLoading, + TResult Function(_ForceCollapse value)? forceCollapse, + TResult Function(_ShowEditPannel value)? setEditPannel, + TResult Function(_DismissEditPannel value)? dismissEditPannel, + }) { + return setEditPannel?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -516,6 +599,17 @@ class _$_DismissEditPannel implements _DismissEditPannel { return dismissEditPannel(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function(bool isLoading)? showLoading, + TResult Function(bool forceCollapse)? forceCollapse, + TResult Function(EditPannelContext editContext)? setEditPannel, + TResult Function()? dismissEditPannel, + }) { + return dismissEditPannel?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -542,6 +636,17 @@ class _$_DismissEditPannel implements _DismissEditPannel { return dismissEditPannel(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_ShowLoading value)? showLoading, + TResult Function(_ForceCollapse value)? forceCollapse, + TResult Function(_ShowEditPannel value)? setEditPannel, + TResult Function(_DismissEditPannel value)? dismissEditPannel, + }) { + return dismissEditPannel?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/home/home_listen_bloc.freezed.dart b/app_flowy/lib/workspace/application/home/home_listen_bloc.freezed.dart index 521bcd3b68..4e72038867 100644 --- a/app_flowy/lib/workspace/application/home/home_listen_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/home/home_listen_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -44,6 +45,13 @@ mixin _$HomeListenEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? started, + TResult Function()? stop, + TResult Function(String msg)? unauthorized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, TResult Function()? stop, @@ -59,6 +67,13 @@ mixin _$HomeListenEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Started value)? started, + TResult Function(_Stop value)? stop, + TResult Function(_Unauthorized value)? unauthorized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_Stop value)? stop, @@ -129,6 +144,16 @@ class _$_Started implements _Started { return started(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? started, + TResult Function()? stop, + TResult Function(String msg)? unauthorized, + }) { + return started?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -153,6 +178,16 @@ class _$_Started implements _Started { return started(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Started value)? started, + TResult Function(_Stop value)? stop, + TResult Function(_Unauthorized value)? unauthorized, + }) { + return started?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -216,6 +251,16 @@ class _$_Stop implements _Stop { return stop(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? started, + TResult Function()? stop, + TResult Function(String msg)? unauthorized, + }) { + return stop?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -240,6 +285,16 @@ class _$_Stop implements _Stop { return stop(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Started value)? started, + TResult Function(_Stop value)? stop, + TResult Function(_Unauthorized value)? unauthorized, + }) { + return stop?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -331,6 +386,16 @@ class _$_Unauthorized implements _Unauthorized { return unauthorized(msg); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? started, + TResult Function()? stop, + TResult Function(String msg)? unauthorized, + }) { + return unauthorized?.call(msg); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -355,6 +420,16 @@ class _$_Unauthorized implements _Unauthorized { return unauthorized(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Started value)? started, + TResult Function(_Stop value)? stop, + TResult Function(_Unauthorized value)? unauthorized, + }) { + return unauthorized?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -406,6 +481,12 @@ mixin _$HomeListenState { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? loading, + TResult Function(String msg)? unauthorized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? loading, TResult Function(String msg)? unauthorized, @@ -419,6 +500,12 @@ mixin _$HomeListenState { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Loading value)? loading, + TResult Function(Unauthorized value)? unauthorized, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(Loading value)? loading, TResult Function(Unauthorized value)? unauthorized, @@ -487,6 +574,15 @@ class _$Loading implements Loading { return loading(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? loading, + TResult Function(String msg)? unauthorized, + }) { + return loading?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -509,6 +605,15 @@ class _$Loading implements Loading { return loading(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Loading value)? loading, + TResult Function(Unauthorized value)? unauthorized, + }) { + return loading?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -598,6 +703,15 @@ class _$Unauthorized implements Unauthorized { return unauthorized(msg); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? loading, + TResult Function(String msg)? unauthorized, + }) { + return unauthorized?.call(msg); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -620,6 +734,15 @@ class _$Unauthorized implements Unauthorized { return unauthorized(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Loading value)? loading, + TResult Function(Unauthorized value)? unauthorized, + }) { + return unauthorized?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/menu/menu_bloc.freezed.dart b/app_flowy/lib/workspace/application/menu/menu_bloc.freezed.dart index 8d1ae22278..43c4bf0e64 100644 --- a/app_flowy/lib/workspace/application/menu/menu_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/menu/menu_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -61,6 +62,16 @@ mixin _$MenuEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? collapse, + TResult Function(HomeStackContext context)? openPage, + TResult Function(String name, String? desc)? createApp, + TResult Function(Either, WorkspaceError> appsOrFail)? + didReceiveApps, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? initial, TResult Function()? collapse, @@ -81,6 +92,15 @@ mixin _$MenuEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(Collapse value)? collapse, + TResult Function(OpenPage value)? openPage, + TResult Function(CreateApp value)? createApp, + TResult Function(ReceiveApps value)? didReceiveApps, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(_Initial value)? initial, TResult Function(Collapse value)? collapse, @@ -155,6 +175,19 @@ class _$_Initial implements _Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? collapse, + TResult Function(HomeStackContext context)? openPage, + TResult Function(String name, String? desc)? createApp, + TResult Function(Either, WorkspaceError> appsOrFail)? + didReceiveApps, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -184,6 +217,18 @@ class _$_Initial implements _Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(Collapse value)? collapse, + TResult Function(OpenPage value)? openPage, + TResult Function(CreateApp value)? createApp, + TResult Function(ReceiveApps value)? didReceiveApps, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -253,6 +298,19 @@ class _$Collapse implements Collapse { return collapse(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? collapse, + TResult Function(HomeStackContext context)? openPage, + TResult Function(String name, String? desc)? createApp, + TResult Function(Either, WorkspaceError> appsOrFail)? + didReceiveApps, + }) { + return collapse?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -282,6 +340,18 @@ class _$Collapse implements Collapse { return collapse(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(Collapse value)? collapse, + TResult Function(OpenPage value)? openPage, + TResult Function(CreateApp value)? createApp, + TResult Function(ReceiveApps value)? didReceiveApps, + }) { + return collapse?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -376,6 +446,19 @@ class _$OpenPage implements OpenPage { return openPage(context); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? collapse, + TResult Function(HomeStackContext context)? openPage, + TResult Function(String name, String? desc)? createApp, + TResult Function(Either, WorkspaceError> appsOrFail)? + didReceiveApps, + }) { + return openPage?.call(context); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -405,6 +488,18 @@ class _$OpenPage implements OpenPage { return openPage(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(Collapse value)? collapse, + TResult Function(OpenPage value)? openPage, + TResult Function(CreateApp value)? createApp, + TResult Function(ReceiveApps value)? didReceiveApps, + }) { + return openPage?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -517,6 +612,19 @@ class _$CreateApp implements CreateApp { return createApp(name, desc); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? collapse, + TResult Function(HomeStackContext context)? openPage, + TResult Function(String name, String? desc)? createApp, + TResult Function(Either, WorkspaceError> appsOrFail)? + didReceiveApps, + }) { + return createApp?.call(name, desc); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -546,6 +654,18 @@ class _$CreateApp implements CreateApp { return createApp(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(Collapse value)? collapse, + TResult Function(OpenPage value)? openPage, + TResult Function(CreateApp value)? createApp, + TResult Function(ReceiveApps value)? didReceiveApps, + }) { + return createApp?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -649,6 +769,19 @@ class _$ReceiveApps implements ReceiveApps { return didReceiveApps(appsOrFail); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? collapse, + TResult Function(HomeStackContext context)? openPage, + TResult Function(String name, String? desc)? createApp, + TResult Function(Either, WorkspaceError> appsOrFail)? + didReceiveApps, + }) { + return didReceiveApps?.call(appsOrFail); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -678,6 +811,18 @@ class _$ReceiveApps implements ReceiveApps { return didReceiveApps(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(Collapse value)? collapse, + TResult Function(OpenPage value)? openPage, + TResult Function(CreateApp value)? createApp, + TResult Function(ReceiveApps value)? didReceiveApps, + }) { + return didReceiveApps?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart b/app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart index d9d2bf0aec..6c1adb6e79 100644 --- a/app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -37,6 +38,12 @@ mixin _$MenuUserEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? fetchWorkspaces, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? initial, TResult Function()? fetchWorkspaces, @@ -50,6 +57,12 @@ mixin _$MenuUserEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(_FetchWorkspaces value)? fetchWorkspaces, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(_Initial value)? initial, TResult Function(_FetchWorkspaces value)? fetchWorkspaces, @@ -118,6 +131,15 @@ class _$_Initial implements _Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? fetchWorkspaces, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -140,6 +162,15 @@ class _$_Initial implements _Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(_FetchWorkspaces value)? fetchWorkspaces, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -204,6 +235,15 @@ class _$_FetchWorkspaces implements _FetchWorkspaces { return fetchWorkspaces(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function()? fetchWorkspaces, + }) { + return fetchWorkspaces?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -226,6 +266,15 @@ class _$_FetchWorkspaces implements _FetchWorkspaces { return fetchWorkspaces(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(_Initial value)? initial, + TResult Function(_FetchWorkspaces value)? fetchWorkspaces, + }) { + return fetchWorkspaces?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/trash/trash_bloc.freezed.dart b/app_flowy/lib/workspace/application/trash/trash_bloc.freezed.dart index e346250ba1..aa26cda8b8 100644 --- a/app_flowy/lib/workspace/application/trash/trash_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/trash/trash_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -63,6 +64,16 @@ mixin _$TrashEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(List trash)? didReceiveTrash, + TResult Function(String trashId)? putback, + TResult Function(Trash trash)? delete, + TResult Function()? restoreAll, + TResult Function()? deleteAll, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? initial, TResult Function(List trash)? didReceiveTrash, @@ -84,6 +95,16 @@ mixin _$TrashEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(ReceiveTrash value)? didReceiveTrash, + TResult Function(Putback value)? putback, + TResult Function(Delete value)? delete, + TResult Function(RestoreAll value)? restoreAll, + TResult Function(DeleteAll value)? deleteAll, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(Initial value)? initial, TResult Function(ReceiveTrash value)? didReceiveTrash, @@ -159,6 +180,19 @@ class _$Initial implements Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(List trash)? didReceiveTrash, + TResult Function(String trashId)? putback, + TResult Function(Trash trash)? delete, + TResult Function()? restoreAll, + TResult Function()? deleteAll, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -189,6 +223,19 @@ class _$Initial implements Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(ReceiveTrash value)? didReceiveTrash, + TResult Function(Putback value)? putback, + TResult Function(Delete value)? delete, + TResult Function(RestoreAll value)? restoreAll, + TResult Function(DeleteAll value)? deleteAll, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -285,6 +332,19 @@ class _$ReceiveTrash implements ReceiveTrash { return didReceiveTrash(trash); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(List trash)? didReceiveTrash, + TResult Function(String trashId)? putback, + TResult Function(Trash trash)? delete, + TResult Function()? restoreAll, + TResult Function()? deleteAll, + }) { + return didReceiveTrash?.call(trash); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -315,6 +375,19 @@ class _$ReceiveTrash implements ReceiveTrash { return didReceiveTrash(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(ReceiveTrash value)? didReceiveTrash, + TResult Function(Putback value)? putback, + TResult Function(Delete value)? delete, + TResult Function(RestoreAll value)? restoreAll, + TResult Function(DeleteAll value)? deleteAll, + }) { + return didReceiveTrash?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -414,6 +487,19 @@ class _$Putback implements Putback { return putback(trashId); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(List trash)? didReceiveTrash, + TResult Function(String trashId)? putback, + TResult Function(Trash trash)? delete, + TResult Function()? restoreAll, + TResult Function()? deleteAll, + }) { + return putback?.call(trashId); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -444,6 +530,19 @@ class _$Putback implements Putback { return putback(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(ReceiveTrash value)? didReceiveTrash, + TResult Function(Putback value)? putback, + TResult Function(Delete value)? delete, + TResult Function(RestoreAll value)? restoreAll, + TResult Function(DeleteAll value)? deleteAll, + }) { + return putback?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -542,6 +641,19 @@ class _$Delete implements Delete { return delete(trash); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(List trash)? didReceiveTrash, + TResult Function(String trashId)? putback, + TResult Function(Trash trash)? delete, + TResult Function()? restoreAll, + TResult Function()? deleteAll, + }) { + return delete?.call(trash); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -572,6 +684,19 @@ class _$Delete implements Delete { return delete(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(ReceiveTrash value)? didReceiveTrash, + TResult Function(Putback value)? putback, + TResult Function(Delete value)? delete, + TResult Function(RestoreAll value)? restoreAll, + TResult Function(DeleteAll value)? deleteAll, + }) { + return delete?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -646,6 +771,19 @@ class _$RestoreAll implements RestoreAll { return restoreAll(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(List trash)? didReceiveTrash, + TResult Function(String trashId)? putback, + TResult Function(Trash trash)? delete, + TResult Function()? restoreAll, + TResult Function()? deleteAll, + }) { + return restoreAll?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -676,6 +814,19 @@ class _$RestoreAll implements RestoreAll { return restoreAll(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(ReceiveTrash value)? didReceiveTrash, + TResult Function(Putback value)? putback, + TResult Function(Delete value)? delete, + TResult Function(RestoreAll value)? restoreAll, + TResult Function(DeleteAll value)? deleteAll, + }) { + return restoreAll?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -745,6 +896,19 @@ class _$DeleteAll implements DeleteAll { return deleteAll(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(List trash)? didReceiveTrash, + TResult Function(String trashId)? putback, + TResult Function(Trash trash)? delete, + TResult Function()? restoreAll, + TResult Function()? deleteAll, + }) { + return deleteAll?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -775,6 +939,19 @@ class _$DeleteAll implements DeleteAll { return deleteAll(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(ReceiveTrash value)? didReceiveTrash, + TResult Function(Putback value)? putback, + TResult Function(Delete value)? delete, + TResult Function(RestoreAll value)? restoreAll, + TResult Function(DeleteAll value)? deleteAll, + }) { + return deleteAll?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart b/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart index cfa257d1ee..2a25991d3c 100644 --- a/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/view/view_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -64,6 +65,16 @@ mixin _$ViewEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? initial, TResult Function(bool isEditing)? setIsEditing, @@ -85,6 +96,16 @@ mixin _$ViewEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(Initial value)? initial, TResult Function(SetEditing value)? setIsEditing, @@ -160,6 +181,19 @@ class _$Initial implements Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -190,6 +224,19 @@ class _$Initial implements Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -287,6 +334,19 @@ class _$SetEditing implements SetEditing { return setIsEditing(isEditing); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + }) { + return setIsEditing?.call(isEditing); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -317,6 +377,19 @@ class _$SetEditing implements SetEditing { return setIsEditing(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + }) { + return setIsEditing?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -417,6 +490,19 @@ class _$Rename implements Rename { return rename(newName); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + }) { + return rename?.call(newName); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -447,6 +533,19 @@ class _$Rename implements Rename { return rename(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + }) { + return rename?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -521,6 +620,19 @@ class _$Delete implements Delete { return delete(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + }) { + return delete?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -551,6 +663,19 @@ class _$Delete implements Delete { return delete(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + }) { + return delete?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -621,6 +746,19 @@ class _$Duplicate implements Duplicate { return duplicate(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + }) { + return duplicate?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -651,6 +789,19 @@ class _$Duplicate implements Duplicate { return duplicate(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + }) { + return duplicate?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -748,6 +899,19 @@ class _$ViewDidUpdate implements ViewDidUpdate { return viewDidUpdate(result); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(bool isEditing)? setIsEditing, + TResult Function(String newName)? rename, + TResult Function()? delete, + TResult Function()? duplicate, + TResult Function(Either result)? viewDidUpdate, + }) { + return viewDidUpdate?.call(result); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -778,6 +942,19 @@ class _$ViewDidUpdate implements ViewDidUpdate { return viewDidUpdate(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(SetEditing value)? setIsEditing, + TResult Function(Rename value)? rename, + TResult Function(Delete value)? delete, + TResult Function(Duplicate value)? duplicate, + TResult Function(ViewDidUpdate value)? viewDidUpdate, + }) { + return viewDidUpdate?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/application/workspace/welcome_bloc.freezed.dart b/app_flowy/lib/workspace/application/workspace/welcome_bloc.freezed.dart index 4a22133d0f..d0b7ebac29 100644 --- a/app_flowy/lib/workspace/application/workspace/welcome_bloc.freezed.dart +++ b/app_flowy/lib/workspace/application/workspace/welcome_bloc.freezed.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target @@ -57,6 +58,15 @@ mixin _$WelcomeEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc)? createWorkspace, + TResult Function(Workspace workspace)? openWorkspace, + TResult Function(Either, WorkspaceError> workspacesOrFail)? + workspacesReveived, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeWhen({ TResult Function()? initial, TResult Function(String name, String desc)? createWorkspace, @@ -75,6 +85,14 @@ mixin _$WelcomeEvent { }) => throw _privateConstructorUsedError; @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateWorkspace value)? createWorkspace, + TResult Function(OpenWorkspace value)? openWorkspace, + TResult Function(WorkspacesReceived value)? workspacesReveived, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs TResult maybeMap({ TResult Function(Initial value)? initial, TResult Function(CreateWorkspace value)? createWorkspace, @@ -148,6 +166,18 @@ class _$Initial implements Initial { return initial(); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc)? createWorkspace, + TResult Function(Workspace workspace)? openWorkspace, + TResult Function(Either, WorkspaceError> workspacesOrFail)? + workspacesReveived, + }) { + return initial?.call(); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -175,6 +205,17 @@ class _$Initial implements Initial { return initial(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateWorkspace value)? createWorkspace, + TResult Function(OpenWorkspace value)? openWorkspace, + TResult Function(WorkspacesReceived value)? workspacesReveived, + }) { + return initial?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -281,6 +322,18 @@ class _$CreateWorkspace implements CreateWorkspace { return createWorkspace(name, desc); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc)? createWorkspace, + TResult Function(Workspace workspace)? openWorkspace, + TResult Function(Either, WorkspaceError> workspacesOrFail)? + workspacesReveived, + }) { + return createWorkspace?.call(name, desc); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -308,6 +361,17 @@ class _$CreateWorkspace implements CreateWorkspace { return createWorkspace(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateWorkspace value)? createWorkspace, + TResult Function(OpenWorkspace value)? openWorkspace, + TResult Function(WorkspacesReceived value)? workspacesReveived, + }) { + return createWorkspace?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -409,6 +473,18 @@ class _$OpenWorkspace implements OpenWorkspace { return openWorkspace(workspace); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc)? createWorkspace, + TResult Function(Workspace workspace)? openWorkspace, + TResult Function(Either, WorkspaceError> workspacesOrFail)? + workspacesReveived, + }) { + return openWorkspace?.call(workspace); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -436,6 +512,17 @@ class _$OpenWorkspace implements OpenWorkspace { return openWorkspace(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateWorkspace value)? createWorkspace, + TResult Function(OpenWorkspace value)? openWorkspace, + TResult Function(WorkspacesReceived value)? workspacesReveived, + }) { + return openWorkspace?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ @@ -538,6 +625,18 @@ class _$WorkspacesReceived implements WorkspacesReceived { return workspacesReveived(workspacesOrFail); } + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult Function()? initial, + TResult Function(String name, String desc)? createWorkspace, + TResult Function(Workspace workspace)? openWorkspace, + TResult Function(Either, WorkspaceError> workspacesOrFail)? + workspacesReveived, + }) { + return workspacesReveived?.call(workspacesOrFail); + } + @override @optionalTypeArgs TResult maybeWhen({ @@ -565,6 +664,17 @@ class _$WorkspacesReceived implements WorkspacesReceived { return workspacesReveived(this); } + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult Function(Initial value)? initial, + TResult Function(CreateWorkspace value)? createWorkspace, + TResult Function(OpenWorkspace value)? openWorkspace, + TResult Function(WorkspacesReceived value)? workspacesReveived, + }) { + return workspacesReveived?.call(this); + } + @override @optionalTypeArgs TResult maybeMap({ diff --git a/app_flowy/lib/workspace/domain/i_share.dart b/app_flowy/lib/workspace/domain/i_share.dart new file mode 100644 index 0000000000..8fa89111ce --- /dev/null +++ b/app_flowy/lib/workspace/domain/i_share.dart @@ -0,0 +1,12 @@ +import 'dart:async'; +import 'package:dartz/dartz.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace-infra/protobuf.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; + +abstract class IShare { + Future> exportText(String docId); + + Future> exportMarkdown(String docId); + + Future> exportURL(String docId); +} diff --git a/app_flowy/lib/workspace/domain/page_stack/page_stack.dart b/app_flowy/lib/workspace/domain/page_stack/page_stack.dart index 1534e86a4f..fa328f44ed 100644 --- a/app_flowy/lib/workspace/domain/page_stack/page_stack.dart +++ b/app_flowy/lib/workspace/domain/page_stack/page_stack.dart @@ -9,7 +9,8 @@ import 'package:app_flowy/workspace/presentation/widgets/prelude.dart'; typedef NavigationCallback = void Function(String id); abstract class NavigationItem { - Widget get naviTitle; + Widget get leftBarItem; + Widget? get rightBarItem => null; String get identifier; NavigationCallback get action => (id) { @@ -29,9 +30,10 @@ abstract class HomeStackContext with NavigationItem { List get navigationItems; @override - Widget get naviTitle; + Widget get leftBarItem; - Widget? Function(BuildContext context) get buildNaviAction; + @override + Widget? get rightBarItem; @override String get identifier; @@ -49,7 +51,7 @@ class HomeStackNotifier extends ChangeNotifier { HomeStackContext stackContext; PublishNotifier collapsedNotifier = PublishNotifier(); - Widget get titleWidget => stackContext.naviTitle; + Widget get titleWidget => stackContext.leftBarItem; HomeStackNotifier({HomeStackContext? context}) : stackContext = context ?? BlankStackContext(); @@ -71,7 +73,7 @@ class HomeStackManager { HomeStackManager(); Widget title() { - return _notifier.context.naviTitle; + return _notifier.context.leftBarItem; } PublishNotifier get collapsedNotifier => _notifier.collapsedNotifier; diff --git a/app_flowy/lib/workspace/infrastructure/deps_resolver.dart b/app_flowy/lib/workspace/infrastructure/deps_resolver.dart index 4ed7e121c8..412022d8f1 100644 --- a/app_flowy/lib/workspace/infrastructure/deps_resolver.dart +++ b/app_flowy/lib/workspace/infrastructure/deps_resolver.dart @@ -1,11 +1,13 @@ import 'package:app_flowy/workspace/application/app/app_bloc.dart'; import 'package:app_flowy/workspace/application/doc/doc_bloc.dart'; +import 'package:app_flowy/workspace/application/doc/share_bloc.dart'; import 'package:app_flowy/workspace/application/menu/menu_bloc.dart'; import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart'; import 'package:app_flowy/workspace/application/trash/trash_bloc.dart'; import 'package:app_flowy/workspace/application/view/view_bloc.dart'; import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart'; import 'package:app_flowy/workspace/domain/i_doc.dart'; +import 'package:app_flowy/workspace/domain/i_share.dart'; import 'package:app_flowy/workspace/domain/i_trash.dart'; import 'package:app_flowy/workspace/domain/i_view.dart'; import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; @@ -23,8 +25,10 @@ import 'package:flowy_sdk/protobuf/flowy-workspace-infra/app_create.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-workspace-infra/view_create.pb.dart'; import 'package:get_it/get_it.dart'; +import 'i_share_impl.dart'; import 'i_user_impl.dart'; import 'i_view_impl.dart'; +import 'repos/share_repo.dart'; class HomeDepsResolver { static Future resolve(GetIt getIt) async { @@ -102,5 +106,11 @@ class HomeDepsResolver { getIt.registerFactory(() => ITrashImpl(repo: getIt())); getIt.registerFactory(() => ITrashListenerImpl(repo: getIt())); getIt.registerFactory(() => TrashBloc(trasnManager: getIt(), listener: getIt())); + + // share + getIt.registerLazySingleton(() => ShareRepo()); + getIt.registerFactory(() => IShareImpl(repo: getIt())); + getIt.registerFactoryParam( + (view, _) => DocShareBloc(view: view, shareManager: getIt())); } } diff --git a/app_flowy/lib/workspace/infrastructure/i_share_impl.dart b/app_flowy/lib/workspace/infrastructure/i_share_impl.dart new file mode 100644 index 0000000000..05ef1f5c2c --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/i_share_impl.dart @@ -0,0 +1,27 @@ +import 'package:app_flowy/workspace/domain/i_share.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace-infra/protobuf.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; +import 'package:dartz/dartz.dart'; + +import 'repos/share_repo.dart'; + +class IShareImpl extends IShare { + ShareRepo repo; + + IShareImpl({required this.repo}); + + @override + Future> exportText(String docId) { + return repo.export(docId, ExportType.Text); + } + + @override + Future> exportMarkdown(String docId) { + return repo.export(docId, ExportType.Markdown); + } + + @override + Future> exportURL(String docId) { + return repo.export(docId, ExportType.Link); + } +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/delta_markdown.dart b/app_flowy/lib/workspace/infrastructure/markdown/delta_markdown.dart new file mode 100644 index 0000000000..ef723f2697 --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/delta_markdown.dart @@ -0,0 +1,30 @@ +library delta_markdown; + +import 'dart:convert'; + +import 'src/delta_markdown_decoder.dart'; +import 'src/delta_markdown_encoder.dart'; +import 'src/version.dart'; + +const version = packageVersion; + +/// Codec used to convert between Markdown and Quill deltas. +const DeltaMarkdownCodec _kCodec = DeltaMarkdownCodec(); + +String markdownToDelta(String markdown) { + return _kCodec.decode(markdown); +} + +String deltaToMarkdown(String delta) { + return _kCodec.encode(delta); +} + +class DeltaMarkdownCodec extends Codec { + const DeltaMarkdownCodec(); + + @override + Converter get decoder => DeltaMarkdownDecoder(); + + @override + Converter get encoder => DeltaMarkdownEncoder(); +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/ast.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/ast.dart new file mode 100644 index 0000000000..5356f1d05f --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/ast.dart @@ -0,0 +1,113 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +typedef Resolver = Node? Function(String name, [String? title]); + +/// Base class for any AST item. +/// +/// Roughly corresponds to Node in the DOM. Will be either an Element or Text. +class Node { + void accept(NodeVisitor visitor) {} + + bool isToplevel = false; + + String? get textContent { + return null; + } +} + +/// A named tag that can contain other nodes. +class Element extends Node { + /// Instantiates a [tag] Element with [children]. + Element(this.tag, this.children) : attributes = {}; + + /// Instantiates an empty, self-closing [tag] Element. + Element.empty(this.tag) + : children = null, + attributes = {}; + + /// Instantiates a [tag] Element with no [children]. + Element.withTag(this.tag) + : children = [], + attributes = {}; + + /// Instantiates a [tag] Element with a single Text child. + Element.text(this.tag, String text) + : children = [Text(text)], + attributes = {}; + + final String tag; + final List? children; + final Map attributes; + String? generatedId; + + /// Whether this element is self-closing. + bool get isEmpty => children == null; + + @override + void accept(NodeVisitor visitor) { + if (visitor.visitElementBefore(this)) { + if (children != null) { + for (final child in children!) { + child.accept(visitor); + } + } + visitor.visitElementAfter(this); + } + } + + @override + String get textContent => children == null + ? '' + : children!.map((child) => child.textContent).join(); +} + +/// A plain text element. +class Text extends Node { + Text(this.text); + + final String text; + + @override + void accept(NodeVisitor visitor) => visitor.visitText(this); + + @override + String get textContent => text; +} + +/// Inline content that has not been parsed into inline nodes (strong, links, +/// etc). +/// +/// These placeholder nodes should only remain in place while the block nodes +/// of a document are still being parsed, in order to gather all reference link +/// definitions. +class UnparsedContent extends Node { + UnparsedContent(this.textContent); + + @override + final String textContent; + + @override + void accept(NodeVisitor visitor); +} + +/// Visitor pattern for the AST. +/// +/// Renderers or other AST transformers should implement this. +abstract class NodeVisitor { + /// Called when a Text node has been reached. + void visitText(Text text); + + /// Called when an Element has been reached, before its children have been + /// visited. + /// + /// Returns `false` to skip its children. + bool visitElementBefore(Element element); + + /// Called when an Element has been reached, after its children have been + /// visited. + /// + /// Will not be called if [visitElementBefore] returns `false`. + void visitElementAfter(Element element); +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/block_parser.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/block_parser.dart new file mode 100644 index 0000000000..faac444b98 --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/block_parser.dart @@ -0,0 +1,1096 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'ast.dart'; +import 'document.dart'; +import 'util.dart'; + +/// The line contains only whitespace or is empty. +final _emptyPattern = RegExp(r'^(?:[ \t]*)$'); + +/// A series of `=` or `-` (on the next line) define setext-style headers. +final _setextPattern = RegExp(r'^[ ]{0,3}(=+|-+)\s*$'); + +/// Leading (and trailing) `#` define atx-style headers. +/// +/// Starts with 1-6 unescaped `#` characters which must not be followed by a +/// non-space character. Line may end with any number of `#` characters,. +final _headerPattern = RegExp(r'^ {0,3}(#{1,6})[ \x09\x0b\x0c](.*?)#*$'); + +/// The line starts with `>` with one optional space after. +final _blockquotePattern = RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); + +/// A line indented four spaces. Used for code blocks and lists. +final _indentPattern = RegExp(r'^(?: | {0,3}\t)(.*)$'); + +/// Fenced code block. +final _codePattern = RegExp(r'^[ ]{0,3}(`{3,}|~{3,})(.*)$'); + +/// Three or more hyphens, asterisks or underscores by themselves. Note that +/// a line like `----` is valid as both HR and SETEXT. In case of a tie, +/// SETEXT should win. +final _hrPattern = RegExp(r'^ {0,3}([-*_])[ \t]*\1[ \t]*\1(?:\1|[ \t])*$'); + +/// One or more whitespace, for compressing. +final _oneOrMoreWhitespacePattern = RegExp('[ \n\r\t]+'); + +/// A line starting with one of these markers: `-`, `*`, `+`. May have up to +/// three leading spaces before the marker and any number of spaces or tabs +/// after. +/// +/// Contains a dummy group at [2], so that the groups in [_ulPattern] and +/// [_olPattern] match up; in both, [2] is the length of the number that begins +/// the list marker. +final _ulPattern = RegExp(r'^([ ]{0,3})()([*+-])(([ \t])([ \t]*)(.*))?$'); + +/// A line starting with a number like `123.`. May have up to three leading +/// spaces before the marker and any number of spaces or tabs after. +final _olPattern = + RegExp(r'^([ ]{0,3})(\d{1,9})([\.)])(([ \t])([ \t]*)(.*))?$'); + +/// A line of hyphens separated by at least one pipe. +final _tablePattern = RegExp(r'^[ ]{0,3}\|?( *:?\-+:? *\|)+( *:?\-+:? *)?$'); + +/// Maintains the internal state needed to parse a series of lines into blocks +/// of Markdown suitable for further inline parsing. +class BlockParser { + BlockParser(this.lines, this.document) { + blockSyntaxes + ..addAll(document.blockSyntaxes) + ..addAll(standardBlockSyntaxes); + } + + final List lines; + + /// The Markdown document this parser is parsing. + final Document document; + + /// The enabled block syntaxes. + /// + /// To turn a series of lines into blocks, each of these will be tried in + /// turn. Order matters here. + final List blockSyntaxes = []; + + /// Index of the current line. + int _pos = 0; + + /// Whether the parser has encountered a blank line between two block-level + /// elements. + bool encounteredBlankLine = false; + + /// The collection of built-in block parsers. + final List standardBlockSyntaxes = [ + const EmptyBlockSyntax(), + const BlockTagBlockHtmlSyntax(), + LongBlockHtmlSyntax(r'^ {0,3}|$)', ''), + LongBlockHtmlSyntax(r'^ {0,3}|$)', ''), + LongBlockHtmlSyntax(r'^ {0,3}|$)', ''), + LongBlockHtmlSyntax('^ {0,3}'), + LongBlockHtmlSyntax('^ {0,3}<\\?', '\\?>'), + LongBlockHtmlSyntax('^ {0,3}'), + LongBlockHtmlSyntax('^ {0,3}'), + const OtherTagBlockHtmlSyntax(), + const SetextHeaderSyntax(), + const HeaderSyntax(), + const CodeBlockSyntax(), + const BlockquoteSyntax(), + const HorizontalRuleSyntax(), + const UnorderedListSyntax(), + const OrderedListSyntax(), + const ParagraphSyntax() + ]; + + /// Gets the current line. + String get current => lines[_pos]; + + /// Gets the line after the current one or `null` if there is none. + String? get next { + // Don't read past the end. + if (_pos >= lines.length - 1) { + return null; + } + return lines[_pos + 1]; + } + + /// Gets the line that is [linesAhead] lines ahead of the current one, or + /// `null` if there is none. + /// + /// `peek(0)` is equivalent to [current]. + /// + /// `peek(1)` is equivalent to [next]. + String? peek(int linesAhead) { + if (linesAhead < 0) { + throw ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.'); + } + // Don't read past the end. + if (_pos >= lines.length - linesAhead) { + return null; + } + return lines[_pos + linesAhead]; + } + + void advance() { + _pos++; + } + + bool get isDone => _pos >= lines.length; + + /// Gets whether or not the current line matches the given pattern. + bool matches(RegExp regex) { + if (isDone) { + return false; + } + return regex.firstMatch(current) != null; + } + + /// Gets whether or not the next line matches the given pattern. + bool matchesNext(RegExp regex) { + if (next == null) { + return false; + } + return regex.firstMatch(next!) != null; + } + + List parseLines() { + final blocks = []; + while (!isDone) { + for (final syntax in blockSyntaxes) { + if (syntax.canParse(this)) { + final block = syntax.parse(this); + if (block != null) { + blocks.add(block); + } + break; + } + } + } + + return blocks; + } +} + +abstract class BlockSyntax { + const BlockSyntax(); + + /// Gets the regex used to identify the beginning of this block, if any. + RegExp? get pattern => null; + + bool get canEndBlock => true; + + bool canParse(BlockParser parser) { + return pattern!.firstMatch(parser.current) != null; + } + + Node? parse(BlockParser parser); + + List parseChildLines(BlockParser parser) { + // Grab all of the lines that form the block element. + final childLines = []; + + while (!parser.isDone) { + final match = pattern!.firstMatch(parser.current); + if (match == null) { + break; + } + childLines.add(match[1]); + parser.advance(); + } + + return childLines; + } + + /// Gets whether or not [parser]'s current line should end the previous block. + static bool isAtBlockEnd(BlockParser parser) { + if (parser.isDone) { + return true; + } + return parser.blockSyntaxes.any((s) => s.canParse(parser) && s.canEndBlock); + } + + /// Generates a valid HTML anchor from the inner text of [element]. + static String generateAnchorHash(Element element) => + element.children!.first.textContent! + .toLowerCase() + .trim() + .replaceAll(RegExp(r'[^a-z0-9 _-]'), '') + .replaceAll(RegExp(r'\s'), '-'); +} + +class EmptyBlockSyntax extends BlockSyntax { + const EmptyBlockSyntax(); + + @override + RegExp get pattern => _emptyPattern; + + @override + Node? parse(BlockParser parser) { + parser + ..encounteredBlankLine = true + ..advance(); + + // Don't actually emit anything. + return null; + } +} + +/// Parses setext-style headers. +class SetextHeaderSyntax extends BlockSyntax { + const SetextHeaderSyntax(); + + @override + bool canParse(BlockParser parser) { + if (!_interperableAsParagraph(parser.current)) { + return false; + } + + var i = 1; + while (true) { + final nextLine = parser.peek(i); + if (nextLine == null) { + // We never reached an underline. + return false; + } + if (_setextPattern.hasMatch(nextLine)) { + return true; + } + // Ensure that we're still in something like paragraph text. + if (!_interperableAsParagraph(nextLine)) { + return false; + } + i++; + } + } + + @override + Node parse(BlockParser parser) { + final lines = []; + late String tag; + while (!parser.isDone) { + final match = _setextPattern.firstMatch(parser.current); + if (match == null) { + // More text. + lines.add(parser.current); + parser.advance(); + continue; + } else { + // The underline. + tag = (match[1]![0] == '=') ? 'h1' : 'h2'; + parser.advance(); + break; + } + } + + final contents = UnparsedContent(lines.join('\n')); + + return Element(tag, [contents]); + } + + bool _interperableAsParagraph(String line) => + !(_indentPattern.hasMatch(line) || + _codePattern.hasMatch(line) || + _headerPattern.hasMatch(line) || + _blockquotePattern.hasMatch(line) || + _hrPattern.hasMatch(line) || + _ulPattern.hasMatch(line) || + _olPattern.hasMatch(line) || + _emptyPattern.hasMatch(line)); +} + +/// Parses setext-style headers, and adds generated IDs to the generated +/// elements. +class SetextHeaderWithIdSyntax extends SetextHeaderSyntax { + const SetextHeaderWithIdSyntax(); + + @override + Node parse(BlockParser parser) { + final element = super.parse(parser) as Element; + element.generatedId = BlockSyntax.generateAnchorHash(element); + return element; + } +} + +/// Parses atx-style headers: `## Header ##`. +class HeaderSyntax extends BlockSyntax { + const HeaderSyntax(); + + @override + RegExp get pattern => _headerPattern; + + @override + Node parse(BlockParser parser) { + final match = pattern.firstMatch(parser.current)!; + parser.advance(); + final level = match[1]!.length; + final contents = UnparsedContent(match[2]!.trim()); + return Element('h$level', [contents]); + } +} + +/// Parses atx-style headers, and adds generated IDs to the generated elements. +class HeaderWithIdSyntax extends HeaderSyntax { + const HeaderWithIdSyntax(); + + @override + Node parse(BlockParser parser) { + final element = super.parse(parser) as Element; + element.generatedId = BlockSyntax.generateAnchorHash(element); + return element; + } +} + +/// Parses email-style blockquotes: `> quote`. +class BlockquoteSyntax extends BlockSyntax { + const BlockquoteSyntax(); + + @override + RegExp get pattern => _blockquotePattern; + + @override + List parseChildLines(BlockParser parser) { + // Grab all of the lines that form the blockquote, stripping off the ">". + final childLines = []; + + while (!parser.isDone) { + final match = pattern.firstMatch(parser.current); + if (match != null) { + childLines.add(match[1]!); + parser.advance(); + continue; + } + + // A paragraph continuation is OK. This is content that cannot be parsed + // as any other syntax except Paragraph, and it doesn't match the bar in + // a Setext header. + if (parser.blockSyntaxes.firstWhere((s) => s.canParse(parser)) + is ParagraphSyntax) { + childLines.add(parser.current); + parser.advance(); + } else { + break; + } + } + + return childLines; + } + + @override + Node parse(BlockParser parser) { + final childLines = parseChildLines(parser); + + // Recursively parse the contents of the blockquote. + final children = BlockParser(childLines, parser.document).parseLines(); + return Element('blockquote', children); + } +} + +/// Parses preformatted code blocks that are indented four spaces. +class CodeBlockSyntax extends BlockSyntax { + const CodeBlockSyntax(); + + @override + RegExp get pattern => _indentPattern; + + @override + bool get canEndBlock => false; + + @override + List parseChildLines(BlockParser parser) { + final childLines = []; + + while (!parser.isDone) { + final match = pattern.firstMatch(parser.current); + if (match != null) { + childLines.add(match[1]); + parser.advance(); + } else { + // If there's a codeblock, then a newline, then a codeblock, keep the + // code blocks together. + final nextMatch = + parser.next != null ? pattern.firstMatch(parser.next!) : null; + if (parser.current.trim() == '' && nextMatch != null) { + childLines..add('')..add(nextMatch[1]); + parser..advance()..advance(); + } else { + break; + } + } + } + return childLines; + } + + @override + Node parse(BlockParser parser) { + final childLines = parseChildLines(parser) + // The Markdown tests expect a trailing newline. + ..add(''); + + // Escape the code. + final escaped = escapeHtml(childLines.join('\n')); + + return Element('pre', [Element.text('code', escaped)]); + } +} + +/// Parses preformatted code blocks between two ~~~ or ``` sequences. +/// +/// See [Pandoc's documentation](http://pandoc.org/README.html#fenced-code-blocks). +class FencedCodeBlockSyntax extends BlockSyntax { + const FencedCodeBlockSyntax(); + + @override + RegExp get pattern => _codePattern; + + @override + List parseChildLines(BlockParser parser, [String? endBlock]) { + endBlock ??= ''; + + final childLines = []; + parser.advance(); + + while (!parser.isDone) { + final match = pattern.firstMatch(parser.current); + if (match == null || !match[1]!.startsWith(endBlock)) { + childLines.add(parser.current); + parser.advance(); + } else { + parser.advance(); + break; + } + } + + return childLines; + } + + @override + Node parse(BlockParser parser) { + // Get the syntax identifier, if there is one. + final match = pattern.firstMatch(parser.current)!; + final endBlock = match.group(1); + var infoString = match.group(2)!; + + final childLines = parseChildLines(parser, endBlock) + // The Markdown tests expect a trailing newline. + ..add(''); + + final code = Element.text('code', childLines.join('\n')); + + // the info-string should be trimmed + // http://spec.commonmark.org/0.22/#example-100 + infoString = infoString.trim(); + if (infoString.isNotEmpty) { + // only use the first word in the syntax + // http://spec.commonmark.org/0.22/#example-100 + infoString = infoString.split(' ').first; + code.attributes['class'] = 'language-$infoString'; + } + + final element = Element('pre', [code]); + return element; + } +} + +/// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. +class HorizontalRuleSyntax extends BlockSyntax { + const HorizontalRuleSyntax(); + + @override + RegExp get pattern => _hrPattern; + + @override + Node parse(BlockParser parser) { + parser.advance(); + return Element.empty('hr'); + } +} + +/// Parses inline HTML at the block level. This differs from other Markdown +/// implementations in several ways: +/// +/// 1. This one is way way WAY simpler. +/// 2. Essentially no HTML parsing or validation is done. We're a Markdown +/// parser, not an HTML parser! +abstract class BlockHtmlSyntax extends BlockSyntax { + const BlockHtmlSyntax(); + + @override + bool get canEndBlock => true; +} + +class BlockTagBlockHtmlSyntax extends BlockHtmlSyntax { + const BlockTagBlockHtmlSyntax(); + + static final _pattern = RegExp( + r'^ {0,3}|/>|$)'); + + @override + RegExp get pattern => _pattern; + + @override + Node parse(BlockParser parser) { + final childLines = []; + + // Eat until we hit a blank line. + while (!parser.isDone && !parser.matches(_emptyPattern)) { + childLines.add(parser.current); + parser.advance(); + } + + return Text(childLines.join('\n')); + } +} + +class OtherTagBlockHtmlSyntax extends BlockTagBlockHtmlSyntax { + const OtherTagBlockHtmlSyntax(); + + @override + bool get canEndBlock => false; + + // Really hacky way to detect "other" HTML. This matches: + // + // * any opening spaces + // * open bracket and maybe a slash ("<" or " RegExp(r'^ {0,3}|\s+[^>]*>)\s*$'); +} + +/// A BlockHtmlSyntax that has a specific `endPattern`. +/// +/// In practice this means that the syntax dominates; it is allowed to eat +/// many lines, including blank lines, before matching its `endPattern`. +class LongBlockHtmlSyntax extends BlockHtmlSyntax { + LongBlockHtmlSyntax(String pattern, String endPattern) + : pattern = RegExp(pattern), + _endPattern = RegExp(endPattern); + + @override + final RegExp pattern; + final RegExp _endPattern; + + @override + Node parse(BlockParser parser) { + final childLines = []; + // Eat until we hit [endPattern]. + while (!parser.isDone) { + childLines.add(parser.current); + if (parser.matches(_endPattern)) { + break; + } + parser.advance(); + } + + parser.advance(); + return Text(childLines.join('\n')); + } +} + +class ListItem { + ListItem(this.lines); + + bool forceBlock = false; + final List lines; +} + +/// Base class for both ordered and unordered lists. +abstract class ListSyntax extends BlockSyntax { + const ListSyntax(); + + @override + bool get canEndBlock => true; + + String get listTag; + + /// A list of patterns that can start a valid block within a list item. + static final blocksInList = [ + _blockquotePattern, + _headerPattern, + _hrPattern, + _indentPattern, + _ulPattern, + _olPattern + ]; + + static final _whitespaceRe = RegExp('[ \t]*'); + + @override + Node parse(BlockParser parser) { + final items = []; + var childLines = []; + + void endItem() { + if (childLines.isNotEmpty) { + items.add(ListItem(childLines)); + childLines = []; + } + } + + Match? match; + bool tryMatch(RegExp pattern) { + match = pattern.firstMatch(parser.current); + return match != null; + } + + String? listMarker; + String? indent; + // In case the first number in an ordered list is not 1, use it as the + // "start". + int? startNumber; + + while (!parser.isDone) { + final leadingSpace = + _whitespaceRe.matchAsPrefix(parser.current)!.group(0)!; + final leadingExpandedTabLength = _expandedTabLength(leadingSpace); + if (tryMatch(_emptyPattern)) { + if (_emptyPattern.firstMatch(parser.next ?? '') != null) { + // Two blank lines ends a list. + break; + } + // Add a blank line to the current list item. + childLines.add(''); + } else if (indent != null && indent.length <= leadingExpandedTabLength) { + // Strip off indent and add to current item. + final line = parser.current + .replaceFirst(leadingSpace, ' ' * leadingExpandedTabLength) + .replaceFirst(indent, ''); + childLines.add(line); + } else if (tryMatch(_hrPattern)) { + // Horizontal rule takes precedence to a list item. + break; + } else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) { + final precedingWhitespace = match![1]; + final digits = match![2] ?? ''; + if (startNumber == null && digits.isNotEmpty) { + startNumber = int.parse(digits); + } + final marker = match![3]; + final firstWhitespace = match![5] ?? ''; + final restWhitespace = match![6] ?? ''; + final content = match![7] ?? ''; + final isBlank = content.isEmpty; + if (listMarker != null && listMarker != marker) { + // Changing the bullet or ordered list delimiter starts a list. + break; + } + listMarker = marker; + final markerAsSpaces = ' ' * (digits.length + marker!.length); + if (isBlank) { + // See http://spec.commonmark.org/0.28/#list-items under "3. Item + // starting with a blank line." + // + // If the list item starts with a blank line, the final piece of the + // indentation is just a single space. + indent = '$precedingWhitespace$markerAsSpaces '; + } else if (restWhitespace.length >= 4) { + // See http://spec.commonmark.org/0.28/#list-items under "2. Item + // starting with indented code." + // + // If the list item starts with indented code, we need to _not_ count + // any indentation past the required whitespace character. + indent = precedingWhitespace! + markerAsSpaces + firstWhitespace; + } else { + indent = precedingWhitespace! + + markerAsSpaces + + firstWhitespace + + restWhitespace; + } + // End the current list item and start a one. + endItem(); + childLines.add(restWhitespace + content); + } else if (BlockSyntax.isAtBlockEnd(parser)) { + // Done with the list. + break; + } else { + // If the previous item is a blank line, this means we're done with the + // list and are starting a top-level paragraph. + if ((childLines.isNotEmpty) && (childLines.last == '')) { + parser.encounteredBlankLine = true; + break; + } + + // Anything else is paragraph continuation text. + childLines.add(parser.current); + } + parser.advance(); + } + + endItem(); + final itemNodes = []; + + items.forEach(removeLeadingEmptyLine); + final anyEmptyLines = removeTrailingEmptyLines(items); + var anyEmptyLinesBetweenBlocks = false; + + for (final item in items) { + final itemParser = BlockParser(item.lines, parser.document); + final children = itemParser.parseLines(); + itemNodes.add(Element('li', children)); + anyEmptyLinesBetweenBlocks = + anyEmptyLinesBetweenBlocks || itemParser.encounteredBlankLine; + } + + // Must strip paragraph tags if the list is "tight". + // http://spec.commonmark.org/0.28/#lists + final listIsTight = !anyEmptyLines && !anyEmptyLinesBetweenBlocks; + + if (listIsTight) { + // We must post-process the list items, converting any top-level paragraph + // elements to just text elements. + for (final item in itemNodes) { + for (var i = 0; i < item.children!.length; i++) { + final child = item.children![i]; + if (child is Element && child.tag == 'p') { + item.children!.removeAt(i); + item.children!.insertAll(i, child.children!); + } + } + } + } + + if (listTag == 'ol' && startNumber != 1) { + return Element(listTag, itemNodes)..attributes['start'] = '$startNumber'; + } else { + return Element(listTag, itemNodes); + } + } + + void removeLeadingEmptyLine(ListItem item) { + if (item.lines.isNotEmpty && _emptyPattern.hasMatch(item.lines.first)) { + item.lines.removeAt(0); + } + } + + /// Removes any trailing empty lines and notes whether any items are separated + /// by such lines. + bool removeTrailingEmptyLines(List items) { + var anyEmpty = false; + for (var i = 0; i < items.length; i++) { + if (items[i].lines.length == 1) { + continue; + } + while (items[i].lines.isNotEmpty && + _emptyPattern.hasMatch(items[i].lines.last)) { + if (i < items.length - 1) { + anyEmpty = true; + } + items[i].lines.removeLast(); + } + } + return anyEmpty; + } + + static int _expandedTabLength(String input) { + var length = 0; + for (final char in input.codeUnits) { + length += char == 0x9 ? 4 - (length % 4) : 1; + } + return length; + } +} + +/// Parses unordered lists. +class UnorderedListSyntax extends ListSyntax { + const UnorderedListSyntax(); + + @override + RegExp get pattern => _ulPattern; + + @override + String get listTag => 'ul'; +} + +/// Parses ordered lists. +class OrderedListSyntax extends ListSyntax { + const OrderedListSyntax(); + + @override + RegExp get pattern => _olPattern; + + @override + String get listTag => 'ol'; +} + +/// Parses tables. +class TableSyntax extends BlockSyntax { + const TableSyntax(); + + static final _pipePattern = RegExp(r'\s*\|\s*'); + static final _openingPipe = RegExp(r'^\|\s*'); + static final _closingPipe = RegExp(r'\s*\|$'); + + @override + bool get canEndBlock => false; + + @override + bool canParse(BlockParser parser) { + // Note: matches *next* line, not the current one. We're looking for the + // bar separating the head row from the body rows. + return parser.matchesNext(_tablePattern); + } + + /// Parses a table into its three parts: + /// + /// * a head row of head cells (`` cells) + /// * a divider of hyphens and pipes (not rendered) + /// * many body rows of body cells (`` cells) + @override + Node? parse(BlockParser parser) { + final alignments = parseAlignments(parser.next!); + final columnCount = alignments.length; + final headRow = parseRow(parser, alignments, 'th'); + if (headRow.children!.length != columnCount) { + return null; + } + final head = Element('thead', [headRow]); + + // Advance past the divider of hyphens. + parser.advance(); + + final rows = []; + while (!parser.isDone && !BlockSyntax.isAtBlockEnd(parser)) { + final row = parseRow(parser, alignments, 'td'); + while (row.children!.length < columnCount) { + // Insert synthetic empty cells. + row.children!.add(Element.empty('td')); + } + while (row.children!.length > columnCount) { + row.children!.removeLast(); + } + rows.add(row); + } + if (rows.isEmpty) { + return Element('table', [head]); + } else { + final body = Element('tbody', rows); + + return Element('table', [head, body]); + } + } + + List parseAlignments(String line) { + line = line.replaceFirst(_openingPipe, '').replaceFirst(_closingPipe, ''); + return line.split('|').map((column) { + column = column.trim(); + if (column.startsWith(':') && column.endsWith(':')) { + return 'center'; + } + if (column.startsWith(':')) { + return 'left'; + } + if (column.endsWith(':')) { + return 'right'; + } + return null; + }).toList(); + } + + Element parseRow( + BlockParser parser, List alignments, String cellType) { + final line = parser.current + .replaceFirst(_openingPipe, '') + .replaceFirst(_closingPipe, ''); + final cells = line.split(_pipePattern); + parser.advance(); + final row = []; + String? preCell; + + for (var cell in cells) { + if (preCell != null) { + cell = preCell + cell; + preCell = null; + } + if (cell.endsWith('\\')) { + preCell = '${cell.substring(0, cell.length - 1)}|'; + continue; + } + + final contents = UnparsedContent(cell); + row.add(Element(cellType, [contents])); + } + + for (var i = 0; i < row.length && i < alignments.length; i++) { + if (alignments[i] == null) { + continue; + } + row[i].attributes['style'] = 'text-align: ${alignments[i]};'; + } + + return Element('tr', row); + } +} + +/// Parses paragraphs of regular text. +class ParagraphSyntax extends BlockSyntax { + const ParagraphSyntax(); + + static final _reflinkDefinitionStart = RegExp(r'[ ]{0,3}\['); + + static final _whitespacePattern = RegExp(r'^\s*$'); + + @override + bool get canEndBlock => false; + + @override + bool canParse(BlockParser parser) => true; + + @override + Node parse(BlockParser parser) { + final childLines = []; + + // Eat until we hit something that ends a paragraph. + while (!BlockSyntax.isAtBlockEnd(parser)) { + childLines.add(parser.current); + parser.advance(); + } + + final paragraphLines = _extractReflinkDefinitions(parser, childLines); + if (paragraphLines == null) { + // Paragraph consisted solely of reference link definitions. + return Text(''); + } else { + final contents = UnparsedContent(paragraphLines.join('\n')); + return Element('p', [contents]); + } + } + + /// Extract reference link definitions from the front of the paragraph, and + /// return the remaining paragraph lines. + List? _extractReflinkDefinitions( + BlockParser parser, List lines) { + bool lineStartsReflinkDefinition(int i) => + lines[i].startsWith(_reflinkDefinitionStart); + + var i = 0; + loopOverDefinitions: + while (true) { + // Check for reflink definitions. + if (!lineStartsReflinkDefinition(i)) { + // It's paragraph content from here on out. + break; + } + var contents = lines[i]; + var j = i + 1; + while (j < lines.length) { + // Check to see if the _next_ line might start a reflink definition. + // Even if it turns out not to be, but it started with a '[', then it + // is not a part of _this_ possible reflink definition. + if (lineStartsReflinkDefinition(j)) { + // Try to parse [contents] as a reflink definition. + if (_parseReflinkDefinition(parser, contents)) { + // Loop again, starting at the next possible reflink definition. + i = j; + continue loopOverDefinitions; + } else { + // Could not parse [contents] as a reflink definition. + break; + } + } else { + contents = '$contents\n${lines[j]}'; + j++; + } + } + // End of the block. + if (_parseReflinkDefinition(parser, contents)) { + i = j; + break; + } + + // It may be that there is a reflink definition starting at [i], but it + // does not extend all the way to [j], such as: + // + // [link]: url // line i + // "title" + // garbage + // [link2]: url // line j + // + // In this case, [i, i+1] is a reflink definition, and the rest is + // paragraph content. + while (j >= i) { + // This isn't the most efficient loop, what with this big ole' + // Iterable allocation (`getRange`) followed by a big 'ole String + // allocation, but we + // must walk backwards, checking each range. + contents = lines.getRange(i, j).join('\n'); + if (_parseReflinkDefinition(parser, contents)) { + // That is the last reflink definition. The rest is paragraph + // content. + i = j; + break; + } + j--; + } + // The ending was not a reflink definition at all. Just paragraph + // content. + + break; + } + + if (i == lines.length) { + // No paragraph content. + return null; + } else { + // Ends with paragraph content. + return lines.sublist(i); + } + } + + // Parse [contents] as a reference link definition. + // + // Also adds the reference link definition to the document. + // + // Returns whether [contents] could be parsed as a reference link definition. + bool _parseReflinkDefinition(BlockParser parser, String contents) { + final pattern = RegExp( + // Leading indentation. + r'''^[ ]{0,3}''' + // Reference id in brackets, and URL. + r'''\[((?:\\\]|[^\]])+)\]:\s*(?:<(\S+)>|(\S+))\s*''' + // Title in double or single quotes, or parens. + r'''("[^"]+"|'[^']+'|\([^)]+\)|)\s*$''', + multiLine: true); + final match = pattern.firstMatch(contents); + if (match == null) { + // Not a reference link definition. + return false; + } + if (match[0]!.length < contents.length) { + // Trailing text. No good. + return false; + } + + var label = match[1]!; + final destination = match[2] ?? match[3]; + var title = match[4]; + + // The label must contain at least one non-whitespace character. + if (_whitespacePattern.hasMatch(label)) { + return false; + } + + if (title == '') { + // No title. + title = null; + } else { + // Remove "", '', or (). + title = title!.substring(1, title.length - 1); + } + + // References are case-insensitive, and internal whitespace is compressed. + label = + label.toLowerCase().trim().replaceAll(_oneOrMoreWhitespacePattern, ' '); + + parser.document.linkReferences + .putIfAbsent(label, () => LinkReference(label, destination!, title!)); + return true; + } +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/delta_markdown_decoder.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/delta_markdown_decoder.dart new file mode 100644 index 0000000000..fe9ae8f05b --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/delta_markdown_decoder.dart @@ -0,0 +1,255 @@ +import 'dart:collection'; +import 'dart:convert'; + +import 'package:flutter_quill/models/documents/attribute.dart'; +import 'package:flutter_quill/models/quill_delta.dart'; + +import 'ast.dart' as ast; +import 'document.dart'; + +class DeltaMarkdownDecoder extends Converter { + @override + String convert(String input) { + final lines = input.replaceAll('\r\n', '\n').split('\n'); + + final markdownDocument = Document().parseLines(lines); + + return jsonEncode(_DeltaVisitor().convert(markdownDocument).toJson()); + } +} + +class _DeltaVisitor implements ast.NodeVisitor { + static final _blockTags = + RegExp('h1|h2|h3|h4|h5|h6|hr|pre|ul|ol|blockquote|p|pre'); + + static final _embedTags = RegExp('hr|img'); + + late Delta delta; + + late Queue activeInlineAttributes; + Attribute? activeBlockAttribute; + late Set uniqueIds; + + ast.Element? previousElement; + late ast.Element previousToplevelElement; + + Delta convert(List nodes) { + delta = Delta(); + activeInlineAttributes = Queue(); + uniqueIds = {}; + + for (final node in nodes) { + node.accept(this); + } + + // Ensure the delta ends with a newline. + if (delta.length > 0 && delta.last.value != '\n') { + delta.insert('\n', activeBlockAttribute?.toJson()); + } + + return delta; + } + + @override + void visitText(ast.Text text) { + // Remove trailing newline + //final lines = text.text.trim().split('\n'); + + /* + final attributes = Map(); + for (final attr in activeInlineAttributes) { + attributes.addAll(attr.toJson()); + } + + for (final l in lines) { + delta.insert(l, attributes); + delta.insert('\n', activeBlockAttribute.toJson()); + }*/ + + final str = text.text; + //if (str.endsWith('\n')) str = str.substring(0, str.length - 1); + + final attributes = {}; + for (final attr in activeInlineAttributes) { + attributes.addAll(attr.toJson()); + } + + var newlineIndex = str.indexOf('\n'); + var startIndex = 0; + while (newlineIndex != -1) { + final previousText = str.substring(startIndex, newlineIndex); + if (previousText.isNotEmpty) { + delta.insert(previousText, attributes.isNotEmpty ? attributes : null); + } + delta.insert('\n', activeBlockAttribute?.toJson()); + + startIndex = newlineIndex + 1; + newlineIndex = str.indexOf('\n', newlineIndex + 1); + } + + if (startIndex < str.length) { + final lastStr = str.substring(startIndex); + delta.insert(lastStr, attributes.isNotEmpty ? attributes : null); + } + } + + @override + bool visitElementBefore(ast.Element element) { + // Hackish. Separate block-level elements with newlines. + final attr = _tagToAttribute(element); + + if (delta.isNotEmpty && _blockTags.firstMatch(element.tag) != null) { + if (element.isToplevel) { + // If the last active block attribute is not a list, we need to finish + // it off. + if (previousToplevelElement.tag != 'ul' && + previousToplevelElement.tag != 'ol' && + previousToplevelElement.tag != 'pre' && + previousToplevelElement.tag != 'hr') { + delta.insert('\n', activeBlockAttribute?.toJson()); + } + + // Only separate the blocks if both are paragraphs. + // + // TODO(kolja): Determine which behavior we really want here. + // We can either insert an additional newline or just have the + // paragraphs as single lines. Zefyr will by default render two lines + // are different paragraphs so for now we will not add an additonal + // newline here. + // + // if (previousToplevelElement != null && + // previousToplevelElement.tag == 'p' && + // element.tag == 'p') { + // delta.insert('\n'); + // } + } else if (element.tag == 'p' && + previousElement != null && + !previousElement!.isToplevel && + !previousElement!.children!.contains(element)) { + // Here we have two children of the same toplevel element. These need + // to be separated by additional newlines. + + delta + // Finish off the last lower-level block. + ..insert('\n', activeBlockAttribute?.toJson()) + // Add an empty line between the lower-level blocks. + ..insert('\n', activeBlockAttribute?.toJson()); + } + } + + // Keep track of the top-level block attribute. + if (element.isToplevel && element.tag != 'hr') { + // Hacky solution for horizontal rule so that the attribute is not added + // to the line feed at the end of the line. + activeBlockAttribute = attr; + } + + if (_embedTags.firstMatch(element.tag) != null) { + // We write out the element here since the embed has no children or + // content. + delta.insert(attr!.toJson()); + } else if (_blockTags.firstMatch(element.tag) == null && attr != null) { + activeInlineAttributes.addLast(attr); + } + + previousElement = element; + if (element.isToplevel) { + previousToplevelElement = element; + } + + if (element.isEmpty) { + // Empty element like
. + //buffer.write(' />'); + + if (element.tag == 'br') { + delta.insert('\n'); + } + + return false; + } else { + //buffer.write('>'); + return true; + } + } + + @override + void visitElementAfter(ast.Element element) { + if (element.tag == 'li' && + (previousToplevelElement.tag == 'ol' || + previousToplevelElement.tag == 'ul')) { + delta.insert('\n', activeBlockAttribute?.toJson()); + } + + final attr = _tagToAttribute(element); + if (attr == null || !attr.isInline || activeInlineAttributes.last != attr) { + return; + } + activeInlineAttributes.removeLast(); + + // Always keep track of the last element. + // This becomes relevant if we have something like + // + //
    + //
  • ...
  • + //
  • ...
  • + //
+ previousElement = element; + } + + /// Uniquifies an id generated from text. + String uniquifyId(String id) { + if (!uniqueIds.contains(id)) { + uniqueIds.add(id); + return id; + } + + var suffix = 2; + var suffixedId = '$id-$suffix'; + while (uniqueIds.contains(suffixedId)) { + suffixedId = '$id-${suffix++}'; + } + uniqueIds.add(suffixedId); + return suffixedId; + } + + Attribute? _tagToAttribute(ast.Element el) { + switch (el.tag) { + case 'em': + return Attribute.italic; + case 'strong': + return Attribute.bold; + case 'ul': + return Attribute.ul; + case 'ol': + return Attribute.ol; + case 'pre': + return Attribute.codeBlock; + case 'blockquote': + return Attribute.blockQuote; + case 'h1': + return Attribute.h1; + case 'h2': + return Attribute.h2; + case 'h3': + return Attribute.h3; + case 'a': + final href = el.attributes['href']; + return LinkAttribute(href); + case 'img': + final href = el.attributes['src']; + return ImageAttribute(href); + case 'hr': + return DividerAttribute(); + } + + return null; + } +} + +class ImageAttribute extends Attribute { + ImageAttribute(String? val) : super('image', AttributeScope.EMBEDS, val); +} + +class DividerAttribute extends Attribute { + DividerAttribute() : super('divider', AttributeScope.EMBEDS, 'hr'); +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/delta_markdown_encoder.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/delta_markdown_encoder.dart new file mode 100644 index 0000000000..12f57e6e8e --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/delta_markdown_encoder.dart @@ -0,0 +1,272 @@ +import 'dart:convert'; + +import 'package:collection/collection.dart' show IterableExtension; +import 'package:flutter_quill/models/documents/attribute.dart'; +import 'package:flutter_quill/models/documents/nodes/embed.dart'; +import 'package:flutter_quill/models/documents/style.dart'; +import 'package:flutter_quill/models/quill_delta.dart'; + +class DeltaMarkdownEncoder extends Converter { + static const _lineFeedAsciiCode = 0x0A; + + late StringBuffer markdownBuffer; + late StringBuffer lineBuffer; + + Attribute? currentBlockStyle; + late Style currentInlineStyle; + + late List currentBlockLines; + + /// Converts the [input] delta to Markdown. + @override + String convert(String input) { + markdownBuffer = StringBuffer(); + lineBuffer = StringBuffer(); + currentInlineStyle = Style(); + currentBlockLines = []; + + final inputJson = jsonDecode(input) as List?; + if (inputJson is! List) { + throw ArgumentError('Unexpected formatting of the input delta string.'); + } + final delta = Delta.fromJson(inputJson); + final iterator = DeltaIterator(delta); + + while (iterator.hasNext) { + final operation = iterator.next(); + + if (operation.data is String) { + final operationData = operation.data as String; + + if (!operationData.contains('\n')) { + _handleInline(lineBuffer, operationData, operation.attributes); + } else { + _handleLine(operationData, operation.attributes); + } + } else if (operation.data is Map) { + _handleEmbed(operation.data as Map); + } else { + throw ArgumentError('Unexpected formatting of the input delta string.'); + } + } + + _handleBlock(currentBlockStyle); // Close the last block + + return markdownBuffer.toString(); + } + + void _handleInline( + StringBuffer buffer, + String text, + Map? attributes, + ) { + final style = Style.fromJson(attributes); + + // First close any current styles if needed + final markedForRemoval = []; + // Close the styles in reverse order, e.g. **_ for _**Test**_. + for (final value + in currentInlineStyle.attributes.values.toList().reversed) { + // TODO(tillf): Is block correct? + if (value.scope == AttributeScope.BLOCK) { + continue; + } + if (style.containsKey(value.key)) { + continue; + } + + final padding = _trimRight(buffer); + _writeAttribute(buffer, value, close: true); + if (padding.isNotEmpty) { + buffer.write(padding); + } + markedForRemoval.add(value); + } + + // Make sure to remove all attributes that are marked for removal. + for (final value in markedForRemoval) { + currentInlineStyle.attributes.removeWhere((_, v) => v == value); + } + + // Now open any new styles. + for (final attribute in style.attributes.values) { + // TODO(tillf): Is block correct? + if (attribute.scope == AttributeScope.BLOCK) { + continue; + } + if (currentInlineStyle.containsKey(attribute.key)) { + continue; + } + final originalText = text; + text = text.trimLeft(); + final padding = ' ' * (originalText.length - text.length); + if (padding.isNotEmpty) { + buffer.write(padding); + } + _writeAttribute(buffer, attribute); + } + + // Write the text itself + buffer.write(text); + currentInlineStyle = style; + } + + void _handleLine(String data, Map? attributes) { + final span = StringBuffer(); + + for (var i = 0; i < data.length; i++) { + if (data.codeUnitAt(i) == _lineFeedAsciiCode) { + if (span.isNotEmpty) { + // Write the span if it's not empty. + _handleInline(lineBuffer, span.toString(), attributes); + } + // Close any open inline styles. + _handleInline(lineBuffer, '', null); + + final lineBlock = Style.fromJson(attributes) + .attributes + .values + .singleWhereOrNull((a) => a.scope == AttributeScope.BLOCK); + + if (lineBlock == currentBlockStyle) { + currentBlockLines.add(lineBuffer.toString()); + } else { + _handleBlock(currentBlockStyle); + currentBlockLines + ..clear() + ..add(lineBuffer.toString()); + + currentBlockStyle = lineBlock; + } + lineBuffer.clear(); + + span.clear(); + } else { + span.writeCharCode(data.codeUnitAt(i)); + } + } + + // Remaining span + if (span.isNotEmpty) { + _handleInline(lineBuffer, span.toString(), attributes); + } + } + + void _handleEmbed(Map data) { + final embed = BlockEmbed(data.keys.first, data.values.first as String); + + if (embed.type == 'image') { + _writeEmbedTag(lineBuffer, embed); + _writeEmbedTag(lineBuffer, embed, close: true); + } else if (embed.type == 'divider') { + _writeEmbedTag(lineBuffer, embed); + _writeEmbedTag(lineBuffer, embed, close: true); + } + } + + void _handleBlock(Attribute? blockStyle) { + if (currentBlockLines.isEmpty) { + return; // Empty block + } + + // If there was a block before this one, add empty line between the blocks + if (markdownBuffer.isNotEmpty) { + markdownBuffer.writeln(); + } + + if (blockStyle == null) { + markdownBuffer + ..write(currentBlockLines.join('\n')) + ..writeln(); + } else if (blockStyle == Attribute.codeBlock) { + _writeAttribute(markdownBuffer, blockStyle); + markdownBuffer.write(currentBlockLines.join('\n')); + _writeAttribute(markdownBuffer, blockStyle, close: true); + markdownBuffer.writeln(); + } else { + // Dealing with lists or a quote. + for (final line in currentBlockLines) { + _writeBlockTag(markdownBuffer, blockStyle); + markdownBuffer + ..write(line) + ..writeln(); + } + } + } + + String _trimRight(StringBuffer buffer) { + final text = buffer.toString(); + if (!text.endsWith(' ')) { + return ''; + } + + final result = text.trimRight(); + buffer + ..clear() + ..write(result); + return ' ' * (text.length - result.length); + } + + void _writeAttribute( + StringBuffer buffer, + Attribute attribute, { + bool close = false, + }) { + if (attribute.key == Attribute.bold.key) { + buffer.write('**'); + } else if (attribute.key == Attribute.italic.key) { + buffer.write('_'); + } else if (attribute.key == Attribute.link.key) { + buffer.write(!close ? '[' : '](${attribute.value})'); + } else if (attribute == Attribute.codeBlock) { + buffer.write(!close ? '```\n' : '\n```'); + } else { + throw ArgumentError('Cannot handle $attribute'); + } + } + + void _writeBlockTag( + StringBuffer buffer, + Attribute block, { + bool close = false, + }) { + if (close) { + return; // no close tag needed for simple blocks. + } + + if (block == Attribute.blockQuote) { + buffer.write('> '); + } else if (block == Attribute.ul) { + buffer.write('* '); + } else if (block == Attribute.ol) { + buffer.write('1. '); + } else if (block.key == Attribute.h1.key && block.value == 1) { + buffer.write('# '); + } else if (block.key == Attribute.h2.key && block.value == 2) { + buffer.write('## '); + } else if (block.key == Attribute.h3.key && block.value == 3) { + buffer.write('### '); + } else { + throw ArgumentError('Cannot handle block $block'); + } + } + + void _writeEmbedTag( + StringBuffer buffer, + BlockEmbed embed, { + bool close = false, + }) { + const kImageType = 'image'; + const kDividerType = 'divider'; + + if (embed.type == kImageType) { + if (close) { + buffer.write('](${embed.data})'); + } else { + buffer.write('!['); + } + } else if (embed.type == kDividerType && close) { + buffer.write('\n---\n\n'); + } + } +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/document.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/document.dart new file mode 100644 index 0000000000..890b858cd2 --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/document.dart @@ -0,0 +1,88 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'ast.dart'; +import 'block_parser.dart'; +import 'extension_set.dart'; +import 'inline_parser.dart'; + +/// Maintains the context needed to parse a Markdown document. +class Document { + Document({ + Iterable? blockSyntaxes, + Iterable? inlineSyntaxes, + ExtensionSet? extensionSet, + this.linkResolver, + this.imageLinkResolver, + }) : extensionSet = extensionSet ?? ExtensionSet.commonMark { + _blockSyntaxes + ..addAll(blockSyntaxes ?? []) + ..addAll(this.extensionSet.blockSyntaxes); + _inlineSyntaxes + ..addAll(inlineSyntaxes ?? []) + ..addAll(this.extensionSet.inlineSyntaxes); + } + + final Map linkReferences = {}; + final ExtensionSet extensionSet; + final Resolver? linkResolver; + final Resolver? imageLinkResolver; + final _blockSyntaxes = {}; + final _inlineSyntaxes = {}; + + Iterable get blockSyntaxes => _blockSyntaxes; + Iterable get inlineSyntaxes => _inlineSyntaxes; + + /// Parses the given [lines] of Markdown to a series of AST nodes. + List parseLines(List lines) { + final nodes = BlockParser(lines, this).parseLines(); + // Make sure to mark the top level nodes as such. + for (final n in nodes) { + n.isToplevel = true; + } + _parseInlineContent(nodes); + return nodes; + } + + /// Parses the given inline Markdown [text] to a series of AST nodes. + List? parseInline(String text) => InlineParser(text, this).parse(); + + void _parseInlineContent(List nodes) { + for (var i = 0; i < nodes.length; i++) { + final node = nodes[i]; + if (node is UnparsedContent) { + final inlineNodes = parseInline(node.textContent)!; + nodes + ..removeAt(i) + ..insertAll(i, inlineNodes); + i += inlineNodes.length - 1; + } else if (node is Element && node.children != null) { + _parseInlineContent(node.children!); + } + } + } +} + +/// A [link reference +/// definition](http://spec.commonmark.org/0.28/#link-reference-definitions). +class LinkReference { + /// Construct a [LinkReference], with all necessary fields. + /// + /// If the parsed link reference definition does not include a title, use + /// `null` for the [title] parameter. + LinkReference(this.label, this.destination, this.title); + + /// The [link label](http://spec.commonmark.org/0.28/#link-label). + /// + /// Temporarily, this class is also being used to represent the link data for + /// an inline link (the destination and title), but this should change before + /// the package is released. + final String label; + + /// The [link destination](http://spec.commonmark.org/0.28/#link-destination). + final String destination; + + /// The [link title](http://spec.commonmark.org/0.28/#link-title). + final String title; +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/emojis.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/emojis.dart new file mode 100644 index 0000000000..cdb3b6940e --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/emojis.dart @@ -0,0 +1,1510 @@ +// GENERATED FILE. DO NOT EDIT. +// +// This file was generated from emojilib's emoji data file: +// https://github.com/muan/emojilib/raw/master/emojis.json +// at 2018-07-02 15:07:49.422933 by the script, tool/update_emojis.dart. + +const emojis = { + 'grinning': '😀', + 'grimacing': '😬', + 'grin': '😁', + 'joy': '😂', + 'rofl': '🤣', + 'smiley': '😃', + 'smile': '😄', + 'sweat_smile': '😅', + 'laughing': '😆', + 'innocent': '😇', + 'wink': '😉', + 'blush': '😊', + 'slightly_smiling_face': '🙂', + 'upside_down_face': '🙃', + 'relaxed': '☺️', + 'yum': '😋', + 'relieved': '😌', + 'heart_eyes': '😍', + 'kissing_heart': '😘', + 'kissing': '😗', + 'kissing_smiling_eyes': '😙', + 'kissing_closed_eyes': '😚', + 'stuck_out_tongue_winking_eye': '😜', + 'zany': '🤪', + 'raised_eyebrow': '🤨', + 'monocle': '🧐', + 'stuck_out_tongue_closed_eyes': '😝', + 'stuck_out_tongue': '😛', + 'money_mouth_face': '🤑', + 'nerd_face': '🤓', + 'sunglasses': '😎', + 'star_struck': '🤩', + 'clown_face': '🤡', + 'cowboy_hat_face': '🤠', + 'hugs': '🤗', + 'smirk': '😏', + 'no_mouth': '😶', + 'neutral_face': '😐', + 'expressionless': '😑', + 'unamused': '😒', + 'roll_eyes': '🙄', + 'thinking': '🤔', + 'lying_face': '🤥', + 'hand_over_mouth': '🤭', + 'shushing': '🤫', + 'symbols_over_mouth': '🤬', + 'exploding_head': '🤯', + 'flushed': '😳', + 'disappointed': '😞', + 'worried': '😟', + 'angry': '😠', + 'rage': '😡', + 'pensive': '😔', + 'confused': '😕', + 'slightly_frowning_face': '🙁', + 'frowning_face': '☹', + 'persevere': '😣', + 'confounded': '😖', + 'tired_face': '😫', + 'weary': '😩', + 'triumph': '😤', + 'open_mouth': '😮', + 'scream': '😱', + 'fearful': '😨', + 'cold_sweat': '😰', + 'hushed': '😯', + 'frowning': '😦', + 'anguished': '😧', + 'cry': '😢', + 'disappointed_relieved': '😥', + 'drooling_face': '🤤', + 'sleepy': '😪', + 'sweat': '😓', + 'sob': '😭', + 'dizzy_face': '😵', + 'astonished': '😲', + 'zipper_mouth_face': '🤐', + 'nauseated_face': '🤢', + 'sneezing_face': '🤧', + 'vomiting': '🤮', + 'mask': '😷', + 'face_with_thermometer': '🤒', + 'face_with_head_bandage': '🤕', + 'sleeping': '😴', + 'zzz': '💤', + 'poop': '💩', + 'smiling_imp': '😈', + 'imp': '👿', + 'japanese_ogre': '👹', + 'japanese_goblin': '👺', + 'skull': '💀', + 'ghost': '👻', + 'alien': '👽', + 'robot': '🤖', + 'smiley_cat': '😺', + 'smile_cat': '😸', + 'joy_cat': '😹', + 'heart_eyes_cat': '😻', + 'smirk_cat': '😼', + 'kissing_cat': '😽', + 'scream_cat': '🙀', + 'crying_cat_face': '😿', + 'pouting_cat': '😾', + 'palms_up': '🤲', + 'raised_hands': '🙌', + 'clap': '👏', + 'wave': '👋', + 'call_me_hand': '🤙', + '+1': '👍', + '-1': '👎', + 'facepunch': '👊', + 'fist': '✊', + 'fist_left': '🤛', + 'fist_right': '🤜', + 'v': '✌', + 'ok_hand': '👌', + 'raised_hand': '✋', + 'raised_back_of_hand': '🤚', + 'open_hands': '👐', + 'muscle': '💪', + 'pray': '🙏', + 'handshake': '🤝', + 'point_up': '☝', + 'point_up_2': '👆', + 'point_down': '👇', + 'point_left': '👈', + 'point_right': '👉', + 'fu': '🖕', + 'raised_hand_with_fingers_splayed': '🖐', + 'love_you': '🤟', + 'metal': '🤘', + 'crossed_fingers': '🤞', + 'vulcan_salute': '🖖', + 'writing_hand': '✍', + 'selfie': '🤳', + 'nail_care': '💅', + 'lips': '👄', + 'tongue': '👅', + 'ear': '👂', + 'nose': '👃', + 'eye': '👁', + 'eyes': '👀', + 'brain': '🧠', + 'bust_in_silhouette': '👤', + 'busts_in_silhouette': '👥', + 'speaking_head': '🗣', + 'baby': '👶', + 'child': '🧒', + 'boy': '👦', + 'girl': '👧', + 'adult': '🧑', + 'man': '👨', + 'woman': '👩', + 'blonde_woman': '👱‍♀️', + 'blonde_man': '👱', + 'bearded_person': '🧔', + 'older_adult': '🧓', + 'older_man': '👴', + 'older_woman': '👵', + 'man_with_gua_pi_mao': '👲', + 'woman_with_headscarf': '🧕', + 'woman_with_turban': '👳‍♀️', + 'man_with_turban': '👳', + 'policewoman': '👮‍♀️', + 'policeman': '👮', + 'construction_worker_woman': '👷‍♀️', + 'construction_worker_man': '👷', + 'guardswoman': '💂‍♀️', + 'guardsman': '💂', + 'female_detective': '🕵️‍♀️', + 'male_detective': '🕵', + 'woman_health_worker': '👩‍⚕️', + 'man_health_worker': '👨‍⚕️', + 'woman_farmer': '👩‍🌾', + 'man_farmer': '👨‍🌾', + 'woman_cook': '👩‍🍳', + 'man_cook': '👨‍🍳', + 'woman_student': '👩‍🎓', + 'man_student': '👨‍🎓', + 'woman_singer': '👩‍🎤', + 'man_singer': '👨‍🎤', + 'woman_teacher': '👩‍🏫', + 'man_teacher': '👨‍🏫', + 'woman_factory_worker': '👩‍🏭', + 'man_factory_worker': '👨‍🏭', + 'woman_technologist': '👩‍💻', + 'man_technologist': '👨‍💻', + 'woman_office_worker': '👩‍💼', + 'man_office_worker': '👨‍💼', + 'woman_mechanic': '👩‍🔧', + 'man_mechanic': '👨‍🔧', + 'woman_scientist': '👩‍🔬', + 'man_scientist': '👨‍🔬', + 'woman_artist': '👩‍🎨', + 'man_artist': '👨‍🎨', + 'woman_firefighter': '👩‍🚒', + 'man_firefighter': '👨‍🚒', + 'woman_pilot': '👩‍✈️', + 'man_pilot': '👨‍✈️', + 'woman_astronaut': '👩‍🚀', + 'man_astronaut': '👨‍🚀', + 'woman_judge': '👩‍⚖️', + 'man_judge': '👨‍⚖️', + 'mrs_claus': '🤶', + 'santa': '🎅', + 'sorceress': '🧙‍♀️', + 'wizard': '🧙‍♂️', + 'woman_elf': '🧝‍♀️', + 'man_elf': '🧝‍♂️', + 'woman_vampire': '🧛‍♀️', + 'man_vampire': '🧛‍♂️', + 'woman_zombie': '🧟‍♀️', + 'man_zombie': '🧟‍♂️', + 'woman_genie': '🧞‍♀️', + 'man_genie': '🧞‍♂️', + 'mermaid': '🧜‍♀️', + 'merman': '🧜‍♂️', + 'woman_fairy': '🧚‍♀️', + 'man_fairy': '🧚‍♂️', + 'angel': '👼', + 'pregnant_woman': '🤰', + 'breastfeeding': '🤱', + 'princess': '👸', + 'prince': '🤴', + 'bride_with_veil': '👰', + 'man_in_tuxedo': '🤵', + 'running_woman': '🏃‍♀️', + 'running_man': '🏃', + 'walking_woman': '🚶‍♀️', + 'walking_man': '🚶', + 'dancer': '💃', + 'man_dancing': '🕺', + 'dancing_women': '👯', + 'dancing_men': '👯‍♂️', + 'couple': '👫', + 'two_men_holding_hands': '👬', + 'two_women_holding_hands': '👭', + 'bowing_woman': '🙇‍♀️', + 'bowing_man': '🙇', + 'man_facepalming': '🤦', + 'woman_facepalming': '🤦‍♀️', + 'woman_shrugging': '🤷', + 'man_shrugging': '🤷‍♂️', + 'tipping_hand_woman': '💁', + 'tipping_hand_man': '💁‍♂️', + 'no_good_woman': '🙅', + 'no_good_man': '🙅‍♂️', + 'ok_woman': '🙆', + 'ok_man': '🙆‍♂️', + 'raising_hand_woman': '🙋', + 'raising_hand_man': '🙋‍♂️', + 'pouting_woman': '🙎', + 'pouting_man': '🙎‍♂️', + 'frowning_woman': '🙍', + 'frowning_man': '🙍‍♂️', + 'haircut_woman': '💇', + 'haircut_man': '💇‍♂️', + 'massage_woman': '💆', + 'massage_man': '💆‍♂️', + 'woman_in_steamy_room': '🧖‍♀️', + 'man_in_steamy_room': '🧖‍♂️', + 'couple_with_heart_woman_man': '💑', + 'couple_with_heart_woman_woman': '👩‍❤️‍👩', + 'couple_with_heart_man_man': '👨‍❤️‍👨', + 'couplekiss_man_woman': '💏', + 'couplekiss_woman_woman': '👩‍❤️‍💋‍👩', + 'couplekiss_man_man': '👨‍❤️‍💋‍👨', + 'family_man_woman_boy': '👪', + 'family_man_woman_girl': '👨‍👩‍👧', + 'family_man_woman_girl_boy': '👨‍👩‍👧‍👦', + 'family_man_woman_boy_boy': '👨‍👩‍👦‍👦', + 'family_man_woman_girl_girl': '👨‍👩‍👧‍👧', + 'family_woman_woman_boy': '👩‍👩‍👦', + 'family_woman_woman_girl': '👩‍👩‍👧', + 'family_woman_woman_girl_boy': '👩‍👩‍👧‍👦', + 'family_woman_woman_boy_boy': '👩‍👩‍👦‍👦', + 'family_woman_woman_girl_girl': '👩‍👩‍👧‍👧', + 'family_man_man_boy': '👨‍👨‍👦', + 'family_man_man_girl': '👨‍👨‍👧', + 'family_man_man_girl_boy': '👨‍👨‍👧‍👦', + 'family_man_man_boy_boy': '👨‍👨‍👦‍👦', + 'family_man_man_girl_girl': '👨‍👨‍👧‍👧', + 'family_woman_boy': '👩‍👦', + 'family_woman_girl': '👩‍👧', + 'family_woman_girl_boy': '👩‍👧‍👦', + 'family_woman_boy_boy': '👩‍👦‍👦', + 'family_woman_girl_girl': '👩‍👧‍👧', + 'family_man_boy': '👨‍👦', + 'family_man_girl': '👨‍👧', + 'family_man_girl_boy': '👨‍👧‍👦', + 'family_man_boy_boy': '👨‍👦‍👦', + 'family_man_girl_girl': '👨‍👧‍👧', + 'coat': '🧥', + 'womans_clothes': '👚', + 'tshirt': '👕', + 'jeans': '👖', + 'necktie': '👔', + 'dress': '👗', + 'bikini': '👙', + 'kimono': '👘', + 'lipstick': '💄', + 'kiss': '💋', + 'footprints': '👣', + 'high_heel': '👠', + 'sandal': '👡', + 'boot': '👢', + 'mans_shoe': '👞', + 'athletic_shoe': '👟', + 'socks': '🧦', + 'gloves': '🧤', + 'scarf': '🧣', + 'womans_hat': '👒', + 'tophat': '🎩', + 'billed_hat': '🧢', + 'rescue_worker_helmet': '⛑', + 'mortar_board': '🎓', + 'crown': '👑', + 'school_satchel': '🎒', + 'pouch': '👝', + 'purse': '👛', + 'handbag': '👜', + 'briefcase': '💼', + 'eyeglasses': '👓', + 'dark_sunglasses': '🕶', + 'ring': '💍', + 'closed_umbrella': '🌂', + 'dog': '🐶', + 'cat': '🐱', + 'mouse': '🐭', + 'hamster': '🐹', + 'rabbit': '🐰', + 'fox_face': '🦊', + 'bear': '🐻', + 'panda_face': '🐼', + 'koala': '🐨', + 'tiger': '🐯', + 'lion': '🦁', + 'cow': '🐮', + 'pig': '🐷', + 'pig_nose': '🐽', + 'frog': '🐸', + 'squid': '🦑', + 'octopus': '🐙', + 'shrimp': '🦐', + 'monkey_face': '🐵', + 'gorilla': '🦍', + 'see_no_evil': '🙈', + 'hear_no_evil': '🙉', + 'speak_no_evil': '🙊', + 'monkey': '🐒', + 'chicken': '🐔', + 'penguin': '🐧', + 'bird': '🐦', + 'baby_chick': '🐤', + 'hatching_chick': '🐣', + 'hatched_chick': '🐥', + 'duck': '🦆', + 'eagle': '🦅', + 'owl': '🦉', + 'bat': '🦇', + 'wolf': '🐺', + 'boar': '🐗', + 'horse': '🐴', + 'unicorn': '🦄', + 'honeybee': '🐝', + 'bug': '🐛', + 'butterfly': '🦋', + 'snail': '🐌', + 'beetle': '🐞', + 'ant': '🐜', + 'grasshopper': '🦗', + 'spider': '🕷', + 'scorpion': '🦂', + 'crab': '🦀', + 'snake': '🐍', + 'lizard': '🦎', + 't-rex': '🦖', + 'sauropod': '🦕', + 'turtle': '🐢', + 'tropical_fish': '🐠', + 'fish': '🐟', + 'blowfish': '🐡', + 'dolphin': '🐬', + 'shark': '🦈', + 'whale': '🐳', + 'whale2': '🐋', + 'crocodile': '🐊', + 'leopard': '🐆', + 'zebra': '🦓', + 'tiger2': '🐅', + 'water_buffalo': '🐃', + 'ox': '🐂', + 'cow2': '🐄', + 'deer': '🦌', + 'dromedary_camel': '🐪', + 'camel': '🐫', + 'giraffe': '🦒', + 'elephant': '🐘', + 'rhinoceros': '🦏', + 'goat': '🐐', + 'ram': '🐏', + 'sheep': '🐑', + 'racehorse': '🐎', + 'pig2': '🐖', + 'rat': '🐀', + 'mouse2': '🐁', + 'rooster': '🐓', + 'turkey': '🦃', + 'dove': '🕊', + 'dog2': '🐕', + 'poodle': '🐩', + 'cat2': '🐈', + 'rabbit2': '🐇', + 'chipmunk': '🐿', + 'hedgehog': '🦔', + 'paw_prints': '🐾', + 'dragon': '🐉', + 'dragon_face': '🐲', + 'cactus': '🌵', + 'christmas_tree': '🎄', + 'evergreen_tree': '🌲', + 'deciduous_tree': '🌳', + 'palm_tree': '🌴', + 'seedling': '🌱', + 'herb': '🌿', + 'shamrock': '☘', + 'four_leaf_clover': '🍀', + 'bamboo': '🎍', + 'tanabata_tree': '🎋', + 'leaves': '🍃', + 'fallen_leaf': '🍂', + 'maple_leaf': '🍁', + 'ear_of_rice': '🌾', + 'hibiscus': '🌺', + 'sunflower': '🌻', + 'rose': '🌹', + 'wilted_flower': '🥀', + 'tulip': '🌷', + 'blossom': '🌼', + 'cherry_blossom': '🌸', + 'bouquet': '💐', + 'mushroom': '🍄', + 'chestnut': '🌰', + 'jack_o_lantern': '🎃', + 'shell': '🐚', + 'spider_web': '🕸', + 'earth_americas': '🌎', + 'earth_africa': '🌍', + 'earth_asia': '🌏', + 'full_moon': '🌕', + 'waning_gibbous_moon': '🌖', + 'last_quarter_moon': '🌗', + 'waning_crescent_moon': '🌘', + 'new_moon': '🌑', + 'waxing_crescent_moon': '🌒', + 'first_quarter_moon': '🌓', + 'waxing_gibbous_moon': '🌔', + 'new_moon_with_face': '🌚', + 'full_moon_with_face': '🌝', + 'first_quarter_moon_with_face': '🌛', + 'last_quarter_moon_with_face': '🌜', + 'sun_with_face': '🌞', + 'crescent_moon': '🌙', + 'star': '⭐', + 'star2': '🌟', + 'dizzy': '💫', + 'sparkles': '✨', + 'comet': '☄', + 'sunny': '☀️', + 'sun_behind_small_cloud': '🌤', + 'partly_sunny': '⛅', + 'sun_behind_large_cloud': '🌥', + 'sun_behind_rain_cloud': '🌦', + 'cloud': '☁️', + 'cloud_with_rain': '🌧', + 'cloud_with_lightning_and_rain': '⛈', + 'cloud_with_lightning': '🌩', + 'zap': '⚡', + 'fire': '🔥', + 'boom': '💥', + 'snowflake': '❄️', + 'cloud_with_snow': '🌨', + 'snowman': '⛄', + 'snowman_with_snow': '☃', + 'wind_face': '🌬', + 'dash': '💨', + 'tornado': '🌪', + 'fog': '🌫', + 'open_umbrella': '☂', + 'umbrella': '☔', + 'droplet': '💧', + 'sweat_drops': '💦', + 'ocean': '🌊', + 'green_apple': '🍏', + 'apple': '🍎', + 'pear': '🍐', + 'tangerine': '🍊', + 'lemon': '🍋', + 'banana': '🍌', + 'watermelon': '🍉', + 'grapes': '🍇', + 'strawberry': '🍓', + 'melon': '🍈', + 'cherries': '🍒', + 'peach': '🍑', + 'pineapple': '🍍', + 'coconut': '🥥', + 'kiwi_fruit': '🥝', + 'avocado': '🥑', + 'broccoli': '🥦', + 'tomato': '🍅', + 'eggplant': '🍆', + 'cucumber': '🥒', + 'carrot': '🥕', + 'hot_pepper': '🌶', + 'potato': '🥔', + 'corn': '🌽', + 'sweet_potato': '🍠', + 'peanuts': '🥜', + 'honey_pot': '🍯', + 'croissant': '🥐', + 'bread': '🍞', + 'baguette_bread': '🥖', + 'pretzel': '🥨', + 'cheese': '🧀', + 'egg': '🥚', + 'bacon': '🥓', + 'steak': '🥩', + 'pancakes': '🥞', + 'poultry_leg': '🍗', + 'meat_on_bone': '🍖', + 'fried_shrimp': '🍤', + 'fried_egg': '🍳', + 'hamburger': '🍔', + 'fries': '🍟', + 'stuffed_flatbread': '🥙', + 'hotdog': '🌭', + 'pizza': '🍕', + 'sandwich': '🥪', + 'canned_food': '🥫', + 'spaghetti': '🍝', + 'taco': '🌮', + 'burrito': '🌯', + 'green_salad': '🥗', + 'shallow_pan_of_food': '🥘', + 'ramen': '🍜', + 'stew': '🍲', + 'fish_cake': '🍥', + 'fortune_cookie': '🥠', + 'sushi': '🍣', + 'bento': '🍱', + 'curry': '🍛', + 'rice_ball': '🍙', + 'rice': '🍚', + 'rice_cracker': '🍘', + 'oden': '🍢', + 'dango': '🍡', + 'shaved_ice': '🍧', + 'ice_cream': '🍨', + 'icecream': '🍦', + 'pie': '🥧', + 'cake': '🍰', + 'birthday': '🎂', + 'custard': '🍮', + 'candy': '🍬', + 'lollipop': '🍭', + 'chocolate_bar': '🍫', + 'popcorn': '🍿', + 'dumpling': '🥟', + 'doughnut': '🍩', + 'cookie': '🍪', + 'milk_glass': '🥛', + 'beer': '🍺', + 'beers': '🍻', + 'clinking_glasses': '🥂', + 'wine_glass': '🍷', + 'tumbler_glass': '🥃', + 'cocktail': '🍸', + 'tropical_drink': '🍹', + 'champagne': '🍾', + 'sake': '🍶', + 'tea': '🍵', + 'cup_with_straw': '🥤', + 'coffee': '☕', + 'baby_bottle': '🍼', + 'spoon': '🥄', + 'fork_and_knife': '🍴', + 'plate_with_cutlery': '🍽', + 'bowl_with_spoon': '🥣', + 'takeout_box': '🥡', + 'chopsticks': '🥢', + 'soccer': '⚽', + 'basketball': '🏀', + 'football': '🏈', + 'baseball': '⚾', + 'tennis': '🎾', + 'volleyball': '🏐', + 'rugby_football': '🏉', + '8ball': '🎱', + 'golf': '⛳', + 'golfing_woman': '🏌️‍♀️', + 'golfing_man': '🏌', + 'ping_pong': '🏓', + 'badminton': '🏸', + 'goal_net': '🥅', + 'ice_hockey': '🏒', + 'field_hockey': '🏑', + 'cricket': '🏏', + 'ski': '🎿', + 'skier': '⛷', + 'snowboarder': '🏂', + 'person_fencing': '🤺', + 'women_wrestling': '🤼‍♀️', + 'men_wrestling': '🤼‍♂️', + 'woman_cartwheeling': '🤸‍♀️', + 'man_cartwheeling': '🤸‍♂️', + 'woman_playing_handball': '🤾‍♀️', + 'man_playing_handball': '🤾‍♂️', + 'ice_skate': '⛸', + 'curling_stone': '🥌', + 'sled': '🛷', + 'bow_and_arrow': '🏹', + 'fishing_pole_and_fish': '🎣', + 'boxing_glove': '🥊', + 'martial_arts_uniform': '🥋', + 'rowing_woman': '🚣‍♀️', + 'rowing_man': '🚣', + 'climbing_woman': '🧗‍♀️', + 'climbing_man': '🧗‍♂️', + 'swimming_woman': '🏊‍♀️', + 'swimming_man': '🏊', + 'woman_playing_water_polo': '🤽‍♀️', + 'man_playing_water_polo': '🤽‍♂️', + 'woman_in_lotus_position': '🧘‍♀️', + 'man_in_lotus_position': '🧘‍♂️', + 'surfing_woman': '🏄‍♀️', + 'surfing_man': '🏄', + 'bath': '🛀', + 'basketball_woman': '⛹️‍♀️', + 'basketball_man': '⛹', + 'weight_lifting_woman': '🏋️‍♀️', + 'weight_lifting_man': '🏋', + 'biking_woman': '🚴‍♀️', + 'biking_man': '🚴', + 'mountain_biking_woman': '🚵‍♀️', + 'mountain_biking_man': '🚵', + 'horse_racing': '🏇', + 'business_suit_levitating': '🕴', + 'trophy': '🏆', + 'running_shirt_with_sash': '🎽', + 'medal_sports': '🏅', + 'medal_military': '🎖', + '1st_place_medal': '🥇', + '2nd_place_medal': '🥈', + '3rd_place_medal': '🥉', + 'reminder_ribbon': '🎗', + 'rosette': '🏵', + 'ticket': '🎫', + 'tickets': '🎟', + 'performing_arts': '🎭', + 'art': '🎨', + 'circus_tent': '🎪', + 'woman_juggling': '🤹‍♀️', + 'man_juggling': '🤹‍♂️', + 'microphone': '🎤', + 'headphones': '🎧', + 'musical_score': '🎼', + 'musical_keyboard': '🎹', + 'drum': '🥁', + 'saxophone': '🎷', + 'trumpet': '🎺', + 'guitar': '🎸', + 'violin': '🎻', + 'clapper': '🎬', + 'video_game': '🎮', + 'space_invader': '👾', + 'dart': '🎯', + 'game_die': '🎲', + 'slot_machine': '🎰', + 'bowling': '🎳', + 'red_car': '🚗', + 'taxi': '🚕', + 'blue_car': '🚙', + 'bus': '🚌', + 'trolleybus': '🚎', + 'racing_car': '🏎', + 'police_car': '🚓', + 'ambulance': '🚑', + 'fire_engine': '🚒', + 'minibus': '🚐', + 'truck': '🚚', + 'articulated_lorry': '🚛', + 'tractor': '🚜', + 'kick_scooter': '🛴', + 'motorcycle': '🏍', + 'bike': '🚲', + 'motor_scooter': '🛵', + 'rotating_light': '🚨', + 'oncoming_police_car': '🚔', + 'oncoming_bus': '🚍', + 'oncoming_automobile': '🚘', + 'oncoming_taxi': '🚖', + 'aerial_tramway': '🚡', + 'mountain_cableway': '🚠', + 'suspension_railway': '🚟', + 'railway_car': '🚃', + 'train': '🚋', + 'monorail': '🚝', + 'bullettrain_side': '🚄', + 'bullettrain_front': '🚅', + 'light_rail': '🚈', + 'mountain_railway': '🚞', + 'steam_locomotive': '🚂', + 'train2': '🚆', + 'metro': '🚇', + 'tram': '🚊', + 'station': '🚉', + 'flying_saucer': '🛸', + 'helicopter': '🚁', + 'small_airplane': '🛩', + 'airplane': '✈️', + 'flight_departure': '🛫', + 'flight_arrival': '🛬', + 'sailboat': '⛵', + 'motor_boat': '🛥', + 'speedboat': '🚤', + 'ferry': '⛴', + 'passenger_ship': '🛳', + 'rocket': '🚀', + 'artificial_satellite': '🛰', + 'seat': '💺', + 'canoe': '🛶', + 'anchor': '⚓', + 'construction': '🚧', + 'fuelpump': '⛽', + 'busstop': '🚏', + 'vertical_traffic_light': '🚦', + 'traffic_light': '🚥', + 'checkered_flag': '🏁', + 'ship': '🚢', + 'ferris_wheel': '🎡', + 'roller_coaster': '🎢', + 'carousel_horse': '🎠', + 'building_construction': '🏗', + 'foggy': '🌁', + 'tokyo_tower': '🗼', + 'factory': '🏭', + 'fountain': '⛲', + 'rice_scene': '🎑', + 'mountain': '⛰', + 'mountain_snow': '🏔', + 'mount_fuji': '🗻', + 'volcano': '🌋', + 'japan': '🗾', + 'camping': '🏕', + 'tent': '⛺', + 'national_park': '🏞', + 'motorway': '🛣', + 'railway_track': '🛤', + 'sunrise': '🌅', + 'sunrise_over_mountains': '🌄', + 'desert': '🏜', + 'beach_umbrella': '🏖', + 'desert_island': '🏝', + 'city_sunrise': '🌇', + 'city_sunset': '🌆', + 'cityscape': '🏙', + 'night_with_stars': '🌃', + 'bridge_at_night': '🌉', + 'milky_way': '🌌', + 'stars': '🌠', + 'sparkler': '🎇', + 'fireworks': '🎆', + 'rainbow': '🌈', + 'houses': '🏘', + 'european_castle': '🏰', + 'japanese_castle': '🏯', + 'stadium': '🏟', + 'statue_of_liberty': '🗽', + 'house': '🏠', + 'house_with_garden': '🏡', + 'derelict_house': '🏚', + 'office': '🏢', + 'department_store': '🏬', + 'post_office': '🏣', + 'european_post_office': '🏤', + 'hospital': '🏥', + 'bank': '🏦', + 'hotel': '🏨', + 'convenience_store': '🏪', + 'school': '🏫', + 'love_hotel': '🏩', + 'wedding': '💒', + 'classical_building': '🏛', + 'church': '⛪', + 'mosque': '🕌', + 'synagogue': '🕍', + 'kaaba': '🕋', + 'shinto_shrine': '⛩', + 'watch': '⌚', + 'iphone': '📱', + 'calling': '📲', + 'computer': '💻', + 'keyboard': '⌨', + 'desktop_computer': '🖥', + 'printer': '🖨', + 'computer_mouse': '🖱', + 'trackball': '🖲', + 'joystick': '🕹', + 'clamp': '🗜', + 'minidisc': '💽', + 'floppy_disk': '💾', + 'cd': '💿', + 'dvd': '📀', + 'vhs': '📼', + 'camera': '📷', + 'camera_flash': '📸', + 'video_camera': '📹', + 'movie_camera': '🎥', + 'film_projector': '📽', + 'film_strip': '🎞', + 'telephone_receiver': '📞', + 'phone': '☎️', + 'pager': '📟', + 'fax': '📠', + 'tv': '📺', + 'radio': '📻', + 'studio_microphone': '🎙', + 'level_slider': '🎚', + 'control_knobs': '🎛', + 'stopwatch': '⏱', + 'timer_clock': '⏲', + 'alarm_clock': '⏰', + 'mantelpiece_clock': '🕰', + 'hourglass_flowing_sand': '⏳', + 'hourglass': '⌛', + 'satellite': '📡', + 'battery': '🔋', + 'electric_plug': '🔌', + 'bulb': '💡', + 'flashlight': '🔦', + 'candle': '🕯', + 'wastebasket': '🗑', + 'oil_drum': '🛢', + 'money_with_wings': '💸', + 'dollar': '💵', + 'yen': '💴', + 'euro': '💶', + 'pound': '💷', + 'moneybag': '💰', + 'credit_card': '💳', + 'gem': '💎', + 'balance_scale': '⚖', + 'wrench': '🔧', + 'hammer': '🔨', + 'hammer_and_pick': '⚒', + 'hammer_and_wrench': '🛠', + 'pick': '⛏', + 'nut_and_bolt': '🔩', + 'gear': '⚙', + 'chains': '⛓', + 'gun': '🔫', + 'bomb': '💣', + 'hocho': '🔪', + 'dagger': '🗡', + 'crossed_swords': '⚔', + 'shield': '🛡', + 'smoking': '🚬', + 'skull_and_crossbones': '☠', + 'coffin': '⚰', + 'funeral_urn': '⚱', + 'amphora': '🏺', + 'crystal_ball': '🔮', + 'prayer_beads': '📿', + 'barber': '💈', + 'alembic': '⚗', + 'telescope': '🔭', + 'microscope': '🔬', + 'hole': '🕳', + 'pill': '💊', + 'syringe': '💉', + 'thermometer': '🌡', + 'label': '🏷', + 'bookmark': '🔖', + 'toilet': '🚽', + 'shower': '🚿', + 'bathtub': '🛁', + 'key': '🔑', + 'old_key': '🗝', + 'couch_and_lamp': '🛋', + 'sleeping_bed': '🛌', + 'bed': '🛏', + 'door': '🚪', + 'bellhop_bell': '🛎', + 'framed_picture': '🖼', + 'world_map': '🗺', + 'parasol_on_ground': '⛱', + 'moyai': '🗿', + 'shopping': '🛍', + 'shopping_cart': '🛒', + 'balloon': '🎈', + 'flags': '🎏', + 'ribbon': '🎀', + 'gift': '🎁', + 'confetti_ball': '🎊', + 'tada': '🎉', + 'dolls': '🎎', + 'wind_chime': '🎐', + 'crossed_flags': '🎌', + 'izakaya_lantern': '🏮', + 'email': '✉️', + 'envelope_with_arrow': '📩', + 'incoming_envelope': '📨', + 'e-mail': '📧', + 'love_letter': '💌', + 'postbox': '📮', + 'mailbox_closed': '📪', + 'mailbox': '📫', + 'mailbox_with_mail': '📬', + 'mailbox_with_no_mail': '📭', + 'package': '📦', + 'postal_horn': '📯', + 'inbox_tray': '📥', + 'outbox_tray': '📤', + 'scroll': '📜', + 'page_with_curl': '📃', + 'bookmark_tabs': '📑', + 'bar_chart': '📊', + 'chart_with_upwards_trend': '📈', + 'chart_with_downwards_trend': '📉', + 'page_facing_up': '📄', + 'date': '📅', + 'calendar': '📆', + 'spiral_calendar': '🗓', + 'card_index': '📇', + 'card_file_box': '🗃', + 'ballot_box': '🗳', + 'file_cabinet': '🗄', + 'clipboard': '📋', + 'spiral_notepad': '🗒', + 'file_folder': '📁', + 'open_file_folder': '📂', + 'card_index_dividers': '🗂', + 'newspaper_roll': '🗞', + 'newspaper': '📰', + 'notebook': '📓', + 'closed_book': '📕', + 'green_book': '📗', + 'blue_book': '📘', + 'orange_book': '📙', + 'notebook_with_decorative_cover': '📔', + 'ledger': '📒', + 'books': '📚', + 'open_book': '📖', + 'link': '🔗', + 'paperclip': '📎', + 'paperclips': '🖇', + 'scissors': '✂️', + 'triangular_ruler': '📐', + 'straight_ruler': '📏', + 'pushpin': '📌', + 'round_pushpin': '📍', + 'triangular_flag_on_post': '🚩', + 'white_flag': '🏳', + 'black_flag': '🏴', + 'rainbow_flag': '🏳️‍🌈', + 'closed_lock_with_key': '🔐', + 'lock': '🔒', + 'unlock': '🔓', + 'lock_with_ink_pen': '🔏', + 'pen': '🖊', + 'fountain_pen': '🖋', + 'black_nib': '✒️', + 'memo': '📝', + 'pencil2': '✏️', + 'crayon': '🖍', + 'paintbrush': '🖌', + 'mag': '🔍', + 'mag_right': '🔎', + 'heart': '❤️', + 'orange_heart': '🧡', + 'yellow_heart': '💛', + 'green_heart': '💚', + 'blue_heart': '💙', + 'purple_heart': '💜', + 'black_heart': '🖤', + 'broken_heart': '💔', + 'heavy_heart_exclamation': '❣', + 'two_hearts': '💕', + 'revolving_hearts': '💞', + 'heartbeat': '💓', + 'heartpulse': '💗', + 'sparkling_heart': '💖', + 'cupid': '💘', + 'gift_heart': '💝', + 'heart_decoration': '💟', + 'peace_symbol': '☮', + 'latin_cross': '✝', + 'star_and_crescent': '☪', + 'om': '🕉', + 'wheel_of_dharma': '☸', + 'star_of_david': '✡', + 'six_pointed_star': '🔯', + 'menorah': '🕎', + 'yin_yang': '☯', + 'orthodox_cross': '☦', + 'place_of_worship': '🛐', + 'ophiuchus': '⛎', + 'aries': '♈', + 'taurus': '♉', + 'gemini': '♊', + 'cancer': '♋', + 'leo': '♌', + 'virgo': '♍', + 'libra': '♎', + 'scorpius': '♏', + 'sagittarius': '♐', + 'capricorn': '♑', + 'aquarius': '♒', + 'pisces': '♓', + 'id': '🆔', + 'atom_symbol': '⚛', + 'u7a7a': '🈳', + 'u5272': '🈹', + 'radioactive': '☢', + 'biohazard': '☣', + 'mobile_phone_off': '📴', + 'vibration_mode': '📳', + 'u6709': '🈶', + 'u7121': '🈚', + 'u7533': '🈸', + 'u55b6': '🈺', + 'u6708': '🈷️', + 'eight_pointed_black_star': '✴️', + 'vs': '🆚', + 'accept': '🉑', + 'white_flower': '💮', + 'ideograph_advantage': '🉐', + 'secret': '㊙️', + 'congratulations': '㊗️', + 'u5408': '🈴', + 'u6e80': '🈵', + 'u7981': '🈲', + 'a': '🅰️', + 'b': '🅱️', + 'ab': '🆎', + 'cl': '🆑', + 'o2': '🅾️', + 'sos': '🆘', + 'no_entry': '⛔', + 'name_badge': '📛', + 'no_entry_sign': '🚫', + 'x': '❌', + 'o': '⭕', + 'stop_sign': '🛑', + 'anger': '💢', + 'hotsprings': '♨️', + 'no_pedestrians': '🚷', + 'do_not_litter': '🚯', + 'no_bicycles': '🚳', + 'non-potable_water': '🚱', + 'underage': '🔞', + 'no_mobile_phones': '📵', + 'exclamation': '❗', + 'grey_exclamation': '❕', + 'question': '❓', + 'grey_question': '❔', + 'bangbang': '‼️', + 'interrobang': '⁉️', + '100': '💯', + 'low_brightness': '🔅', + 'high_brightness': '🔆', + 'trident': '🔱', + 'fleur_de_lis': '⚜', + 'part_alternation_mark': '〽️', + 'warning': '⚠️', + 'children_crossing': '🚸', + 'beginner': '🔰', + 'recycle': '♻️', + 'u6307': '🈯', + 'chart': '💹', + 'sparkle': '❇️', + 'eight_spoked_asterisk': '✳️', + 'negative_squared_cross_mark': '❎', + 'white_check_mark': '✅', + 'diamond_shape_with_a_dot_inside': '💠', + 'cyclone': '🌀', + 'loop': '➿', + 'globe_with_meridians': '🌐', + 'm': 'Ⓜ️', + 'atm': '🏧', + 'sa': '🈂️', + 'passport_control': '🛂', + 'customs': '🛃', + 'baggage_claim': '🛄', + 'left_luggage': '🛅', + 'wheelchair': '♿', + 'no_smoking': '🚭', + 'wc': '🚾', + 'parking': '🅿️', + 'potable_water': '🚰', + 'mens': '🚹', + 'womens': '🚺', + 'baby_symbol': '🚼', + 'restroom': '🚻', + 'put_litter_in_its_place': '🚮', + 'cinema': '🎦', + 'signal_strength': '📶', + 'koko': '🈁', + 'ng': '🆖', + 'ok': '🆗', + 'up': '🆙', + 'cool': '🆒', + 'new': '🆕', + 'free': '🆓', + 'zero': '0️⃣', + 'one': '1️⃣', + 'two': '2️⃣', + 'three': '3️⃣', + 'four': '4️⃣', + 'five': '5️⃣', + 'six': '6️⃣', + 'seven': '7️⃣', + 'eight': '8️⃣', + 'nine': '9️⃣', + 'keycap_ten': '🔟', + 'asterisk': '*⃣', + '1234': '🔢', + 'eject_button': '⏏️', + 'arrow_forward': '▶️', + 'pause_button': '⏸', + 'next_track_button': '⏭', + 'stop_button': '⏹', + 'record_button': '⏺', + 'play_or_pause_button': '⏯', + 'previous_track_button': '⏮', + 'fast_forward': '⏩', + 'rewind': '⏪', + 'twisted_rightwards_arrows': '🔀', + 'repeat': '🔁', + 'repeat_one': '🔂', + 'arrow_backward': '◀️', + 'arrow_up_small': '🔼', + 'arrow_down_small': '🔽', + 'arrow_double_up': '⏫', + 'arrow_double_down': '⏬', + 'arrow_right': '➡️', + 'arrow_left': '⬅️', + 'arrow_up': '⬆️', + 'arrow_down': '⬇️', + 'arrow_upper_right': '↗️', + 'arrow_lower_right': '↘️', + 'arrow_lower_left': '↙️', + 'arrow_upper_left': '↖️', + 'arrow_up_down': '↕️', + 'left_right_arrow': '↔️', + 'arrows_counterclockwise': '🔄', + 'arrow_right_hook': '↪️', + 'leftwards_arrow_with_hook': '↩️', + 'arrow_heading_up': '⤴️', + 'arrow_heading_down': '⤵️', + 'hash': '#️⃣', + 'information_source': 'ℹ️', + 'abc': '🔤', + 'abcd': '🔡', + 'capital_abcd': '🔠', + 'symbols': '🔣', + 'musical_note': '🎵', + 'notes': '🎶', + 'wavy_dash': '〰️', + 'curly_loop': '➰', + 'heavy_check_mark': '✔️', + 'arrows_clockwise': '🔃', + 'heavy_plus_sign': '➕', + 'heavy_minus_sign': '➖', + 'heavy_division_sign': '➗', + 'heavy_multiplication_x': '✖️', + 'heavy_dollar_sign': '💲', + 'currency_exchange': '💱', + 'copyright': '©️', + 'registered': '®️', + 'tm': '™️', + 'end': '🔚', + 'back': '🔙', + 'on': '🔛', + 'top': '🔝', + 'soon': '🔜', + 'ballot_box_with_check': '☑️', + 'radio_button': '🔘', + 'white_circle': '⚪', + 'black_circle': '⚫', + 'red_circle': '🔴', + 'large_blue_circle': '🔵', + 'small_orange_diamond': '🔸', + 'small_blue_diamond': '🔹', + 'large_orange_diamond': '🔶', + 'large_blue_diamond': '🔷', + 'small_red_triangle': '🔺', + 'black_small_square': '▪️', + 'white_small_square': '▫️', + 'black_large_square': '⬛', + 'white_large_square': '⬜', + 'small_red_triangle_down': '🔻', + 'black_medium_square': '◼️', + 'white_medium_square': '◻️', + 'black_medium_small_square': '◾', + 'white_medium_small_square': '◽', + 'black_square_button': '🔲', + 'white_square_button': '🔳', + 'speaker': '🔈', + 'sound': '🔉', + 'loud_sound': '🔊', + 'mute': '🔇', + 'mega': '📣', + 'loudspeaker': '📢', + 'bell': '🔔', + 'no_bell': '🔕', + 'black_joker': '🃏', + 'mahjong': '🀄', + 'spades': '♠️', + 'clubs': '♣️', + 'hearts': '♥️', + 'diamonds': '♦️', + 'flower_playing_cards': '🎴', + 'thought_balloon': '💭', + 'right_anger_bubble': '🗯', + 'speech_balloon': '💬', + 'left_speech_bubble': '🗨', + 'clock1': '🕐', + 'clock2': '🕑', + 'clock3': '🕒', + 'clock4': '🕓', + 'clock5': '🕔', + 'clock6': '🕕', + 'clock7': '🕖', + 'clock8': '🕗', + 'clock9': '🕘', + 'clock10': '🕙', + 'clock11': '🕚', + 'clock12': '🕛', + 'clock130': '🕜', + 'clock230': '🕝', + 'clock330': '🕞', + 'clock430': '🕟', + 'clock530': '🕠', + 'clock630': '🕡', + 'clock730': '🕢', + 'clock830': '🕣', + 'clock930': '🕤', + 'clock1030': '🕥', + 'clock1130': '🕦', + 'clock1230': '🕧', + 'afghanistan': '🇦🇫', + 'aland_islands': '🇦🇽', + 'albania': '🇦🇱', + 'algeria': '🇩🇿', + 'american_samoa': '🇦🇸', + 'andorra': '🇦🇩', + 'angola': '🇦🇴', + 'anguilla': '🇦🇮', + 'antarctica': '🇦🇶', + 'antigua_barbuda': '🇦🇬', + 'argentina': '🇦🇷', + 'armenia': '🇦🇲', + 'aruba': '🇦🇼', + 'australia': '🇦🇺', + 'austria': '🇦🇹', + 'azerbaijan': '🇦🇿', + 'bahamas': '🇧🇸', + 'bahrain': '🇧🇭', + 'bangladesh': '🇧🇩', + 'barbados': '🇧🇧', + 'belarus': '🇧🇾', + 'belgium': '🇧🇪', + 'belize': '🇧🇿', + 'benin': '🇧🇯', + 'bermuda': '🇧🇲', + 'bhutan': '🇧🇹', + 'bolivia': '🇧🇴', + 'caribbean_netherlands': '🇧🇶', + 'bosnia_herzegovina': '🇧🇦', + 'botswana': '🇧🇼', + 'brazil': '🇧🇷', + 'british_indian_ocean_territory': '🇮🇴', + 'british_virgin_islands': '🇻🇬', + 'brunei': '🇧🇳', + 'bulgaria': '🇧🇬', + 'burkina_faso': '🇧🇫', + 'burundi': '🇧🇮', + 'cape_verde': '🇨🇻', + 'cambodia': '🇰🇭', + 'cameroon': '🇨🇲', + 'canada': '🇨🇦', + 'canary_islands': '🇮🇨', + 'cayman_islands': '🇰🇾', + 'central_african_republic': '🇨🇫', + 'chad': '🇹🇩', + 'chile': '🇨🇱', + 'cn': '🇨🇳', + 'christmas_island': '🇨🇽', + 'cocos_islands': '🇨🇨', + 'colombia': '🇨🇴', + 'comoros': '🇰🇲', + 'congo_brazzaville': '🇨🇬', + 'congo_kinshasa': '🇨🇩', + 'cook_islands': '🇨🇰', + 'costa_rica': '🇨🇷', + 'croatia': '🇭🇷', + 'cuba': '🇨🇺', + 'curacao': '🇨🇼', + 'cyprus': '🇨🇾', + 'czech_republic': '🇨🇿', + 'denmark': '🇩🇰', + 'djibouti': '🇩🇯', + 'dominica': '🇩🇲', + 'dominican_republic': '🇩🇴', + 'ecuador': '🇪🇨', + 'egypt': '🇪🇬', + 'el_salvador': '🇸🇻', + 'equatorial_guinea': '🇬🇶', + 'eritrea': '🇪🇷', + 'estonia': '🇪🇪', + 'ethiopia': '🇪🇹', + 'eu': '🇪🇺', + 'falkland_islands': '🇫🇰', + 'faroe_islands': '🇫🇴', + 'fiji': '🇫🇯', + 'finland': '🇫🇮', + 'fr': '🇫🇷', + 'french_guiana': '🇬🇫', + 'french_polynesia': '🇵🇫', + 'french_southern_territories': '🇹🇫', + 'gabon': '🇬🇦', + 'gambia': '🇬🇲', + 'georgia': '🇬🇪', + 'de': '🇩🇪', + 'ghana': '🇬🇭', + 'gibraltar': '🇬🇮', + 'greece': '🇬🇷', + 'greenland': '🇬🇱', + 'grenada': '🇬🇩', + 'guadeloupe': '🇬🇵', + 'guam': '🇬🇺', + 'guatemala': '🇬🇹', + 'guernsey': '🇬🇬', + 'guinea': '🇬🇳', + 'guinea_bissau': '🇬🇼', + 'guyana': '🇬🇾', + 'haiti': '🇭🇹', + 'honduras': '🇭🇳', + 'hong_kong': '🇭🇰', + 'hungary': '🇭🇺', + 'iceland': '🇮🇸', + 'india': '🇮🇳', + 'indonesia': '🇮🇩', + 'iran': '🇮🇷', + 'iraq': '🇮🇶', + 'ireland': '🇮🇪', + 'isle_of_man': '🇮🇲', + 'israel': '🇮🇱', + 'it': '🇮🇹', + 'cote_divoire': '🇨🇮', + 'jamaica': '🇯🇲', + 'jp': '🇯🇵', + 'jersey': '🇯🇪', + 'jordan': '🇯🇴', + 'kazakhstan': '🇰🇿', + 'kenya': '🇰🇪', + 'kiribati': '🇰🇮', + 'kosovo': '🇽🇰', + 'kuwait': '🇰🇼', + 'kyrgyzstan': '🇰🇬', + 'laos': '🇱🇦', + 'latvia': '🇱🇻', + 'lebanon': '🇱🇧', + 'lesotho': '🇱🇸', + 'liberia': '🇱🇷', + 'libya': '🇱🇾', + 'liechtenstein': '🇱🇮', + 'lithuania': '🇱🇹', + 'luxembourg': '🇱🇺', + 'macau': '🇲🇴', + 'macedonia': '🇲🇰', + 'madagascar': '🇲🇬', + 'malawi': '🇲🇼', + 'malaysia': '🇲🇾', + 'maldives': '🇲🇻', + 'mali': '🇲🇱', + 'malta': '🇲🇹', + 'marshall_islands': '🇲🇭', + 'martinique': '🇲🇶', + 'mauritania': '🇲🇷', + 'mauritius': '🇲🇺', + 'mayotte': '🇾🇹', + 'mexico': '🇲🇽', + 'micronesia': '🇫🇲', + 'moldova': '🇲🇩', + 'monaco': '🇲🇨', + 'mongolia': '🇲🇳', + 'montenegro': '🇲🇪', + 'montserrat': '🇲🇸', + 'morocco': '🇲🇦', + 'mozambique': '🇲🇿', + 'myanmar': '🇲🇲', + 'namibia': '🇳🇦', + 'nauru': '🇳🇷', + 'nepal': '🇳🇵', + 'netherlands': '🇳🇱', + 'new_caledonia': '🇳🇨', + 'new_zealand': '🇳🇿', + 'nicaragua': '🇳🇮', + 'niger': '🇳🇪', + 'nigeria': '🇳🇬', + 'niue': '🇳🇺', + 'norfolk_island': '🇳🇫', + 'northern_mariana_islands': '🇲🇵', + 'north_korea': '🇰🇵', + 'norway': '🇳🇴', + 'oman': '🇴🇲', + 'pakistan': '🇵🇰', + 'palau': '🇵🇼', + 'palestinian_territories': '🇵🇸', + 'panama': '🇵🇦', + 'papua_new_guinea': '🇵🇬', + 'paraguay': '🇵🇾', + 'peru': '🇵🇪', + 'philippines': '🇵🇭', + 'pitcairn_islands': '🇵🇳', + 'poland': '🇵🇱', + 'portugal': '🇵🇹', + 'puerto_rico': '🇵🇷', + 'qatar': '🇶🇦', + 'reunion': '🇷🇪', + 'romania': '🇷🇴', + 'ru': '🇷🇺', + 'rwanda': '🇷🇼', + 'st_barthelemy': '🇧🇱', + 'st_helena': '🇸🇭', + 'st_kitts_nevis': '🇰🇳', + 'st_lucia': '🇱🇨', + 'st_pierre_miquelon': '🇵🇲', + 'st_vincent_grenadines': '🇻🇨', + 'samoa': '🇼🇸', + 'san_marino': '🇸🇲', + 'sao_tome_principe': '🇸🇹', + 'saudi_arabia': '🇸🇦', + 'senegal': '🇸🇳', + 'serbia': '🇷🇸', + 'seychelles': '🇸🇨', + 'sierra_leone': '🇸🇱', + 'singapore': '🇸🇬', + 'sint_maarten': '🇸🇽', + 'slovakia': '🇸🇰', + 'slovenia': '🇸🇮', + 'solomon_islands': '🇸🇧', + 'somalia': '🇸🇴', + 'south_africa': '🇿🇦', + 'south_georgia_south_sandwich_islands': '🇬🇸', + 'kr': '🇰🇷', + 'south_sudan': '🇸🇸', + 'es': '🇪🇸', + 'sri_lanka': '🇱🇰', + 'sudan': '🇸🇩', + 'suriname': '🇸🇷', + 'swaziland': '🇸🇿', + 'sweden': '🇸🇪', + 'switzerland': '🇨🇭', + 'syria': '🇸🇾', + 'taiwan': '🇹🇼', + 'tajikistan': '🇹🇯', + 'tanzania': '🇹🇿', + 'thailand': '🇹🇭', + 'timor_leste': '🇹🇱', + 'togo': '🇹🇬', + 'tokelau': '🇹🇰', + 'tonga': '🇹🇴', + 'trinidad_tobago': '🇹🇹', + 'tunisia': '🇹🇳', + 'tr': '🇹🇷', + 'turkmenistan': '🇹🇲', + 'turks_caicos_islands': '🇹🇨', + 'tuvalu': '🇹🇻', + 'uganda': '🇺🇬', + 'ukraine': '🇺🇦', + 'united_arab_emirates': '🇦🇪', + 'uk': '🇬🇧', + 'england': '🏴󠁧󠁢󠁥󠁮󠁧󠁿', + 'scotland': '🏴󠁧󠁢󠁳󠁣󠁴󠁿', + 'wales': '🏴󠁧󠁢󠁷󠁬󠁳󠁿', + 'us': '🇺🇸', + 'us_virgin_islands': '🇻🇮', + 'uruguay': '🇺🇾', + 'uzbekistan': '🇺🇿', + 'vanuatu': '🇻🇺', + 'vatican_city': '🇻🇦', + 'venezuela': '🇻🇪', + 'vietnam': '🇻🇳', + 'wallis_futuna': '🇼🇫', + 'western_sahara': '🇪🇭', + 'yemen': '🇾🇪', + 'zambia': '🇿🇲', + 'zimbabwe': '🇿🇼', +}; diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/extension_set.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/extension_set.dart new file mode 100644 index 0000000000..eadda7bc05 --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/extension_set.dart @@ -0,0 +1,64 @@ +import 'block_parser.dart'; +import 'inline_parser.dart'; + +/// ExtensionSets provide a simple grouping mechanism for common Markdown +/// flavors. +/// +/// For example, the [gitHubFlavored] set of syntax extensions allows users to +/// output HTML from their Markdown in a similar fashion to GitHub's parsing. +class ExtensionSet { + ExtensionSet(this.blockSyntaxes, this.inlineSyntaxes); + + /// The [ExtensionSet.none] extension set renders Markdown similar to + /// [Markdown.pl]. + /// + /// However, this set does not render _exactly_ the same as Markdown.pl; + /// rather it is more-or-less the CommonMark standard of Markdown, without + /// fenced code blocks, or inline HTML. + /// + /// [Markdown.pl]: http://daringfireball.net/projects/markdown/syntax + static final ExtensionSet none = ExtensionSet([], []); + + /// The [commonMark] extension set is close to compliance with [CommonMark]. + /// + /// [CommonMark]: http://commonmark.org/ + static final ExtensionSet commonMark = + ExtensionSet([const FencedCodeBlockSyntax()], [InlineHtmlSyntax()]); + + /// The [gitHubWeb] extension set renders Markdown similarly to GitHub. + /// + /// This is different from the [gitHubFlavored] extension set in that GitHub + /// actually renders HTML different from straight [GitHub flavored Markdown]. + /// + /// (The only difference currently is that [gitHubWeb] renders headers with + /// linkable IDs.) + /// + /// [GitHub flavored Markdown]: https://github.github.com/gfm/ + static final ExtensionSet gitHubWeb = ExtensionSet([ + const FencedCodeBlockSyntax(), + const HeaderWithIdSyntax(), + const SetextHeaderWithIdSyntax(), + const TableSyntax() + ], [ + InlineHtmlSyntax(), + StrikethroughSyntax(), + EmojiSyntax(), + AutolinkExtensionSyntax(), + ]); + + /// The [gitHubFlavored] extension set is close to compliance with the [GitHub + /// flavored Markdown spec]. + /// + /// [GitHub flavored Markdown]: https://github.github.com/gfm/ + static final ExtensionSet gitHubFlavored = ExtensionSet([ + const FencedCodeBlockSyntax(), + const TableSyntax() + ], [ + InlineHtmlSyntax(), + StrikethroughSyntax(), + AutolinkExtensionSyntax(), + ]); + + final List blockSyntaxes; + final List inlineSyntaxes; +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/html_renderer.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/html_renderer.dart new file mode 100644 index 0000000000..d6630e297c --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/html_renderer.dart @@ -0,0 +1,121 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'ast.dart'; +import 'block_parser.dart'; +import 'document.dart'; +import 'extension_set.dart'; +import 'inline_parser.dart'; + +/// Converts the given string of Markdown to HTML. +String markdownToHtml(String markdown, + {Iterable? blockSyntaxes, + Iterable? inlineSyntaxes, + ExtensionSet? extensionSet, + Resolver? linkResolver, + Resolver? imageLinkResolver, + bool inlineOnly = false}) { + final document = Document( + blockSyntaxes: blockSyntaxes, + inlineSyntaxes: inlineSyntaxes, + extensionSet: extensionSet, + linkResolver: linkResolver, + imageLinkResolver: imageLinkResolver); + + if (inlineOnly) { + return renderToHtml(document.parseInline(markdown)!); + } + + // Replace windows line endings with unix line endings, and split. + final lines = markdown.replaceAll('\r\n', '\n').split('\n'); + + return '${renderToHtml(document.parseLines(lines))}\n'; +} + +/// Renders [nodes] to HTML. +String renderToHtml(List nodes) => HtmlRenderer().render(nodes); + +/// Translates a parsed AST to HTML. +class HtmlRenderer implements NodeVisitor { + HtmlRenderer(); + + static final _blockTags = RegExp('blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre'); + + late StringBuffer buffer; + late Set uniqueIds; + + String render(List nodes) { + buffer = StringBuffer(); + uniqueIds = {}; + + for (final node in nodes) { + node.accept(this); + } + + return buffer.toString(); + } + + @override + void visitText(Text text) { + buffer.write(text.text); + } + + @override + bool visitElementBefore(Element element) { + // Hackish. Separate block-level elements with newlines. + if (buffer.isNotEmpty && _blockTags.firstMatch(element.tag) != null) { + buffer.write('\n'); + } + + buffer.write('<${element.tag}'); + + // Sort the keys so that we generate stable output. + final attributeNames = element.attributes.keys.toList() + ..sort((a, b) => a.compareTo(b)); + + for (final name in attributeNames) { + buffer.write(' $name="${element.attributes[name]}"'); + } + + // attach header anchor ids generated from text + if (element.generatedId != null) { + buffer.write(' id="${uniquifyId(element.generatedId!)}"'); + } + + if (element.isEmpty) { + // Empty element like
. + buffer.write(' />'); + + if (element.tag == 'br') { + buffer.write('\n'); + } + + return false; + } else { + buffer.write('>'); + return true; + } + } + + @override + void visitElementAfter(Element element) { + buffer.write(''); + } + + /// Uniquifies an id generated from text. + String uniquifyId(String id) { + if (!uniqueIds.contains(id)) { + uniqueIds.add(id); + return id; + } + + var suffix = 2; + var suffixedId = '$id-$suffix'; + while (uniqueIds.contains(suffixedId)) { + suffixedId = '$id-${suffix++}'; + } + uniqueIds.add(suffixedId); + return suffixedId; + } +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/inline_parser.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/inline_parser.dart new file mode 100644 index 0000000000..0f15a99e4d --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/inline_parser.dart @@ -0,0 +1,1271 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:charcode/charcode.dart'; + +import 'ast.dart'; +import 'document.dart'; +import 'emojis.dart'; +import 'util.dart'; + +/// Maintains the internal state needed to parse inline span elements in +/// Markdown. +class InlineParser { + InlineParser(this.source, this.document) : _stack = [] { + // User specified syntaxes are the first syntaxes to be evaluated. + syntaxes.addAll(document.inlineSyntaxes); + + final documentHasCustomInlineSyntaxes = document.inlineSyntaxes + .any((s) => !document.extensionSet.inlineSyntaxes.contains(s)); + + // This first RegExp matches plain text to accelerate parsing. It's written + // so that it does not match any prefix of any following syntaxes. Most + // Markdown is plain text, so it's faster to match one RegExp per 'word' + // rather than fail to match all the following RegExps at each non-syntax + // character position. + if (documentHasCustomInlineSyntaxes) { + // We should be less aggressive in blowing past "words". + syntaxes.add(TextSyntax(r'[A-Za-z0-9]+(?=\s)')); + } else { + syntaxes.add(TextSyntax(r'[ \tA-Za-z0-9]*[A-Za-z0-9](?=\s)')); + } + + syntaxes + ..addAll(_defaultSyntaxes) + // Custom link resolvers go after the generic text syntax. + ..insertAll(1, [ + LinkSyntax(linkResolver: document.linkResolver), + ImageSyntax(linkResolver: document.imageLinkResolver) + ]); + } + + static final List _defaultSyntaxes = + List.unmodifiable([ + EmailAutolinkSyntax(), + AutolinkSyntax(), + LineBreakSyntax(), + LinkSyntax(), + ImageSyntax(), + // Allow any punctuation to be escaped. + EscapeSyntax(), + // "*" surrounded by spaces is left alone. + TextSyntax(r' \* '), + // "_" surrounded by spaces is left alone. + TextSyntax(r' _ '), + // Parse "**strong**" and "*emphasis*" tags. + TagSyntax(r'\*+', requiresDelimiterRun: true), + // Parse "__strong__" and "_emphasis_" tags. + TagSyntax(r'_+', requiresDelimiterRun: true), + CodeSyntax(), + // We will add the LinkSyntax once we know about the specific link resolver. + ]); + + /// The string of Markdown being parsed. + final String source; + + /// The Markdown document this parser is parsing. + final Document document; + + final List syntaxes = []; + + /// The current read position. + int pos = 0; + + /// Starting position of the last unconsumed text. + int start = 0; + + final List _stack; + + List? parse() { + // Make a fake top tag to hold the results. + _stack.add(TagState(0, 0, null, null)); + + while (!isDone) { + // See if any of the current tags on the stack match. This takes + // priority over other possible matches. + if (_stack.reversed + .any((state) => state.syntax != null && state.tryMatch(this))) { + continue; + } + + // See if the current text matches any defined markdown syntax. + if (syntaxes.any((syntax) => syntax.tryMatch(this))) { + continue; + } + + // If we got here, it's just text. + advanceBy(1); + } + + // Unwind any unmatched tags and get the results. + return _stack[0].close(this, null); + } + + int charAt(int index) => source.codeUnitAt(index); + + void writeText() { + writeTextRange(start, pos); + start = pos; + } + + void writeTextRange(int start, int end) { + if (end <= start) { + return; + } + + final text = source.substring(start, end); + final nodes = _stack.last.children; + + // If the previous node is text too, just append. + if (nodes.isNotEmpty && nodes.last is Text) { + final textNode = nodes.last as Text; + nodes[nodes.length - 1] = Text('${textNode.text}$text'); + } else { + nodes.add(Text(text)); + } + } + + /// Add [node] to the last [TagState] on the stack. + void addNode(Node node) { + _stack.last.children.add(node); + } + + /// Push [state] onto the stack of [TagState]s. + void openTag(TagState state) => _stack.add(state); + + bool get isDone => pos == source.length; + + void advanceBy(int length) { + pos += length; + } + + void consume(int length) { + pos += length; + start = pos; + } +} + +/// Represents one kind of Markdown tag that can be parsed. +abstract class InlineSyntax { + InlineSyntax(String pattern) : pattern = RegExp(pattern, multiLine: true); + + final RegExp pattern; + + /// Tries to match at the parser's current position. + /// + /// The parser's position can be overriden with [startMatchPos]. + /// Returns whether or not the pattern successfully matched. + bool tryMatch(InlineParser parser, [int? startMatchPos]) { + startMatchPos ??= parser.pos; + + final startMatch = pattern.matchAsPrefix(parser.source, startMatchPos); + if (startMatch == null) { + return false; + } + + // Write any existing plain text up to this point. + parser.writeText(); + + if (onMatch(parser, startMatch)) { + parser.consume(startMatch[0]!.length); + } + return true; + } + + /// Processes [match], adding nodes to [parser] and possibly advancing + /// [parser]. + /// + /// Returns whether the caller should advance [parser] by `match[0].length`. + bool onMatch(InlineParser parser, Match match); +} + +/// Represents a hard line break. +class LineBreakSyntax extends InlineSyntax { + LineBreakSyntax() : super(r'(?:\\| +)\n'); + + /// Create a void
element. + @override + bool onMatch(InlineParser parser, Match match) { + parser.addNode(Element.empty('br')); + return true; + } +} + +/// Matches stuff that should just be passed through as straight text. +class TextSyntax extends InlineSyntax { + TextSyntax(String pattern, {String? sub}) + : substitute = sub, + super(pattern); + + final String? substitute; + + @override + bool onMatch(InlineParser parser, Match match) { + if (substitute == null) { + // Just use the original matched text. + parser.advanceBy(match[0]!.length); + return false; + } + + // Insert the substitution. + parser.addNode(Text(substitute!)); + return true; + } +} + +/// Escape punctuation preceded by a backslash. +class EscapeSyntax extends InlineSyntax { + EscapeSyntax() : super(r'''\\[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~]'''); + + @override + bool onMatch(InlineParser parser, Match match) { + // Insert the substitution. + parser.addNode(Text(match[0]![1])); + return true; + } +} + +/// Leave inline HTML tags alone, from +/// [CommonMark 0.28](http://spec.commonmark.org/0.28/#raw-html). +/// +/// This is not actually a good definition (nor CommonMark's) of an HTML tag, +/// but it is fast. It will leave text like `]*)?>'); +} + +/// Matches autolinks like ``. +/// +/// See . +class EmailAutolinkSyntax extends InlineSyntax { + EmailAutolinkSyntax() : super('<($_email)>'); + + static const _email = + r'''[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}''' + r'''[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*'''; + + @override + bool onMatch(InlineParser parser, Match match) { + final url = match[1]!; + final anchor = Element.text('a', escapeHtml(url)); + anchor.attributes['href'] = Uri.encodeFull('mailto:$url'); + parser.addNode(anchor); + + return true; + } +} + +/// Matches autolinks like ``. +class AutolinkSyntax extends InlineSyntax { + AutolinkSyntax() : super(r'<(([a-zA-Z][a-zA-Z\-\+\.]+):(?://)?[^\s>]*)>'); + + @override + bool onMatch(InlineParser parser, Match match) { + final url = match[1]!; + final anchor = Element.text('a', escapeHtml(url)); + anchor.attributes['href'] = Uri.encodeFull(url); + parser.addNode(anchor); + + return true; + } +} + +/// Matches autolinks like `http://foo.com`. +class AutolinkExtensionSyntax extends InlineSyntax { + AutolinkExtensionSyntax() : super('$start(($scheme)($domain)($path))'); + + /// Broken up parts of the autolink regex for reusability and readability + + // Autolinks can only come at the beginning of a line, after whitespace, or + // any of the delimiting characters *, _, ~, and (. + static const start = r'(?:^|[\s*_~(>])'; + // An extended url autolink will be recognized when one of the schemes + // http://, https://, or ftp://, followed by a valid domain + static const scheme = r'(?:(?:https?|ftp):\/\/|www\.)'; + // A valid domain consists of alphanumeric characters, underscores (_), + // hyphens (-) and periods (.). There must be at least one period, and no + // underscores may be present in the last two segments of the domain. + static const domainPart = r'\w\-'; + static const domain = '[$domainPart][$domainPart.]+'; + // A valid domain consists of alphanumeric characters, underscores (_), + // hyphens (-) and periods (.). + static const path = r'[^\s<]*'; + // Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will not + // be considered part of the autolink + static const truncatingPunctuationPositive = r'[?!.,:*_~]'; + + static final regExpTrailingPunc = + RegExp('$truncatingPunctuationPositive*' r'$'); + static final regExpEndsWithColon = RegExp(r'\&[a-zA-Z0-9]+;$'); + static final regExpWhiteSpace = RegExp(r'\s'); + + @override + bool tryMatch(InlineParser parser, [int? startMatchPos]) { + return super.tryMatch(parser, parser.pos > 0 ? parser.pos - 1 : 0); + } + + @override + bool onMatch(InlineParser parser, Match match) { + var url = match[1]!; + var href = url; + var matchLength = url.length; + + if (url[0] == '>' || url.startsWith(regExpWhiteSpace)) { + url = url.substring(1, url.length - 1); + href = href.substring(1, href.length - 1); + parser.pos++; + matchLength--; + } + + // Prevent accidental standard autolink matches + if (url.endsWith('>') && parser.source[parser.pos - 1] == '<') { + return false; + } + + // When an autolink ends in ), we scan the entire autolink for the total + // number of parentheses. If there is a greater number of closing + // parentheses than opening ones, we don’t consider the last character + // part of the autolink, in order to facilitate including an autolink + // inside a parenthesis: + // https://github.github.com/gfm/#example-600 + if (url.endsWith(')')) { + final opening = _countChars(url, '('); + final closing = _countChars(url, ')'); + + if (closing > opening) { + url = url.substring(0, url.length - 1); + href = href.substring(0, href.length - 1); + matchLength--; + } + } + + // Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will + // not be considered part of the autolink, though they may be included + // in the interior of the link: + // https://github.github.com/gfm/#example-599 + final trailingPunc = regExpTrailingPunc.firstMatch(url); + if (trailingPunc != null) { + url = url.substring(0, url.length - trailingPunc[0]!.length); + href = href.substring(0, href.length - trailingPunc[0]!.length); + matchLength -= trailingPunc[0]!.length; + } + + // If an autolink ends in a semicolon (;), we check to see if it appears + // to resemble an + // [entity reference](https://github.github.com/gfm/#entity-references); + // if the preceding text is & followed by one or more alphanumeric + // characters. If so, it is excluded from the autolink: + // https://github.github.com/gfm/#example-602 + if (url.endsWith(';')) { + final entityRef = regExpEndsWithColon.firstMatch(url); + if (entityRef != null) { + // Strip out HTML entity reference + url = url.substring(0, url.length - entityRef[0]!.length); + href = href.substring(0, href.length - entityRef[0]!.length); + matchLength -= entityRef[0]!.length; + } + } + + // The scheme http will be inserted automatically + if (!href.startsWith('http://') && + !href.startsWith('https://') && + !href.startsWith('ftp://')) { + href = 'http://$href'; + } + + final anchor = Element.text('a', escapeHtml(url)); + anchor.attributes['href'] = Uri.encodeFull(href); + parser + ..addNode(anchor) + ..consume(matchLength); + return false; + } + + int _countChars(String input, String char) { + var count = 0; + + for (var i = 0; i < input.length; i++) { + if (input[i] == char) { + count++; + } + } + + return count; + } +} + +class _DelimiterRun { + _DelimiterRun._( + {this.char, + this.length, + this.isLeftFlanking, + this.isRightFlanking, + this.isPrecededByPunctuation, + this.isFollowedByPunctuation}); + + static const String punctuation = r'''!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~'''; + // TODO(srawlins): Unicode whitespace + static const String whitespace = ' \t\r\n'; + + final int? char; + final int? length; + final bool? isLeftFlanking; + final bool? isRightFlanking; + final bool? isPrecededByPunctuation; + final bool? isFollowedByPunctuation; + + // ignore: prefer_constructors_over_static_methods + static _DelimiterRun? tryParse( + InlineParser parser, int runStart, int runEnd) { + bool leftFlanking, + rightFlanking, + precededByPunctuation, + followedByPunctuation; + String preceding, following; + if (runStart == 0) { + rightFlanking = false; + preceding = '\n'; + } else { + preceding = parser.source.substring(runStart - 1, runStart); + } + precededByPunctuation = punctuation.contains(preceding); + + if (runEnd == parser.source.length - 1) { + leftFlanking = false; + following = '\n'; + } else { + following = parser.source.substring(runEnd + 1, runEnd + 2); + } + followedByPunctuation = punctuation.contains(following); + + // http://spec.commonmark.org/0.28/#left-flanking-delimiter-run + if (whitespace.contains(following)) { + leftFlanking = false; + } else { + leftFlanking = !followedByPunctuation || + whitespace.contains(preceding) || + precededByPunctuation; + } + + // http://spec.commonmark.org/0.28/#right-flanking-delimiter-run + if (whitespace.contains(preceding)) { + rightFlanking = false; + } else { + rightFlanking = !precededByPunctuation || + whitespace.contains(following) || + followedByPunctuation; + } + + if (!leftFlanking && !rightFlanking) { + // Could not parse a delimiter run. + return null; + } + + return _DelimiterRun._( + char: parser.charAt(runStart), + length: runEnd - runStart + 1, + isLeftFlanking: leftFlanking, + isRightFlanking: rightFlanking, + isPrecededByPunctuation: precededByPunctuation, + isFollowedByPunctuation: followedByPunctuation); + } + + @override + String toString() => + ''; + + // Whether a delimiter in this run can open emphasis or strong emphasis. + bool get canOpen => + isLeftFlanking! && + (char == $asterisk || !isRightFlanking! || isPrecededByPunctuation!); + + // Whether a delimiter in this run can close emphasis or strong emphasis. + bool get canClose => + isRightFlanking! && + (char == $asterisk || !isLeftFlanking! || isFollowedByPunctuation!); +} + +/// Matches syntax that has a pair of tags and becomes an element, like `*` for +/// ``. Allows nested tags. +class TagSyntax extends InlineSyntax { + TagSyntax(String pattern, {String? end, this.requiresDelimiterRun = false}) + : endPattern = RegExp((end != null) ? end : pattern, multiLine: true), + super(pattern); + + final RegExp endPattern; + + /// Whether this is parsed according to the same nesting rules as [emphasis + /// delimiters][]. + /// + /// [emphasis delimiters]: http://spec.commonmark.org/0.28/#can-open-emphasis + final bool requiresDelimiterRun; + + @override + bool onMatch(InlineParser parser, Match match) { + final runLength = match.group(0)!.length; + final matchStart = parser.pos; + final matchEnd = parser.pos + runLength - 1; + if (!requiresDelimiterRun) { + parser.openTag(TagState(parser.pos, matchEnd + 1, this, null)); + return true; + } + + final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd); + if (delimiterRun != null && delimiterRun.canOpen) { + parser.openTag(TagState(parser.pos, matchEnd + 1, this, delimiterRun)); + return true; + } else { + parser.advanceBy(runLength); + return false; + } + } + + bool onMatchEnd(InlineParser parser, Match match, TagState state) { + final runLength = match.group(0)!.length; + final matchStart = parser.pos; + final matchEnd = parser.pos + runLength - 1; + final openingRunLength = state.endPos - state.startPos; + final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd); + + if (openingRunLength == 1 && runLength == 1) { + parser.addNode(Element('em', state.children)); + } else if (openingRunLength == 1 && runLength > 1) { + parser + ..addNode(Element('em', state.children)) + ..pos = parser.pos - (runLength - 1) + ..start = parser.pos; + } else if (openingRunLength > 1 && runLength == 1) { + parser + ..openTag( + TagState(state.startPos, state.endPos - 1, this, delimiterRun)) + ..addNode(Element('em', state.children)); + } else if (openingRunLength == 2 && runLength == 2) { + parser.addNode(Element('strong', state.children)); + } else if (openingRunLength == 2 && runLength > 2) { + parser + ..addNode(Element('strong', state.children)) + ..pos = parser.pos - (runLength - 2) + ..start = parser.pos; + } else if (openingRunLength > 2 && runLength == 2) { + parser + ..openTag( + TagState(state.startPos, state.endPos - 2, this, delimiterRun)) + ..addNode(Element('strong', state.children)); + } else if (openingRunLength > 2 && runLength > 2) { + parser + ..openTag( + TagState(state.startPos, state.endPos - 2, this, delimiterRun)) + ..addNode(Element('strong', state.children)) + ..pos = parser.pos - (runLength - 2) + ..start = parser.pos; + } + + return true; + } +} + +/// Matches strikethrough syntax according to the GFM spec. +class StrikethroughSyntax extends TagSyntax { + StrikethroughSyntax() : super('~+', requiresDelimiterRun: true); + + @override + bool onMatchEnd(InlineParser parser, Match match, TagState state) { + final runLength = match.group(0)!.length; + final matchStart = parser.pos; + final matchEnd = parser.pos + runLength - 1; + final delimiterRun = _DelimiterRun.tryParse(parser, matchStart, matchEnd)!; + if (!delimiterRun.isRightFlanking!) { + return false; + } + + parser.addNode(Element('del', state.children)); + return true; + } +} + +/// Matches links like `[blah][label]` and `[blah](url)`. +class LinkSyntax extends TagSyntax { + LinkSyntax({Resolver? linkResolver, String pattern = r'\['}) + : linkResolver = (linkResolver ?? (_, [__]) => null), + super(pattern, end: r'\]'); + + static final _entirelyWhitespacePattern = RegExp(r'^\s*$'); + + final Resolver linkResolver; + + // The pending [TagState]s, all together, are "active" or "inactive" based on + // whether a link element has just been parsed. + // + // Links cannot be nested, so we must "deactivate" any pending ones. For + // example, take the following text: + // + // Text [link and [more](links)](links). + // + // Once we have parsed `Text [`, there is one (pending) link in the state + // stack. It is, by default, active. Once we parse the next possible link, + // `[more](links)`, as a real link, we must deactive the pending links (just + // the one, in this case). + var _pendingStatesAreActive = true; + + @override + bool onMatch(InlineParser parser, Match match) { + final matched = super.onMatch(parser, match); + if (!matched) { + return false; + } + + _pendingStatesAreActive = true; + + return true; + } + + @override + bool onMatchEnd(InlineParser parser, Match match, TagState state) { + if (!_pendingStatesAreActive) { + return false; + } + + final text = parser.source.substring(state.endPos, parser.pos); + // The current character is the `]` that closed the link text. Examine the + // next character, to determine what type of link we might have (a '(' + // means a possible inline link; otherwise a possible reference link). + if (parser.pos + 1 >= parser.source.length) { + // In this case, the Markdown document may have ended with a shortcut + // reference link. + + return _tryAddReferenceLink(parser, state, text); + } + // Peek at the next character; don't advance, so as to avoid later stepping + // backward. + final char = parser.charAt(parser.pos + 1); + + if (char == $lparen) { + // Maybe an inline link, like `[text](destination)`. + parser.advanceBy(1); + final leftParenIndex = parser.pos; + final inlineLink = _parseInlineLink(parser); + if (inlineLink != null) { + return _tryAddInlineLink(parser, state, inlineLink); + } + + // Reset the parser position. + parser + ..pos = leftParenIndex + + // At this point, we've matched `[...](`, but that `(` did not pan out + // to be an inline link. We must now check if `[...]` is simply a + // shortcut reference link. + ..advanceBy(-1); + return _tryAddReferenceLink(parser, state, text); + } + + if (char == $lbracket) { + parser.advanceBy(1); + // At this point, we've matched `[...][`. Maybe a *full* reference link, + // like `[foo][bar]` or a *collapsed* reference link, like `[foo][]`. + if (parser.pos + 1 < parser.source.length && + parser.charAt(parser.pos + 1) == $rbracket) { + // That opening `[` is not actually part of the link. Maybe a + // *shortcut* reference link (followed by a `[`). + parser.advanceBy(1); + return _tryAddReferenceLink(parser, state, text); + } + final label = _parseReferenceLinkLabel(parser); + if (label != null) { + return _tryAddReferenceLink(parser, state, label); + } + return false; + } + + // The link text (inside `[...]`) was not followed with a opening `(` nor + // an opening `[`. Perhaps just a simple shortcut reference link (`[...]`). + + return _tryAddReferenceLink(parser, state, text); + } + + /// Resolve a possible reference link. + /// + /// Uses [linkReferences], [linkResolver], and [_createNode] to try to + /// resolve [label] and [state] into a [Node]. If [label] is defined in + /// [linkReferences] or can be resolved by [linkResolver], returns a [Node] + /// that links to the resolved URL. + /// + /// Otherwise, returns `null`. + /// + /// [label] does not need to be normalized. + Node? _resolveReferenceLink( + String label, TagState state, Map linkReferences) { + final normalizedLabel = label.toLowerCase(); + final linkReference = linkReferences[normalizedLabel]; + if (linkReference != null) { + return _createNode(state, linkReference.destination, linkReference.title); + } else { + // This link has no reference definition. But we allow users of the + // library to specify a custom resolver function ([linkResolver]) that + // may choose to handle this. Otherwise, it's just treated as plain + // text. + + // Normally, label text does not get parsed as inline Markdown. However, + // for the benefit of the link resolver, we need to at least escape + // brackets, so that, e.g. a link resolver can receive `[\[\]]` as `[]`. + return linkResolver(label + .replaceAll(r'\\', r'\') + .replaceAll(r'\[', '[') + .replaceAll(r'\]', ']')); + } + } + + /// Create the node represented by a Markdown link. + Node _createNode(TagState state, String destination, String? title) { + final element = Element('a', state.children); + element.attributes['href'] = escapeAttribute(destination); + if (title != null && title.isNotEmpty) { + element.attributes['title'] = escapeAttribute(title); + } + return element; + } + + // Add a reference link node to [parser]'s AST. + // + // Returns whether the link was added successfully. + bool _tryAddReferenceLink(InlineParser parser, TagState state, String label) { + final element = + _resolveReferenceLink(label, state, parser.document.linkReferences); + if (element == null) { + return false; + } + parser + ..addNode(element) + ..start = parser.pos; + _pendingStatesAreActive = false; + return true; + } + + // Add an inline link node to [parser]'s AST. + // + // Returns whether the link was added successfully. + bool _tryAddInlineLink(InlineParser parser, TagState state, InlineLink link) { + final element = _createNode(state, link.destination, link.title); + parser + ..addNode(element) + ..start = parser.pos; + _pendingStatesAreActive = false; + return true; + } + + /// Parse a reference link label at the current position. + /// + /// Specifically, [parser.pos] is expected to be pointing at the `[` which + /// opens the link label. + /// + /// Returns the label if it could be parsed, or `null` if not. + String? _parseReferenceLinkLabel(InlineParser parser) { + // Walk past the opening `[`. + parser.advanceBy(1); + if (parser.isDone) { + return null; + } + + final buffer = StringBuffer(); + while (true) { + final char = parser.charAt(parser.pos); + if (char == $backslash) { + parser.advanceBy(1); + final next = parser.charAt(parser.pos); + if (next != $backslash && next != $rbracket) { + buffer.writeCharCode(char); + } + buffer.writeCharCode(next); + } else if (char == $rbracket) { + break; + } else { + buffer.writeCharCode(char); + } + parser.advanceBy(1); + if (parser.isDone) { + return null; + } + // TODO(srawlins): only check 999 characters, for performance reasons? + } + + final label = buffer.toString(); + + // A link label must contain at least one non-whitespace character. + if (_entirelyWhitespacePattern.hasMatch(label)) { + return null; + } + + return label; + } + + /// Parse an inline [InlineLink] at the current position. + /// + /// At this point, we have parsed a link's (or image's) opening `[`, and then + /// a matching closing `]`, and [parser.pos] is pointing at an opening `(`. + /// This method will then attempt to parse a link destination wrapped in `<>`, + /// such as `()`, or a bare link destination, such as + /// `(http://url)`, or a link destination with a title, such as + /// `(http://url "title")`. + /// + /// Returns the [InlineLink] if one was parsed, or `null` if not. + InlineLink? _parseInlineLink(InlineParser parser) { + // Start walking to the character just after the opening `(`. + parser.advanceBy(1); + + _moveThroughWhitespace(parser); + if (parser.isDone) { + return null; // EOF. Not a link. + } + + if (parser.charAt(parser.pos) == $lt) { + // Maybe a `<...>`-enclosed link destination. + return _parseInlineBracketedLink(parser); + } else { + return _parseInlineBareDestinationLink(parser); + } + } + + /// Parse an inline link with a bracketed destination (a destination wrapped + /// in `<...>`). The current position of the parser must be the first + /// character of the destination. + InlineLink? _parseInlineBracketedLink(InlineParser parser) { + parser.advanceBy(1); + + final buffer = StringBuffer(); + while (true) { + final char = parser.charAt(parser.pos); + if (char == $backslash) { + parser.advanceBy(1); + final next = parser.charAt(parser.pos); + if (char == $space || char == $lf || char == $cr || char == $ff) { + // Not a link (no whitespace allowed within `<...>`). + return null; + } + // TODO: Follow the backslash spec better here. + // http://spec.commonmark.org/0.28/#backslash-escapes + if (next != $backslash && next != $gt) { + buffer.writeCharCode(char); + } + buffer.writeCharCode(next); + } else if (char == $space || char == $lf || char == $cr || char == $ff) { + // Not a link (no whitespace allowed within `<...>`). + return null; + } else if (char == $gt) { + break; + } else { + buffer.writeCharCode(char); + } + parser.advanceBy(1); + if (parser.isDone) { + return null; + } + } + final destination = buffer.toString(); + + parser.advanceBy(1); + final char = parser.charAt(parser.pos); + if (char == $space || char == $lf || char == $cr || char == $ff) { + final title = _parseTitle(parser); + if (title == null && parser.charAt(parser.pos) != $rparen) { + // This looked like an inline link, until we found this $space + // followed by mystery characters; no longer a link. + return null; + } + return InlineLink(destination, title: title); + } else if (char == $rparen) { + return InlineLink(destination); + } else { + // We parsed something like `[foo](X`. Not a link. + return null; + } + } + + /// Parse an inline link with a "bare" destination (a destination _not_ + /// wrapped in `<...>`). The current position of the parser must be the first + /// character of the destination. + InlineLink? _parseInlineBareDestinationLink(InlineParser parser) { + // According to + // [CommonMark](http://spec.commonmark.org/0.28/#link-destination): + // + // > A link destination consists of [...] a nonempty sequence of + // > characters [...], and includes parentheses only if (a) they are + // > backslash-escaped or (b) they are part of a balanced pair of + // > unescaped parentheses. + // + // We need to count the open parens. We start with 1 for the paren that + // opened the destination. + var parenCount = 1; + final buffer = StringBuffer(); + + while (true) { + final char = parser.charAt(parser.pos); + switch (char) { + case $backslash: + parser.advanceBy(1); + if (parser.isDone) { + return null; // EOF. Not a link. + } + + final next = parser.charAt(parser.pos); + // Parentheses may be escaped. + // + // http://spec.commonmark.org/0.28/#example-467 + if (next != $backslash && next != $lparen && next != $rparen) { + buffer.writeCharCode(char); + } + buffer.writeCharCode(next); + break; + + case $space: + case $lf: + case $cr: + case $ff: + final destination = buffer.toString(); + final title = _parseTitle(parser); + if (title == null && parser.charAt(parser.pos) != $rparen) { + // This looked like an inline link, until we found this $space + // followed by mystery characters; no longer a link. + return null; + } + // [_parseTitle] made sure the title was follwed by a closing `)` + // (but it's up to the code here to examine the balance of + // parentheses). + parenCount--; + if (parenCount == 0) { + return InlineLink(destination, title: title); + } + break; + + case $lparen: + parenCount++; + buffer.writeCharCode(char); + break; + + case $rparen: + parenCount--; + // ignore: invariant_booleans + if (parenCount == 0) { + final destination = buffer.toString(); + return InlineLink(destination); + } + buffer.writeCharCode(char); + break; + + default: + buffer.writeCharCode(char); + } + parser.advanceBy(1); + if (parser.isDone) { + return null; // EOF. Not a link. + } + } + } + + // Walk the parser forward through any whitespace. + void _moveThroughWhitespace(InlineParser parser) { + while (true) { + final char = parser.charAt(parser.pos); + if (char != $space && + char != $tab && + char != $lf && + char != $vt && + char != $cr && + char != $ff) { + return; + } + parser.advanceBy(1); + if (parser.isDone) { + return; + } + } + } + + // Parse a link title in [parser] at it's current position. The parser's + // current position should be a whitespace character that followed a link + // destination. + String? _parseTitle(InlineParser parser) { + _moveThroughWhitespace(parser); + if (parser.isDone) { + return null; + } + + // The whitespace should be followed by a title delimiter. + final delimiter = parser.charAt(parser.pos); + if (delimiter != $apostrophe && + delimiter != $quote && + delimiter != $lparen) { + return null; + } + + final closeDelimiter = delimiter == $lparen ? $rparen : delimiter; + parser.advanceBy(1); + + // Now we look for an un-escaped closing delimiter. + final buffer = StringBuffer(); + while (true) { + final char = parser.charAt(parser.pos); + if (char == $backslash) { + parser.advanceBy(1); + final next = parser.charAt(parser.pos); + if (next != $backslash && next != closeDelimiter) { + buffer.writeCharCode(char); + } + buffer.writeCharCode(next); + } else if (char == closeDelimiter) { + break; + } else { + buffer.writeCharCode(char); + } + parser.advanceBy(1); + if (parser.isDone) { + return null; + } + } + final title = buffer.toString(); + + // Advance past the closing delimiter. + parser.advanceBy(1); + if (parser.isDone) { + return null; + } + _moveThroughWhitespace(parser); + if (parser.isDone) { + return null; + } + if (parser.charAt(parser.pos) != $rparen) { + return null; + } + return title; + } +} + +/// Matches images like `![alternate text](url "optional title")` and +/// `![alternate text][label]`. +class ImageSyntax extends LinkSyntax { + ImageSyntax({Resolver? linkResolver}) + : super(linkResolver: linkResolver, pattern: r'!\['); + + @override + Node _createNode(TagState state, String destination, String? title) { + final element = Element.empty('img'); + element.attributes['src'] = escapeHtml(destination); + element.attributes['alt'] = state.textContent; + if (title != null && title.isNotEmpty) { + element.attributes['title'] = escapeAttribute(title); + } + return element; + } + + // Add an image node to [parser]'s AST. + // + // If [label] is present, the potential image is treated as a reference image. + // Otherwise, it is treated as an inline image. + // + // Returns whether the image was added successfully. + @override + bool _tryAddReferenceLink(InlineParser parser, TagState state, String label) { + final element = + _resolveReferenceLink(label, state, parser.document.linkReferences); + if (element == null) { + return false; + } + parser + ..addNode(element) + ..start = parser.pos; + return true; + } +} + +/// Matches backtick-enclosed inline code blocks. +class CodeSyntax extends InlineSyntax { + CodeSyntax() : super(_pattern); + + // This pattern matches: + // + // * a string of backticks (not followed by any more), followed by + // * a non-greedy string of anything, including newlines, ending with anything + // except a backtick, followed by + // * a string of backticks the same length as the first, not followed by any + // more. + // + // This conforms to the delimiters of inline code, both in Markdown.pl, and + // CommonMark. + static const String _pattern = r'(`+(?!`))((?:.|\n)*?[^`])\1(?!`)'; + + @override + bool tryMatch(InlineParser parser, [int? startMatchPos]) { + if (parser.pos > 0 && parser.charAt(parser.pos - 1) == $backquote) { + // Not really a match! We can't just sneak past one backtick to try the + // next character. An example of this situation would be: + // + // before ``` and `` after. + // ^--parser.pos + return false; + } + + final match = pattern.matchAsPrefix(parser.source, parser.pos); + if (match == null) { + return false; + } + parser.writeText(); + if (onMatch(parser, match)) { + parser.consume(match[0]!.length); + } + return true; + } + + @override + bool onMatch(InlineParser parser, Match match) { + parser.addNode(Element.text('code', escapeHtml(match[2]!.trim()))); + return true; + } +} + +/// Matches GitHub Markdown emoji syntax like `:smile:`. +/// +/// There is no formal specification of GitHub's support for this colon-based +/// emoji support, so this syntax is based on the results of Markdown-enabled +/// text fields at github.com. +class EmojiSyntax extends InlineSyntax { + // Emoji "aliases" are mostly limited to lower-case letters, numbers, and + // underscores, but GitHub also supports `:+1:` and `:-1:`. + EmojiSyntax() : super(':([a-z0-9_+-]+):'); + + @override + bool onMatch(InlineParser parser, Match match) { + final alias = match[1]; + final emoji = emojis[alias!]; + if (emoji == null) { + parser.advanceBy(1); + return false; + } + parser.addNode(Text(emoji)); + + return true; + } +} + +/// Keeps track of a currently open tag while it is being parsed. +/// +/// The parser maintains a stack of these so it can handle nested tags. +class TagState { + TagState(this.startPos, this.endPos, this.syntax, this.openingDelimiterRun) + : children = []; + + /// The point in the original source where this tag started. + final int startPos; + + /// The point in the original source where open tag ended. + final int endPos; + + /// The syntax that created this node. + final TagSyntax? syntax; + + /// The children of this node. Will be `null` for text nodes. + final List children; + + final _DelimiterRun? openingDelimiterRun; + + /// Attempts to close this tag by matching the current text against its end + /// pattern. + bool tryMatch(InlineParser parser) { + final endMatch = + syntax!.endPattern.matchAsPrefix(parser.source, parser.pos); + if (endMatch == null) { + return false; + } + + if (!syntax!.requiresDelimiterRun) { + // Close the tag. + close(parser, endMatch); + return true; + } + + // TODO: Move this logic into TagSyntax. + final runLength = endMatch.group(0)!.length; + final openingRunLength = endPos - startPos; + final closingMatchStart = parser.pos; + final closingMatchEnd = parser.pos + runLength - 1; + final closingDelimiterRun = + _DelimiterRun.tryParse(parser, closingMatchStart, closingMatchEnd); + if (closingDelimiterRun != null && closingDelimiterRun.canClose) { + // Emphasis rules #9 and #10: + final oneRunOpensAndCloses = + (openingDelimiterRun!.canOpen && openingDelimiterRun!.canClose) || + (closingDelimiterRun.canOpen && closingDelimiterRun.canClose); + if (oneRunOpensAndCloses && + (openingRunLength + closingDelimiterRun.length!) % 3 == 0) { + return false; + } + // Close the tag. + close(parser, endMatch); + return true; + } else { + return false; + } + } + + /// Pops this tag off the stack, completes it, and adds it to the output. + /// + /// Will discard any unmatched tags that happen to be above it on the stack. + /// If this is the last node in the stack, returns its children. + List? close(InlineParser parser, Match? endMatch) { + // If there are unclosed tags on top of this one when it's closed, that + // means they are mismatched. Mismatched tags are treated as plain text in + // markdown. So for each tag above this one, we write its start tag as text + // and then adds its children to this one's children. + final index = parser._stack.indexOf(this); + + // Remove the unmatched children. + final unmatchedTags = parser._stack.sublist(index + 1); + parser._stack.removeRange(index + 1, parser._stack.length); + + // Flatten them out onto this tag. + for (final unmatched in unmatchedTags) { + // Write the start tag as text. + parser.writeTextRange(unmatched.startPos, unmatched.endPos); + + // Bequeath its children unto this tag. + children.addAll(unmatched.children); + } + + // Pop this off the stack. + parser.writeText(); + parser._stack.removeLast(); + + // If the stack is empty now, this is the special "results" node. + if (parser._stack.isEmpty) { + return children; + } + final endMatchIndex = parser.pos; + + // We are still parsing, so add this to its parent's children. + if (syntax!.onMatchEnd(parser, endMatch!, this)) { + parser.consume(endMatch[0]!.length); + } else { + // Didn't close correctly so revert to text. + parser + ..writeTextRange(startPos, endPos) + .._stack.last.children.addAll(children) + ..pos = endMatchIndex + ..advanceBy(endMatch[0]!.length); + } + + return null; + } + + String get textContent => children.map((child) => child.textContent).join(); +} + +class InlineLink { + InlineLink(this.destination, {this.title}); + + final String destination; + final String? title; +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/util.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/util.dart new file mode 100644 index 0000000000..aed4c3c3fc --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/util.dart @@ -0,0 +1,71 @@ +import 'dart:convert'; + +import 'package:charcode/charcode.dart'; + +String escapeHtml(String html) => + const HtmlEscape(HtmlEscapeMode.element).convert(html); + +// Escape the contents of [value], so that it may be used as an HTML attribute. + +// Based on http://spec.commonmark.org/0.28/#backslash-escapes. +String escapeAttribute(String value) { + final result = StringBuffer(); + int ch; + for (var i = 0; i < value.codeUnits.length; i++) { + ch = value.codeUnitAt(i); + if (ch == $backslash) { + i++; + if (i == value.codeUnits.length) { + result.writeCharCode(ch); + break; + } + ch = value.codeUnitAt(i); + switch (ch) { + case $quote: + result.write('"'); + break; + case $exclamation: + case $hash: + case $dollar: + case $percent: + case $ampersand: + case $apostrophe: + case $lparen: + case $rparen: + case $asterisk: + case $plus: + case $comma: + case $dash: + case $dot: + case $slash: + case $colon: + case $semicolon: + case $lt: + case $equal: + case $gt: + case $question: + case $at: + case $lbracket: + case $backslash: + case $rbracket: + case $caret: + case $underscore: + case $backquote: + case $lbrace: + case $bar: + case $rbrace: + case $tilde: + result.writeCharCode(ch); + break; + default: + result.write('%5C'); + result.writeCharCode(ch); + } + } else if (ch == $quote) { + result.write('%22'); + } else { + result.writeCharCode(ch); + } + } + return result.toString(); +} diff --git a/app_flowy/lib/workspace/infrastructure/markdown/src/version.dart b/app_flowy/lib/workspace/infrastructure/markdown/src/version.dart new file mode 100644 index 0000000000..19433ffa4a --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/markdown/src/version.dart @@ -0,0 +1,2 @@ +// Generated code. Do not modify. +const packageVersion = '0.0.2'; diff --git a/app_flowy/lib/workspace/infrastructure/repos/share_repo.dart b/app_flowy/lib/workspace/infrastructure/repos/share_repo.dart new file mode 100644 index 0000000000..de9482859f --- /dev/null +++ b/app_flowy/lib/workspace/infrastructure/repos/share_repo.dart @@ -0,0 +1,15 @@ +import 'dart:async'; +import 'package:dartz/dartz.dart'; +import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace-infra/protobuf.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; + +class ShareRepo { + Future> export(String docId, ExportType type) { + final request = ExportRequest.create() + ..docId = docId + ..exportType = type; + + return WorkspaceEventExportDocument(request).send(); + } +} diff --git a/app_flowy/lib/workspace/presentation/home/navigation.dart b/app_flowy/lib/workspace/presentation/home/navigation.dart index 328b4cec14..069c0ee6e7 100644 --- a/app_flowy/lib/workspace/presentation/home/navigation.dart +++ b/app_flowy/lib/workspace/presentation/home/navigation.dart @@ -61,14 +61,22 @@ class FlowyNavigation extends StatelessWidget { ); }, update: (_, notifier, controller) => controller!..update(notifier), - child: Row(children: [ - Selector>( - selector: (context, notifier) => notifier.collapasedNotifier, - builder: (ctx, collapsedNotifier, child) => _renderCollapse(ctx, collapsedNotifier)), - Selector>( + child: Expanded( + child: Row(children: [ + Selector>( + selector: (context, notifier) => notifier.collapasedNotifier, + builder: (ctx, collapsedNotifier, child) => _renderCollapse(ctx, collapsedNotifier)), + Selector>( selector: (context, notifier) => notifier.navigationItems, - builder: (ctx, items, child) => Row(children: _renderNavigationItems(items))), - ]), + builder: (ctx, items, child) => Expanded( + child: Row( + children: _renderNavigationItems(items), + // crossAxisAlignment: WrapCrossAlignment.start, + ), + ), + ), + ]), + ), ); } @@ -106,7 +114,13 @@ class FlowyNavigation extends StatelessWidget { Widget last = NaviItemWidget(newItems.removeLast()); List widgets = List.empty(growable: true); - widgets.addAll(newItems.map((item) => NaviItemDivider(child: NaviItemWidget(item))).toList()); + // widgets.addAll(newItems.map((item) => NaviItemDivider(child: NaviItemWidget(item))).toList()); + + for (final item in newItems) { + widgets.add(NaviItemWidget(item)); + widgets.add(const Text('/')); + } + widgets.add(last); return widgets; @@ -129,39 +143,13 @@ class FlowyNavigation extends StatelessWidget { } } -class IconNaviItemWidget extends StatelessWidget { - final NavigationItem item; - const IconNaviItemWidget(this.item, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return SizedBox( - height: 24, - child: InkWell( - child: item.naviTitle, - onTap: () { - debugPrint('show app document'); - }, - ).padding(horizontal: 8, vertical: 2), - ); - } -} - class NaviItemWidget extends StatelessWidget { final NavigationItem item; const NaviItemWidget(this.item, {Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return SizedBox( - height: 24, - child: InkWell( - child: item.naviTitle, - onTap: () { - debugPrint('show app document'); - }, - ).padding(horizontal: 8, vertical: 2), - ); + return Expanded(child: item.leftBarItem.padding(horizontal: 2, vertical: 2)); } } @@ -172,7 +160,7 @@ class NaviItemDivider extends StatelessWidget { @override Widget build(BuildContext context) { return Row( - children: [child, const Text('/').padding(horizontal: 2)], + children: [child, const Text('/')], ); } } @@ -184,7 +172,7 @@ class EllipsisNaviItem extends NavigationItem { }); @override - Widget get naviTitle => const FlowyText.medium('...'); + Widget get leftBarItem => const FlowyText.medium('...'); @override NavigationCallback get action => (id) {}; diff --git a/app_flowy/lib/workspace/presentation/stack_page/blank/blank_page.dart b/app_flowy/lib/workspace/presentation/stack_page/blank/blank_page.dart index a38b307a79..a769c49980 100644 --- a/app_flowy/lib/workspace/presentation/stack_page/blank/blank_page.dart +++ b/app_flowy/lib/workspace/presentation/stack_page/blank/blank_page.dart @@ -9,10 +9,10 @@ class BlankStackContext extends HomeStackContext { String get identifier => "1"; @override - Widget get naviTitle => const FlowyText.medium('Blank page', fontSize: 12); + Widget get leftBarItem => const FlowyText.medium('Blank page', fontSize: 12); @override - Widget? Function(BuildContext context) get buildNaviAction => (_) => null; + Widget? get rightBarItem => null; @override HomeStackType get type => HomeStackType.blank; diff --git a/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart b/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart index fa65236450..1e098aae90 100644 --- a/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart +++ b/app_flowy/lib/workspace/presentation/stack_page/doc/doc_stack_page.dart @@ -1,15 +1,23 @@ import 'package:app_flowy/startup/startup.dart'; +import 'package:app_flowy/workspace/application/doc/share_bloc.dart'; import 'package:app_flowy/workspace/domain/i_view.dart'; import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart'; import 'package:app_flowy/workspace/domain/view_ext.dart'; +import 'package:app_flowy/workspace/infrastructure/repos/view_repo.dart'; +import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; +import 'package:flowy_log/flowy_log.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace-infra/export.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-workspace-infra/view_create.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; import 'package:flutter/material.dart'; import 'package:dartz/dartz.dart' as dartz; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:clipboard/clipboard.dart'; import 'doc_page.dart'; @@ -33,10 +41,10 @@ class DocStackContext extends HomeStackContext { } @override - Widget get naviTitle => FlowyText.medium(_view.name, fontSize: 12); + Widget get leftBarItem => DocLeftBarItem(view: _view); @override - Widget? Function(BuildContext context) get buildNaviAction => _buildNaviAction; + Widget? get rightBarItem => DocShareButton(view: _view); @override String get identifier => _view.id; @@ -45,7 +53,7 @@ class DocStackContext extends HomeStackContext { HomeStackType get type => _view.stackType(); @override - Widget buildWidget() => DocStackPage(_view, key: ValueKey(_view.id)); + Widget buildWidget() => DocPage(view: _view, key: ValueKey(_view.id)); @override List get navigationItems => _makeNavigationItems(); @@ -58,81 +66,163 @@ class DocStackContext extends HomeStackContext { // }).toList(); List _makeNavigationItems() { - return [this]; + return [ + this, + ]; } @override void dispose() { _listener.stop(); } - - Widget _buildNaviAction(BuildContext context) { - return const DocShareButton(); - } } -class DocShareButton extends StatelessWidget { - const DocShareButton({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - double buttonWidth = 60; - return RoundedTextButton( - title: 'Share', - height: 30, - width: buttonWidth, - fontSize: 12, - borderRadius: Corners.s6Border, - color: Colors.lightBlue, - onPressed: () { - final actionList = ShareActions(onSelected: (result) { - result.fold(() {}, (action) { - switch (action) { - case ShareAction.markdown: - break; - case ShareAction.copyLink: - break; - } - }); - }); - actionList.show( - context, - context, - anchorDirection: AnchorDirection.bottomWithCenterAligned, - anchorOffset: Offset(-(buttonWidth / 2), 10), - ); - }, - ); - } -} - -class DocStackPage extends StatefulWidget { +class DocLeftBarItem extends StatefulWidget { final View view; - const DocStackPage(this.view, {Key? key}) : super(key: key); + + const DocLeftBarItem({required this.view, Key? key}) : super(key: key); @override - _DocStackPageState createState() => _DocStackPageState(); + State createState() => _DocLeftBarItemState(); } -class _DocStackPageState extends State { +class _DocLeftBarItemState extends State { + final _controller = TextEditingController(); + final _focusNode = FocusNode(); + late ViewRepository repo; + @override - Widget build(BuildContext context) { - return DocPage(view: widget.view); + void initState() { + repo = ViewRepository(view: widget.view); + _focusNode.addListener(_handleFocusChanged); + + super.initState(); } @override void dispose() { + _controller.dispose(); + _focusNode.dispose(); super.dispose(); } @override - void deactivate() { - super.deactivate(); + Widget build(BuildContext context) { + _controller.text = widget.view.name; + + final theme = context.watch(); + return IntrinsicWidth( + child: TextField( + controller: _controller, + focusNode: _focusNode, + scrollPadding: EdgeInsets.zero, + decoration: const InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + isDense: true, + ), + style: TextStyle( + color: theme.shader1, + fontSize: 14, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + // cursorColor: widget.cursorColor, + // obscureText: widget.enableObscure, + ), + ); } + void _handleFocusChanged() { + if (_controller.text.isEmpty) { + _controller.text = widget.view.name; + return; + } + + if (_controller.text != widget.view.name) { + repo.updateView(name: _controller.text); + } + } +} + +class DocShareButton extends StatelessWidget { + final View view; + DocShareButton({Key? key, required this.view}) : super(key: ValueKey(view.id)); + @override - void didUpdateWidget(covariant DocStackPage oldWidget) { - super.didUpdateWidget(oldWidget); + Widget build(BuildContext context) { + double buttonWidth = 60; + return BlocProvider( + create: (context) => getIt(param1: view), + child: BlocListener( + listener: (context, state) { + state.map( + initial: (_) {}, + loading: (_) {}, + finish: (state) { + state.successOrFail.fold( + _handleExportData, + _handleExportError, + ); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return RoundedTextButton( + title: 'Share', + height: 30, + width: buttonWidth, + fontSize: 12, + borderRadius: Corners.s6Border, + color: Colors.lightBlue, + onPressed: () => _showActionList(context, Offset(-(buttonWidth / 2), 10)), + ); + }, + ), + ), + ); + } + + void _handleExportData(ExportData exportData) { + switch (exportData.exportType) { + case ExportType.Link: + // TODO: Handle this case. + break; + case ExportType.Markdown: + FlutterClipboard.copy(exportData.data).then((value) => Log.info('copied to clipboard')); + break; + case ExportType.Text: + // TODO: Handle this case. + break; + } + } + + void _handleExportError(WorkspaceError error) {} + + void _showActionList(BuildContext context, Offset offset) { + final actionList = ShareActions(onSelected: (result) { + result.fold(() {}, (action) { + switch (action) { + case ShareAction.markdown: + context.read().add(const DocShareEvent.shareMarkdown()); + break; + case ShareAction.copyLink: + showWorkInProgressDialog(context); + break; + } + }); + }); + actionList.show( + context, + context, + anchorDirection: AnchorDirection.bottomWithCenterAligned, + anchorOffset: offset, + ); + } + + void showWorkInProgressDialog(BuildContext context) { + const FlowyAlertDialog(title: "Work in progress").show(context); } } @@ -140,9 +230,7 @@ class ShareActions with ActionList implements FlowyOverlayDe final Function(dartz.Option) onSelected; final _items = ShareAction.values.map((action) => ShareActionWrapper(action)).toList(); - ShareActions({ - required this.onSelected, - }); + ShareActions({required this.onSelected}); @override double get maxWidth => 130; @@ -167,12 +255,7 @@ class ShareActions with ActionList implements FlowyOverlayDe FlowyOverlayDelegate? get delegate => this; @override - void didRemove() { - onSelected(dartz.none()); - } - - @override - ListOverlayFooter? get footer => null; + void didRemove() => onSelected(dartz.none()); } enum ShareAction { diff --git a/app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart b/app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart index f641cc002e..2ae3c470ef 100644 --- a/app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart +++ b/app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart @@ -26,10 +26,10 @@ class TrashStackContext extends HomeStackContext { String get identifier => "TrashStackContext"; @override - Widget get naviTitle => const FlowyText.medium('Trash', fontSize: 12); + Widget get leftBarItem => const FlowyText.medium('Trash', fontSize: 12); @override - Widget? Function(BuildContext context) get buildNaviAction => (_) => null; + Widget? get rightBarItem => null; @override HomeStackType get type => HomeStackType.trash; diff --git a/app_flowy/lib/workspace/presentation/widgets/dialogs.dart b/app_flowy/lib/workspace/presentation/widgets/dialogs.dart index 06f1682f31..b215dcc816 100644 --- a/app_flowy/lib/workspace/presentation/widgets/dialogs.dart +++ b/app_flowy/lib/workspace/presentation/widgets/dialogs.dart @@ -53,7 +53,8 @@ class _CreateTextFieldDialog extends State { VSpace(Insets.sm * 1.5), ], FlowyFormTextInput( - hintText: widget.value, + hintText: "Page name", + initialValue: widget.value, textStyle: const TextStyle(fontSize: 24, fontWeight: FontWeight.w400), autoFocus: true, onChanged: (text) { @@ -77,6 +78,52 @@ class _CreateTextFieldDialog extends State { } } +class FlowyAlertDialog extends StatefulWidget { + final String title; + final void Function()? cancel; + final void Function()? confirm; + + const FlowyAlertDialog({ + required this.title, + this.confirm, + this.cancel, + Key? key, + }) : super(key: key); + + @override + State createState() => _CreateFlowyAlertDialog(); +} + +class _CreateFlowyAlertDialog extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final theme = context.watch(); + return StyledDialog( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ...[ + FlowyText.medium(widget.title, color: theme.shader4), + ], + if (widget.confirm != null) ...[ + const VSpace(20), + OkCancelButton( + onOkPressed: widget.confirm!, + onCancelPressed: widget.confirm, + ) + ] + ], + ), + ); + } +} + class OkCancelDialog extends StatelessWidget { final VoidCallback? onOkPressed; final VoidCallback? onCancelPressed; diff --git a/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart b/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart index 83e2010ca2..0af6784ef9 100644 --- a/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart +++ b/app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart @@ -21,12 +21,12 @@ class HomeTopBar extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ const FlowyNavigation(), - const Spacer(), + const HSpace(16), ChangeNotifierProvider.value( value: Provider.of(context, listen: false), child: Consumer( builder: (BuildContext context, HomeStackNotifier notifier, Widget? child) { - return notifier.stackContext.buildNaviAction(context) ?? const SizedBox(); + return notifier.stackContext.rightBarItem ?? const SizedBox(); }, ), ) // _renderMoreButton(), diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/header.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/header.dart index 0344706573..e2b3855728 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/header.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/header.dart @@ -98,11 +98,14 @@ class MenuAppHeader extends StatelessWidget { } Widget _renderAddButton(BuildContext context) { - return AddButton( - onSelected: (viewType) { - context.read().add(AppEvent.createView("New view", "", viewType)); - }, - ).padding(right: MenuAppSizes.headerPadding); + return Tooltip( + message: "Quickly add a page inside", + child: AddButton( + onSelected: (viewType) { + context.read().add(AppEvent.createView("New view", "", viewType)); + }, + ).padding(right: MenuAppSizes.headerPadding), + ); } void _handleAction(BuildContext context, Option action) { diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/right_click_action.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/right_click_action.dart index c8491ee8da..9ff429706c 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/right_click_action.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/right_click_action.dart @@ -32,9 +32,6 @@ class AppDisclosureActionSheet with ActionList implemen void didRemove() { onSelected(dartz.none()); } - - @override - ListOverlayFooter? get footer => null; } class DisclosureActionWrapper extends ActionItem { diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/disclosure_action.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/disclosure_action.dart index bb3b14feda..32aca2ded0 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/disclosure_action.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/disclosure_action.dart @@ -52,9 +52,6 @@ class ViewDisclosureButton extends StatelessWidget void didRemove() { onSelected(dartz.none()); } - - @override - ListOverlayFooter? get footer => null; } class ViewDisclosureActionWrapper extends ActionItem { diff --git a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart index 945632e501..4c70f3fce3 100644 --- a/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart +++ b/app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/item.dart @@ -56,11 +56,10 @@ class ViewSectionItem extends StatelessWidget { List children = [ SizedBox(width: 16, height: 16, child: state.view.thumbnail()), const HSpace(2), - FlowyText.regular(state.view.name, fontSize: 12), + Expanded(child: FlowyText.regular(state.view.name, fontSize: 12)), ]; if (onHover || state.isEditing) { - children.add(const Spacer()); children.add( ViewDisclosureButton( onTap: () => context.read().add(const ViewEvent.setIsEditing(true)), diff --git a/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart b/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart index 9addd9481c..4410237941 100644 --- a/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart +++ b/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart @@ -17,7 +17,7 @@ abstract class ActionList { double get itemHeight => ActionListSizes.itemHeight; - ListOverlayFooter? get footer; + ListOverlayFooter? get footer => null; void Function(dartz.Option) get selectCallback; diff --git a/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart b/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart index 16fd01ff3e..5e72c30196 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart @@ -10,8 +10,7 @@ import 'package:provider/provider.dart'; import 'package:textstyle_extensions/textstyle_extensions.dart'; class FlowyFormTextInput extends StatelessWidget { - static EdgeInsets kDefaultTextInputPadding = - EdgeInsets.only(bottom: Insets.sm, top: 4); + static EdgeInsets kDefaultTextInputPadding = EdgeInsets.only(bottom: Insets.sm, top: 4); final String? label; final bool? autoFocus; @@ -61,8 +60,7 @@ class FlowyFormTextInput extends StatelessWidget { inputDecoration: InputDecoration( isDense: true, contentPadding: contentPadding ?? kDefaultTextInputPadding, - border: const ThinUnderlineBorder( - borderSide: BorderSide(width: 5, color: Colors.red)), + border: const ThinUnderlineBorder(borderSide: BorderSide(width: 5, color: Colors.red)), //focusedBorder: UnderlineInputBorder(borderSide: BorderSide(width: .5, color: Colors.red)), hintText: hintText, ), @@ -143,8 +141,7 @@ class StyledSearchTextInputState extends State { @override void initState() { - _controller = - widget.controller ?? TextEditingController(text: widget.initialValue); + _controller = widget.controller ?? TextEditingController(text: widget.initialValue); _focusNode = FocusNode( debugLabel: widget.label ?? '', onKey: (FocusNode node, RawKeyEvent evt) { @@ -160,8 +157,7 @@ class StyledSearchTextInputState extends State { canRequestFocus: true, ); // Listen for focus out events - _focusNode - .addListener(() => widget.onFocusChanged?.call(_focusNode.hasFocus)); + _focusNode.addListener(() => widget.onFocusChanged?.call(_focusNode.hasFocus)); widget.onFocusCreated?.call(_focusNode); if (widget.autoFocus ?? false) { scheduleMicrotask(() => _focusNode.requestFocus()); @@ -210,8 +206,7 @@ class StyledSearchTextInputState extends State { InputDecoration( prefixIcon: widget.prefixIcon, suffixIcon: widget.suffixIcon, - contentPadding: - widget.contentPadding ?? EdgeInsets.all(Insets.m), + contentPadding: widget.contentPadding ?? EdgeInsets.all(Insets.m), border: const OutlineInputBorder(borderSide: BorderSide.none), isDense: true, icon: widget.icon == null ? null : Icon(widget.icon), @@ -259,8 +254,7 @@ class ThinUnderlineBorder extends InputBorder { bool get isOutline => false; @override - UnderlineInputBorder copyWith( - {BorderSide? borderSide, BorderRadius? borderRadius}) { + UnderlineInputBorder copyWith({BorderSide? borderSide, BorderRadius? borderRadius}) { return UnderlineInputBorder( borderSide: borderSide ?? this.borderSide, borderRadius: borderRadius ?? this.borderRadius, @@ -280,8 +274,7 @@ class ThinUnderlineBorder extends InputBorder { @override Path getInnerPath(Rect rect, {TextDirection? textDirection}) { return Path() - ..addRect(Rect.fromLTWH(rect.left, rect.top, rect.width, - math.max(0.0, rect.height - borderSide.width))); + ..addRect(Rect.fromLTWH(rect.left, rect.top, rect.width, math.max(0.0, rect.height - borderSide.width))); } @override @@ -292,8 +285,7 @@ class ThinUnderlineBorder extends InputBorder { @override ShapeBorder? lerpFrom(ShapeBorder? a, double t) { if (a is UnderlineInputBorder) { - final newBorderRadius = - BorderRadius.lerp(a.borderRadius, borderRadius, t); + final newBorderRadius = BorderRadius.lerp(a.borderRadius, borderRadius, t); if (newBorderRadius != null) { return UnderlineInputBorder( @@ -308,8 +300,7 @@ class ThinUnderlineBorder extends InputBorder { @override ShapeBorder? lerpTo(ShapeBorder? b, double t) { if (b is UnderlineInputBorder) { - final newBorderRadius = - BorderRadius.lerp(b.borderRadius, borderRadius, t); + final newBorderRadius = BorderRadius.lerp(b.borderRadius, borderRadius, t); if (newBorderRadius != null) { return UnderlineInputBorder( borderSide: BorderSide.lerp(borderSide, b.borderSide, t), @@ -335,8 +326,7 @@ class ThinUnderlineBorder extends InputBorder { double gapPercentage = 0.0, TextDirection? textDirection, }) { - if (borderRadius.bottomLeft != Radius.zero || - borderRadius.bottomRight != Radius.zero) { + if (borderRadius.bottomLeft != Radius.zero || borderRadius.bottomRight != Radius.zero) { canvas.clipPath(getOuterPath(rect, textDirection: textDirection)); } canvas.drawLine(rect.bottomLeft, rect.bottomRight, borderSide.toPaint()); diff --git a/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart b/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart index 2ff674ae84..e6e9684854 100644 --- a/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart +++ b/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart @@ -17,12 +17,15 @@ class RoundedInputField extends StatefulWidget { final String errorText; final TextStyle style; final ValueChanged? onChanged; + final String? initialValue; late bool enableObscure; var _text = ""; RoundedInputField({ Key? key, this.hintText, + this.errorText = "", + this.initialValue, this.icon, this.obscureText = false, this.obscureIcon, @@ -32,7 +35,6 @@ class RoundedInputField extends StatefulWidget { this.highlightBorderColor = Colors.transparent, this.cursorColor = Colors.black, this.style = const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), - this.errorText = "", }) : super(key: key) { enableObscure = obscureText; } @@ -62,6 +64,7 @@ class _RoundedInputFieldState extends State { borderRadius: Corners.s10Border, borderColor: borderColor, child: TextFormField( + initialValue: widget.initialValue, onChanged: (value) { widget._text = value; if (widget.onChanged != null) { diff --git a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pb.dart b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pb.dart index 750ce7d1e8..9aa1e297d9 100644 --- a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pb.dart +++ b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pb.dart @@ -77,17 +77,22 @@ class ExportRequest extends $pb.GeneratedMessage { class ExportData extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ExportData', createEmptyInstance: create) ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data') + ..e(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'exportType', $pb.PbFieldType.OE, defaultOrMaker: ExportType.Text, valueOf: ExportType.valueOf, enumValues: ExportType.values) ..hasRequiredFields = false ; ExportData._() : super(); factory ExportData({ $core.String? data, + ExportType? exportType, }) { final _result = create(); if (data != null) { _result.data = data; } + if (exportType != null) { + _result.exportType = exportType; + } return _result; } factory ExportData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); @@ -119,5 +124,14 @@ class ExportData extends $pb.GeneratedMessage { $core.bool hasData() => $_has(0); @$pb.TagNumber(1) void clearData() => clearField(1); + + @$pb.TagNumber(2) + ExportType get exportType => $_getN(1); + @$pb.TagNumber(2) + set exportType(ExportType v) { setField(2, v); } + @$pb.TagNumber(2) + $core.bool hasExportType() => $_has(1); + @$pb.TagNumber(2) + void clearExportType() => clearField(2); } diff --git a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbenum.dart b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbenum.dart index 325ae2b7b8..011b134883 100644 --- a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbenum.dart +++ b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbenum.dart @@ -11,11 +11,13 @@ import 'package:protobuf/protobuf.dart' as $pb; class ExportType extends $pb.ProtobufEnum { static const ExportType Text = ExportType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Text'); - static const ExportType RichText = ExportType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RichText'); + static const ExportType Markdown = ExportType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Markdown'); + static const ExportType Link = ExportType._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Link'); static const $core.List values = [ Text, - RichText, + Markdown, + Link, ]; static final $core.Map<$core.int, ExportType> _byValue = $pb.ProtobufEnum.initByValue(values); diff --git a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbjson.dart b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbjson.dart index f23787f7a0..05d7aac4d4 100644 --- a/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbjson.dart +++ b/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace-infra/export.pbjson.dart @@ -13,12 +13,13 @@ const ExportType$json = const { '1': 'ExportType', '2': const [ const {'1': 'Text', '2': 0}, - const {'1': 'RichText', '2': 1}, + const {'1': 'Markdown', '2': 1}, + const {'1': 'Link', '2': 2}, ], }; /// Descriptor for `ExportType`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List exportTypeDescriptor = $convert.base64Decode('CgpFeHBvcnRUeXBlEggKBFRleHQQABIMCghSaWNoVGV4dBAB'); +final $typed_data.Uint8List exportTypeDescriptor = $convert.base64Decode('CgpFeHBvcnRUeXBlEggKBFRleHQQABIMCghNYXJrZG93bhABEggKBExpbmsQAg=='); @$core.Deprecated('Use exportRequestDescriptor instead') const ExportRequest$json = const { '1': 'ExportRequest', @@ -35,8 +36,9 @@ const ExportData$json = const { '1': 'ExportData', '2': const [ const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'}, + const {'1': 'export_type', '3': 2, '4': 1, '5': 14, '6': '.ExportType', '10': 'exportType'}, ], }; /// Descriptor for `ExportData`. Decode as a `google.protobuf.DescriptorProto`. -final $typed_data.Uint8List exportDataDescriptor = $convert.base64Decode('CgpFeHBvcnREYXRhEhIKBGRhdGEYASABKAlSBGRhdGE='); +final $typed_data.Uint8List exportDataDescriptor = $convert.base64Decode('CgpFeHBvcnREYXRhEhIKBGRhdGEYASABKAlSBGRhdGESLAoLZXhwb3J0X3R5cGUYAiABKA4yCy5FeHBvcnRUeXBlUgpleHBvcnRUeXBl'); diff --git a/app_flowy/pubspec.lock b/app_flowy/pubspec.lock index 9223049c22..f13f2caa2b 100644 --- a/app_flowy/pubspec.lock +++ b/app_flowy/pubspec.lock @@ -5,259 +5,266 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "23.0.0" + version: "30.0.0" analyzer: dependency: transitive description: name: analyzer - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.0" + version: "2.7.0" animations: dependency: transitive description: name: animations - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.0" + version: "2.3.0" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.8.2" avatars: dependency: "direct main" description: name: avatars - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" bloc: dependency: transitive description: name: bloc - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "7.2.1" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.3" + version: "2.1.1" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.0" + version: "3.0.1" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.6" + version: "2.1.5" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "7.0.1" + version: "7.2.2" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "5.1.0" + version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "8.1.1" + version: "8.1.3" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" cli_util: dependency: transitive description: name: cli_util - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.3.3" + version: "0.3.5" + clipboard: + dependency: "direct main" + description: + name: clipboard + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.15.0" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.1" cross_file: dependency: transitive description: name: cross_file - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.3.1+5" + version: "0.3.2" crypto: dependency: transitive description: name: crypto - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.1" csslib: dependency: transitive description: name: csslib - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.3" + version: "1.0.4" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.3" + version: "2.2.0" dartz: dependency: transitive description: name: dartz - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.10.0-nullsafety.2" diff_match_patch: dependency: transitive description: name: diff_match_patch - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.1" equatable: dependency: "direct main" description: name: equatable - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" expandable: dependency: "direct main" description: name: expandable - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.2" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" flowy_editor: @@ -318,65 +325,65 @@ packages: dependency: "direct main" description: name: flutter_bloc - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "7.3.1" + version: "7.3.3" flutter_cache_manager: dependency: transitive description: name: flutter_cache_manager - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" flutter_colorpicker: dependency: "direct main" description: name: flutter_colorpicker - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.6.0" + version: "0.6.1" flutter_inappwebview: dependency: transitive description: name: flutter_inappwebview - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "5.3.2" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "5.0.3" + version: "5.1.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" flutter_keyboard_visibility_web: dependency: transitive description: name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.3" + version: "2.0.4" flutter_quill: dependency: "direct main" description: @@ -390,7 +397,7 @@ packages: dependency: "direct main" description: name: flutter_svg - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.22.0" flutter_test: @@ -407,427 +414,427 @@ packages: dependency: "direct dev" description: name: freezed - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.14.3" + version: "0.14.5" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.14.3" frontend_server_client: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.2" get_it: dependency: "direct main" description: name: get_it - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "7.2.0" gettext_parser: dependency: transitive description: name: gettext_parser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" glob: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.0" + version: "2.1.0" html: dependency: transitive description: name: html - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.15.0" http: dependency: transitive description: name: http - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.4" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" i18n_extension: dependency: transitive description: name: i18n_extension - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.3" image_picker: dependency: transitive description: name: image_picker - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.8.4+2" + version: "0.8.4+4" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.3" + version: "2.1.4" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" intl: dependency: transitive description: name: intl - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" isolates: dependency: transitive description: name: isolates - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.3+8" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.6.3" json_annotation: dependency: transitive description: name: json_annotation - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.1.0" + version: "4.3.0" lint: dependency: transitive description: name: lint - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.5.3" + version: "1.7.2" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" loading_indicator: dependency: transitive description: name: loading_indicator - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.2" logger: dependency: transitive description: name: logger - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.0" + version: "1.1.0" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.1" + version: "1.0.2" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.11" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.7.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.0" + version: "1.0.1" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.0" + version: "2.0.2" package_info_plus: dependency: "direct main" description: name: package_info_plus - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.3" package_info_plus_macos: dependency: transitive description: name: package_info_plus_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" package_info_plus_web: dependency: transitive description: name: package_info_plus_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.8.0" path_drawing: dependency: transitive description: name: path_drawing - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.5.1" + version: "0.5.1+1" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.1" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.2" + version: "2.0.6" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.2" + version: "2.1.0" path_provider_macos: dependency: transitive description: name: path_provider_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" pedantic: dependency: transitive description: name: pedantic - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.11.1" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.2.0" + version: "4.4.0" photo_view: dependency: transitive description: name: photo_view - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.0" + version: "3.0.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.2.3" + version: "4.2.4" protobuf: dependency: transitive description: name: protobuf - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.0.1" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.0" + version: "2.1.0" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.0" + version: "1.1.0" quiver: dependency: transitive description: name: quiver - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.1+1" quiver_hashcode: dependency: transitive description: name: quiver_hashcode - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" rxdart: dependency: transitive description: name: rxdart - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.27.2" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" sized_context: dependency: "direct main" description: name: sized_context - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0+1" sky_engine: @@ -839,240 +846,240 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.5" + version: "1.1.1" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.8.1" sprintf: dependency: transitive description: name: sprintf - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "6.0.0" sqflite: dependency: transitive description: name: sqflite - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0+4" sqflite_common: dependency: transitive description: name: sqflite_common - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.1+1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" string_validator: dependency: transitive description: name: string_validator - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.0" styled_widget: dependency: "direct main" description: name: styled_widget - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.3.1+2" + version: "0.4.0+3" synchronized: dependency: transitive description: name: synchronized - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.3" textstyle_extensions: dependency: transitive description: name: textstyle_extensions - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0-nullsafety" time: dependency: "direct main" description: name: time - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" tuple: dependency: transitive description: name: tuple - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" universal_platform: dependency: transitive description: name: universal_platform - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0+1" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "6.0.9" + version: "6.0.12" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.2" + version: "2.0.4" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.1" + version: "2.0.2" uuid: dependency: transitive description: name: uuid - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.4" + version: "3.0.5" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" video_player: dependency: transitive description: name: video_player - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.5" + version: "2.2.7" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "4.2.0" video_player_web: dependency: transitive description: name: video_player_web - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.4" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.0" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.0" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.5" + version: "2.2.10" window_size: dependency: "direct main" description: @@ -1086,28 +1093,28 @@ packages: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "5.1.2" + version: "5.3.1" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" youtube_player_flutter: dependency: transitive description: name: youtube_player_flutter - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "8.0.0" sdks: diff --git a/app_flowy/pubspec.yaml b/app_flowy/pubspec.yaml index 8fab40e4a9..d46a16d793 100644 --- a/app_flowy/pubspec.yaml +++ b/app_flowy/pubspec.yaml @@ -65,6 +65,8 @@ dependencies: package_info_plus: ^1.3.0 url_launcher: ^6.0.2 avatars: ^2.0.0 + # file_picker: ^4.2.1 + clipboard: ^0.1.3 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/app_flowy/windows/flutter/generated_plugin_registrant.cc b/app_flowy/windows/flutter/generated_plugin_registrant.cc index 67dc1fc80d..284e845c9c 100644 --- a/app_flowy/windows/flutter/generated_plugin_registrant.cc +++ b/app_flowy/windows/flutter/generated_plugin_registrant.cc @@ -8,7 +8,7 @@ #include #include -#include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -16,8 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlowyEditorPlugin")); FlowyInfraUIPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlowyInfraUIPlugin")); - UrlLauncherPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("UrlLauncherPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowSizePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("WindowSizePlugin")); } diff --git a/delta_markdown b/delta_markdown new file mode 160000 index 0000000000..3e121c546f --- /dev/null +++ b/delta_markdown @@ -0,0 +1 @@ +Subproject commit 3e121c546fe40f91e9817ad0e0f04910315db681 diff --git a/doc/imgs/run.png b/doc/imgs/run.png new file mode 100644 index 0000000000..bfcff15502 Binary files /dev/null and b/doc/imgs/run.png differ diff --git a/rust-lib/flowy-workspace-infra/src/entities/share/export.rs b/rust-lib/flowy-workspace-infra/src/entities/share/export.rs index e82b59d674..88ad942636 100644 --- a/rust-lib/flowy-workspace-infra/src/entities/share/export.rs +++ b/rust-lib/flowy-workspace-infra/src/entities/share/export.rs @@ -5,7 +5,8 @@ use std::convert::TryInto; #[derive(PartialEq, Debug, ProtoBuf_Enum, Clone)] pub enum ExportType { Text = 0, - RichText = 1, + Markdown = 1, + Link = 2, } impl std::default::Default for ExportType { @@ -16,7 +17,8 @@ impl std::convert::From for ExportType { fn from(val: i32) -> Self { match val { 0 => ExportType::Text, - 1 => ExportType::RichText, + 1 => ExportType::Markdown, + 2 => ExportType::Link, _ => { log::error!("Invalid export type: {}", val); ExportType::Text @@ -54,4 +56,7 @@ impl TryInto for ExportRequest { pub struct ExportData { #[pb(index = 1)] pub data: String, + + #[pb(index = 2)] + pub export_type: ExportType, } diff --git a/rust-lib/flowy-workspace-infra/src/protobuf/model/export.rs b/rust-lib/flowy-workspace-infra/src/protobuf/model/export.rs index 3e6aba2462..48779acd48 100644 --- a/rust-lib/flowy-workspace-infra/src/protobuf/model/export.rs +++ b/rust-lib/flowy-workspace-infra/src/protobuf/model/export.rs @@ -217,6 +217,7 @@ impl ::protobuf::reflect::ProtobufValue for ExportRequest { pub struct ExportData { // message fields pub data: ::std::string::String, + pub export_type: ExportType, // special fields pub unknown_fields: ::protobuf::UnknownFields, pub cached_size: ::protobuf::CachedSize, @@ -258,6 +259,21 @@ impl ExportData { pub fn take_data(&mut self) -> ::std::string::String { ::std::mem::replace(&mut self.data, ::std::string::String::new()) } + + // .ExportType export_type = 2; + + + pub fn get_export_type(&self) -> ExportType { + self.export_type + } + pub fn clear_export_type(&mut self) { + self.export_type = ExportType::Text; + } + + // Param is passed by value, moved + pub fn set_export_type(&mut self, v: ExportType) { + self.export_type = v; + } } impl ::protobuf::Message for ExportData { @@ -272,6 +288,9 @@ impl ::protobuf::Message for ExportData { 1 => { ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?; }, + 2 => { + ::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.export_type, 2, &mut self.unknown_fields)? + }, _ => { ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; }, @@ -287,6 +306,9 @@ impl ::protobuf::Message for ExportData { if !self.data.is_empty() { my_size += ::protobuf::rt::string_size(1, &self.data); } + if self.export_type != ExportType::Text { + my_size += ::protobuf::rt::enum_size(2, self.export_type); + } my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); self.cached_size.set(my_size); my_size @@ -296,6 +318,9 @@ impl ::protobuf::Message for ExportData { if !self.data.is_empty() { os.write_string(1, &self.data)?; } + if self.export_type != ExportType::Text { + os.write_enum(2, ::protobuf::ProtobufEnum::value(&self.export_type))?; + } os.write_unknown_fields(self.get_unknown_fields())?; ::std::result::Result::Ok(()) } @@ -339,6 +364,11 @@ impl ::protobuf::Message for ExportData { |m: &ExportData| { &m.data }, |m: &mut ExportData| { &mut m.data }, )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum>( + "export_type", + |m: &ExportData| { &m.export_type }, + |m: &mut ExportData| { &mut m.export_type }, + )); ::protobuf::reflect::MessageDescriptor::new_pb_name::( "ExportData", fields, @@ -356,6 +386,7 @@ impl ::protobuf::Message for ExportData { impl ::protobuf::Clear for ExportData { fn clear(&mut self) { self.data.clear(); + self.export_type = ExportType::Text; self.unknown_fields.clear(); } } @@ -375,7 +406,8 @@ impl ::protobuf::reflect::ProtobufValue for ExportData { #[derive(Clone,PartialEq,Eq,Debug,Hash)] pub enum ExportType { Text = 0, - RichText = 1, + Markdown = 1, + Link = 2, } impl ::protobuf::ProtobufEnum for ExportType { @@ -386,7 +418,8 @@ impl ::protobuf::ProtobufEnum for ExportType { fn from_i32(value: i32) -> ::std::option::Option { match value { 0 => ::std::option::Option::Some(ExportType::Text), - 1 => ::std::option::Option::Some(ExportType::RichText), + 1 => ::std::option::Option::Some(ExportType::Markdown), + 2 => ::std::option::Option::Some(ExportType::Link), _ => ::std::option::Option::None } } @@ -394,7 +427,8 @@ impl ::protobuf::ProtobufEnum for ExportType { fn values() -> &'static [Self] { static values: &'static [ExportType] = &[ ExportType::Text, - ExportType::RichText, + ExportType::Markdown, + ExportType::Link, ]; values } @@ -425,25 +459,31 @@ impl ::protobuf::reflect::ProtobufValue for ExportType { static file_descriptor_proto_data: &'static [u8] = b"\ \n\x0cexport.proto\"T\n\rExportRequest\x12\x15\n\x06doc_id\x18\x01\x20\ \x01(\tR\x05docId\x12,\n\x0bexport_type\x18\x02\x20\x01(\x0e2\x0b.Export\ - TypeR\nexportType\"\x20\n\nExportData\x12\x12\n\x04data\x18\x01\x20\x01(\ - \tR\x04data*$\n\nExportType\x12\x08\n\x04Text\x10\0\x12\x0c\n\x08RichTex\ - t\x10\x01J\xd1\x02\n\x06\x12\x04\0\0\x0c\x01\n\x08\n\x01\x0c\x12\x03\0\0\ - \x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\ - \x02\x08\x15\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x16\n\x0c\n\x05\x04\ - \0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\ - \x0b\x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x14\x15\n\x0b\n\x04\x04\ - \0\x02\x01\x12\x03\x04\x04\x1f\n\x0c\n\x05\x04\0\x02\x01\x06\x12\x03\x04\ - \x04\x0e\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0f\x1a\n\x0c\n\x05\ - \x04\0\x02\x01\x03\x12\x03\x04\x1d\x1e\n\n\n\x02\x04\x01\x12\x04\x06\0\ - \x08\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x12\n\x0b\n\x04\x04\x01\ - \x02\0\x12\x03\x07\x04\x14\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\x07\x04\ - \n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\x0f\n\x0c\n\x05\x04\x01\ - \x02\0\x03\x12\x03\x07\x12\x13\n\n\n\x02\x05\0\x12\x04\t\0\x0c\x01\n\n\n\ - \x03\x05\0\x01\x12\x03\t\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\n\x04\r\ - \n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\n\x04\x08\n\x0c\n\x05\x05\0\x02\0\ - \x02\x12\x03\n\x0b\x0c\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x0b\x04\x11\n\ - \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x0b\x04\x0c\n\x0c\n\x05\x05\0\x02\ - \x01\x02\x12\x03\x0b\x0f\x10b\x06proto3\ + TypeR\nexportType\"N\n\nExportData\x12\x12\n\x04data\x18\x01\x20\x01(\tR\ + \x04data\x12,\n\x0bexport_type\x18\x02\x20\x01(\x0e2\x0b.ExportTypeR\nex\ + portType*.\n\nExportType\x12\x08\n\x04Text\x10\0\x12\x0c\n\x08Markdown\ + \x10\x01\x12\x08\n\x04Link\x10\x02J\xb1\x03\n\x06\x12\x04\0\0\x0e\x01\n\ + \x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\ + \n\x03\x04\0\x01\x12\x03\x02\x08\x15\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\ + \x04\x16\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\ + \x02\0\x01\x12\x03\x03\x0b\x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\ + \x14\x15\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x1f\n\x0c\n\x05\x04\0\ + \x02\x01\x06\x12\x03\x04\x04\x0e\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\ + \x04\x0f\x1a\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x1d\x1e\n\n\n\x02\ + \x04\x01\x12\x04\x06\0\t\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x12\n\ + \x0b\n\x04\x04\x01\x02\0\x12\x03\x07\x04\x14\n\x0c\n\x05\x04\x01\x02\0\ + \x05\x12\x03\x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\x0f\ + \n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x07\x12\x13\n\x0b\n\x04\x04\x01\ + \x02\x01\x12\x03\x08\x04\x1f\n\x0c\n\x05\x04\x01\x02\x01\x06\x12\x03\x08\ + \x04\x0e\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x08\x0f\x1a\n\x0c\n\x05\ + \x04\x01\x02\x01\x03\x12\x03\x08\x1d\x1e\n\n\n\x02\x05\0\x12\x04\n\0\x0e\ + \x01\n\n\n\x03\x05\0\x01\x12\x03\n\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\ + \x03\x0b\x04\r\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x0b\x04\x08\n\x0c\n\ + \x05\x05\0\x02\0\x02\x12\x03\x0b\x0b\x0c\n\x0b\n\x04\x05\0\x02\x01\x12\ + \x03\x0c\x04\x11\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x0c\x04\x0c\n\x0c\ + \n\x05\x05\0\x02\x01\x02\x12\x03\x0c\x0f\x10\n\x0b\n\x04\x05\0\x02\x02\ + \x12\x03\r\x04\r\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\r\x04\x08\n\x0c\n\ + \x05\x05\0\x02\x02\x02\x12\x03\r\x0b\x0cb\x06proto3\ "; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; diff --git a/rust-lib/flowy-workspace-infra/src/protobuf/proto/export.proto b/rust-lib/flowy-workspace-infra/src/protobuf/proto/export.proto index b0f27c3870..f78932dc5d 100644 --- a/rust-lib/flowy-workspace-infra/src/protobuf/proto/export.proto +++ b/rust-lib/flowy-workspace-infra/src/protobuf/proto/export.proto @@ -6,8 +6,10 @@ message ExportRequest { } message ExportData { string data = 1; + ExportType export_type = 2; } enum ExportType { Text = 0; - RichText = 1; + Markdown = 1; + Link = 2; } diff --git a/rust-lib/flowy-workspace/src/services/view_controller.rs b/rust-lib/flowy-workspace/src/services/view_controller.rs index 1f0f177e5e..69037b0109 100644 --- a/rust-lib/flowy-workspace/src/services/view_controller.rs +++ b/rust-lib/flowy-workspace/src/services/view_controller.rs @@ -147,7 +147,10 @@ impl ViewController { .read_document_data(doc_identifier, self.database.db_pool()?) .await?; - Ok(ExportData { data: doc.data }) + Ok(ExportData { + data: doc.data, + export_type: params.export_type, + }) } // belong_to_id will be the app_id or view_id.